Outils pour utilisateurs

Outils du site


developpement:c:toc

Introduction au langage C

Particularités du langage C

  • Langage de haut niveau plutôt général, ne vise pas qu'un type particulier d'applications
  • Très proche du processeur, avec le bénéfice de la puissance, suffisamment distant permettre la programmation structurée
  • Très portable, conséquence de sa forte normalisation
  • Très puissant, seulement 800 des 13 000 lignes de code du UNIX sont écrites en assembleur, le reste est écrit en C.
  • Efficacité dans l'écriture et le code généré en plus d'un accès à toutes les ressources matérielles et logicielles du système.
  • Conséquence :
    • La plupart des logiciels sur IBM-PC sont écrits en C.
    • Devenu un langage quasiment standard pour tous les environnements graphiques
    • Il tend fortement à s'imposer comme le principal langage de développement des applications en micro-informatique
  • Le langage C en tant que tel est relativement restreint et compact. Il se réduit pratiquement aux opérations exécutables par le processeur pour les calculs et la logique d'exécution du programme.
  • Les autres opérations, comme les entrées/sorties, les appels au système d'exploitation et les instructions qui manipulent une chaîne de caractères ou un tableau, sont réalisés par des appels a des fonctions d'une bibliothèque (programmathèque standard considérée comme externe au langage).
  • La richesse des opérateurs → une grande concision dans l'écriture des expressions, ce qui permet une efficacité plus grande au compilateur.
  • Le préprocesseur permet des opérations sur le texte du programme source avant sa traduction et donc une souplesse supplémentaire au développeur.
  • Le langage C encourage la conception modulaire du programme qui se décompose en un ou plusieurs modules sources indépendants, eux-mêmes décomposés en fonctions
  • Le langage C permet la récursivité
  • Permissibité sur la manipulation des types de données dans le sens que le langage dispose de types de bases, mais il est possible d'en créer de nouveaux en regroupant de diverses façons les types déjà définis.

Limites du langage

Le C a les défauts de sa compacité et de sa souplesse :

  • Conversions implicites lors du mélange de certains types.
  • La concision dans l'écriture des expressions.
  • La manipulation systématique du type pointeur ou de types composés complexes, éveidemment d'une grande puissance nécessitent une bonne habitude, à défaut de quoi, il peut y avoir un manque de clarté et des risques d'erreurs.
  • Il n'a pas d'énoncé permettant de récupérer les erreurs de division par zéro.
  • Il ne vérifie aucunement les valeurs d'un indice d'un tableau pour savoir s'il respecte les bornes de celui-ci.
  • Il n'est pas orienté-objet, il faut utiliser le C++ pour ce faire.

Conclusion

  • Compromis raisonnable entre simplicité et puissance, rigueur et souplesse
  • Portabilité sur une vaste gamme de matériels étant donné l'indépendance du langage par rapport aux systèmes d'exploitation
  • La disponibilité de C sur une vaste gamme de machines, allant des micro-ordinateurs aux mainframes et minis.
  • Le compilateur occupe très peu d'espace mémoire
  • Modularité poussée à l'extrême

C'est un langage pour les programmeurs conscients des limites de l'ordinateur et qui veulent en tirer le maximum des possibilités offerts par le langage.

Règles d'écriture d'un programme en C

  • Le C fait la différence entre minuscules et majuscules, c'est-à-dire qu'il est sensible à la casse (case sensitive)
  • Les espaces sont utilisables à volonté dans un fichier source
    • Exceptions → dans les instructions du langage, les identificateurs de variables, de fonctions, etc.
  • Pour augmenter la lisibilité (la qualité visuelle) du programme source, les indentations et les lignes blanches ne sont pas limités dans la quantité employée
  • Le point d'entrée à un programme C est la fonction main() qui doit être présente dans tous les programmes C.
  • Toutes les fonctions prédéfinies se trouvent dans les librairies de fonctions, à l'exception des fonctions standards du langage.
  • Toutes les variables utilisées dans un programme doivent être déclarées
  • Le délimiteur de fin d'instruction est le point-virgule ; ou l'accolade fermée }.
  • Les blocs d'instructions sont délimités par l'accolade ouvrante { et l'accolade fermée }.
  • Les commentaires sont entre oblique et étoile → /* Ceci est un commentaire */
  • Tout ce qui est réalisé en C est fait par l'intermédiaire de fonctions.

Les types

Toute donnée en C appartient à un certain type. toutes les valeurs (constantes et variables) utilisées dans les programmes sont classées selon cette typologie. Dans la déclaration d'une variable, le type indique au compilateur le nombre d'octets à réserver.

Classification des types :

  • Type de base
  • Type structure
  • Type union
  • Type énuméré
  • Type défini par une définition de type (typedef)

Types de base

En C, il existe trois types de base :

  1. Les entiers
  2. Les réels
  3. Les caractères

Les types de base correspondent directement aux différents codages des variables en mémoire. Alors, ils posent un éventuel problème de portabilité puisqu'ils peuvent dépendre du matériel (machines à 16 ou 32 bits)

Type entier

Correspond à la taille naturelle de l'entier manipulé par le microprocesseur, donc à la taille du mot mémoire ou des registres, c'est-à-dire 16, 32 ou 64 bits.

  • int → entier signé
  • unsigned int → entier non-signé

Pour préciser explicitement la taille désirée, on utilise des modificateurs : short et long.

  • short int → entier signé codé sur 2 octets de -32 768 à 32 767.
  • unsigned short int → entier non-signé codé sur 2 octets, 0 à 65 535 (216)
  • long int → entier signé codé sur 4 octets de -2 147 483 648 à 2 147 483 647.
  • unsigned long int → entier non-signé codé sur 4 octets, 0 à 4 294 967 295 (232)

Le int est facultatif parce que dès qu'il y a un modificateur long, short ou unsigned, l'identificateur est sous-entendu. De plus, un int peut être de 2 ou 4 octets. S'il est de 2 octets, il correspond à un short et s'il est de 4 octets, il correspond à un long.

Types réels

Les nombres réels (décimaux) sont stockés en C autrement que les entiers. Les nombres réels sont des nombres à virgule flottante. La virgule en tant que séparateur entre la partie entière et la partie décimale n'est pas figée, contrairement aux nombres à virgule fixe. La grandeur d'un tel nombre est donnée par un exposant adéquat.

  • 3.58 * 101 → 35.8
  • 0.358 * 102 → 35.8
  • 358.0 * 10-1 → 35.8

Codage :

Type flottant

Les nombres flottants (réels) sont codés avec une mantisse (les chiffres significatifs) et un exposant.

  • float → flottant codé sur 4 octets
  • double → flottant codé sur 8 octets
  • long double → flottant codé sur 10 octets

Le nombre de chiffres significatifs et la valeur de maximum de l'exposant dépendent du codage exact retenu, mais sont pour le codage de type IEEE.

  • float → 7 chiffres significatifs et un exposant de <html>10&plusmn;38</html>.
  • double → 15 chiffres significatifs et un exposant de <html>10&plusmn;308</html>.
  • long double → 15 chiffres significatifs et un exposant de <html>10&plusmn;4932</html> (norme ANSI).
Type Occupation mémoire Plage des valeurs
float 4 octets (32bits) 3.4 * 10-38 à 3.4 * 1038
double 8 octets (32bits) 1.7 * 10-308 à 1.7 * 10308
long double 10 octets (32bits) 3.4 * 10-4932 à 3.4 * 104932

Type caractère

Le type caractère spécifie une variable pouvant contenir un caractère unique codé par un entier sur un octet.

  • char → Caractère signé de -128 à 127
  • unsigned char → Caractère non signé de 0 à 255

Cette valeur numérique entière représente le code ASCII du caractère dans la plupart des cas, ou dans un autre code standard. Si on limite au code ASCII à 128 caractères, les deux déclarations précédentes sont équivalentes. Si on désire utiliser 256 codes possibles, comme dans le cas des caractères graphiques IBM-PC, il faut utiliser le modificateur unsigned pour ne pas traiter des caractères à valeur négative. Dans une représentation signée, le bit de poids fort est considéré comme le bit de signe.

Le type caractère est véritablement considéré comme ayant une valeur numérique entière, donc on peut l'utiliser directement dans les expressions de calcul.

aléatoire d'une lettre minuscule
c = 'a' + rand()%26;

Le type char correspond à un caractère unique et une chaîne de caractères correspond à un tableau d'éléments de type char.

Structures de contrôles

Blocs d'instructions

L'ensemble constitué par plusieurs instructions entre accolades est appelé un bloc d'instructions (compound statement). Syntaxiquement, un bloc d'instruction compte pour une seule instruction.

Illustration d'un bloc d'instruction

L'énoncé if-else

L'énoncé if-else sert à contrôler l'exécution d'un programme. Il permet de faire un choix entre deux alternatives. La condition doit être entre parenthèses.

if (<condition>) <énoncé1>;
else <énoncé2>;

Si la condition entre parenthèses est vraie, ou si l'expression retourne un résultat non-nul, alors l'énoncé 1 est exécuté et l'exécution de l'énoncé if est terminé.

Si la condition entre parenthèses est fausse, ou si l'expression retourne un résultat nul, alors l'énoncé 2 est exécuté (s'il existe) ou rien n'est exécuté s'il est omis.

if (i>10) i = 0;
else {
  printf("La valeur de i est : %d\n",i);
  i++;
}
if (i) instruction; /* est équivalent à if(i!=0) instruction; */
if (!i) instruction; /* est équivalent à if(i==0) instruction; */
Erreur très fréquente : if (i=0) → Condition invariable, parce que c'est une affectation. La partie alternative else sera toujours exécutée, peu importe la valeur de la variable i, parce que i=0 sera interprété comme faux.

La condition du if peut comporter plusieurs conditions partielles.

de if avec deux conditions
if (x>y && x>0)
  printf("x est plus grand que y et est positif.");

Pour afficher le message, il faut que les deux conditions x>y et x>0 soient vraies. Sinon, l'évaluation donnerait la valeur 0 (pour false) et donc le message ne serait pas affiché.

Il n'est pas obligatoire que la condition d'un if soit :

  • une opération comparative
  • une opération logique

Étant donné que chaque expression dont la valeur diffère de 0 vaut true et que chaque expression de valeur nulle vaut false, on peut utiliser des expressions constantes comme conditions d'instructions if.

avec constantes
if (5) printf("La condition est vraie.");
if (0) printf("La condition est fausse et ce message ne sera pas affiché.");

Tests imbriqués

Pour des choix à plusieurs cas en fonction de conditions successives, on utilise une imbrication des conditions.

d'imbrication if
if (condition1) instruction1;
  else if (condition2) instruction2;
    else if (condition3) instruction3;
      else instruction4;

Chaque instruction est associée à une condition et le dernier else, s'il existe, correspond au cas où aucune condition n'est vraie.

Dans une imbrication, un else se rapporte toujours au if le plus rapproché. Pour rattacher une branche else à un autre if que le premier if précédant et dépourvu de branche else, il faut utiliser des accolades en créant un ou plusieurs blocs.

if
if (n>0)
  if (a>b z=a;
else z=b;

Dans l'exemple précédant, malgré le fait que l'indentation est mal faite, le else n'est pas associé au premier if, mais bien au if directement au-dessus. Comme spécifié précédemment, on peut corriger avec les accolades.

avec accolades
if (n>0) {
  if (a>b) z=a;
}
else z=b;

Dans le cas précédant, le else est associé au premier if.

L'énoncé switch

L'instruction switch réalise un aiguillage vers différentes instructions en fonction du contenu d'une variable de contrôle.

de switch
switch (expression) {
  case constante_1 : [instruction(s);]
  case constante_1 : [instruction(s);]
  .
  .
  case constante_n : [instruction(s);]
  [default : instruction(s);]
}

L'expression qui est l'objet du switch est évaluée, convertie éventuellement en entier, et sa valeur est recherchée successivement parmi les valeurs des diverses expression constantes entières. En cas d'égalité, les instructions correspondantes sont exécutées.

Les crochets ([]) indiquent que les instructions sont facultatives après une constante case (les crochets ne font pas partie de l'instruction switch.

Lorsqu'aucun cas n'est sélectionné, on exécute les instructions correspondant au cas default s'il existe, la branche default est facultative.

L'instruction break fait sortir avant terme du switch et provoque un saut après la structure switch. Toutes les instructions éventuelles du switch placées après l'instruction break ne seront pas exécutés et le programme se poursuit à l'instruction placée immédiatement après la structure switch.

Les constantes case sont des valeurs numériques entières, ou des constantes caractères, représentés dans la machine par des nombres entiers.

Une seule instruction ou un seul groupe d'instruction peut correspondre à plusieurs étiquettes case. Alors, on écrit les instructions après la dernière des étiquettes case concernées par les instructions.

case 'a' :
case 'A' : printf("\n\nLa somme des nombre est : %f, x+y);
           break;

Opérateurs

Le langage C dispose de plus de 40 opérateurs. Ils ont des critères de classification selon :

  1. Le nombre d'opérandes manipulés par l'opérateur
    • Les opérateurs unaires admettent un seul opérande
    • Les opérateurs binaires admettent deux opérandes
    • Les opérateurs ternaires admettent trois opérandes (? → opérateur conditionnel)
  2. La nature des opérations exécutés ou la nature de leurs opérandes

Types d'opérateurs

  1. Opérateurs arithmétiques
    • Addition / soustraction
    • Multiplication / division
    • Modulo
    • Affectation
    • Incrémentation / décrémentation
    • Plus / moins (unaire)
    • Opérateurs combinés
  2. Opérateurs logiques et relationnels
    • Négation (!)
    • ET et OU logique (&& et ||)
    • Tests
      • Supériorité / infériorité (>=, <=, <, >)
      • Égalité / inégalité (==, !=)
  3. Opérateurs de traitement de bits
    • Complémentation (~)
    • ET (&) et OU (|) niveau bit
    • Décalage
      • Gauche (<<)
      • Droite (>>)
  4. Opérateurs d'adressage
    • Opérateur adresse
    • Opérateur d'indexation
    • Opérateur d'indirection
    • Opérateur d'accès (point)
  5. Opérateur conditionnel (?)
  6. Opérateur d'évaluation séquentielle (,)
  7. Opérateur de taille (sizeof)
  8. Opérateur de conversion explicite (cast)
  9. Opérateurs pour la sélection des composants (., ->)
    • Sélecteur de structure
  10. Opérateurs de parenthésage (())

La priorité des opérateurs est très importante.

Les opérateurs binaires, ou dyadiques, agissent sur deux opérandes. Les opérateurs unaires, ou monadiques, agissent sur un seul opérande.

Priorité des opérateurs

Les différents opérateurs n'ont pas tous la même priorité.

Niveau Opérateurs Sens de priorité
1 ( ) [ ] Gauche à droite
2 ! ~ ++ – Droite à gauche
3 * / % Gauche à droite
4 + - Gauche à droite
5 << >> Gauche à droite
6 < <= > >= Gauche à droite
7 == != Gauche à droite
8 & (et bit) Gauche à droite
9 ^ (ou exclusif) Gauche à droite
10 | (ou bit) Gauche à droite
11 && Gauche à droite
12 || Gauche à droite
13 = += -= Droite à gauche

Certains opérateurs se situent au même niveau de priorité, alors l'ordre d'exécution dépend de leur position relative dans l'expression arithmétique. Pour la plupart des niveaux, une opération sera d'abord effectuée si elle se trouve plus à gauche dans l'expression.

[L'ordre des opérateurs n'est pas innocent]

Le programmeur peut imposer lui-même une conversion en préfixant l'opération d'un type entre parenthèses. Ainsi, en écrivant x=(float) i/j; on force le compilateur à effectuer une conversion d'entier à réel sur i. Alors le dividende est un réel et le résultat est un réel. Le fait d'imposer une conversion porte, en anglais, le nom de casting.

Les opérateurs arithmétiques

Les opérateurs arithmétiques suivant sont disponibles :

Opérateur Symbole Type Signification
Addition + Binaire
Soustraction - Binaire
Multiplication * Binaire
Division / Binaire La division entière → 5/3 = 1
Modulo % Binaire Le reste de la division → 5%3 = 2
Affectation = Binaire
Incrément ++ Unaire Ajoute 1 à la valeur d'une variable
Décrément Unaire Soustrait 1 à la valeur d'une variable
Plus + Unaire Ajout du standard ANSI
Moins - Unaire Agit sur l'opérande qui le suit, en changeant le signe

L'opérateur d'affectation est spécifique à C, car dans d'autres langages, l'affectation n'est pas considérée comme opérateur. Le résultat de cet opérateur est la valeur affectée.

i = 3;  /* met 3 dans i et retourne 3 qui pourra être utilisé à d'autres fins */
i=(j=5)+9;  /* affecte la valeur 14 à i */

Incrémentation / décrémentation

Si l'opérateur précède l'opérande, il modifie tout de suite et la nouvelle valeur est dans le reste de l'expression.

de pré-incrémentation
i = --j; /* est équivalent à : j=j-1; i=j; */

Si l'opérateur suit l'opérande, la valeur initiale de l'opérande est utilisée dans l'expression et on modifie la valeur après.

de post-incrémentation
i = j++; /* est équivalent à : i=j; j=j+1; */

Les deux instructions compteur++ et ++compteur ont le même effets s'ils sont indépendant et ne font pas partie d'une autre expression/instruction.

L'opérateur qui suit (compteur++) est appelée la post-incrémentation et l'opérateur qui précède (++compteur) est appelée la pré-incrémentation.

Opérateurs combinés

Les opérateurs combinés ne sont pas de nouveaux opérateurs, mais plutôt deux opérateurs qui agissent comme un seul. Combiner l'opérateur d'affectation d'affectation (=) avec tous les opérateurs binaires permet d'optenir une expression plus concise et accélère l'exécution du programme.

d'opération combinée
i *= 5;  /* équivaut à : i = i * 5; */

La variable à gauche de l'opérateur (dans l'exemple précédent, i) servira deux fois :

  • une fois pour le calcul
  • une autre fois pour l'affectation

Alors, le compilateur n'a pas à calculer à deux reprises l'emplacement de cette variable en mémoire, donc on accélère l'exécution.

La construction a op=b signifie a = a op(b), donc on doit toujours évaluer l'expression à droite du = avant de lui appliquer l'opérateur combiné.

Opérateurs logiques

Les opérateurs logiques par ordre décroissant de priorité :

Priorité Symbole Signification
1 ! Non (négation)
2 > >= < <= Tests de supériorité et d'infériorité
3 == != Tests d'égalité et d'inégalité
4 && || ET et OU logiques

L'opérateur de négation (!) inverse une condition.

  • Si i > j est vrai, alors !(i > j) est faux.
  • Si i > j est faux, alors !(i > j) est vrai.
  • if (i) est équivalent à if (i != 0)
  • if (!i) est équivalent à if (i == 0)
avec les opérateurs logiques
main() {
  int i,j;
  printf("Entrez i et j : ");
  scanf("%d%d", &i, &j);
  if (i==j) printf("Egalite entre i et j.");
  if (i!=j) printf("i est different de j.");
  if (i>j) printf("i est plus grand que j.");
  if (i>=j) printf("i est plus grand ou egal a j.");
  if (i>=2 && i<=10) printf("i est entre 2 et 10 inclus.");
  if ((i>=2)||(i<=10)) printf("Toujours vrai");
}

Le ET binaire

Il permet de combiner deux valeurs selon la table de vérité ET.

<html><table><tr><td> <table class=“inline”>

<tr class="row0">
	<th class="col0" colspan="3"> Table de vérité ET </th>
</tr>
<tr class="row1">
	<th class="col0 rightalign">  </th><th class="col1"> 0 </th><th class="col2"> 1 </th>
</tr>
<tr class="row2">
	<th class="col0"> 0 </th><td class="col1"> 0 </td><td class="col2"> 0 </td>
</tr>
<tr class="row3">
	<th class="col0"> 1 </th><td class="col1"> 0 </td><td class="col2"> 1 </td>
</tr>

</table> </td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td> <table class=“inline”>

<tr class="row0">
	<th class="col0" colspan="2"> Exemple </th>
</tr>
<tr class="row1">
	<td class="col0"> </td><td class="col1"> 1 101 011 110 000 101 </td>
</tr>
<tr class="row2">
	<td class="col0"> ET </td><td class="col1"> 0 101 001 100 011 110 </td>
</tr>
<tr class="row3">
	<td class="col0"> Résultat </td><td class="col1"> 0 101 001 100 000 100 </td>
</tr>

</table> </td></tr></table></html>

Le OU binaire

Il permet de combiner deux valeurs selon la table de vérité OU.

<html><table><tr><td> <table class=“inline”>

<tr class="row0">
	<th class="col0" colspan="3"> Table de vérité OU </th>
</tr>
<tr class="row1">
	<th class="col0 rightalign">  </th><th class="col1"> 0 </th><th class="col2"> 1 </th>
</tr>
<tr class="row2">
	<th class="col0"> 0 </th><td class="col1"> 0 </td><td class="col2"> 1 </td>
</tr>
<tr class="row3">
	<th class="col0"> 1 </th><td class="col1"> 1 </td><td class="col2"> 1 </td>
</tr>

</table> </td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td> <table class=“inline”>

<tr class="row0">
	<th class="col0" colspan="2"> Exemple </th>
</tr>
<tr class="row1">
	<td class="col0"> </td><td class="col1"> 1 101 011 110 000 101 </td>
</tr>
<tr class="row2">
	<td class="col0"> OU </td><td class="col1"> 0 101 001 100 011 110 </td>
</tr>
<tr class="row3">
	<td class="col0"> Résultat </td><td class="col1"> 1 101 011 110 011 111 </td>
</tr>

</table> </td></tr></table></html>

Le OU exclusif

Il permet aussi de combiner deux valeurs selon la règle de la table de vérité OU exclusif (XOR).

<html><table><tr><td> <table class=“inline”>

<tr class="row0">
	<th class="col0" colspan="3"> Table de vérité XOR </th>
</tr>
<tr class="row1">
	<th class="col0 rightalign">  </th><th class="col1"> 0 </th><th class="col2"> 1 </th>
</tr>
<tr class="row2">
	<th class="col0"> 0 </th><td class="col1"> 0 </td><td class="col2"> 1 </td>
</tr>
<tr class="row3">
	<th class="col0"> 1 </th><td class="col1"> 1 </td><td class="col2"> 0 </td>
</tr>

</table> </td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td> <table class=“inline”>

<tr class="row0">
	<th class="col0" colspan="2"> Exemple </th>
</tr>
<tr class="row1">
	         <td class="col0"> </td><td class="col1"> 1 101 011 110 000 101 </td>
</tr>
<tr class="row2">
	      <td class="col0"> XOR </td><td class="col1"> 0 101 001 100 011 110 </td>
</tr>
<tr class="row3">
	<td class="col0"> Résultat </td><td class="col1"> 1 000 010 010 011 011 </td>
</tr>

</table> </td></tr></table></html>

Lorsqu'on utilise le ou exclusif, pour que le résultat soit 1, il faut que les deux bits soient de valeur différente.

Opérations de traitement de bits

Ce sont des opérations qu'on trouve plutôt dans les langages d'assemblage que dans les langages de haut niveau et qui permettent des manipulation de données au niveau du bit.

Ces opérations ne s'appliquent pas à des float ou des double, ces opérations s'appliquent à des valeurs entières (int, long, char).

Symbole Signification
& ET (AND)
| OU (OR)
^ OU exclusif (XOR)
<< Décalage à gauche
>> Décalage à droite
~ Complément à un
ET OU OU exclusif
1&1=1 1|1=1 1^1=0
1&0=0 1|0=1 1^0=1
0&1=0 0|1=1 1^1=1
0&0=0 0|0=0 1^0=0

L'opérateur de traitement de bit & permet de forcer à 0 des zones binaires particulières à l'intérieur d'une donnée et de tester si tel bit (ou tel groupe de bits) vaut 1. Exemple → compteur = i & 0177 (le 0177 en octal, ou 000001111111 en binaire). Affecter à compteur les 7 bits les moins significatifs de i permet d'isoler le code ASCII d'un caractère lu, ou encore, de restreindre à 127 (la valeur décimale de 0177 octal) la valeur à affecter à la variable compteur.

L'opérateur | sert à forcer à 1 des bits de données. Exemple → i = i|0xFF03;. Force à 1 les deux bits les moins significatifs et les 8 bits les plus significatifs de i.

Opérateurs d'adressage

Opérateur adresse

L'opérateur d'adressage & fournit l'adresse de son opérande qui doit être une Lvalue (une expression désignant une adresse en mémoire, par exemple le nom d'une variable).

&x donne l'adresse de la variable x qui est une Lvalue. Sa priorité est de 14, sur une échelle de 15, et 15 étant le plus haut.

Opérateur d'indexation

Le mécanisme d'indexation est lié à la notion des pointeurs. Pour un tableau mono-indicé a[], l'identificateur a étant une constante de type pointeur sur le début du tableau, l'expression a[i] représente le i-ème élément suivant l'élément zéro, donc le i+1 ème élément du tableau.

Opérateur d'indirection

Les opérateurs point (.) et -> permettent l'accès aux champs des variables de type structure ou union.

Les structures et les unions sont souvent désignées de façon indirecte par pointeur plutôt que par un identificateur.

avec l'opérateur d'indirection
struct modele *ptr;
i = (*ptr).champ;
/* est équivalent à */
i = ptr->champ;

Opérateur conditionnel

Il existe en langage C un opérateur ternaire (à trois opérandes), semblable au if-else.

<condition> ? <expression1> : <expression2>;

Avec l'opérateur conditionnel, la condition est évaluée en premier. Si la condition est vraie, alors le résultat est la valeur de l'expression 1. Si la condition est fausse, alors le résultat est la valeur de l'expression 2.

avec l'opérateur conditionnel
int i1, i2, i3;
/* on veut faire i3 = max(i1,i2); */
i3 = (i1>i2) ? i1 : i2;
/* l'instruction précédente est équivalente à */
if (i1>i2) i3 = i1;
else i3 = i2;

La première forme (avec l'opérateur conditionnel) est plus concise puisqu'on n'y mentionne i3 qu'une seule fois.

L'opérateur d'évaluation séquentielle

La syntaxe de l'opérateur d'évaluation séquentielle est la suivante :

<expression1>, <expression2>

Lors de l'exécution de cette opération, la première expression est évaluée et son résultat est ignoré. La seconde expression est évaluée et son résultat devient celui de l'expression au complet. Même si le résultat de la première expression est rejeté, il est important de faire l'évaluation puisqu'elle peut contenir des opérateurs ++ ou , ou une affectation modifiant la valeur de la variable.

L'utilisation de cet opérateur est surtout lors des étapes d'initialisation et de réinitialisation d'une boucle for, pour manipuler plusieurs variables. En effet, une seule expression est permise à ces endroits. Il ne s'agit pas d'énoncés que l'on peut remplacer par un bloc. On se sert donc finalement de l'opérateur d'évaluation séquentielle pour rassembler plusieurs expressions dans une seule.

Chacune des trois expressions d'une boucle for peut être omise, ce qui est bien car elles ne sont pas toujours nécessaires.

d'évaluations séquentielles avec for
for (somme = 0,i=1; i<10;i++) somme += i;
for (somme = 0, i=1; i<=10) somme += i++;
for (somme = i = 0; i<10) somme += ++i;

Opérateur de taille

L'opérateur unaire de dimension (taille) sizeof calcule l'occupation mémoire en octets requise par une variable ou par un type de donnée.

sizeof(<expression>)  /* l'expression est un identificateur de variable */
sizeof(<type>)  /* le type est un identificateur de type */

L'opérateur retourne une constante entière qui représente la taille mémoire en octets occupée par la variable ou par le type. L'opérateur permet de résoudre, d'une façon portable et indépendante du codage en mémoire, les problèmes liés à la taille des données :

  • Allocation dynamique
  • Lecture / écriture dans un fichier
de sizeof
short s;
int i;
long l;
double d;
sizeof(s);  sizeof(short);  /* retourne 2 (octets) */
sizeof(i);  sizeof(int);    /* retourne 2 ou 4 (octets) selon l'occupation
                               mémoire sur un ordinateur particulier concerné */
sizeof(l);  sizeof(long);   /* retourne 4 (octets) */
sizeof(d);  sizeof(double); /* retourne 8 (octets) */

Instructions répétitives

Les itérations (structures répétitives ou boucles) permettent l'exécution plusieurs fois de certaines parties du programme, sans réécrire les instructions.

Les structures répétitives :

  • while ⇒ évaluation → exécution
  • for ⇒ évaluation → exécution
  • do while ⇒ exécution → évaluation

Intruction while

La structure while permet de faire répéter l'exécution du bloc d'instructions tant qu'une certaine condition est remplie (donc égale à true).

avec une instruction
while (<expression>)
  instruction;
avec un bloc d'instructions
while (<expression>) {
  instruction1;
  instruction2;
  instructionn;
}

Traduction d'une instruction while

int x=10;
while (x>0) {
  printf("%d",x);
  x--;
}

Un while n'accepte formellement qu'une instruction dépendante, donc pour plusieurs instructions à exécuter, il faut utiliser le bloc d'instructions, afin de tenir compte de cette exigence syntaxique.

de boucle while avec et sans un bloc d'instructions
int a=-4;
while (a!=0)  /* tant que a est différent de 0 */
{
  a++;
  printf("%d\t", a);
}
 
/* boucle sans bloc d'instructions */
while (a!=0)
  a++;
  printf("%d\t",a); /* instructions non placée entre accolades, donc ce n'est pas un bloc */
à l'écran du résultat de la première boucle
-3    -2    -1    0
à l'écran du résultat de la deuxième boucle
0

Pour le deuxième while, seulement l'instruction a++; dépend de la condition de bouclage. L'instruction printf(“%d\t”, a); sera exécutée une seule fois parce qu'ell ne dépend d'aucune condition. Elle sera exécutée à la sortie de la boucle quand a=0.

Instruction for

Comme la structure while, la structure for est une boucle itérative, une instruction répétitive qui teste une condition avant d'exécuter les instructions. Les instructions sont exécutées et répétées tant que la condition de bouclage est remplie (true). Cet énoncé implante une boucle généralisée dans laquelle tous les éléments de contrôle de la boucle sont rassemblés au même endroit.

Elle comprend trois éléments :

  1. Initialisation → condition de départ
    • Plusieurs initialisations possibles, séparés par des virgules
  2. Condition de bouclage → la boucle est exécutés tant que cette condition est vérifiée (évaluation de la condition avant l'entrée dans la boucle)
  3. Réinitialisation → Ce qu'il faut faire à chaque fin de boucle
    • Plusieurs initialisations possibles, séparés par des virgules
d'un énoncé for
for (initialisation; condition; réinitialisation) instruction(s)

L'énoncé for effectue les opérations suivantes :

  1. Évaluation de la première expression à l'initialisation de la boucle
  2. Évaluation de la condition de la poursuite de la boucle
  3. Si la condition est fausse, l'exécution de la boucle for est terminée (les étapes 4, 5, 6 ne sont pas exécutés)
  4. Exécution de ou des instruction(s)
  5. Évaluation de l'expression2, l'étape de réinitialisation
  6. Retour à l'étape 2.
de boucle for
for (i=1; i<=10; i++) {
  somme += i;
}

La structure for constitue une alternative syntaxique au while dans laquelle tous les éléments relatifs au contrôle de la boucle (initialisation, condition de bouclage et réinitialisation) sont rassemblés dans l'entête de boucle, ce qui a pour avantage d'être lisible et compacte. On peut toujours écrire une boucle for à l'aide d'un énoncé while.

avec boucle while
somme = 0;
i = 1; /* initialisation */
while (i<=10)  /* condition de bouclage */
{
  somme += i;  /* instruction */
  i++;  /* réinitialisation */
}

La première forme (boucle for) est plus claire car elle regroupe tous les éléments de contrôle de la boucle. Ceci est encore plus évident lorsque le corps de la boucle contient plusieurs instructions.

Les classes de mémorisation

Définition de fonctions

Pour définir une fonction en C, il faut coder les instructions qu'elle doit exécuter, en respectant certaines règles syntaxiques. Le code source de la fonction est appelé définition de la fonction. Une définition de fonction doit spécifier :

  • La classe de mémorisation de la fonction
  • Le type de valeur renvoyée par la fonction
  • Le nom de la fonction
  • Les paramètres (arguments) qui sont passés à la fonction
  • Les variables locales et externes utilisés par la fonction
  • D'autres fonctions invoquées par la fonction
  • Les instruction qui doit exécuter la fonction
<classe> <type> <déclarateur/nom> <paramètres>
de la fonction
{
  <Définition de variables locales>
  <Définition de variables externes>
  <Déclaration de fonctions supplémentaires>
  ...
  <Instructions>
}
  1. La classe de mémorisation → Détermine, en liaison avec le niveau interne ou externe de la déclaration, la durée de vie et la visibilité de la variable.
    • extern
    • auto
    • static
    • register
  2. Le type → Correspond à la représentation et au codage de la variable en mémoire
  3. Le déclarateur → Désigne la variable et complète sa définition

Types de classes de mémorisation

extern ⇒ Indique qu'il s'agit de la déclaration d'une variable définie dans un autre module. Les variables globales ont la classe extern lorsqu'elles sont définies sans aucune indication de classe de mémorisation.

de variable extern
int e;   /* e a la classe extern */
main () {
  ...
}

auto ⇒ Dans une déclaration interne à un bloc, indique qu'il s'agit d'une variable à durée de vie temporaire avec une visibilité restreinte au bloc (option par défaut).

static ⇒ Implique une durée de vie permanente, mais a une signification différente dépendant de l'emplacement de la déclaration.

  • Au niveau externe, la visibilité est limité au module.
  • Au niveau interne à un bloc, elle spécifie cette permanence et s'oppose à la classe auto.

register ⇒ demande une allocation dans un registre du microprocesseur dans un but d'optimisation du temps d'exécution.

developpement/c/toc.txt · Dernière modification : 2022/10/25 20:53 de sgariepy