Objectifs pédagogiques

Les principaux objectifs de cette feuille d'exercices sont :

  • de découvrir la syntaxe du langage C à travers des exercices de lecture, de mise en forme et de recherche d'erreurs ;
  • de coder des premiers programmes élémentaires « classiques » (afficher la valeur d'une fonction, une table de multiplication, etc.).

Pour traiter ces exercices, il serait idéal d'avoir étudié le cours jusqu'au chapitre C2‑VII inclus. Néanmoins, cela obligerait à retarder considérablement les séances de travaux pratiques en classe. De plus, certains exercices ne font appel qu'à un nombre ciblé de connaissances et des renvois aux principaux éléments de cours requis sont indiqués au fur et à mesure des questions, de sorte qu'il est possible d'aborder les questions dans un état d'esprit de découverte, sans objectif de maîtrise.

Travail demandé

Les exercices sont presque tous indépendants les uns des autres, mais ordonnés par niveaux croissants de difficultés et de connaissances requises. Certaines questions peuvent être traitées sur cahier, mais la plupart nécessitent l'emploi d'un ordinateur.

Pour coder les programmes, il est vivement recommandé d'utiliser un éditeur de code comme, typiquement, Sublime Text, à paramétrer pour obtenir une coloration syntaxique adaptée au langage C (cf. chap. C1‑II ).

  • Au fur et à mesure, enregistrer chaque fichier source principal en le nommant conformément à la numérotation des exercices, c'est‑à‑dire par exemple pour l'exercice 1, question a :
    C2exo1_a.c
  • Tous les fichiers, y compris les exécutables générés par la chaîne de compilation locale, doivent être regroupés dans un répertoire d'exercices nommé par exemple C2_EXOS, lui‑même placé dans le répertoire principal de programmation, lui‑même placé dans répertoire personnel d'étudiant.
  • La compilation et l'exécution des programmes peuvent être effectués : avec la commande gcc directement dans un terminal exécuté depuis le répertoire d'exercices (cf. chap. C1‑II ).

Répondre aux questions supplémentaires sur feuille ou cahier.

  1. Analyse syntaxique d'un programme simple
  2. Lire attentivement le code source ci‑dessous.
    /* Academic dull program to discover C language elements
     */
    #include <stdio.h>                 
    
    int main(void)
    {                               
      int typedNumber = 0;                       
      printf("Tapez un chiffre compris entre 1 et 3,\n"); 
      printf("ou tout autre chiffre pour quitter.\n");
    
      do {       
        printf("> ");
        scanf("%d", &typedNumber);  
        if (typedNumber == 1) { 
          printf("  C'est peu !\n");
        }            
        else if (typedNumber == 2) {
          printf("  C'est moyen...\n");
        }
        else if (typedNumber == 3) {
          printf("  C'est beaucoup !\n");
        }
      }
      while (typedNumber >= 1 && typedNumber <= 3);
    
      printf("Au revoir.\n");  
      return 0;  
    }
    
    1. Syntaxiquement, comment s'appelle l'élément de code des lignes nº 1 et 2 ? À quoi sert‑il ? (cf. chap. C2‑II )
    2. Syntaxiquement, comment s'appelle l'élément de code de la ligne nº 3 ? À quoi sert‑il ? (cf. chap. C2‑II )
    3. Syntaxiquement, comment s'appelle l'élément de code de la ligne nº 7 ? À quoi sert‑il ? (cf. chap. C2‑III )
    4. De quoi est composée l'instruction de la ligne nº 12 ? Quel est son but ? (cf. chap. C2‑VII )
    5. De combien d'instructions le bloc de définition de la fonction main est‑il constitué ? Indiquer le nº de la première ligne de chacune de ces instructions (cf. chap. C2‑II ).
    6. De combien d'instructions le bloc de l'instruction do while est‑il constitué ? Indiquer le nº de la première ligne de chacune de ces instructions (cf. chap. C2‑II  ).
    7. Syntaxiquement, comment s'appelle l'élément de code à la ligne nº 24, placé entre le mot‑clef while et le symbole ; ? De combien d'atomes est‑il constitué ? En établir la liste (cf. chap. C2‑II  ).
    8. Syntaxiquement, comment s'appelle l'élément de code && ? Que code‑t‑il ? (cf. chap. C2‑IV )
  3. Mise en forme et analyse sémantique d'un code source
  4. Le programme ci‑dessous affiche un tableau de dix lignes de conversion de températures de °C en °F W, entre deux valeurs limites saisies par l'utilisateur.
    /* Academic program to convert temperatures from °C to °F */
    #include <stdio.h>
    int main(void) {
    const float C_TO_F_RATE = 9.0/5.0;
    const float C_TO_F_OFFSET = 32.0;
    int maxTemp_C = 0;
    int minTemp_C = 0;
    printf(" -- Tableau de conversion °C -> °F -- \n");
    printf("Tapez les deux valeurs limites du tableau en °C : ");
    scanf("%d %d", &minTemp_C, &maxTemp_C);
    if (maxTemp_C < minTemp_C) {
    int bufferVar = minTemp_C;
    minTemp_C = maxTemp_C;
    maxTemp_C = bufferVar;}
    if (maxTemp_C < minTemp_C + 10) {
    maxTemp_C = minTemp_C + 10;}
    int deltaTemp_C = (maxTemp_C - minTemp_C) / 10;
    for (int temp_C = minTemp_C; temp_C <= maxTemp_C; temp_C += deltaTemp_C) {
    float temp_F = temp_C * C_TO_F_RATE + C_TO_F_OFFSET;
    printf("%4d °C = %4.0f °F\n", temp_C, temp_F);
    } return 0; }
    
    1. Indenter les instructions conformément à la structure du programme et avec la même convention d'indentation que celle du programme de l'exercice 1 (cf. chap. C2‑X ).
    2. Ajouter judicieusement des lignes d'aération pour séparer les différentes parties du programme et les rendre ainsi plus facile à repérer (cf. chap. C2‑X ).
    3. À quoi servent les deux constantes déclarées C_TO_F_RATE et C_TO_F_OFFSET au tout début du bloc de définition de la fonction main ? (cf. l'article Wikipedia W)
    4. Pourquoi l'ordre de saisie des deux valeurs limites du tableau n'a‑t‑il pas d'incidence sur l'affichage ? Que se passe‑t‑il si l'utilisateur tape deux valeurs limites égales ?
    5. * En observant la sortie du programme sur le terminal, analyser l'instruction d'affichage des valeurs de températures (appel de la fonction printf à la ligne nº 20 dans le code supra – cf. chap. C2‑VII ). L'affichage est‑il satisfaisant pour les valeurs de températures usuelles ?
    6. * Observer les constantes littérales (c'est‑à‑dire les valeurs numériques) employées dans le code source. En déduire une modification pour rendre le programme plus facile à faire évoluer (cf. chap. C2‑III ).
    7. Ajouter une ligne de commentaire avant chaque groupe d'instructions constituant une partie distincte du programme.
  5. Recherche d'erreurs de syntaxe
  6. Dans les extraits de code source ci‑dessous, rechercher les erreurs de syntaxe (et uniquement de syntaxe) dans chaque extrait, sachant que le nombre d'erreurs est indiqué pour aider. Proposer une correction à chaque erreur.

    Remarque. Sauf lorsque le cadre commence en ligne nº 1, le code n'est pas complet. Le fait qu'un identificateur apparaisse sans avoir été déclaré n'est a priori pas une erreur (la déclaration est supposée codée précédemment).

    Pour tester un tel extrait de code et sa correction avec un compilateur, il est donc indispensable :

    • de le copier dans le bloc de définition d'une fonction main (cf. chap. C2‑I ) ;
    • de faire précéder cette fonction de toutes les directives d'inclusion requises (cf. chap. C2‑I ) et d'une déclaration ad‑hoc pour chaque identificateur figurant dans le code (cf. chap. C2‑III ).
    1. Une erreur de syntaxe (cf. chap. C2‑I ) :
    2. #include <stdio.h>
      int main
      {
        printf("Hello, Wordl\nn");
        return 0;
      }
      
    3. Deux fois la même erreur lexicographique (cf. chap. C2‑II ) :
    4.   float price_in_£ = raw_price * (1.0 + VAT_RATE);
        printf("The item costs %5.2f pounds.\n", price_in_£);
      
    5. Deux erreurs (cf. chap. C2‑II  et  et chap. C2‑VII ) :
    6.   globalTempVariation = lastMeasure - 1stMeasure;
        printf("The temperature has varied of %d °C.\n, globalTempVariation");
      
    7. Trois erreurs de syntaxe (cf. chap. C2‑V  ) et  :
    8.   if (measuredPressure > LOW_PRESSURE) && (measuredPressure < HIGH_PRESSURE) {
          compressorRelayState = LOW;
          alertSignal = LOW;
        }
        else if (measuredPressure ≤ LOW_PRESSURE) {
          compressorRelayState = HIGH;
          alertSignal = LOW;
        else {
          compressorRelayState = LOW;
          alertSignal = HIGH;
        }
        
      
    9. Trois erreurs de syntaxe (cf. chap. C2‑VII  et chap. C2‑V ) :
    10.   int counter = 0;
        char typedChar;
        do {
          printf("Compteur de boucle = \n", ++counter);
          printf("Souhaitez-vous continuer, tapez "o", sinon toute autre touche : "); 
          scanf(" %c", &typedChar);  
        }
        while (typedChar == 'o')
      
  7. Recherche d'erreurs de codage
  8. Dans les extraits de code source ci‑dessous, rechercher les erreurs de codage, sachant :

    • qu'il peut y avoir plusieurs erreurs dans chaque extrait de code ;
    • qu'il peut s'agir d'erreurs d'exécution et non pas seulement de syntaxe (pour le programme de la question d), il faut le tester, en ayant compris son but).

    Pour chaque erreur, proposer une correction.

    Remarque. Comme pour l'exercice précédent, sauf lorsque le cadre commence en ligne nº 1, le code n'est pas complet. Le fait qu'un identificateur apparaisse sans avoir été déclaré n'est a priori pas une erreur (la déclaration est supposée codée précédemment).

    1.   if (currentDate = birthdayDate) {
          print("Joyeux anniversaire !\n");
        }
        else {
          print("Joyeux non-anniversaire !\n");
        }
      
    2.   if (x >= 0) 
          printf("x positif");
          if (y >= 0) 
            printf(" et y aussi.\n");
      
    3.   for (int p = 10; p <= 0; p--) {
          printf("L'inverse de %d est %g\n.", p, 1.0/p);
        }
      
    4. #include <stdio.h>
      
      void Main(){}
        int newAttempt = 3;
        int secretCode = 1234;
      
        do {
          int typedCode = 0;
          if (newAttempt <= 2) {
            printf("Il vous reste %d tentatives.\n");
          }
          printf("Tapez votre code secret : ");
          scanf("%d", &typedCode)
          newAtempt--;
        }
        while (typedCode == secretCode || newAttemps > 0);
        
        if (typedCode == secretcode) {
          printf("Bienvenue !\n");
        }
        else {
          printf("Code incorrect, contactez votre administrateur !\n");
          return -1;
        }
        return 0;
      }
      
    5. Remarque : ce programme comporte de nombreuses erreurs de syntaxe ainsi que quelques erreurs de logique. Pour les détecter, il est recommandé :
      • de commencer par corriger immédiatement les erreurs évidentes de syntaxe ;
      • puis de recourir à un compilateur pour trouver toutes les erreurs restantes et les corriger ;
      • et enfin, d'exécuter le programme avec divers scénarios et constater les dysfonctionnements à corriger…
  9. Codage de fonctions mathématiques
  10. En s'inspirant du programme analysé à l'exercice 1, coder un programme qui, en boucle :

    • demande à l'utilisateur de saisir un entier positif n,
    • affiche la valeur du nombre f(n) où f est une fonction spécifiée aux questions a) et b) ci‑après,

    sachant que :

    • si une valeur négative est saisie, un bref message s'affiche pour signaler l'impossibilité du calcul ;
    • l'exécution du programme s'achève lorsque l'utilisateur saisit la valeur 0.
    1. Traiter l'exercice avec la fonction f(n) = √n (racine carrée, en anglais square root). On exploitera le fichier d'en‑tête de la bibliothèque standard math.h – cf. chap. C2‑IV .
    2. Traiter l'exercice avec la fonction f(n) = n! (factorielle) W. On codera le calcul de la valeur de cette fonction avec une boucle for (cf. chap. C2‑V ) en stockant le résultat dans une variable f déclarée de type int juste avant la boucle.
    3. Pour le programme de la question b), que se passe‑t‑il lorsque n > 12 ? Pour cela, tester la valeur obtenue pour n = 13  n = 14, etc. puis consulter le cours, chap. C3‑II .
  11. Codage de bifurcations multiples
    1. Recoder le programme de l'exercice 1 en employant une bifurcation multiple switch (cf. chap. C2‑V ) à la place de la succession de bifurcations simples if… else if… (lignes nº 13 à 21) – ne rien changer aux autres lignes du code source.
    2. Vérifier que le programme s'exécute correctement, exactement comme avant.
    3. Dans le programme de l'exercice précédent, question a), est‑il possible de traiter les trois cas – valeur négative, positive ou nulle – à l'aide d'une bifurcation switch pour la saisie de l'utilisateur ? Sinon, pourquoi ?
  12. Codage d'un jeu de devinette
  13. Dans le code source ci‑dessous, les lignes nº 11 & 12 génèrent un nombre entier aléatoire x compris entre 1 et 100. En s'inspirant des exercices précédents (notamment l'exercice 1), compléter ce code pour composer un programme qui demande à l'utilisateur de deviner ce nombre, en affichant à chaque tentative si le nombre qu'il a saisi est plus petit ou plus grand que x. La valeur 0 sera utilisée pour permettre au joueur d'abandonner le jeu.

    /* Simple guess game program
     */
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    const int MAX_NUMBER = 100;
     
    int main(void)
    {
      srand(time(NULL)); 
      int x = 1 + rand() % MAX_NUMBER; // number to be guessed
      int typedNumber = 0;
      printf("%d\n", x);              // uncomment for debuggging - comment for playing
      printf("Try to guess the hidden number between 1 and %d (0 to quit):\n", MAX_NUMBER);
      
      do {
        printf("> ");
        scanf("%d", &typedNumber);  
      // ... to be completed
    }
    
  14. Affichage d'une table de multiplication
  15. Coder à l'aide d'une boucle for (cf. chap. C2‑V ) un programme qui affiche la table de multiplication d'un nombre entier positif n jusqu'à un facteur multiplicatif m donnés. Par exemple, si n = 7 et m = 10, le programme doit afficher :

     0 x 7 = 0
     1 x 7 = 7
     2 x 7 = 14
     3 x 7 = 21
     4 x 7 = 28
     5 x 7 = 35
     6 x 7 = 42
     7 x 7 = 49
     8 x 7 = 56
     9 x 7 = 63
    10 x 7 = 70
    

    On déclarera les constantes entières nommées :

    • NUMBER pour le nombre n qui fait l'objet de la table de multiplication ;
    • MAX_FACTOR pour la valeur maximale du facteur multiplicatif m de la table.

    Tester le bon fonctionnement du programme pour diverses valeurs de n et m.

  16. Affichage d'une liste de multiples
  17. Coder un programme qui affiche la liste de tous les multiples d'un nombre entier positif n qui sont inférieurs à une valeur maximale m, avec un saut de ligne à chaque dizaine. Par exemple, si n = 4 et m = 100, le programme doit afficher :

     4  8 
    12 16 20 
    24 28 
    32 36 40 
    44 48 
    52 56 60 
    64 68 
    72 76 80 
    84 88 
    92 96 100 
    

    On pourra procéder à l'aide d'une boucle for (cf. chap. C2‑V ) par incrémentation unitaire d'un nombre k, en testant sa divisibilité par n. On s'aidera pour cela de l'opérateur modulo : l'expression k % n == 0 est vraie si et seulement si k est divisible par n (cf. chap. C2‑IV  et chap. C3‑II ).

    On déclarera les constantes entières nommées :

    • FACTOR pour le nombre n dont on veut afficher les multiples ;
    • MAX_NUMBER pour le nombre maximal m en dessous duquel on cherche les multiples de n.

    Tester le bon fonctionnement du programmes pour diverses valeurs de n et m. Pour les valeurs de n supérieures à 10, prendre m = 1000 et effectuer un saut de ligne à chaque centaine.

  18. * Affichage d'une liste de nombres premiers
  19. En s'inspirant de l'exercice précédent, coder un programme qui affiche la liste de tous les nombres premiers W inférieurs à une valeur maximale m = 1000 avec un saut de ligne à chaque centaine, comme ci‑dessous :

      2   3   5   7  11  13  17  19  23  29  31  37  41  43  47  53  59  61  67  71  73  79  83  89  97 
    101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 
    211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 
    307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 
    401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 
    503 509 521 523 541 547 557 563 569 571 577 587 593 599 
    601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 
    701 709 719 727 733 739 743 751 757 761 769 773 787 797 
    809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 
    907 911 919 929 937 941 947 953 967 971 977 983 991 997 
    

    Remarque. L'algorithme le plus « simple » pour déterminer si un nombre est premier – à savoir tester tous les nombres en recherchant leurs diviseurs parmi tous les nombres inférieurs – peut quand même faire l'objet d'optimisations. En effet :

    • hormis 2, aucun nombre pair n'est premier, donc on peut ne tester que les nombres impairs à partir de 3 ;
    • de même, pour chaque nombre testé, on peut ne chercher ses éventuels diviseurs que parmi les nombres impairs, et s'arrêter lorsqu'on a atteint sa racine carrée.
  20. * Décomposition d'un entier en facteurs premiers
  21. En s'inspirant des programmes précédents, coder un programme qui demande à l'utilisateur de saisir un nombre entier positif et qui affiche sa décomposition en facteurs premiers W. Par exemple, si l'utilisateur saisit le nombre 120, le programme doit afficher :
    120 = 2 x 2 x 2 x 3 x 5

    De plus, si l'utilisateur saisit un nombre premier, alors le programme produire un affichage de la forme :
    nombre = nombre   it's a prime number!
    Rappel : Le nombre 1 n'est pas premier ; le nombre 2 est premier.

    Tester ce programme avec tous les nombres de 1 à 13, puis avec le nombre 2 000 000 000 puis avec les nombres premiers :

    • 2 000 003,
    • 20 000 003,
    • 200 000 033,
    • 2 000 000 011.