Programmation C
Structuration du code
Cours Exercices Correction des exercices

Instructions de structuration itérative Instructions de structuration conditionnelle Fonctions Portée des constantes et des variables

Instructions de structuration itérative

  • while
    • Syntaxe
      while ( condition ) {
        ...
      };
    • Exécution du bloc de code entre accolades tant que l'évaluation de la condition indiquée entre parenthèses a pour résultat vrai
    • Une première évaluation de la condition avant la première exécution du bloc de code
      -> Si obtention de faux lors de cette première évaluation, sortie directe du while sans exécution du bloc de code
      -> Possibilité de non exécution du bloc de code
    • Exemples
    • #include <stdio.h>

      void main(void) {
        int n = 1000000;
        printf("Nombre de cubes = %d\n", n);
        int TAILLE = 0;
        int cote = 1;
        while (n >= cote * cote) {
          n -= cote * cote;
          cote++;
          TAILLE++;
        }
        printf("TAILLE : %d\n", TAILLE);
        printf("%d cubes inutilises\n", n);
      }

      While1.c - While1.exe
      • Calcul de la taille (hauteur) d'une pyramide de cubes telle que le nombre de cubes est 1*1=1 au dernier rang, 2*2=4 à l'avant dernier rang, 3*3=9 à l'avant-avant dernier rang...
      • Donnée initiale : le nombre de cubes disponibles
      • Implantation d'un while permettant, en partant du haut, d'ajouter des rangs tant qu'il reste suffisament de cubes pour qu'il soit possible d'ajouter encore un rang
      Nombre de cubes = 1000000
      Taille : 143
      15016 cubes inutilises

      #include <stdio.h>
      #include <stdlib.h>

      void main(void) {
        const int SEED = 3;
        srand(SEED);
        int cpt = 0;
        int n = 0;
        while (n != 10000) {
          while ((rand() % 1000) != 0) {
            cpt++;
          }
          n++;
        }
        printf("%d tirages\n", cpt);
      }

      While2.c - While2.exe
      • Calcul du nombre de nombres aléatoires qu'il faut générer pour obtenir 10000 fois un nombre tiré au sort entre 0 et 999 égal à 0
      • Un premier while pour implanter la recherche de 10000 zéros
      • Un second while imbriqué dans le premier pour implanter la recherche d'un nombre aléatoire égal à 0
      • Utilisation des fonctions rand et srand de l'API standard stdlib
        • int rand(void); : retour d'un nombre entier tiré au sort dans l'intervalle [0,RAND_MAX]
        • void srand(unsigned int seed); : initialisation du générateur de nombres aléatoires avec une valeur germe donnée
          -> possibilité de générer les mêmes suites de nombres aléatoires
      • Remarque : Mauvaise qualité fonctionnelle et mathématique du générateur de nombres aléatoires du langage C
        -> Recommandation : en utiliser un autre en faisant appel à une API spécialisée (Exemple : Mersenne Twister)
      9841153 tirages
  • do while
    • Syntaxe
      do {
        ...
      } while ( condition );
    • Exécution du bloc de code entre accolades tant que l'évaluation de la condition indiquée entre parenthèses a pour résultat vrai
    • Evaluation de la condition après une première exécution du bloc de code
      -> Exécution du bloc de code au moins une fois
    • Exemple
    • #include <stdio.h>
      #include <stdlib.h>

      void main(void) {
        const int SEED = 1;
        srand(SEED);
        const int n = 1000000;
        int somme = 0;
        int cpt = 0;
        do {
          somme += (rand() % 101);
          cpt++;
        } while (somme <= n);
        printf("%d nombres nombres tires dans [0,100]", cpt);
        printf(" pour depasser %d\n", n);
      }

      DoWhile1.c - DoWhile1.exe
      • Calcul du nombre de fois où il faut sommer un nombre entier tiré au sort dans l'intervalle [0,100] pour dépasser la valeur 1000000
      • Un do while car il faut générer au moins un nombre aléatoire
      19982 nombres tires dans [0,100] pour depasser 1000000

      #include <stdio.h>
      #include <stdlib.h>

      void main(void) {
        const int SEED = 2;
        srand(SEED);
        const int NOMBRE_DES = 5;
        int cpt = 0;
        int i;
        int tirage;
        int de;
        do {
          cpt++;
          de = 1 + rand() % 6;
          //printf("%d", de);
          i = 0;
          do {
            i++;
            tirage = 1 + rand() % 6;
            //printf(" %d", tirage);
          } while ((i < NOMBRE_DES - 1) && (tirage == de));
          //printf("\n");
        } while (tirage != de);
        printf("%d tirages pour trouver\n", cpt);
        printf(" %d des identiques\n", NOMBRE_DES);
      }

      DoWhile2.c - DoWhile2.exe
      • Calcul du nombre de fois où il faut tirer au sort 5 dés pour trouver une combinaison de 5 dés identiques
      • Un premier do while car il faut faire au moins un tirage de combinaison
      • Un second do while imbriqué dans le premier car il faut tirer 1 + au moins un autre dé pour se rendre compte que la combinaison en train d'être tirée comporte un dé différent du premier dé tiré
      • Instructions printf en commentaires : code qui permet de faire afficher les combinaisons tirées au fur et à mesure de l'exécution de la recherche
      221 tirages pour trouver 5 des identiques
  • for
    • Syntaxe
      for ( initialisation ; condition ; instruction ) {
        ...
      };
    • Exécution du bloc de code entre accolades tant que l'évaluation de la condition indiquée entre parenthèses a pour résultat vrai
    • Trois zones à renseigner : initialisation, condition et instruction
    • Séquencement des zones
      • initialisation
      • test de la condition
      • bloc de code
      • instruction
      • test de la condition
      • bloc de code
      • instruction
      • test de la condition
      • bloc de code
      • instruction
      • ...
      • test de la condition
    • Après initialisation et test de la condition, cycle sur la séquence bloc de code - instruction - test de la condition
    • Possibilité de non exécution du bloc de code -> implantation de l'équivalent d'un while
    • for = while maquillé
    • Question : Pourquoi le for existe-t-il ?
    • Exemple
    • #include <stdio.h>

      void main(void) {
        for (char car = 'a'; car <= 'z'; car++) {
          printf("%c", car);
        }
        printf("\n");
      }

      For1.c - For1.exe
      • Affichage les 26 lettres de l'alphabet minuscule dans l'ordre 'a' à 'z'
      • Un for d'itération du caractère 'a' au caractère 'z' inclus
      abcdefghijklmnopqrstuvwxyz

      #include <stdio.h>

      void main(void) {
        const int ETAGES = 10;
        const int SUP = 3;
        for (int e = 0; e < ETAGES; e++) {
          for (int i = 0; i < ETAGES - e - 1; i++) {
            printf(" ");
          }
          for (int i = 0; i < SUP + 2 * e; i++) {
            printf("*");
          }
          printf("\n");
        }
      }

      For2.c - For2.exe
      • Affichage d'une pyramide formée de 10 étages d'étoiles avec 3 étoiles sur le dernier étage, 5 sur l'avant dernier, 7 sur l'avant avant dernier...
      • Un for pour organiser l'affichage successif de chacun des 10 étages
      • Deux for successifs imbriqués dans le premier for
        • Le premier pour afficher le nombre de caractères ' ' nécessaires en début de ligne pour décaler l'affichage de la première étoile de la ligne
        • Le second pour afficher le nombre de caractères '*' présents sur la ligne
               ***
              *****
             *******
            *********
           ***********
          *************
         ***************
        *****************
       *******************
      *********************

Instructions de structuration conditionnelle

  • if
    • Syntaxe
      if ( condition ) {
        bloc 1
      }
      else {
        bloc 2
      }
    • Exécution de l'un deux blocs d'instructions suivant le résultat de l'évaluation de la condition donnée entre parenthèses
      • Si vrai, exécution du bloc 1
      • Si faux exécution du bloc 2
    • Pas d'obligation d'avoir un else
    • Exemple
    • #include <stdio.h>
      #include <stdlib.h>

      void main(void) {
        const int SEED = 3;
        srand(SEED);
        int v1 = rand() % 101;
        int v2 = rand() % 101;
        int max;
        if (v1 > v2) {
          max = v1;
        }
        else {
          max = v2;
        }
        printf("La valeur max de %d et %d est %d\n", v1, v2, max);
      }

      If1.c - If1.exe
      • Tirage au sort de deux nombres entiers, calcul puis affichage de la valeur maximale des deux entiers
      • Un if de comparaison des deux valeurs par test de supériorité de la premiére par rapport à la seconde avec affectation de la variable max avec la première valeur dans le bloc "alors" et avec la seconde valeur dans le bloc "sinon"
      La valeur max de 48 et 25 est 48

      #include <stdio.h>
      #include <stdlib.h>

      void main(void) {
        const int SEED = 2;
        srand(SEED);
        int v1 = rand() % 101;
        int v2 = rand() % 101;
        int v3 = rand() % 101;
        int max;
        if ((v1 >= v2) && (v1 >= v3)) {
          max = v1;
        }
        else {
          if (v2 >= v3) {
            max = v2;
          }
          else {
            max = v3;
          }
        }
        printf("La valeur max de %d, %d et %d est %d\n", v1, v2, v3, max);
      }

      If2.c - If2.exe
      • Tirage au sort de trois nombres entiers, calcul puis affichage de la valeur maximale des trois entiers
      • Un premier if de comparaison de la première valeur par rapport aux deux autres par test de supériorité ou égalité avec, dans le bloc "alors", affectation de la variable max avec la première valeur et, dans le "sinon", un second if d'arbitrage entre les deuxième et troisième valeurs dont on sait que c'est parmi elles que se trouve le maximum des trois valeurs
      La valeur max de 45, 27 et 59 est 59
  • switch
    • Syntaxe
      switch ( expression ) {
        case v1 : {
          bloc 1
        }
        break;
        case v2
        case v3 : {
          bloc 2
        }
        break;
        default : {
          bloc 3
        }
        break;
      };
    • Exécution de tel ou tel bloc d'instructions suivant la valeur obtenue par évaluation de l'expression donnée entre parenthèses (variable, appel de fonction...)
    • Contraintes
      • Expression à résultat de type entier ou caractère
      • Tests d'égalité uniquement autorisés, pas de test de différence, d'infériorité ou de supériorité
    • Bloc default exécuté si aucune égalité constatée
    • Pas d'obligation d'avoir un default
    • Exemple
    • #include <stdio.h>
      #include <stdlib.h>

      void main(void) {
        const int SEED = 3;
        srand(SEED);
        int couleur = rand() % 4;
        switch (couleur) {
          case 0: {
            printf("Trefle");
          }
          break;
          case 1: {
            printf("Carreau");
          }
          break;
          case 2: {
            printf("Coeur");
           }
          break;
          case 3: {
            printf("Pique");
          }
          break;
        }
        printf("\n");
      }

      Switch1.c - Switch1.exe
      • Tirage au sort d'un nombre entier compris entre 0 et 3
      • Utilisation de ce nombre entier dans un switch pour afficher un nom de couleur de carte à jouer lui correspondant bijectivement
      Trefle

      #include <stdio.h>
      #include <stdlib.h>

      void main(void) {
        rand();
        char lettre = 'a' + (rand() % 26);
        printf("%c : ", lettre);
        switch (lettre) {
          case 'a':
          case 'e':
          case 'i':
          case 'o':
          case 'u':
          case 'y': {
            printf("Voyelle");
          }
          break;
          default: {
            printf("Consonne");
          }
          break;
        }
        printf("\n");
      }

      Switch2.c - Switch2.exe
      • Tirage au sort d'une lettre minuscule (un caractère) et affichage de "voyelle" s'il s'agit d'une voyelle, "consonne" s'il s'agit d'une consonne
      • Un switch portant sur le caractère. Affichage de "voyelle" s'il est égal à l'une des 6 voyelles et de "consonne" par default
      h : Consonne
  • Opérateur conditionnel
    • Syntaxe
      expression1 ? expression2 : expression3;
      Retour soit du résultat obtenu par évaluation de expression2 soit du résultat de l'évaluation de expression3 suivant le résultat de l'évaluation de expression1 interprété en tant que booléen : expression2 pour vrai, expression3 pour faux
    • Exemples
      • a ? b+c : b*c :  si a différent de 0 alors évaluation et retour de b+c, si a égal 0 alors évaluation et retour de b*c
      • (a>b) ? a : b : si a > b alors retour de a, si a <= b alors retour de b
    • Exemple
    • #include <stdio.h>

      void main(void) {
        int a = 5;
        int b = 3;
        int max = a > b ? a : b;
        printf("La valeur maximale de %d et %d est %d.\n",a, b, max);
        printf("\n");
        double d = 0.0;
        d ? printf("La valeur de la variable d est differente de 0.0.\n") :
          printf("La valeur de la variable d est egale a 0.0.\n");
      }

      OperateurConditionnel.c - OperateurConditionnel.exe
      • Affectation de la variable max avec le résultat d'une expression conditionnelle de comparaison des variables a et b pour retourner la valeur maximale de leurs deux valeurs
      • Pas très C, mais possible : Utilisation d'une expression conditionnelle pour arbitrer entre deux affichages
      La valeur maximale de 5 et 3 est 5.

      La valeur de la variable d est egale a 0.0.

Fonctions

  • Possibilité de déporter un algorithme ou une portion d'algorithme dans une "fonction"
    -> Une application = un ensemble de fonctions s'utilisant les unes les autres dont une seule fonction "main" : fonction lancée au lancement de l'application
  • Intérêts du développement avec découpage en fonctions
    • Isoler les uns des autres les divers algorithmes développés
    • Utiliser tout algorithme là ou on le souhaite en appelant la fonction qui l'implante au lieu de l'écrire directement
    • Faciliter le découpage d'un problème complexe en problèmes élémentaires implantés sous la forme de fonctions
    • ...
  • Inconvénient
    • Objectivement, aucun
  • Caractéristiques d'une fonction
    • Un nom qui la désigne et qui sera utilisé lors de son appel
    • Une liste ordonnée de paramètres (paramètres formels) qui devront être fournis lors de son appel (paramètres effectifs, paramètres d'appel)
      • Liste éventuellement vide
      • Description de chaque paramètre par son type et son nom (respect de l'ordre et du type des paramètres formels vis à vis des paramètres d'appels fournis)
    • Eventuellement, une valeur retournée définie, s'il y en a une, par son type
    • Possibilité de définir des paramètres de n'importe quel type, non pointeur ou pointeur
    • Possibilité de retourner une valeur de n'importe quel type, non pointeur ou pointeur
  • Surcharge
    • Pas de surcharge contrairement à C++ ou à Java
      -> Impossibilité d'avoir deux fonctions portant le même nom même si elles ont des listes de paramètres formels différentes (nombre de paramètres et/ou types des paramètres)
  • Fonctions imbriquées
    • Contrairement à certains langages, impossibilité de développer une fonction dans une fonction
    • Un programme = une liste de fonctions (pas un arbre de fonctions)
  • Fonctions void
    • Syntaxe
      void nomFonction(liste des paramètres formels) {
        ...
      };
    • Paramètres formels donnés sous la forme d'une liste nomType nomParamètre, chaque couple étant séparé des autres par une virgule
    • Si pas de paramètre formel, écriture de "void"
    • Corps de la fonction écrit comme n'importe quel algorithme
    • Exemple
    • #include <stdio.h>

      void test1(void) {
        printf("Essai fonction void sans parametre\n");
      }

      void test2(int n) {
        printf("Essai fonction void avec un parametre entier : %d\n", n);
      }

      void main(void) {
        test1();
        printf("\n");
        test2(10);
        printf("\n");
      }

      FonctionsVoid.c - FonctionsVoid.exe
      • Trois fonctions void dont la fonction main
      • Deux des trois fonctions n'ont pas de paramètre, la troisième possède un seul paramètre de type int
      • Appels successifs par la fonction main de chacune des deux autres fonctions
      Essai fonction void sans parametre

      Essai fonction void avec un parametre entier : 10
  • Fonctions non void
    • Syntaxe
      nomType nomFonction(liste des paramètres formels) {
        ...
        return val;
      }
    • nomType : nom du type de la valeur unique retournée par la fonction
    • Paramètres formels donnés sous la forme d'une liste nomType nomParamètre, chaque couple étant séparé des autres par une virgule
    • Si pas de paramètre formel, écriture de "void"
    • Corps de la fonction écrit comme n'importe quel algorithme
    • Conduction de tous les chemins d'exécution de l'algorithme de la fonction à un return dont la valeur retournée doit être du type de retour indiqué en entête de fonction
    • Exemple
    • #include <stdio.h>
      #include <stdlib.h>

      char test1(void) {
        return 'a'+rand()%26;
      }

      double test2(double a) {
        return a * a;
      }

      void main(void) {
        char c = 'a';
        printf("%c\n", c);
        c = test1();
        printf("%c\n", c);
        printf("Carre de %lf = %lf\n",2.5,test2(2.5));
      }

      FonctionsNonVoid.c - FonctionsNonVoid.exe
      • Deux fonctions non void et la fonction main
      • Fonction test1 : pas de paramètre, retour d'un caractère minuscule tiré au sort
      • Fonction test2 : un paramètre de type double, retour de la valeur du paramètre multipliée par elle-même (carré)
      a
      p
      Carre de 2.500000 = 6.250000
  • Entête d'une fonction
    • Contenu
      • Type retour
      • Nom de la fonction
      • Liste ordonnée des paramètres avec pour chacun d'eux le type et le nom
    • Informations nécessaires au compilateur C pour vérifier que le corps de la fonction est syntaxiquement correct et peut être traduit en langage machine
    • Autre nom pour entête : prototype
  • Signature d'une fonction
    • Contenu
      • Nom de la fonction
      • Liste ordonnée des types des paramètres
    • Informations nécessaires au compilateur C pour vérifier qu'un appel de fonction correspond bien à une fonction qui existe
  • Le passage de paramètres
    • Un seul type de passage de paramètre : le passage par valeur
    • Possibilité de passer en entête des paramètres de n'importe quel type
      • Types "classiques"
      • Types pointeurs
    • "Passage par adresse" = passage en valeur d'une adresse (d'un pointeur)
    • Méthode pour passer un paramètre destiné à être modifié par la fonction
      • Développer un entête où le paramètre formel destiné à être modifié est passé par adresse
      • Lors de l'appel à la fonction, passer l'adresse de la variable à modifier en paramètre effectif
      • Dans la fonction, écrire à l'adresse reçue en entête
    • Exemples
    • #include <stdio.h>

      void permutation(int a,int b) {
        printf("# %3d %3d #\n", a, b);
        int aux = a;
        a = b;
        b = aux;
        printf("# %3d %3d #\n", a, b);
      }

      void main(void) {
        permutation(3, 5);
        printf("\n");
        int v1 = 8;
        int v2 = 6;
        printf("@ %3d %3d @\n", v1, v2);
        permutation(v1, v2);
        printf("@ %3d %3d @\n", v1, v2);
      }

      FonctionsPassageParValeur.c - FonctionsPassageParValeur.exe
      • Une fonction avec deux int passés en paramètres en valeur, permutation des valeurs des paramètres formels dans le corps de la fonction
      • Si paramètres effectifs constants, fonction sans action extérieure à elle-même concernant ses paramètres
      • Si variables utilisées comme paramètres effectifs : non modification des variables passées en paramètre alors que les paramètres formels correspondants sont modifiés
        -> fonction sans action extérieure à elle-même concernant ses paramètres
      # 3 5 #
      # 5 3 #

      @ 8 6 @
      # 8 6 #
      # 6 8 #
      @ 8 6 @

      #include <stdio.h>

      void permutation1(int a, int b) {
        printf("=== %3d %3d\n", a, b);
        int aux = a;
        a = b;
        b = aux;
        printf("=== %3d %3d\n", a, b);
      }

      void permutation2(int* a, int* b) {
        printf("### %3d %3d\n", *a, *b);
        int aux = *a;
        *a = *b;
        *b = aux;
        printf("### %3d %3d\n", *a, *b);
      }

      void main(void) {
        int v1 = 8;
        int v2 = 6;
        printf("::: %3d %3d\n", v1, v2);
        permutation1(v1, v2);
        printf("::: %3d %3d\n", v1, v2);
        printf("\n");
        printf("::: %3d %3d\n", v1, v2);
        permutation2(&v1, &v2);
        printf("::: %3d %3d\n", v1, v2);
        printf("\n");
      }

      FonctionsPassageParAdresse.c - FonctionsPassageParAdresse.exe
      • Deux fonctions à deux paramètres formels de type int pour la première et int* pour la seconde, et ayant pour but la permutation des valeurs de leurs paramètres formels respectifs
        • Dans la première fonction, paramètres formels de type int
        • Dans la seconde fonction, paramètres formels de type int*
      • Pas de permutation réalisée sur les paramètres effectifs par la première fonction
      • Permutation effective des valeurs des paramètres effectifs passés par l'intermédiaire de leurs adresses mémoire réalisée par la seconde fonction
      • Attention, obligation de passer des adresses d'int à la seconde fonction
        -> Obligation de passer des adresses de variable
        -> Utilisation de l'opérateur & pour obtenir l'adresse de ces variables
      ::: 8 6
      === 8 6
      === 6 8
      ::: 8 6

      ::: 8 6
      ### 8 6
      ### 6 8
      ::: 6 8
  • Récursivité
    • Support de la récursivité par le langage C
    • Exemple
    • #include <stdio.h>

      long long factoriel(int n) {
        if (n == 0) {
          return 1;
        }
        return n * factoriel(n - 1);
      }

      void main(void) {
        for (int nb = 0; nb <= 25; nb++) {
          printf("%2d! = %25lld\n", nb, factoriel(nb));
        }
      }

      FonctionRecursive.c - FonctionRecursive.exe
      • Une fonction récursive de calcul de n!
      • Un paramètre d'entête de type int : n
      • Un retour de type long long : n!
      • Fonction basée sur la définition récurente de n!
        • Si n = 0, n! = 1
        • Si n > 0, n! = n*(n-1)!
      • Fonction main : affichage de toute les valeurs de n! pour n de 0 à 25
      • Même avec des long long, pas de miracle, overflow assez rapide à n = 21
       0! =                         1
       1! =                         1
       2! =                         2
       3! =                         6
       4! =                        24
       5! =                       120
       6! =                       720
       7! =                      5040
       8! =                     40320
       9! =                    362880
      10! =                   3628800
      11! =                  39916800
      12! =                 479001600
      13! =                6227020800
      14! =               87178291200
      15! =             1307674368000
      16! =            20922789888000
      17! =           355687428096000
      18! =          6402373705728000
      19! =        121645100408832000
      20! =       2432902008176640000
      21! =      -4249290049419214848
      22! =      -1250660718674968576
      23! =       8128291617894825984
      24! =      -7835185981329244160
      25! =       7034535277573963776
  • Ordre d'écriture des fonctions
    • Obligation qu'une fonction soit déclarée (prototypée) avant de pouvoir être utilisée dans une autre fonction
    • Cas simple : écriture entière possible de toute fonction avant le premier endroit où elle est utilisée dans une autre fonction
    • Problème : ordre de précédence parfois impossible à respecter
      Exemple : récursivité où une fonction A utilise une fonction B et où la fonction B utilise la fonction A
    • Solution : pas d'obligation que la fonction A soit écrite entièrement pour pouvoir être utilisée dans B, seule obligation : connaître (déclarer) son entête
      -> Déclaration préalable de l'entête de la fonction A, puis écriture complète de la fonction B (qui utilise A), puis écriture complète de la fonction A selon l'entête déclaré
    • Déclaration : Entête "classique" d'une fonction suivie d'un point virgule
    • Déclaration simplifiable par suppression des noms des paramètres
      -> Liste des paramètres restreinte à la liste des types des paramètres
      Conséquence : perte d'expressivité
    • Exemple
    • #include <stdio.h>

      void fonctionB(int n);

      void fonctionA(int n) {
        if (n >= 0) {
          printf("%d\n", n);
          fonctionB(n - 5);
        }
      }

      void fonctionB(int n) {
        if (n >= 0) {
          printf("%d\n", n);
          fonctionA(n - 3);
        }
      }

      void main(void) {
        fonctionA(30);
      }

      FonctionDeclarationSignature.c - FonctionDeclarationSignature.exe
      • Affichage de la liste des valeurs parcourues en partant d'une valeur n strictement positive et en retranchant tantôt 5 tantôt 3 tant que l'on n'a pas atteint 0 inclus
      • Deux fonctions telles que la première (fonctionA chargée de retrancher 5) utilise la seconde (fonctionB chargée de retrancher 3) et la seconde utilise la première
        -> récursivité sur un cycle de deux fonctions
      • Déclaration de l'entête (du prototype) de fonctionB avant la fonction fonctionA développée entièrement pour que fonctionB soit utilisable pour l'écriture de fonctionA
      • Pas de problème lors de l'écriture complète de fonctionA car fonctionB est écrite avant
      30
      25
      22
      17
      14
      9
      6
      1
  • Variables de type fonction
    • Pour C, une fonction = un pointeur
      -> Possibilité de déclarer des variables de type pointeur sur fonction
    • Syntaxe
      typeRetour (*nomVariable)(...)
      Définition de la variable nomVariable en tant que variable du type pointeur sur fonction de type retour typeRetour et de liste de paramètres donnée à la place des points de suspension
    • Exemple
      void (*fct)(int, double)
      Définition de la variable fct de type pointeur sur fonction void ayant un int en premier paramètre et un double en second paramètre
    • Possibilité d'affecter une variable pointeur sur fonction avec une "vraie" fonction et d'utiliser la variable comme si elle était une fonction
    • Exemple
    • #include <stdio.h>
      #include <stdlib.h>

      void test1(int a, double b) {
        printf("%d %lf\n", a, b);
      }

      char test2(int a) {
        return (char) a;
      }

      double test3(void) {
        return 1.0;
      }

      int addition(int a, int b) {
        return a + b;
      }

      int multiplication(int a, int b) {
        return a * b;
      }

      void main(void) {
        printf("%p\n", test1);
        printf("%p\n", test2);
        printf("%p\n", test3);
        printf("\n");
        void (*f1)(intdouble) = test1;
        char (*f2)(int) = test2;
        double (*f3)(void) = test3;
        f1(2, 3.1);
        printf("%c\n", f2(109));
        printf("%lf\n", f3());
        printf("\n");
        int (*operation)(intint);
        if (rand() % 2 == 0) {
          operation = addition;
        }
        else {
          operation = multiplication;
        }
        printf("%d\n", operation(5, 6));
      }

      FonctionVariablesFonctions.c - FonctionVariablesFonctions.exe
      • Définition de trois fonctions test1, test2 et test3 de signatures différentes
      • Affichage des adresses de ces trois fonctions
      • Définition de trois variables matchant avec les signatures des trois fonctions, initialisation par affectation avec les adresses des trois fonctions
      • Exécution des variables en tant que fonctions
      • Définition de deux fonctions addition et multiplication de signatures identiques
      • Définition d'une variable opération matchant avec la signature commune aux deux fonctions
      • Suivant le résultat d'un rand(), affectation de addition ou de multiplication à opération
      • Exécution d'opération
      00007FF795721070
      00007FF795721090
      00007FF7957210A0

      2 3.100000
      m
      1.000000

      30

Portée des constantes et des variables

  • Structurer le code = créer des blocs d'instructions délimités au début par une accolade ouvrante et à la fin une accolade fermante
  • Possibilité de définir des constantes et des variables dans n'importe quel bloc de code
    • Constante/variable utilisable à partir de sa définition et jusqu'à l'accolade fermante du bloc de code de définition
    • Constante/variable locale à son bloc de code de définition
  • Possibilité d'avoir plusieurs constantes/variables différentes portant le même nom si localisées dans des blocs de code différents
  • Possibilité d'avoir deux constantes/variables de même nom si l'une d'elle est définie dans un bloc de code inclus dans le bloc de code où l'autre est définie
    • Masquage de la variable définie dans le bloc extérieur par la variable définie dans le bloc intérieur le temps de l'exécution de celui-ci
    • Méthodologie de programmation non recommandée car peu claire
  • Paramètre(s) de fonction assimilable(s) à des variables locales à leur fonction de définition
    • Définition jusqu'à l'accolade fermante de fonction
    • Pas forcément recommandé, mais utilisation possible des paramètres de fonction en tant que variables locales
  • Possibilité de définir une constante ou une variable n'importe où en dehors de toute fonction
    • Utilisation possible à partir de la définition, mais définition globale au programme -> pas possible d'avoir une autre définition sous le même nom où que ce soit dans le programme
    • Conseil : limiter l'utilisation de variables globales pour éviter la possibilité que des problèmes d'anarchie mémoire puissent apparaître
      • Variable unique utilisée à des fins différentes par des fonctions différentes
      • Variable unique utilisée par deux fonctions différentes alors que chacune d'elle pense en avoir l'usage exclusif
      • Compilation impossible car deux variables globales portant le même nom ont été définies
      • ...
    • Utilisable pour partager de façon simple de l'information entre fonctions
  • Exemple
  • #include <stdio.h>

    char i = 'A';

    void main(void) {
      int n = 10;
      for (int i = 0; i < 10; i++) {
        double n = 0;
        for (int i = 0; i < 100; i++) {
          n += 1.0;
        }
        printf("%d %lf\n",i, n);
      }
      printf("%c %d\n",i, n);
    }

    PorteeVariablesEtConstantes.c - PorteeVariablesEtConstantes.exe
    • Définition de trois variables nommée i
      • Une variable globale de type char initialisée à 'A'
      • Une variable locale au for principal, de type int, qui masque la variable i globale au cours de l'exécution du for
      • Une variable locale au for intérieur, de type int, qui masque les deux autres variables i au cours de l'exécution du for intérieur
    • Définition de deux variables nommées n
      • Une variable locale à la fonction main de type int initialisée à 10
      • Une varriable locale au for principal, de type double, qui masque la variable n locale à la fonction main
    • Affichage par le printf intérieur au for principal des variables i et n locales au for principal
    • Affichage par le printf extérieur au for principal de la variable globale i et de la variable n locale à la fonction main
    0 100.000000
    1 100.000000
    2 100.000000
    3 100.000000
    4 100.000000
    5 100.000000
    6 100.000000
    7 100.000000
    8 100.000000
    9 100.000000
    A 10