Syntaxe du C

Merci de me signaler toute omission, ambiguïté ou erreur à cette adresse : webmaster@barbier-accary.info.

attention ! C fait la différence entre minuscules et majuscules, donc attention à la typographie !

 les variables 

  1. typographie (obligations et conseils)

    • Les noms de variables sont constitués des 2 fois 26 lettres de l'alphabet, ainsi que des 10 chiffres et du symbole "_" (underscore, souligné). Tout autre caractère (notamment les lettres accentuées) est prohibé. De plus, un nom de variable ne peut pas commencer par un chiffre.
    • Généralement, les noms de variables sont en minuscules. On n'utilise les majuscules et le souligné que pour augmenter la lisibilité (ex : maVariable ou ma_variable). On s'efforcera en tous cas de ne pas employer de majuscule pour la première lettre d'une variable afin de faire la différence au premier coup d'oeil entre variable et type ou fonction.
    • Les types sont soumis à la même typographie que les variables. Cependant, afin de les différencier de ces dernières, on préfèrera au contraire faire commencer le nom d'un type par une majuscule.
    • Toute phrase C se termine par un ";". On appelle ici phrase un bloc sémantique d'instruction tel que "je déclare des entiers i et j" qui s'écrit int i, j; ou encore "j'affecte à la variable v la valeur de retour de la fonction F qui n'a pas de paramètre" qui s'écrit v = F();. Les blocs composés tels que if (<test>) { <instructions du alors> } else { <instructions du sinon> } ou les boucles ne se terminent pas par un point virgule car les accolades (nécessaires lorsqu'il y a plus d'une instruction) jouent le rôle de délimiteur.
  2. les types simples

    • Les types de base sont l'entier (int), le flottant (float) et le caractère (char).
    • Attention, on utilise parfois par abus de langage le terme réel pour parler d'un flottant. Ces deux termes ne sont pas équivalents : l'ensemble des réels est dense alors que celui des flottants ne l'est pas (à une précision p donnée, aucun flottant n'existe entre 1.10-p et 2.10-p par exemple).
    • exemples de définition :
        int i;
        float r;
        char c;
    • Pour déclarer deux variables d'un même type, on peut écrire deux déclarations sur deux lignes séparées ou plus simplement séparer les deux noms de variables par une virgule dans la même déclaration (ex : int i,j;)
    • Deux adjectifs permettent de modifier les type int : short et long. short utilise deux fois moins d'octets pour stocker la variable, et long deux fois plus.
    • Par défaut, les nombres utilisés sont signés (positifs ou négatifs). Lorsque l'on sait que l'on n'utilisera que des nombres positifs, on peut doubler le nombre de valeurs possibles en ajoutant unsigned avant le type int ou char dans la définition (ex: unsigned int entierpos;). Attention dans ce cas aux effets de boucle lorsque l'on décrémente un entier non signé ayant pour valeur 0, le test (entierpos < 0) sera ainsi toujours faux !!
    • Il existe un quatrième type de base : double. Un double est un flottant en double précision.
    • D'autres types de base sont progressivement rajoutés. Parmi eux bool qui peut prendre les valeurs vrai (true) et faux (false).
  3. les types évolués

    • La première évolution des types simples consiste en l'utilisation de tableaux.
      On défini un tableau en ajoutant après le nom de la variable le nombre de cases entre crochets : <type d'une case> mon_tableau[<nombre de cases>];.
      Attention, on ne peut pas affecter une valeur à un tableau à l'aide de l'opérateur d'affectation =. On doit faire des affectations case par case. On accède à la ième case d'un tableau par mon_tableau[i-1].
      Attention, les n cases d'un tableau sont numérotées de 0 à n-1 !!!
    • Une chaîne de caractères est un tableau particulier : char chaine[128];. Pour simplifier les manipulations des chaînes de caractères, on utilise des instructions du fichier d'en-têtes <string.h> (cf utilisation de fichiers d'en-têtes) comme strcpy (pour string copy) et strcat (pour string concate).
      Ainsi strcpy (chaine, "mon "); donne la valeur "mon " à la variable chaine.
      Puis strcat(chaine, "nom"); concatène la valeur "nom" à celle de chaine.
      Dès lors, la valeur de la variable chaine vaut "mon nom".
      Attention, la taille d'une chaîne de caractères doit être strictement supérieure au nombre de caractères qui la compose. En effet, C ajoute le caractère de contrôle invisible '\0' pour marquer la fin de la chaîne.
    • On peut également définir des types à l'aide de l'instruction typedef (ex : typedef unsigned char bool; pour ajouter le type bool au C s'il n'existe pas).
    • Pour définir une structure, on panache généralement l'utilisation des instructions typedef et struct.
      La structure permet de regrouper des champs hétérogènes à l'intérieur d'une même variable.
      Prenons l'exemple d'une structure personne regroupant son nom, son prénom et son âge :
      • déclaration :
              typedef struct
              {
                     char nom[20];
                     char prenom[20];
                     unsigned int age;
              } Personne;
      • utilisation :
              Personne personne1;
           strcpy(personne1.nom, "Dupont");
           strcpy(personne1.prenom, "Jean");
           personne1.age = 20;

        Remarque : on utilise un point "." pour séparer le nom de la variable et un de ses champs.
  4. les constantes

    • En C, les constantes sont des variables qui ne peuvent changer de valeur. Pour définir une constante, il suffit donc de faire précéder la déclaration classique par le mot prédéfini const.

 les fonctions (et procédures

  1. typographie conseillée

    • Les noms de fonctions (et procédures) sont soumis à la même typographie que les types. On utilisera de préférence une majuscule pour la première lettre.
  2. construction

    • Une fonction est un sous-programme renvoyant une valeur d'un type donné et qui peut utiliser des paramètres de types hétérogènes.
      Déclaration de fonction :
      • <type fonction> MaFonction(<type param1> param1, ..., <type paramN> paramN)
        {
            <déclaration des variables locales>
            <corps de la fonction>
            return(<constante, variable ou résultat du type de la fonction>);
        }
    • Si aucun paramètre n'est nécessaire, il suffit de mettre le type void entre les parenthèses.
    • Appel de fonction :
      • <type param1> var1;
        ...
        <type paramN> varN;
        <type fonction> ma_variable;
        ma_variable = MaFonction(var1, ..., varN);
    • Les noms des paramètres transmis et reçus n'ont pas à être identiques ! Seuls doivent correspondre les types. C'est l'ordre qui détermine l'équivalence entre les noms.
    • Par défaut, la transmission des paramètres se fait par valeur, c'est-à-dire que lors d'un appel de fonction, une nouvelle variable est créée pour chaque paramètre puis initialisée avec la valeur du paramètre transmis.
      Attention, puisqu'il y a recopie lors d'un passage par valeur, toute modification d'un paramètre à l'intérieur d'une fonction n'affecte pas le paramètre transmis lors de l'appel !
    • Si l'on désire que les modifications apportées à un paramètre transmis soient effectives en dehors de la fonction, il faut faire un passage par référence (il suffit de mettre un & entre le type et le nom du paramètre). Une référence est un synonyme ; le paramètre transmis et celui reçu sont donc totalement équivalents.
      Remarque : il n'est pas nécessaire de mettre un & pour pouvoir modifier les cases d'un tableau.
    • En fait, le passage par référence n'existe qu'en C++, en C on utilise le processus quasi-similaire de passage par pointeur mais nous ne l'étudierons qu'en IF5.
  3. prototypage

    • Il peut arriver qu'on veuille ou doive indiquer au compilateur qu'une fonction existe avant qu'on ne la définisse car la compilation est séquentielle, donc toute fonction ne connaît que ce qui a été déclaré au-dessus (ex : 2 fonctions qui s'appellent.mutuellement dans leur corps).
    • Pour cela, il suffit de mettre son prototype, c'est-à-dire la première ligne de la déclaration d'une fonction terminée par un point virgule. On pourra alors définir réellement la fonction plus bas.
  4. les procédures

    • En C, il n'existe pas de procédure au sens propre du terme puisque tout sous-programme doit renvoyer une valeur. Les procédures sont donc des fonctions qui ne renvoient rien. Pour définir une procédure, il suffit d'indiquer lors de la déclaration de la fonction-procédure que le type renvoyé est vide (void) et de ne pas terminer le corps par la commande return devenue inutile.

 les fichiers d'en-têtes 

  • Toutes les instructions de C ne sont pas connues directement du compilateur. Il revient au programmeur d'inclure les fichiers d'en-têtes contenant les instructions utilisées.
  • Les fichiers d'en-têtes sont des fichiers .h qu'il faut inclure de la manière suivante : #include <nom.h>
    Attention, les chevrons font partie de la syntaxe et il n'y a pas de point virgule à la fin ! Ce sont des instructions de pré-compilation (qui ne font donc pas partie du C)
    Afin de différencier les fichiers d'en-têtes du C et ceux définis par le programmeur, on mettra des guillemets plutôt que des chevrons pour ces derniers : #include "mon-en-tete.h"
  • Par exemple pour les entrées-sorties (interactions avec l'utilisateur) il faut inclure le fichier d'en-tête iostream.h. Pour utiliser des fonctions mathématiques (cos, constante M_PI, ...) il faut inclure le fichier d'en-tête math.h. Pour les fonctions sur les chaînes de caractères, on utilise string.h.
  • Pour définir vos propres fichiers d'en-têtes, il suffit donc de créer un fichier .h dans lequel vous ne mettrez que les prototypes des fonctions que vous définirez dans un autre fichier .c (généralement de même nom). Les fichiers .h et .c créés doivent être ajoutés au projet.

 les entrées-sorties 

  1. <iostream.h> pour input-output stream (flux d'entrée et de sortie) : syntaxe C++

    • affichage : cout << "entier : " << 1 << ", flottant : " << 1.0 << ", caractère : " << 'c' << ", chaîne : " << "chaine" << endl;
    • endl indique un saut de ligne (END of Line).
    • saisie : cin >> variable;
      On n'écrit jamais cin >> var1 >> var2; ou cin >> var1 >> endl;
      Si variable est un tableau, il faut faire un cin pour chacune des cases du tableau : for(i=0; i<n; i++) cin >> tableau[i];
      Exception : les chaînes de caractères : char machaine[128]; cin >> machaine;
  2. <cstdio.h> pour C standard input-output : syntaxe C

    • affichage : printf("entier : %d, flottant : %f, caractère : %c, chaîne : %s\n", 1, 1.0, 'c', "chaine");
    • \n est le caractère d'échappement indiquant un saut de ligne.
    • saisie : scanf("%d", &variable_entiere);
      On utilise le caractère correspondant au type (%d, %f, %c, %s).
      Attention : le & est obligatoire pour saisir un entier, un fottant ou un caractère mais il ne doit pas être utilisé lors de la saisie d'une chaîne de caractères.

 les opérateurs 

  1. Opérateur d'affectation

    • = est l'opérateur d'affectation, il faut le lire reçoit. Ainsi, x = 2 * y; se lit x reçoit le double de la valeur de y.
  2. Opérateurs arithmétiques

    • +, -, *, /.
      Attention, l'opérateur / représente la division entière si les deux opérandes sont des nombres entiers.
    • % : calcule le modulo (reste de la division entière).
    • sqrt(x) : calcule la racine carrée de x.
  3. Opérateurs mixtes (affectation et arithmétique)

    • ++ : var_entiere++ ou ++var_entiere est équivalent à var_entiere = var_entiere + 1
    • -- : var_entiere-- ou --var_entiere est équivalent à var_entiere = var_entiere - 1
    • différence entre var++ et ++var (idem avec --) :
      int i=0, j;
      j = i++;
      /* on a alors i=1 et j=0 */
               int i=0, j;
      j = ++i;
      /* on a alors i=1 et j=1 */
      En fait, si l'opérateur est utilisé en préfixe, il est exécuté avant toute autre opération (donc avant l'affectation) alors que s'il est postfixé l'affectation est effectuée la première puis l'opérateur est utilisé sur la variable.
    • += : var1+= var2 est équivalent à var1 = var1 + var2
    • de même, il existe -=, *=, /=
  4. Opérateurs de test

    • pour les comparaisons de nombres :
      == : (var1 == var2) se lit est-ce que var1 à la même valeur que var2 ?
      et les autres opérateurs : <, >, <=, >=
    • pour les comparaisons de chaînes de caractères : strcmp(chaine1,chaine2) qui renvoit un nombre négatif, nul (égalité) ou positif.
    • pour les opérations logiques : && (et), || (ou), ! (non)
    • Attention, par précaution, on met des parenthèses autour de chaque test pour éviter les problèmes de priorité d'opérateurs. Par exemple, pour vérifier que trois variables sont triées, on écrit (((var1<var2) && (var2<var3)) || ((var1>var2) && (var2 > var3))).
  5. Opérateurs booléens bit à bit

    • Si l'on utilise un seul & ou un seul |, le et binaire et le ou binaire se font bit à bit.
      Ex : 11 & 6 == 2 car en binaire 1011 & 0110 == 0010
    • Appliqués à des entiers, les opérateurs << et >> sont des opérateurs de déplacement et correspondent respectivement à des multiplication et division par des puissances de 2.
      Ex : 1 << 3 == 8 car on déplace l'entier 1 de 3 positions sur la gauche, ce qui donne 1000, soit 23=8.

 les structures de test 

  1. "if"

    • syntaxe : if (<test>) { <instructions du alors> } else { <instructions du sinon> }
    • les accolades d'un sous-bloc ne sont nécessaires que s'il contient plus d'une phrase C.
    • Les if peuvent être imbriqués les uns dans les autres. Dans ce cas, il est préférable de toujours mettre des accolades afin d'éviter les problèmes de mauvais branchements de parties alors ou sinon.
  2. "(...) ? ... : ..."

    • syntaxe : (<test>) ? { <instructions du si vrai> } : { <instructions du si faux> }
    • on n'utilise cette syntaxe que pour de petits traitements comme la définition d'une fonction minimum :
        double Minimum(double x, double y) { return (x<y) ? x : y; }
  3. "switch ... case"

    • syntaxe : switch (<variable entière>) {
                 case <valeur 1> :
                         <instructions>
                 [break;]
                 ...
                 case <valeur N> :
                         <instructions>
                 [break;]
                 [default: <instructions pour les autres cas>]
             }
    • dans la syntaxe ci-dessus, les crochets [] indiquent les parties optionnelles. Si un break n'est pas placé à la fin des instructions d'un case, alors les instructions du case d'après seront également exécutées. La partie default permet de gérer tous les autres cas non déclarés explicitement.
    • Cette stucture est par exemple particulièrement adaptée à la gestion des choix d'un menu.

 les boucles 

  1. "do ... while"

    • syntaxe : do { <corps de la boucle> } while (<test de répétition de boucle>);
    • Le corps de la boucle est effectué tant que le test de répétition est vrai.
  2. "while"

    • syntaxe : while(<test d'entrée de boucle>) { <corps de la boucle> }
    • tant que le test d'entrée est vrai le corps de la boucle est effectué.
  3. différences entre do-while et while

    • Le corps d'une boucle do-while est effectué au moins une fois alors que celui d'une boucle while peut ne jamais être effectué !
  4. "for"

    • syntaxe : for(<initialisation>; <test d'entrée de boucle>; <incrémentation>) { <corps de boucle> }
    • En fait, une boucle for est une écriture plus simple d'une boucle while. Les deux programmes ci-dessous sont totalement équivalents :
      i = 0;
      while (i<10)
      {
          //...
          i++;
      }
                  
      for(i=0; i<10; i++)
      {
          //...
      }
       

 programme type 

  1. construction

    <inclusion des fichiers d'en-têtes>
     
    <déclarations des types>
     
    <déclarations des fonctions et procédures>
     
    int main(void)
    {
        <corps du programme principal>
     
        return EXIT_SUCCESS;
    }
  2. commentaires

    • Remarque : il est fortement souhaitable de commenter le code source afin d'expliciter le rôle des fonctions et des variables !
    • Les commentaires doivent être encadrés par /* et */.
    • D'autre part, tout ce qui suit // sur une ligne est considéré comme un commentaire également (issu du C++).
  3. présentation

    • Remarque : l'indentation est primordiale ! Autrement dit, il est important pour la lisibilité (debogage) de décaler la marge gauche du code pour marquer l'appartenance à une fonction ou une boucle.
    • Visual C++ vous propose automatiquement d'indenter votre code, laissez le faire !!

 Codage des nombres en binaire 

  1. les entiers naturels

    • Nous utilisons habituellement la base 10. Ainsi, le nombre 52 vaut 2*100+5*101.
    • On souhaite ici représenter les nombres en base 2, donc trouver une décomposition de la forme x0*20 + x1*21 + x2*22 + ....
      Pour cela, on divise par 2 de manière récursive puis on note les restes (0 ou 1) de droite à gauche.
      Exemple : 52=26*2+0 puis 26=13*2+0, 13=6*2+1, 6=3*2+0, 3=1*2+1 et 1=0*2+1 donc 52 s'écrit 110100.
  2. les entiers relatifs

    • Pour pouvoir coder des entiers positifs et négatifs, il faut réserver un bit pour le signe, c'est le bit de poids fort qui joue ce rôle (le plus à gauche).
      Le bit de poids fort d'un entier négatif vaut 1 alors qu'il vaut 0 pour un entier négatif.
      Généralement, les int sont codés sur 4 octets, soit 32 bits.
    • Pour représenter un entier négatif en binaire, on écrit sa valeur absolue en binaire puis on prend son complément à 1 (on met des 1 à la place des 0 et vice-versa) puis on ajoute 1 à ce nombre.
      Exemple : sur 8 bits, -52 s'écrit 11001100 car 52 s'écrit 00110100 et son complément à 1 est 11001011.
    • Si on ajoute les entiers n et -n, on obtient bien zéro mais avec un 1 de retenue dans le bit suivant du bit de poids fort (le neuvième si on travaille avec 8 bits).
  3. les flottants

    • Un float est codé sur 32 bits de la façon suivante (dans cet ordre) 
      • 1 bit de signe
      • 8 bits d'exposant (de -126 à 127  00000000 et 11111111 sont interdits)
      • 23 bits de mantisse
      et on note donc s|eeeeeeee|mmm...mmm pour représenter le flottant (-1)s * 1,mmm...mmm * 2eeeeeeee-127.
    • Rappelons que les flottants ne constituent pas un ensemble dense et que tous les réels ne sont donc pas codables. Cependant la précision d'un float est déjà très importante. En cas de besoin, on utilise le type double qui défini les flottants en double précision (11 bits d'exposant et 52 bits de mantisse).
    • Pour coder un flottant en binaire, on code tout d'abord la valeur absolue de la partie entière puis la partie décimale en la décomposant en puissance négative de 2 (on prend cette fois les diviseurs et on les note de gauche à droite).
      Exemple : pour -52,40625 la valeur absolue de la partie entière (52) s'écrit 110100 et la partie décimale 0,40625 = 0*0,5 + 1*0,25 + 1*0,125 + 0*0,0625 + 1*0,03125 soit 01101.
    • Ensuite, le nombre -110100,01101 est noté (-1)1 * 1,1010001101 * 26=133-127 et on a donc 1 pour le bit de signe, 10000101b = 00000110b + 01111111b pour l'exposant et 1010001101 pour mantisse (le 1, étant obligatoire, il n'est pas représenté).
      Finalement -52,43 s'écrit 1|10000101|10100011010000000000000.