Le C en 20 heures


précédentsommairesuivant

XVI. Opérateur ternaire

Le langage C possède un opérateur ternaire un peu exotique qui peut être utilisé comme alternative à if - else et qui a l'avantage de pouvoir être intégré dans une expression. L'expression suivante : <expr1> ? <expr2> : <expr3> est traduite comme ceci :

  • Si <expr1> est non nulle, alors la valeur de <expr2> est fournie comme résultat.
  • Sinon, c'est la valeur de <expr3> qui est fournie comme résultat.
La suite d'instructions :
 
Sélectionnez
if (a>b)
   maximum=a;
else
   maximum=b;

peut être remplacée par :
 
Sélectionnez
maximum = (a > b) ? a : b;

Employé de façon irréfléchie, l'opérateur ternaire peut nuire à la lisibilité d'un programme, mais si nous l'utilisons avec précaution, il fournit des solutions élégantes et concises.

Par exemple :
 
Sélectionnez
printf("Vous avez %i carte%c \n", n, (n==1) ? ' ' : 's');

XVI-A. Tableaux de chaînes de caractères

La déclaration char mois[12][10] réserve l'espace mémoire pour 12 mots contenant 10 caractères (dont 9 caractères significatifs, c'est-à-dire sans \0) : 

Image non disponible
Figure 14.1 - Une matrice de caractères

Lors de la déclaration, il est possible d'initialiser toutes les cases du tableau par des chaînes de caractères constantes :

 
Sélectionnez
char mois[12][10]={"janvier","février","mars","avril","mai","juin",
              "juillet","août","septembre","octobre",
              "novembre","décembre"};
 
Image non disponible
Figure 14.2 - Le contenu du tableau mois

Il est possible d'accéder aux différentes chaînes de caractères d'un tableau, en indiquant simplement la ligne correspondante.

L'exécution des trois instructions suivantes…
 
Sélectionnez
char mois[12][10]= {"janvier","février","mars","avril",
       "mai","juin","juillet","août","septembre",
       "octobre","novembre","décembre"};
int i = 4;
printf("Aujourd'hui, nous sommes en %s !\n", mois[i]);

…affichera, par exemple, la phrase : Aujourd'hui, nous sommes en mai! Des expressions comme mois[i] représentent l'adresse du premier élément d'une chaîne de caractères.

La remarque précédente est très pratique pour résoudre le problème qui va suivre. Nous allons écrire un programme qui lit un verbe régulier en « er » au clavier et qui affiche la conjugaison au présent de l'indicatif de ce verbe. Nous contrôlerons s'il s'agit bien d'un verbe en « er » avant de conjuguer.

 
Sélectionnez
Verbe : programmer
je programme
tu programmes
il ou elle programme
nous programmons
vous programmez
ils ou elles programment

Nous utiliserons deux tableaux de chaînes de caractères : sujets pour les sujets et terminaisons pour les terminaisons.

Solution possible :

 
Sélectionnez
#include <stdio.h> 
#include <string.h> 
int main() {
   int i;
   
   char sujets[6][13]  = {"je","tu","il ou elle","nous","vous","ils ou elles"};
   char terminaisons[6][5] = {"e","es","e","ons","ez","ent"};
   
   char verbe[15]; /* chaîne contenant le verbe */
   int longueur;   /* longueur du verbe */
   
   printf("Quel verbe souhaitez-vous conjuguer ? ");
   scanf("%s", verbe);
   
   /* S'agit-il d'un verbe se terminant par "er" ? */
   longueur=strlen(verbe);
   if ((verbe[longueur-2] != 'e') || (verbe[longueur-1] != 'r'))
      printf("%s n'est pas un verbe du premier groupe !!!\n",verbe);
   else  {
      /* Supprimer la terminaison "er" */
      verbe[longueur-2]='\0';
      /* Conjuguer le verbe */
      for (i=0; i<6; i++)
         printf("%s %s%s\n",sujets[i], verbe, terminaisons[i]);
   }
   return 0;
}

XVI-B. Pointeurs et tableaux

Nous allons à présent examiner les liens très étroits qu'il y a entre pointeurs et tableaux. En fait, nous allons voir qu'à chaque fois que vous manipulez des tableaux, comme par exemple à la ligne contenant le printf du programme ci‐dessous, le langage C va transformer votre instruction tableau[i] en se servant de pointeurs…

 
Sélectionnez
int tableau[10]={1,2,3,4,5,6,7,8,9,10};
int i;
for (i=0;i<10;i++)
   printf("%d ",tableau[i]);

Comme nous l'avons déjà constaté précédemment, le nom d'un tableau représente l'adresse de son premier élément. En d'autres termes : &tableau[0] et tableau sont une seule et même adresse.

En simplifiant, nous pouvons retenir que le nom d'un tableau est un pointeur constant sur le premier élément du tableau.

En déclarant un tableau tableau de type int et un pointeur p sur int,
 
Sélectionnez
int tableau[10];
int *p;

l'instruction : p = tableau; est équivalente à p = &tableau[0];
Si p pointe sur une case quelconque d'un tableau, alors p+1 pointe sur la case suivante.
Ainsi, après l'instruction : p = p+1;
le pointeur p pointe sur tableau[1], 
Table 14.2 - Pointeurs et tableaux (1)
*(p+1)désigne le contenu de tableau[1]
*(p+2)désigne le contenu de tableau[2]
*(p+i)désigne le contenu de tableau[i]

Table 14.2 - Pointeurs et tableaux (1)

Il peut sembler surprenant que p+i n'adresse pas le ième octet derrière p, mais la ième case derrière p … Ceci s'explique par la stratégie de programmation des créateurs du langage C : si nous travaillons avec des pointeurs, les erreurs les plus sournoises sont causées par des pointeurs mal placés et des adresses mal calculées. En C, le compilateur peut calculer automatiquement l'adresse de l'élément p+i en ajoutant à p la taille d'une case multipliée par i. Ceci est possible, parce que :

  • chaque pointeur accède à un seul type de données ;
  • le compilateur connaît le nombre d'octets utilisés pour chaque type.

Enfin, comme tableau représente l'adresse de tableau[0] : 

Table 14.3 - Pointeurs et tableaux (2)
*(tableau+1) désigne le contenu de tableau[1]
*(tableau+2) désigne le contenu de tableau[2]
*(tableau+i) désigne le contenu de tableau[i]

Voici un récapitulatif de tout ceci.

Soit un tableau tableau d'un type quelconque et i un indice entier alors : 

Table 14.4 - Pointeurs et tableaux (3)
tableau désigne l'adresse de tableau[0]
tableau+i désigne l'adresse de tableau[i]
*(tableau+i) désigne le contenu de tableau[i]

Si p = tableau, alors : 

Table 14.5 - Pointeurs et tableaux (4)
p désigne l'adresse de tableau[0]
p+i désigne l'adresse de tableau[i]
*(p+i) désigne le contenu de tableau[i]

Les deux programmes suivants copient les éléments strictement positifs d'un tableau tableau dans un deuxième tableau positifs.

Formalisme tableau :

 
Sélectionnez
int main() {
   int tableau[5] = {-4, 4, 1, 0, -3};
   int positifs[5];
   int i,j;  /* indices courants dans tableau et positifs */
   for (i=0,j=0 ; i<5 ; i++)
      if (tableau[i]>0){
         positifs[j] = tableau[i];
         j++;
      }
   return 0;
}

Nous pouvons remplacer systématiquement la notation tableau[i] par *(tableau+i), ce qui conduit à ce programme qui repose sur le formalisme des pointeurs :

 
Sélectionnez
int main() {
   int tableau[5] = {-4, 4, 1, 0, -3};
   int positifs[5];
   int i,j;  /* indices courants dans tableau et positifs */
   for (i=0,j=0 ; i<5 ; i++)
      if (*(tableau+i)>0){
         *(positifs+j) = *(tableau+i);
         j++;
      }
   return 0;
}
Voici un exemple de programme qui range les éléments d'un tableau tableau dans l'ordre inverse. Le programme utilise des pointeurs p1 et p2 et une variable numérique aux pour la permutation des éléments :
 
Sélectionnez
#include <stdio.h>
#define TAILLE 100
int main() {
   
   int tableau[TAILLE]; /* tableau donné  */
   int dim;             /* nombre réel d'éléments du tableau */
   int aux;             /* variable auxiliaire pour la permutation */
   int *p1, *p2;        /* pointeurs d'aide */
   int i;
   
   printf("Dimension du tableau (maximum : %d) : ",TAILLE);
   scanf("%d", &dim );
   
   i=1;
   for (p1=tableau; p1<tableau+dim; p1++) {
      printf("Valeur de l'élément %d : ", i++);
      scanf("%d", p1); //  notez l'abscence de  '&' !!!
   }
   
   /* Affichage du tableau avant inversion */
   for (p1=tableau; p1<tableau+dim; p1++)
      printf("%d ", *p1);   // ne pas oublier l'étoile !!!
   printf("\n");
   
   /* Inversion du tableau */
   for (p1=tableau,p2=tableau+(dim-1); p1<p2; p1++,p2--) {
      aux = *p1;
      *p1  = *p2;
      *p2  = aux;
   }
   /* Affichage du résultat */
   for (p1=tableau; p1<tableau+dim; p1++)
      printf("%d ", *p1);
   printf("\n");
   
   return 0;
}

XVI-C. Tableaux de pointeurs

Attention, nous allons compliquer un peu les choses…

Si nous avons besoin d'un ensemble de pointeurs du même type, nous pouvons les réunir dans un tableau de pointeurs.

Déclaration d'un tableau de pointeurs :
 
Sélectionnez
<Type> *<NomTableau>[<N>]

…déclare un tableau <NomTableau> de <N> pointeurs sur des données du type <Type>.
Par exemple :
 
Sélectionnez
double * tableau[10];
  • les crochets [ ] ont une priorité supérieure à l'étoile *
  • en lisant de droite à gauche nous voyons que tableau[10] sera du type double *
  • nous déclarons donc un tableau de 10 pointeurs sur des double.
 
Sélectionnez
#include <stdio.h>
#include <malloc.h>
#include <string.h>
int main() {
   char * jours[7];
   int i;
   for (i=0;i<7;i++) {
      jours[i]=(char *) malloc(9);
   }
   strcpy(jours[0],"lundi");
   strcpy(jours[1],"mardi");
   strcpy(jours[2],"mercredi");
   //  ...
   return 0;
}

Voilà ce que cela donne en pratique :

 
Sélectionnez
char mois[12][10]= {"janvier","février","mars","avril",
        "mai","juin","juillet","aout","septembre","octobre",
        "novembre","décembre"};

déclare un tableau mois[] de 12 pointeurs sur char. Chacun des pointeurs est initialisé avec l'adresse de l'une des 12 chaînes de caractères (voir figure) : 

Image non disponible
Figure 14.3 - Un tableau de pointeurs sur des chaînes de caractères

Nous pouvons afficher les 12 chaînes de caractères en fournissant les adresses contenues dans le tableau mois à la fonction printf :

 
Sélectionnez
int i;
for (i=0; i<12; i++)
   printf("%s\n", mois[i]);

XVI-D. Choix multiples avec switch

Supposons que vous ayez une cascade de if et else imbriqués à écrire :

 
Sélectionnez
int i;
printf ("Entrez une valeur:");
scanf("%d",&i);
if (i==0)
   printf ("Nombre nul\n");
else {
   if (i==1)
      printf ("Nombre non égal à un\n");
   else
      printf ("Autre type de nombre\n");
}

Dans ce cas, nous pouvons, pour augmenter la lisibilité du programme, utiliser switch/case/break :

 
Sélectionnez
int i;
printf ("Entrez une valeur:");
scanf("%d",&i);
switch (i) {
   case 0:
      printf ("Nombre nul\n");
      break;
   case 1:
      printf ("Nombre égal à un\n");
      break;
   default:
      printf("Autre type de nombre\n");
      break;
}
Notez l'usage du break, en effet, si vous ne faites pas de break, le programme, après être entré dans un case, continuera sur le case suivant et ainsi de suite. Testez le programme suivant en entrant la valeur 0 :
 
Sélectionnez
int i;
printf ("Entrer une valeur:");
scanf("%d",&i);
switch (i) {
   case 0:
      printf ("Nombre nul\n");
      /* pas de break */
   case 1:
      printf("Nombre égal à un\n");
      break;
   default:
      printf("Autre type de nombre\n");
      break;
}

Le programme affichera à l'exécution :
 
Sélectionnez
Nombre nul
Nombre égal à un

Omettre un break n'est pas forcément une faute et cette particularité peut être utilisée pour réaliser des conditions « ou ». Par exemple, la portion de code suivante affichera un message si a vaut 1,2 ou 5, un autre message si a vaut 3 ou 4 et un troisième message sinon.

 
Sélectionnez
int a;
printf ("Entrez une valeur:");
scanf("%d",&a);
switch (a) {
   case 1: case 2: case 5:
      printf("Voici le premier message\n");
      break;
   case 3: case 4:
      printf("Voici le second message\n");
      break;
   default:
      printf("Voici le message par défaut\n");
      break;
}

XVI-E. Édition de liens

Jusqu'à présent, nous « construisions » nos programmes exécutables en entrant la commande suivante :

 
Sélectionnez
gcc -o essai essai.c

En réalité, cette commande réalise deux opérations : la compilation proprement dite, et l'édition de liens (si la compilation s'est terminée avec succès). Pour réaliser uniquement l'étape de compilation, il faut entrer :

 
Sélectionnez
gcc -c essai.c

Cette étape a pour effet de générer le fichier essai.o qui ne contient pour le moment que le code objet qui correspond au source compilé, mais qui ne lie pas les appels de fonctions des bibliothèques extérieures telles que printf, scanf, feof, sqrt à leur code respectif.

L'étape suivante, appelée édition de liens, est réalisée en entrant la commande :

 
Sélectionnez
gcc -o essai essai.o -lm

Lors de cette étape, des « références » aux fonctions appelées dans le fichier objet sont ajoutées. Pour cela, les fonctions utilisées sont recherchées automatiquement dans les bibliothèques standard (on y trouvera printf, scanf…) et dans les bibliothèques spécifiées dans la commande (ici ‐lm qui désigne la bibliothèque mathématique et qui permettra de trouver une référence à la fonction sqrt par exemple)(26)

Depuis le début, nous avons employé la syntaxe la plus rapide, et avons lancé la compilation puis l'édition de liens en une seule fois :

 
Sélectionnez
gcc -o essai essai.c

précédentsommairesuivant
Précisons au passage qu'il existe deux sortes d'édition de liens : l'édition de liens dynamiques, dont nous venons de parler, insère dans le programme exécutable des références vers les fonctions des bibliothèques, qui seront chargées par ailleurs à l'exécution. L'édition de liens statiques (options ‐static de gcc) insère le code complet des fonctions dans l'exécutable. Il en résulte un fichier plus gros, mais qui a l'avantage de ne plus dépendre d'autres bibliothèques…

  

Licence Creative Commons
Le contenu de cet article est rédigé par Eric Berthomier et Daniel Schang et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.