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.
if
(
a>
b)
maximum=
a;
else
maximum=
b;
peut être remplacée par :
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.
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) :
Lors de la déclaration, il est possible d'initialiser toutes les cases du tableau par des chaînes de caractères constantes :
char
mois[12
][10
]={
"
janvier
"
,"
février
"
,"
mars
"
,"
avril
"
,"
mai
"
,"
juin
"
,
"
juillet
"
,"
août
"
,"
septembre
"
,"
octobre
"
,
"
novembre
"
,"
décembre
"
}
;
Il est possible d'accéder aux différentes chaînes de caractères d'un tableau, en indiquant simplement la ligne correspondante.
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.
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 :
#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…
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.
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],
*(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] :
*(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 :
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] |
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 :
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 :
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
;
}
#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.
<Type> *<NomTableau>[<N>]
…déclare un tableau <NomTableau> de <N> pointeurs sur des données du type <Type>.
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.
#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 :
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) :
Nous pouvons afficher les 12 chaînes de caractères en fournissant les adresses contenues dans le tableau mois à la fonction printf :
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 :
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 :
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
;
}
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 :
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.
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 :
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 :
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 :
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 :
gcc -o essai essai.c