• 2. Comment créer une fonction qui renvoie plus d'une valeur ?

      Prenons un premier exemple pour faire un rappel sur le principe de fonctionnement d'un fonction :

      • une fonction possède 0, 1 ou plusieurs paramètres (pour la fonction produit 2 paramètres de type char)
      • une fonction renvoie au maximum 1 valeurs (ou 0 valeur avec void) grace au mot clé return 

      Principe général d'une fonction 
      Code de la fonction qui renvoie une valeur
      Appel de la fonction produit

      short produit(char n1,char n2){

        short resultat = n1*n2;

        return resultat;

      }

      int main(){

        short res;

        res = produit(10, 5);

        printf("res=%d", res);

        return 0;

      }

      En python une fonction peut renvoyer plusieurs valeurs, mais pas en C. Rappelons que python 1.0 a été créé en 1994, alors que le langage C a été créé en 1972 et que le langage C a pour volonté d'être rapide et proche de la machine, ce que ne propose pas python (qui a plein d'autres avantages mais qui est bien moins rapide que le C). 

      Donc en C, dans une fonction on ne peut pas renvoyer plus d'une valeur avec le mot clé return ! 

      Peut-on renvoyer plus d'une valeur ? 
      Renvoyer plusieurs valeurs avec le mot clé return est impossible en C
      Mais c'est possible en python 

      def produit(a, b):

        somme = a + b

        produit = a * b

        return somme, produit

      Et pourtant il existe une solution à ce problème :les pointeurs .

      Les pointeurs sont très utilisés en C et notamment avec les fonctions. Pourquoi ? Nous l'avons dit, les fonctions peuvent prendre plusieurs paramètres en entrée mais ne peuvent renvoyer qu’un seul paramètre de sortie grace au mot clé return. Ce qui est très limitant. Avec les pointeurs, il est possible de passer l'adresse d'une variable et donc de pouvoir modifier cette variable dans la fonction et qu'elle soit ainsi modifiée dans le main.

      Prenons 2 exemples de fonction impossible à coder sans l’utilisation des pointeurs :

      • la fonction produit qui fait le produit de 2 valeurs de type char, résultat dans une variable de type short(2 octets), la fonction renvoie aussi un code d'erreur si le produit des 2 variables dépasse la taille d'un octet. On a donc 2 variables renvoyée erreur et res. erreur sera renvoyée via le mot clé return et res sera passé en pointeur.
      • la fonction MinMax qui prend en paramètre un tableau et doit renvoyer le min et le max de ce tableau 

      Principe général fonction et pointeur
      Code de la fonction qui renvoie erreur et utilise un pointeur pour modifier res
      Appel de la fonction produit (utilisation de l'adresse de res)

      char produit(short *res ,char n1, char n2) {

         *res = n1 * n2;

         int erreur = 0;

         if (*res > 127 || *res < -128)

              erreur = 1;

          return erreur;

      }

      int main()

      {

          char erreur;

          short res;

          erreur = produit(&res, 100, 5);

          printf("erreur=%d, res=%d", erreur, res);

          return 0;

      }

      Principe général fonction et pointeur
      Code de la fonction qui modifie min et max
      Appel de la fonction produit (utilisation de l'adresse de min et max).

      void MinMax(int* min, int* max, int tab[], int taille) {

          *min = *max = tab[0];

          for (int i = 1; i < taille; i++) {

              if (tab[i] < *min)

                  *min = tab[i];

              if (tab[i] > *max)

                  *max = tab[i];

          }

      }

      #define TAILLE 4

      int main(){

        int min, max, tab[TAILLE] = { 2,3,1,6 };

        MinMax(&min, &max, tab, TAILLE);

        printf("min=%d, max=%d\n", min, max);

      }

    • 3. Les fonctions et les tableaux

      Lorsque l’on passe un tableau à une fonction, on passe l’adresse du début du tableau. Donc comme pour les pointeurs passés en argument, les éléments du tableau peuvent être modifiés dans une fonction

      Prenons un exemple avec 3 fonctions de modification de tableau utilisant soit le formalisme tableau soit le formalisme pointeur. Ces 3 fonctions modifient les valeurs du tableau passé en argument de l'indice 0 à l'indice taille qui est le deuxième élément à être passé à la fonction. La fonction main permet de modifier les éléments du tableau pour les 5 premiers éléments pour la première fonction, puis pour les 4 premiers éléments et enfin pour les 3 premiers éléments pour modif_tab3. La fonction affiche_tab affiche les 5 éléments du tableau aprés chaque modification.

      Programme de test
      Résultat 

      void modif_tab1(int tab[], int taille) {

          for (int i = 0; i < taille; i++)

              tab[i] = 11;

      }

      void modif_tab2(int* tab, int taille) {

          for (int i = 0; i < taille; i++)

              *(tab++) = 12;

      }

      void modif_tab3(int* tab, int taille) {

          for (int i = 0; i < taille; i++)

              tab[i] = 13;

      }

      void affiche_tab(int tab[], int taille) {

          for (int i = 0; i < taille; i++)

              printf("%2d ", tab[i]);

          printf("\n");

      }

      int main() {

          int tab[5] = { 1,3,10,20,2 };

          affiche_tab(tab, 5);

          modif_tab1(tab,5);

          affiche_tab(tab, sizeof(tab)/sizeof(int) );

          modif_tab2(tab, 4);

          affiche_tab(tab, 5);

          modif_tab3(tab, 3);

          affiche_tab(tab, 5);

      }

       1  3 10 20  2

      11 11 11 11 11

      12 12 12 12 11

      13 13 13 12 11

    • 4. Fonctions et chaines de caractères

      Nous avons vu que la différence entre une chaine de caractères et un tableau d’entier ou de réels est liée à la valeur 0 ou '\0' que l’on trouve en fin de chaine de caractères et qui permet de connaitre la taille de la chaine. Ainsi les fonctions associées aux chaines de caractères n’ont pas besoin de 2 arguments (le tableau et la taille du tableau) mais seulement du tableau de caractères.

      Prenons un exemple utilisant 3 fonctions de recopie de chaine de caractères codés avec le formalisme tableau et pointeurs. Pour ces 3 fonctions qui recopient caractère par caractère la chaine src vers dest, remarquez l'ajout aprés la boucle de recopie du '\0' terminal qui n'est pas recopié dans la boucle.

      Exemple de code de copie de chaine de caractères
      Résultat

      void str_copy(char dest[], char src[]) {

          int i;

          for (i = 0; src[i] != '\0'; i++)

              dest[i] = src[i];

          dest[i] = '\0';//ajout du 0 terminal qui n'a pas été copié dans la boucle

      }

      void str_copy1(char dest[], char src[]) {

          int i = 0;

          do {

              dest[i] = src[i];

              i++;

          } while (src[i] != '\0');

          dest[i] = '\0';//ajout du 0 terminal qui n'a pas été copié dans la boucle

      }

      void str_copy2(char* dest, char* src) {

          do {

              *(dest++) = *(src++);

          } while (*src != '\0');

          *dest = '\0';//ajout du 0 terminal qui n'a pas été copié dans la boucle

      }

      int main() {

          char test1[20] = "bonjour";

          char test2[20];

          str_copy(test2, test1);//copie test1 vers test2

          puts(test2);

          str_copy1(test1, "au revoir");//copie "au revoir" dans test1

          puts(test1);

          str_copy2(test1, test2);// copie test2 vers test1

          puts(test1);

      }

      bonjour

      au revoir

      bonjour

    • 5. Retour sur le fichier son

      Revenons sur le projet de fichier son https://github.com/jlsalvat/wave_bmp_visualStudio pour créer 3 fonctions :

      • readSizeWave qui prend en argument le nom du fichier à ouvrir et renvoie le nombre d'échantillons aprés l'avoir lue à l'emplacement 20 du header
      • readWave qui prend en argument le nom du fichier, la taille du tableau et va renvoyer le header et les données (sous formes de tableau), 
      • writeWave qui prend en argument les mêmes arguments que ceux de readWave.

      L'avantage de découper notre programme en 3 fonctions est double : pouvoir ouvrir un fichier, lire ses échantillons, les modifier et écrire dans un autre fichier, ce qui n'était pas possible avant et rendre la lecture du main plus simple puisqu'il sera constitué de 3 fonctions seulement (et du code modifiant le programme) et un programme simple à lire et simple à modifier (et donc à maintenir).

      int readSizeWave(char* fichier) {

          FILE* file;

          int taille;

          // Ouvrir le fichier en mode lecture modification binaire

          file = fopen(fichier, "rb");

          if (file == NULL) {

              printf("Impossible d'ouvrir le fichier.\n");

              return -1;

          }

          // Aller à la position de la taille des datas (40 octets)

          fseek(file, 40, SEEK_SET);

          // Lire la taille du tableau

          fread(&taille, sizeof(int), 1, file);

          fclose(file);

          return taille;

      }

      void readWave(char* fichier, uint8_t *header, uint8_t* data, int taille) {

          FILE* file;

          // Ouvrir le fichier en mode lecture binaire

          file = fopen(fichier, "rb");

          if (file == NULL) {

              printf("Impossible d'ouvrir le fichier.\n");

              exit(EXIT_FAILURE);

          }

          //Lire le header

          fread(header, 1, 44, file);

          // Lire toutes les données du tableau

          fread(data, taille,1 , file);

          fclose(file);

      }

       

      void WriteWave(char* fichier, uint8_t* header, uint8_t* data, int taille) {

          FILE* file;

          // Ouvrir le fichier en mode écriture binaire

          file = fopen(fichier, "wb");

          if (file == NULL) {

              printf("Impossible d'ouvrir le fichier.\n");

              exit(EXIT_FAILURE);

          }

          fwrite(header, 44, 1, file);

          fwrite(data, taille,1 , file);

          fclose(file);

      }

       

      Dans le programme principal on retrouve l'ensemble des concepts vu dans les chapitres précédents :

      • en jaune la déclaration des 3 fonctions et le code des 3 fonctions (à ajouter pour que le programme fonctionne)
      • en verten vert la déclaration d'un pointeur et la création d'un tableau dynamique permettant d'accueillir les échantillons
      • en bleu l'appel aux 3 fonctions

      #include <stdlib.h>

      #include <stdint.h>

      #define _USE_MATH_DEFINES

      #include <math.h>

      //déclaration des fonctions

      //constantes nécessaire au programme

      #define FREQ 8000.0

      #define DO 277.18

      #define RE 311.13

      #define MI 329.63

      #define FA 349.23

      #define SOL 392.00

      #define LA 440.00

      #define SI 493.88

      #define DO_C5 523.25

      //affiche le tableau

      void displayData(uint8_t data[], int taille) {

          for (int i = 0; i < taille; i++) {

              printf("%hhu\n", data[i]);

          }

      }

      //génère une sinusoide de frequence freq pour un fichier wave à 8kHz

      void changeDataWave(uint8_t* data, int debut, int fin, double freq) {

          for (int i = debut; i < fin; i++)

              data[i] = 127 + 80 * sin(2 * M_PIFREQ * freq * (i - debut));

      }

      int main() {

          uint8_t header[44];

          uint8_t* data;

          int taille = readSizeWave("..\\ressources\\sinus.wav");

          data = malloc(taille);

          readWave("..\\ressources\\sinus.wav", header,data,taille);

          changeDataWave(data, 0, 2000, DO);

          changeDataWave(data, 2001, 4000, RE);

          changeDataWave(data, 4001, 6000, MI);

          changeDataWave(data, 6001, 8000, FA);

          changeDataWave(data, 8001, 10000, SOL);

          changeDataWave(data, 10001, 12000, LA);

          changeDataWave(data, 12001, 14000, SI);

          changeDataWave(data, 14001, 20000, DO_C5);

          displayData(data, 200);

          writeWave("..\\ressources\\sinus_back.wav", header, data,taille);

          free(data);

          return 0;

      }

      //code des fonctions

    • 6. Test de fin de chapitre