Exercices en langage C
C. Delannoy
2 Exe rcices en langage C
PREM IERE PARTIE :
EXERCICES D 'APPLICATIO N
Cette prem iè re partie vous propose des exercices, à résoudre, de préférence, pendantla ph ase d'étude du langage C lui-
m ê m e. Elle épouse la structure d'un cours "classique"1, sous la form e de 7 ch apitres : types de base, opérateurs et
expressions ;entrées-sorties conversationnelles ;instructions de contrôle ;les fonctions ;les tableaux et les pointeurs ;
les ch aînes de caractè res ;les structures.
Ch aque ch apitre com porte :
- des exercices d'application im m édiate destinés à faciliter l'assim ilation du cours correspondant,
- des exercices, sans grande difficulté algorith m ique m ettant en oeuvre les différentes notions acquises au cours des
précédents ch apitres.
Notez que l'utilisation des fich iers, ainsi que la gestion dynam ique ne sontpas abordés dans cette prem iè re partie ;ces
deux points ferontch acun l'objetd'un ch apitre approprié dans la seconde partie de l'ouvrage.
1 Un telcours vous est proposé, par exem ple, dans "Apprendre à program m er en Turbo C" ou dans "C norm e ANSI - Guide com plet de
program m ation" du m ê m e auteur, égalem entaux éditions Eyrolles.
I: TYPES D E BASE,
O PERATEURS
ET EXPRESSIO NS
Exe rcice I.1
___________________________________________________________________________
Enoncé
Elim iner les parenth è ses superflues dans les expressions suivantes :
a = (x+5) /* expression 1 */
a = (x=y) + 2 /* expression 2 */
a = (x==y) /* expression 3 */
(a<b) && (c<d) /* expression 4 */
(i++) * (n+p) /* expression 5 */
___________________________________________________________________________
Solution
a = x+5 /* expression 1 */
L'opérateur + estprioritaire sur l'opérateur d'affectation =.
a = (x=y) + 2 /* expression 2 */
Ici, l'opérateur + étantprioritaire sur =, les parenth è ses sontindispensables.
a = x==y /* expression 3 */
4 Exe rcices en langage C
L'opérateur == estprioritaire sur =.
a<b && c<d /* expression 4 */
L'opérateur & & estprioritaire sur l'opérateur <.
i++ * (n+p) /* expression 5 */
L'opérateur + + estprioritaire sur *;en revanch e, *estprioritaire sur + ;de sorte qu'on ne peutélim iner les derniè res
parenth è ses.
Exe rcice I.2
___________________________________________________________________________
Enoncé
Soientles déclarations :
char c = 'x01' ;
short int p = 10 ;
Quels sontle type etla valeur de ch acune des expressions suivantes :
p + 3 /* 1 */
c + 1 /* 2 */
p + c /* 3 */
3 * p + 5 * c /* 4 */
___________________________________________________________________________
Solution
1)p estd'abord soum is à la conversion "systém atique" sh ort-> int, avantd'ê tre ajouté à la valeur 3 (int). Le résultat13
estde type int.
2)c estd'abord soum is à la conversion "systém atique" ch ar -> int(ce qui aboutità la valeur 1), avantd'ê tre ajouté à la
valeur 1 (int). Le résultat2 estde type int.
I. Types de base, opérate urs e te xpressions 5
3)p estd'abord soum is à la conversion systém atique sh ort-> int, tandis que c estsoum is à la conversion systém atique
ch ar -> int;les résultats sontalors additionnés pour aboutir à la valeur 11 de type int.
4)p etc sontd'abord aux m ê m es conversions systém atiques que ci-dessus ;le résultat35 estde type int.
Exe rcice I.3
___________________________________________________________________________
Enoncé
Soientles déclarations :
char c = 'x05' ;
int n = 5 ;
long p = 1000 ;
float x = 1.25 ;
double z = 5.5 ;
Quels sontle type etla valeur de ch acune des expressions suivantes :
n + c + p /* 1 */
2 * x + c /* 2 */
(char) n + c /* 3 */
(float) z + n / 2 /* 4 */
___________________________________________________________________________
Solution
1)c esttoutd'abord converti en int, avantd'ê tre ajouté à n. Le résultat(10), de type int, estalors converti en long, avant
d'ê tre ajouté à p. On obtientfinalem entla valeur 1010, de type long.
2)On évalue d'abord la valeur de 2*x, en convertissant2 (int) en float, ce qui fournitla valeur 2.5 (de type float). Par
ailleurs, c estconverti en int(conversion systém atique). On évalue ensuite la valeur de 2*x, en convertissant2 (int) en
float, ce qui fournitla valeur 2.5 (de type float). Pour effectuer l'addition, on convertitalors la valeur entiè re 5 (c) en
float, avantde l'ajouter au résultatprécédent. On obtientfinalem entla valeur 7.75, de type float.
6 Exe rcices en langage C
3)n est tout d'abord converti en ch ar (à cause de l'opérateur de "cast"), tandis que c est converti (conversion
systém atique) en int. Puis, pour procéder à l'addition, ilest nécessaire de reconvertir la valeur de (ch ar) n en int.
Finalem ent, on obtientla valeur 10, de type int.
4)z estd'abord converti en float, ce qui fournitla valeur 5.5 (approxim ative, car, en fait, on obtientune valeur un peu
m oins précise que ne le serait5.5 exprim é en double ). Par ailleurs, on procè de à la division entiè re de n par 2, ce qui
fournit la valeur entiè re 2. Cette derniè re est ensuite convertie en float, avant d'ê tre ajoutée à 5.5, ce qui fournit le
résultat7.5, de type float.
Rem arque :
Dans la prem iè re définition de Kernigh an et Ritch ie, les valeurs de type float étaient, elles aussi, soum ises à une
conversion systém atique en double . Dans ce cas, les expressions 3 et4 étaientalors de type double .
Exe rcice I.4
___________________________________________________________________________
Enoncé
Soientles déclarations suivantes :
int n = 5, p = 9 ;
int q ;
float x ;
Quelle estla valeur affectée aux différentes variables concernées par ch acune des instructions suivantes :
q = n < p ; /* 1 */
q = n == p ; /* 2 */
q = p % n + p > n ; /* 3 */
x = p / n ; /* 4 */
x = (float) p / n ; /* 5 */
x = (p + 0.5) / n ; /* 6 */
x = (int) (p + 0.5) / n ; /* 7 */
q = n * (p > n ? n : p) ; /* 8 */
q = n * (p < n ? n : p) ; /* 9 *:
___________________________________________________________________________
I. Types de base, opérate urs e te xpressions 7
Solution
1)1
2)0
3)5 (p%n vaut4, tandis que p> n vaut1)
4)1 (p/n estd'abord évalué en int, ce qui fournit1 ;puis le résultatestconverti en float, avantd'ê tre affecté à x).
5)1.8 (p estconverti en float, avantd'ê tre divisé par le résultatde la conversion de n en float).
6)1.9 (p est converti en float, avant d'ê tre ajouté à 0.5 ;le résultat est divisé par le résultat de la conversion de n en
float).
7)1 (p estconverti en float, avantd'ê tre ajouté à 0.5 ;le résultat(5.5)estalors converti en intavantd'ê tre divisé par n).
8)25
9 )45
Exe rcice I.5
___________________________________________________________________________
Enoncé
Quels résultats fournitle program m e suivant:
#include <stdio.h>
main ()
{
int i, j, n ;
i = 0 ; n = i++ ;
printf ("A : i = %d n = %d n", i, n ) ;
i = 10 ; n = ++ i ;
printf ("B : i = %d n = %d n", i, n ) ;
8 Exe rcices en langage C
i = 20 ; j = 5 ; n = i++ * ++ j ;
printf ("C : i = %d j = %d n = %d n", i, j, n ) ;
i = 15 ; n = i += 3 ;
printf ("D : i = %d n = %d n", i, n) ;
i = 3 ; j = 5 ; n = i *= --j ;
printf ("E : i = %d j = %d n = %d n", i, n) ;
}
___________________________________________________________________________
Solution
A : i = 1 n = 0
B : i = 11 n = 11
C : i = 21 j = 6 n = 120
D : i = 18 n = 18
E : i = 12 j = 12 n = 6
Exe rcice I.6
___________________________________________________________________________
Enoncé
Quels résultats fournira ce program m e :
#include <stdio.h>
main()
{
int n=10, p=5, q=10, r ;
r = n == (p = q) ;
printf ("A : n = %d p = %d q = %d r = %dn", n, p, q, r) ;
n = p = q = 5 ;
n += p += q ;
printf ("B : n = %d p = %d q = %dn", n, p, q) ;
I. Types de base, opérate urs e te xpressions 9
q = n < p ? n++ : p++ ;
printf ("C : n = %d p = %d q = %dn", n, p, q) ;
q = n > p ? n++ : p++ ;
printf ("D : n = %d p = %d q = %dn", n, p, q) ;
}
___________________________________________________________________________
Solution
A : n = 10 p = 10 q = 10 r = 1
B : n = 15 p = 10 q = 5
C : n = 15 p = 11 q = 10
D : n = 16 p = 11 q = 15
Exe rcice I.7
___________________________________________________________________________
Enoncé
Quels résultats fournira ce program m e :
#include <stdio.h>
main()
{
int n, p, q ;
n = 5 ; p = 2 ; /* cas 1 */
q = n++ >p || p++ != 3 ;
printf ("A : n = %d p = %d q = %dn", n, p, q) ;
n = 5 ; p = 2 ; /* cas 2 */
q = n++<p || p++ != 3 ;
printf ("B : n = %d p = %d q = %dn", n, p, q) ;
n = 5 ; p = 2 ; /* cas 3 */
10 Exe rcices en langage C
q = ++n == 3 && ++p == 3 ;
printf ("C : n = %d p = %d q = %dn", n, p, q) ;
n = 5 ; p = 2 ; /* cas 4 */
q = ++n == 6 && ++p == 3 ;
printf ("D : n = %d p = %d q = %dn", n, p, q) ;
}
___________________________________________________________________________
Solution
Ilne faut pas oublier que les opérateurs & & et || n'évaluent leur deuxiè m e opérande que lorsque cela est nécessaire.
Ainsi, ici, iln'estpas évalué dans les cas 1 et3. Voici les résultats fournis par le program m e :
A : n = 6 p = 2 q = 1
B : n = 6 p = 3 q = 1
C : n = 6 p = 2 q = 0
D : n = 6 p = 3 q = 1
II: LES ENTREES-SO RTIES
CO NVERSATIO NNELLES
Exe rcice II.1
___________________________________________________________________________
Enoncé
Quels serontles résultats fournis par ce program m e :
#include <stdio.h>
main ()
{ int n = 543 ;
int p = 5 ;
float x = 34.5678;
printf ("A : %d %fn", n, x) ;
printf ("B : %4d %10fn", n, x) ;
printf ("C : %2d %3fn", n, x) ;
printf ("D : %10.3f %10.3en", x, x) ;
printf ("E : %-5d %fn", n, x) ;
printf ("F : %*dn", p, n) ;
printf ("G : %*.*fn", 12, 5, x) ;
printf ("H : %x : %8x :n", n, n) ;
printf ("I : %o : %8o :n", n, n) ;
}
_______________________________________________________________
Solution
A : 543 34.567799
B : 543 34.567799
12 Exe rcices en langage C
C : 543 34.567799
D : 34.568 3.457e+01
E : 543 34.567799
F : 543
G : 34.56780
H : 21f : 21f :
I : 1037 : 1037 :
Exe rcice II.2
___________________________________________________________________________
Enoncé
Quels serontles résultats fournis par ce program m e :
#include <stdio.h>
main()
{ char c ;
int n ;
c = 'S' ;
printf ("A : %cn", c) ;
n = c ;
printf ("B : %cn", n) ;
printf ("C : %d %dn", c, n) ;
printf ("D : %x %xn", c, n) ;
}
_______________________________________________________________
Solution
A : S
B : S
C : 83 83
D : 53 53
II. Les entrées-sorties conve rsationne lles 13
Exe rcice II.3
___________________________________________________________________________
Enoncé
Quelles serontles valeurs lues dans les variables n etp (de type int), par l'instruction suivante :
scanf ("%d %d", &n, &p) ;
lorsqu'on lui fournit les données suivantes (le sym bole ^ représente un espace et le sym bole @ représente une fin de
ligne, c'est-à -dire une "validation"):
a)
253^45@
b)
^253^@
^^ 4 ^ 5 @
_______________________________________________________________
Solution
a)n = 243, p = 45
b)n = 253, p = 4 (les derniers caractè res de la deuxiè m e ligne pourrontéventuellem entê tre utilisés par une instruction
de lecture ultérieure).
Exe rcice II.4
___________________________________________________________________________
Enoncé
Quelles serontles valeurs lues dans les variables n etp (de type int), par l'instruction suivante :
scanf ("%4d %2d", &n, &p) ;
lorsqu'on lui fournit les données suivantes (le sym bole ^ représente un espace et le sym bole @ représente une fin de
ligne, c'est-à -dire une "validation"):
14 Exe rcices en langage C
a)
12^45@
b)
123456@
c)
123456^7@
d)
1^458@
e)
^^^4567^^8912@
_______________________________________________________________
Solution
Rappelons que lorsqu'une indication de longueur estprésente dans le code form atfourni à scanf(com m e, par exem ple, le
4 de %4d), scanf interrom pt son exploration si le nom bre correspondant de caractè res a été exploré, sans qu'un
séparateur (ou "espace blanc") n'aitété trouvé. Notez bien, cependant, que les éventuels caractè res séparateurs "sautés"
auparavantne sontpas considérés dans ce com pte. Voici les résultats obtenus :
a)n=12, p=45
b)n=1234, p=56
c) n=1234, p=56
d)n=1, p=45
e)n=4567, p=89
En a, on obtiendrait exactem ent les m ê m es résultats sans indication de longueur (c'est-à -dire avec %d %d). En b, en
revanch e, sans l'indication de longueur 4, les résultats seraientdifférents (n vaudrait123456, tandis qu'ilm anqueraitdes
inform ations pour p). En c, les inform ations ^ et7 ne sontpas prises en com pte par scanf(elles le serontéventuellem ent
par une proch aine lecture!) ;sans la prem iè re indication de longueur, les résultats seraientdifférents : 123456 pour n (en
supposantque cela ne conduise pas à une valeur non représentable dans le type int) et 7 pour p. En d, cette fois, c'est
l'indication de longueur 2 qui a de l'im portance ;en son abscence, n vaudrait effectivem ent 1, m ais p vaudrait 458.
Enfin, en e, les deux indications de longueur sont im portantes ;notez bien que les trois espaces placés avant les
caractè res pris en com pte pour n, ainsi que les 2 espaces placés avantles caractè res pris en com pte pour p ne sont pas
com ptabilisés dans la longueur im posée.
Exe rcice II.5
___________________________________________________________________________
II. Les entrées-sorties conve rsationne lles 15
Enoncé
Soitle program m e suivant:
#include <stdio.h>
main()
{
int n, p ;
do
{ printf ("donnez 2 entiers (0 pour finir) : ") ;
scanf("%4d%2d", &n, &p) ;
printf ("merci pour : %d %dn", n, p) ;
}
while (n) ;
}
Quels résultats fournira-t-il, en supposantqu'on lui entre les données suivantes (attention, on supposera que les données
sont frappées au clavier et les résultats affich és à l'écran, ce qui signifie qu'ily aura "m ixage" entre ces deux sortes
d'inform ations):
1 2
3
4
123456
78901234 5
6 7 8 9 10
0
0
12
_______________________________________________________________
Solution
Ici, on retrouve le m écanism e lié à l'indication d'une longueur m axim ale dans le code form at, com m e dans l'exercice
précédent. De plus, on exploite le fait que les inform ations d'une ligne qui n'ont pas été prises en com pte lors d'une
lecture restent disponibles pour la lecture suivante. Enfin, rappelons que, tant que scanf n'a pas reçu suffisam m ent
d'inform ation, com pte tenu des différents codes form atspécifiés (et non pas des variables indiquées), elle en attend de
nouvelles. Voici finalem entles résultats obtenus :
donnez 2 entiers (0 pour finir)
1 2
merci pour : 1 2
16 Exe rcices en langage C
donnez 2 entiers (0 pour finir)
3
4
merci pour : 3 4
donnez 2 entiers (0 pour finir)
123456
merci pour : 1234 56
donnez 2 entiers (0 pour finir)
78901234 5
merci pour : 7890 12
donnez 2 entiers (0 pour finir)
merci pour : 34 5
donnez 2 entiers (0 pour finir)
6 7 8 9 10
merci pour : 6 7
donnez 2 entiers (0 pour finir)
merci pour : 8 9
donnez 2 entiers (0 pour finir)
0
merci pour : 10 0
donnez 2 entiers (0 pour finir)
0
12
merci pour : 0 12
III: LES INSTRUCTIO NS
D E CO NTRO LE
Exe rcice III.1
___________________________________________________________________________
Enoncé
Quelles erreurs ontété com m ises dans ch acun des groupes d'instructions suivants :
1)
if (a<b) printf ("ascendant")
else printf ("non ascendant") ;
2)
int n ;
...
switch (2*n+1)
{ case 1 : printf ("petit") ;
case n : printf ("moyen") ;
}
3)
#define LIMITE 100
int n ;
...
switch (n)
{ case LIMITE-1 : printf ("un peu moins") ;
case LIMITE : printf ("juste") ;
case LIMITE+1 : printf ("un peu plus") ;
}
4)
const int LIMITE=100
int n ;
18 Exe rcices en langage C
...
switch (n)
{ case LIMITE-1 : printf ("un peu moins") ;
case LIMITE : printf ("juste") ;
case LIMITE+1 : printf ("un peu plus") ;
}
_______________________________________________________________
Solution
1)Ilm anque un point-virgule à la fin du prem ier printf:
if (a<b) printf ("ascendant") ;
else printf ("non ascendant") ;
2)Les valeurs suivantle m otcase doiventobligatoirem entê tre des "expressions constantes", c'est-à -dire des expressions
calculables par le com pilateur lui-m ê m e. Ce n'estpas le cas de n.
3)Aucune erreur, les expressions telles que LIM ITE-1 étantbien des expressions constantes.
4) Ici, les expressions suivant le m ot case ne sont plus des expressions constantes, car le sym bole LIM ITE a été défini
sous form e d'une "constante sym bolique" (en C+ + , cependant, ces instructions serontcorrectes).
Exe rcice III.2
___________________________________________________________________________
Enoncé
Soitle program m e suivant:
#include <stdio.h>
main()
{ int n ;
scanf ("%d", &n) ;
switch (n)
{ case 0 : printf ("Nuln") ;
case 1 :
III. Les instructions de contrôle 19
case 2 : printf ("Petitn") ;
break ;
case 3 :
case 4 :
case 5 : printf ("Moyenn") ;
default : printf ("Grandn") ;
}
}
Quels résultats affich e-t-illorsqu'on lui fourniten donnée :
a)0
b)1
c) 4
d)10
e)-5
___________________________________________________________________________
Solution
a)
Nul
Petit
b)
Petit
c)
Moyen
Grand
d)
Grand
e)
Grand
Exe rcice III.3
___________________________________________________________________________
20 Exe rcices en langage C
Enoncé
Quelles erreurs ontété com m ises dans ch acune des instructions suivantes :
a)
do c = getchar() while (c != 'n') ;
b)
do while ( (c = getchar()) != 'n') ;
c)
do {} while (1) ;
___________________________________________________________________________
Solution
a)Ilm anque un point-virgule :
do c = getchar() ; while (c != 'n') ;
b)Ilm anque une instruction (éventuellem ent"vide")aprè s le m otdo. On pourraitécrire, par exem ple :
do {} while ( (c = getchar()) != 'n') ;
ou :
do ; while ( (c = getchar()) != 'n') ;
c) Iln'y aura pas d'erreur de com pilation ;toutefois, ils'agitd'une "boucle infinie".
Exe rcice III.4
___________________________________________________________________________
Enoncé
Ecrire plus lisiblem ent:
do {} while (printf("donnez un nombre >0 "), scanf ("%d", &n), n<=0) ;
___________________________________________________________________________
III. Les instructions de contrôle 21
Solution
Plusieurs possibilités existent, puisqu'il"suffit" de reporter, dans le corps de la boucle, des instructions figurant
"artificiellem ent" sous form e d'expressions dans la condition de poursuite :
do
printf("donnez un nombre >0 ") ;
while (scanf ("%d", &n), n<=0) ;
ou, m ieux :
do
{ printf("donnez un nombre >0 ") ;
scanf ("%d", &n) ;
}
while (n<=0) ;
Exe rcice III.5
___________________________________________________________________________
Enoncé
Soitle petitprogram m e suivant:
#include <stdio.h>
main()
{ int i, n, som ;
som = 0 ;
for (i=0 ; i<4 ; i++)
{ printf ("donnez un entier ") ;
scanf ("%d", &n) ;
som += n ;
}
printf ("Somme : %dn", som) ;
}
Ecrire un program m e réalisantexactem entla m ê m e ch ose, en em ployant, à la place de l'instruction for :
22 Exe rcices en langage C
a)une instruction w h ile ,
b)une instruction do ... w h ile .
___________________________________________________________________________
Solution
a)
#include <stdio.h>
main()
{ int i, n, som ;
som = 0 ;
i = 0 ; /* ne pas oublier cette "initialisation" */
while (i<4)
{ printf ("donnez un entier ") ;
scanf ("%d", &n) ;
som += n ;
i++ ; /* ni cette "incrémentation" */
}
printf ("Somme : %dn", som) ;
}
b)
#include <stdio.h>
main()
{ int i, n, som ;
som = 0 ;
i = 0 ; /* ne pas oublier cette "initialisation" */
do
{ printf ("donnez un entier ") ;
scanf ("%d", &n) ;
som += n ;
i++ ; /* ni cette "incrémentation" */
}
while (i<4) ; /* attention, ici, toujours <4 */
printf ("Somme : %dn", som) ;
}
III. Les instructions de contrôle 23
Exe rcice III.6
___________________________________________________________________________
Enoncé
Quels résultats fournitle program m e suivant:
#include <stdio.h>
main()
{ int n=0 ;
do
{ if (n%2==0) { printf ("%d est pairn", n) ;
n += 3 ;
continue ;
}
if (n%3==0) { printf ("%d est multiple de 3n", n) ;
n += 5 ;
}
if (n%5==0) { printf ("%d est multiple de 5n", n) ;
break ;
}
n += 1 ;
}
while (1) ;
}
___________________________________________________________________________
Solution
0 est pair
3 est multiple de 3
9 est multiple de 3
15 est multiple de 3
20 est multiple de 5
24 Exe rcices en langage C
Exe rcice III.7
___________________________________________________________________________
Enoncé
Quels résultats fournitle program m e suivant:
#include <stdio.h>
main()
{ int n, p ;
n=0 ;
while (n<=5) n++ ;
printf ("A : n = %dn", n) ;
n=p=0 ;
while (n<=8) n += p++ ;
printf ("B : n = %dn", n) ;
n=p=0 ;
while (n<=8) n += ++p ;
printf ("C : n = %dn", n) ;
n=p=0 ;
while (p<=5) n+= p++ ;
printf ("D : n = %dn", n) ;
n=p=0 ;
while (p<=5) n+= ++p ;
printf ("D : n = %dn", n) ;
}
___________________________________________________________________________
Solution
A : n = 6
B : n = 10
C : n = 10
D : n = 15
III. Les instructions de contrôle 25
D : n = 21
Exe rcice III.8
___________________________________________________________________________
Enoncé
Quels résultats fournitle program m e suivant:
#include <stdio.h>
main()
{ int n, p ;
n=p=0 ;
while (n<5) n+=2 ; p++ ;
printf ("A : n = %d, p = %d n", n, p) ;
n=p=0 ;
while (n<5) { n+=2 ; p++ ; }
printf ("B : n = %d, p = %d n", n, p) ;
}
___________________________________________________________________________
Solution
A : n = 6, p = 1
B : n = 6, p = 3
Exe rcice III.9
___________________________________________________________________________
26 Exe rcices en langage C
Enoncé
Quels résultats fournitle program m e suivant:
#include <stdio.h>
main()
{ int i, n ;
for (i=0, n=0 ; i<5 ; i++) n++ ;
printf ("A : i = %d, n = %dn", i, n) ;
for (i=0, n=0 ; i<5 ; i++, n++) {}
printf ("B : i = %d, n = %dn", i, n) ;
for (i=0, n=50 ; n>10 ; i++, n-= i ) {}
printf ("C : i = %d, n = %dn", i, n) ;
for (i=0, n=0 ; i<3 ; i++, n+=i, printf ("D : i = %d, n = %dn", i, n) ) ;
printf ("E : i = %d, n = %dn", i, n) ;
}
___________________________________________________________________________
Solution
A : i = 5, n = 5
B : i = 5, n = 5
C : i = 9, n = 5
D : i = 1, n = 1
D : i = 2, n = 3
D : i = 3, n = 6
E : i = 3, n = 6
III. Les instructions de contrôle 27
Exe rcice III.10
___________________________________________________________________________
Enoncé
Ecrire un program m e qui calcule les racines carrées de nom bres fournis en donnée. Ils'arrê tera lorqu'on lui fournira la
valeur 0. Ilrefusera les valeurs négatives. Son exécution se présentera ainsi :
donnez un nombre positif : 2
sa racine carrée est : 1.414214e+00
donnez un nombre positif : -1
svp positif
donnez un nombre positif : 5
sa racine carrée est : 2.236068e+00
donnez un nombre positif : 0
Rappelons que la fonction sqrtfournitla racine carrée (double )de la valeur (double )qu'on lui fourniten argum ent.
___________________________________________________________________________
Solution
Ilexiste beaucoup de "rédactions possibles" ;en voici 3 :
#include <stdio.h>
#include <math.h> /* indispensable pour sqrt (qui fourni un résultat */
/* de type double */
main()
{ double x ;
do
{ printf ("donnez un nombre positif : ") ;
scanf ("%le", &x) ;
if (x < 0) printf ("svp positif n") ;
if (x <=0) continue ;
printf ("sa racine carrée est : %len", sqrt (x) ) ;
}
while (x) ;
}
28 Exe rcices en langage C
#include <stdio.h>
#include <math.h>
main()
{ double x ;
do
{ printf ("donnez un nombre positif : ") ;
scanf ("%le", &x) ;
if (x < 0) { printf ("svp positif n") ;
continue ;
}
if (x>0) printf ("sa racine carrée est : %len", sqrt (x) ) ;
}
while (x) ;
}
#include <stdio.h>
#include <math.h>
main()
{ double x ;
do
{ printf ("donnez un nombre positif : ") ;
scanf ("%le", &x) ;
if (x < 0) { printf ("svp positif n") ;
continue ;
}
if (x>0) printf ("sa racine carrée est : %len", sqrt (x) ) ;
if (x==0) break ;
}
while (1) ;
}
Rem arque :
Ilne fautsurtoutpas oublier #include < m ath .h > car, sinon, le com pilateur considè re (en l'abscence du prototype)
que sqrtfournitun résultatde type int.
III. Les instructions de contrôle 29
Exe rcice III.11
___________________________________________________________________________
Enoncé
Calculer la som m e des n prem iers term es de la "série h arm onique", c'est-à -dire la som m e :
1 + 1/2 + 1/3 + 1/4 + ..... + 1/n
La valeur de n sera lue en donnée.
___________________________________________________________________________
Solution
#include <stdio.h>
main()
{
int nt ; /* nombre de termes de la série harmonique */
float som ; /* pour la somme de la série */
int i ;
do
{ printf ("combien de termes : ") ;
scanf ("%d", &nt) ;
}
while (nt<1) ;
for (i=1, som=0 ; i<=nt ; i++) som += (float)1/i ;
printf ("Somme des %d premiers termes = %f", nt, som) ;
}
Rem arques :
1)Rappelons que dans :
som += (float)1/i
l'expression de droite estévaluée en convertissantd'abord 1 eti en float.
30 Exe rcices en langage C
Ilfautéviter d'écrire :
som += 1/i
auquelcas, les valeurs de 1/i seraient toujours nulles (sauf pour i=1) puique l'opérateur /, lorsqu'ilporte sur des
entiers, correspond à la division entiè re.
De m ê m e, en écrivant:
som += (float) (1/i)
le résultatne seraitpas plus satisfaisantpuisque la conversion en flottantn'auraitlieu qu'aprè s la division (en entier).
En revanch e, on pourraitécrire :
som += 1.0/i ;
2)Si l'on ch erch aità exécuter ce program m e pour des valeurs élevées de n (en prévoyantalors une variable de type
floatou double ), on constateraitque la valeur de la som m e sem ble "converger" vers une lim ite (bien qu'en th éorie la
série h arm onique "diverge"). Cela provient tout sim plem ent de ce que, dè s que la valeur de 1/i est "petite" devant
som , le résultat de l'addition de 1/i et de som est exactem ent som . On pourrait toutefois am éliorer le résultat en
effectuantla som m e "à l'envers" (en effet, dans ce cas, le rapportentre la valeur à ajouter etla som m e courante serait
plus faible que précédem m ent)..
Exe rcice III.12
___________________________________________________________________________
Enoncé
Affich er un triangle isocè le form é d'étoiles. La h auteur du triangle (c'est-à -dire le nom bre de lignes) sera fourni en
donnée, com m e dans l'exem ple ci-dessous. On s'arrangera pour que la derniè re ligne du triangle s'affich e sur le bord
gauch e de l'écran.
combien de lignes ? 10
*
***
*****
*******
*********
***********
*************
***************
III. Les instructions de contrôle 31
*****************
*******************
___________________________________________________________________________
Solution
#include <stdio.h>
#define car '*' /* caractère de remplissage */
main()
{ int nlignes ; /* nombre total de lignes */
int nl ; /* compteur de ligne */
int nesp ; /* nombre d'espaces précédent une étoile */
int j ;
printf ("combien de lignes ? ") ;
scanf ("%d", &nlignes) ;
for (nl=0 ; nl<nlignes ; nl++)
{ nesp = nlignes - nl - 1 ;
for (j=0 ; j<nesp ; j++) putchar (' ') ;
for (j=0 ; j<2*nl+1 ; j++) putchar (car) ;
putchar ('n') ;
}
}
Exe rcice III.13
___________________________________________________________________________
Enoncé
Affich er toutes les m aniè res possibles d'obtenir un franc avec des piè ces de 2 centim es, 5 centim es et10 centim es. Dire
com bien de possibilités ontété ainsi trouvées. Les résultats serontaffich és com m e suit:
1 F = 50 X 2c
32 Exe rcices en langage C
1 F = 45 X 2c 2 X 5c
1 F = 40 X 2c 4 X 5c
1 F = 35 X 2c 6 X 5c
1 F = 30 X 2c 8 X 5c
1 F = 25 X 2c 10 X 5c
1 F = 20 X 2c 12 X 5c
1 F = 15 X 2c 14 X 5c
1 F = 10 X 2c 16 X 5c
1 F = 5 X 2c 18 X 5c
1 F = 20 X 5c
1 F = 45 X 2c 1 X 10c
1 F = 40 X 2c 2 X 5c 1 X 10c
1 F = 35 X 2c 4 X 5c 1 X 10c
1 F = 10 X 2c 2 X 5c 7 X 10c
1 F = 5 X 2c 4 X 5c 7 X 10c
1 F = 6 X 5c 7 X 10c
1 F = 10 X 2c 8 X 10c
1 F = 5 X 2c 2 X 5c 8 X 10c
1 F = 4 X 5c 8 X 10c
1 F = 5 X 2c 9 X 10c
1 F = 2 X 5c 9 X 10c
1 F = 10 X 10c
En tout, il y a 66 façons de faire 1 F
___________________________________________________________________________
Solution
#include <stdio.h>
main()
{
int nbf ; /* compteur du nombre de façons de faire 1 F */
int n10 ; /* nombre de pièces de 10 centimes */
int n5 ; /* nombre de pièces de 5 centimes */
int n2 ; /* nombre de pièces de 2 centimes */
nbf = 0 ;
for (n10=0 ; n10<=10 ; n10++)
for (n5=0 ; n5<=20 ; n5++)
for (n2=0 ; n2<=50 ; n2++)
III. Les instructions de contrôle 33
if ( 2*n2 + 5*n5 + 10*n10 == 100)
{ nbf ++ ;
printf ("1 F = ") ;
if (n2) printf ("%2d X 2c ", n2 ) ;
if (n5) printf ("%2d X 5c ", n5 ) ;
if (n10) printf ("%2d X 10c", n10) ;
printf ("n") ;
}
printf ("nEn tout, il y a %d façons de faire 1 Fn", nbf) ;
}
Exe rcice III.14
___________________________________________________________________________
Enoncé
Ecrire un program m e qui déterm ine la niem e valeur un (n étant fourni en donnée) de la "suite de Fibonacci" définie
com m e suit:
u1 = 1
u2 = 1
un = un-1 + un-2 pour n> 2
_______________________________________________________________
Solution
#include <stdio.h>
main()
{
int u1, u2, u3 ; /* pour "parcourir" la suite */
int n ; /* rang du terme demandé */
int i ; /* compteur */
34 Exe rcices en langage C
do
{ printf ("rang du terme demandé (au moins 3) ? ") ;
scanf ("%d", &n) ;
}
while (n<3) ;
u2 = u1 = 1 ; /* les deux premiers termes */
i = 2 ;
while (i++ < n) /* attention, l'algorithme ne fonctionne */
{ u3 = u1 + u2 ; /* que pour n > 2 */
u1 = u2 ;
u2 = u3 ;
}
/* autre formulation possible : */
/* for (i=3 ; i<=n ; i++, u1=u2, u2=u3) u3 = u1 + u2 ; */
printf ("Valeur du terme de rang %d : %d", n, u3) ;
}
Notez que, com m e à l'accoutum ée en C, beaucoup de form ulations sont possibles. Nous en avons d'ailleurs placé une
seconde en com m entaire de notre program m e.
Exe rcice III.15
___________________________________________________________________________
Enoncé
Ecrire un program m e qui trouve la plus grande etla plus petite valeur d'une succession de notes (nom bres entiers entre 0
et20) fournies en données, ainsi que le nom bre de fois où ce m axim um etce m inim um ontété attribués. On supposera
que les notes, en nom bre non connu à l'avance, serontterm inées par une valeur négative. On s'astreindra à ne pas utiliser
de "tableau". L'exécution du program m e pourra se présenter ainsi :
donnez une note (-1 pour finir) : 12
donnez une note (-1 pour finir) : 8
donnez une note (-1 pour finir) : 13
donnez une note (-1 pour finir) : 7
III. Les instructions de contrôle 35
donnez une note (-1 pour finir) : 11
donnez une note (-1 pour finir) : 12
donnez une note (-1 pour finir) : 7
donnez une note (-1 pour finir) : 9
donnez une note (-1 pour finir) : -1
note maximale : 13 attribuée 1 fois
note minimale : 7 attribuée 2 fois
_______________________________________________________________
Solution
#include <stdio.h>
main()
{
int note ; /* note "courante" */
int max ; /* note maxi */
int min ; /* note mini */
int nmax ; /* nombre de fois où la note maxi a été trouvée */
int nmin ; /* nombre de fois où la note mini a été trouvée */
max = -1 ; /* initialisation max (possible car toutes notes >=0 */
min = 21 ; /* initialisation min (possible car toutes notes < 21) */
while (printf ("donnez une note (-1 pour finir) : "),
scanf ("%d", &note),
note >=0)
{ if (note == max) nmax++ ;
if (note > max) { max = note ;
nmax = 1 ;
}
if (note == min) nmin++ ;
if (note < min) { min = note ;
nmin = 1 ;
}
}
/* attention, si aucune note (cad si max<0) */
/* les résultats sont sans signification */
if (max >= 0)
{ printf ("nnote maximale : %d attribuée %d foisn", max, nmax) ;
36 Exe rcices en langage C
printf ("note minimale : %d attribuée %d foisn", min, nmin) ;
}
}
Exe rcice III.16
___________________________________________________________________________
Enoncé
Ecrire un program m e qui affich e la "table de m ultiplication" des nom bres de 1 à 10, sous la form e suivante :
I 1 2 3 4 5 6 7 8 9 10
-----------------------------------------------
1 I 1 2 3 4 5 6 7 8 9 10
2 I 2 4 6 8 10 12 14 16 18 20
3 I 3 6 9 12 15 18 21 24 27 30
4 I 4 8 12 16 20 24 28 32 36 40
5 I 5 10 15 20 25 30 35 40 45 50
6 I 6 12 18 24 30 36 42 48 54 60
7 I 7 14 21 28 35 42 49 56 63 70
8 I 8 16 24 32 40 48 56 64 72 80
9 I 9 18 27 36 45 54 63 72 81 90
10 I 10 20 30 40 50 60 70 80 90 100
_______________________________________________________________
Solution
#include <stdio.h>
#define NMAX 10 /* nombre de valeurs */
main()
{ int i, j ;
/* affichage ligne en-tête */
printf (" I") ;
for (j=1 ; j<=NMAX ; j++) printf ("%4d", j) ;
III. Les instructions de contrôle 37
printf ("n") ;
printf ("-------") ;
for (j=1 ; j<=NMAX ; j++) printf ("----") ;
printf ("n") ;
/* affichage des différentes lignes */
for (i=1 ; i<=NMAX ; i++)
{ printf ("%4d I", i) ;
for (j=1 ; j<=NMAX ; j++)
printf ("%4d", i*j) ;
printf ("n") ;
}
IV: LES FO NCTIO NS
N.B. Ici, on ne trouvera aucun exercice faisant intervenir des pointeurs, et par conséquent aucun exercice m ettant en
oeuvre une transm ission d'argum ents par adresse. De tels exercices apparaîtrontdans le ch apitre suivant.
Exe rcice IV.1
___________________________________________________________________________
Enoncé
a)Que fournitle program m e suivant:
#include <stdio.h>
main()
{
int n, p=5 ;
n = fct (p) ;
printf ("p = %d, n = %dn", p, n) ;
}
int fct (int r)
{ return 2*r ;
}
b)Ajouter une déclaration convenable de la fonction fct:
- sous la form e la plus brè ve possible (suivantla norm e ANSI),
40 Exe rcices en langage C
- sous form e d'un "prototype".
_______________________________________________________________
Solution
a)Bien qu'ilne possè de pas de déclaration de la fonction fct, le program m e m ain estcorrect. En effet, la norm e ANSI
autorise qu'une fonction ne soitpas déclarée, auquelcas elle est considérée com m e fournissant un résultat de type int.
Cette facilité esttoutefois fortem entdéconseillée (etelle ne sera plus acceptée de C+ + ). Voici les résultats fournis par
le program m e :
p = 5, n = 10
b)La déclaration la plus brè ve sera :
int fct () ;
La déclaration (vivem entconseillée), sous form e de prototype sera :
int fct (int) ;
ou, éventuellem ent, sous form e d'un prototype "com plet" :
int fct (int r) ;
Dans ce dernier cas, le nom r n'a aucune signification : on utilise souventle m ê m e nom (lorsqu'on le connaît!) que dans
l'en-tê te de la fonction, m ais ilpourraits'agir de n'im porte quelautre nom de variable).
Exe rcice IV.2
___________________________________________________________________________
Enoncé
Ecrire :
IV. Les fonctions 41
- une fonction, nom m ée f1, se contentant d'affich er "bonjour" (elle ne possédera aucun argum ent, ni valeur de
retour),
- une fonction, nom m ée f2, qui affich e "bonjour" un nom bre de fois égalà la valeur reçue en argum ent(int)etqui ne
renvoie aucune valeur,
- une fonction, nom m ée f3, qui faitla m ê m e ch ose que f2, m ais qui, de plus, renvoie la valeur (int)0.
Ecrire un petitprogram m e appelantsuccessivem entch acune de ces 3 fonctions, aprè s les avoir convenablem entdéclarées
sous form e d'un prototype.
_______________________________________________________________
Solution
#include <stdio.h>
void f1 (void)
{
printf ("bonjourn") ;
}
void f2 (int n)
{
int i ;
for (i=0 ; i<n ; i++)
printf ("bonjourn") ;
}
int f3 (int n)
{
int i ;
for (i=0 ; i<n ; i++)
printf ("bonjourn") ;
return 0 ;
}
main()
{
void f1 (void) ;
void f2 (int) ;
int f3 (int) ;
f1 () ;
f2 (3) ;
f3 (3) ;
42 Exe rcices en langage C
}
Exe rcice IV.3
___________________________________________________________________________
Enoncé
Quels résultats fournira ce program m e :
#include <stdio.h>
int n=10, q=2 ;
main()
{
int fct (int) ;
void f (void) ;
int n=0, p=5 ;
n = fct(p) ;
printf ("A : dans main, n = %d, p = %d, q = %dn", n, p, q) ;
f() ;
}
int fct (int p)
{
int q ;
q = 2 * p + n ;
printf ("B : dans fct, n = %d, p = %d, q = %dn", n, p, q) ;
return q ;
}
void f (void)
{
int p = q * n ;
printf ("C : dans f, n = %d, p = %d, q = %dn", n, p, q) ;
}
_______________________________________________________________
IV. Les fonctions 43
Solution
B : dans fct, n = 10, p = 5, q = 20
A : dans main, n = 20, p = 5, q = 2
C : dans f, n = 10, p = 20, q = 2
Exe rcice IV.4
___________________________________________________________________________
Enoncé
Ecrire une fonction qui reçoiten argum ents 2 nom bres flottants etun caractè re etqui fournitun résultatcorrespondantà
l'une des 4 opérations appliquées à ses deux prem iers argum ents, en fonction de la valeur du dernier, à savoir : addition
pour le caractè re + , soustraction pour -, m ultiplication pour *etdivision pour /(toutautre caractè re que l'un des 4 cités
sera interprété com m e une addition). On ne tiendra pas com pte des risques de division par zéro.
Ecrire un petit program m e (m ain) utilisant cette fonction pour effectuer les 4 opérations sur deux nom bres fournis en
donnée.
_______________________________________________________________
Solution
#include <stdio.h>
float oper (float v1, float v2, char op)
{ float res ;
switch (op)
{ case '+' : res = v1 + v2 ;
break ;
case '-' : res = v1 - v2 ;
break ;
case '*' : res = v1 * v2 ;
break ;
case '/' : res = v1 / v2 ;
44 Exe rcices en langage C
break ;
default : res = v1 + v2 ;
}
return res ;
}
main()
{
float oper (float, float, char) ; /* prototype de oper */
float x, y ;
printf ("donnez deux nombres réels : ") ;
scanf ("%e %e", &x, &y) ;
printf ("leur somme est : %en", oper (x, y, '+') ) ;
printf ("leur différence est : %en", oper (x, y, '-') ) ;
printf ("leur produit est : %en", oper (x, y, '*') ) ;
printf ("leur quotient est : %en", oper (x, y, '/') ) ;
}
Exe rcice IV.5
___________________________________________________________________________
Enoncé
Transform er le program m e (fonction + m ain)écritdans l'exercice précédentde m aniè re à ce que la fonction ne dispose
plus que de 2 argum ents, le caractè re indiquantla nature de l'opération à effectuer étantprécisé, cette fois, à l'aide d'une
variable globale.
_______________________________________________________________
Solution
#include <stdio.h>
IV. Les fonctions 45
char op ; /* variable globale pour la nature de l'opération */
/* attention : doit être déclarée avant d'être utilisée */
float oper (float v1, float v2)
{ float res ;
switch (op)
{ case '+' : res = v1 + v2 ;
break ;
case '-' : res = v1 - v2 ;
break ;
case '*' : res = v1 * v2 ;
break ;
case '/' : res = v1 / v2 ;
break ;
default : res = v1 + v2 ;
}
return res ;
}
main()
{
float oper (float, float) ; /* prototype de oper */
float x, y ;
printf ("donnez deux nombres réels : ") ;
scanf ("%e %e", &x, &y) ;
op = '+' ;
printf ("leur somme est : %en", oper (x, y) ) ;
op = '-' ;
printf ("leur différence est : %en", oper (x, y) ) ;
op = '*' ;
printf ("leur produit est : %en", oper (x, y) ) ;
op = '/' ;
printf ("leur quotient est : %en", oper (x, y) ) ;
}
Rem arque :
Ils'agissait ici d'un exercice d'"école" destiné à forcer l'utilisation d'une variable globale. Dans la pratique, on
évitera le plus possible ce genre de program m ation qui favorise trop largem entles risques d'"effets de bord".
46 Exe rcices en langage C
Exe rcice IV.6
___________________________________________________________________________
Enoncé
Ecrire une fonction, sans argum entni valeur de retour, qui se contente d'affich er, à ch aque appel, le nom bre totalde fois
où elle a été appelée sous la form e :
appel numéro 3
_______________________________________________________________
Solution
La m eilleure solution consiste à prévoir, au sein de la fonction en question, une variable de classe statique. Elle sera
initialisée une seule fois à zéro (ou à toute autre valeur éventuellem entexplicitée)au débutde l'exécution du program m e.
Ici, nous avons, de plus, prévu un petitprogram m e d'essai.
#include <stdio.h>
void fcompte (void)
{
static int i ; /* il est inutile, mais pas défendu, d'écrire i=0 */
i++ ;
printf ("appel numéro %dn", i) ;
}
/* petit programme d'essai de fcompte */
main()
{ void fcompte (void) ;
int i ;
for (i=0 ; i<3 ; i++) fcompte () ;
}
Là encore, la dém arch e consistantà utiliser com m e com pteur d'appels une variable globale (qui devraitalors ê tre connue
du program m e utilisateur)està proscrire.
IV. Les fonctions 47
Exe rcice IV.7
___________________________________________________________________________
Enoncé
Ecrire 2 fonctions à un argum ent entier et une valeur de retour entiè re perm ettant de préciser si l'argum ent reçu est
m ultiple de 2 (pour la prem iè re fonction)ou m ultiple de 3 (pour la seconde fonction).
Utiliser ces deux fonctions dans un petit program m e qui lit un nom bre entier et qui précise s'ilest pair, m ultiple de 3
et/ou m ultiple de 6, com m e dans cetexem ple (ily a deux exécutions):
donnez un entier : 9
il est multiple de 3
_______________
donnez un entier : 12
il est pair
il est multiple de 3
il est divisible par 6
_______________________________________________________________
Solution
#include <stdio.h>
int mul2 (int n)
{
if (n%2) return 0 ;
else return 1 ;
}
int mul3 (int n)
{
if (n%3) return 0 ;
else return 1 ;
}
main()
{
int mul2 (int) ;
48 Exe rcices en langage C
int mul3 (int) ;
int n ;
printf ("donnez un entier : ") ;
scanf ("%d", &n) ;
if (mul2(n)) printf ("il est pairn") ;
if (mul3(n)) printf ("il est multiple de 3n") ;
if (mul2(n) && mul3(n)) printf ("il est divisible par 6n") ;
}
V: TABLEAUX ET
PO INTEURS
Exe rcice V.1
___________________________________________________________________________
Enoncé
Quels résultats fournira ce program m e :
#include <stdio.h>
main()
{
int t [3] ;
int i, j ;
int * adt ;
for (i=0, j=0 ; i<3 ; i++) t[i] = j++ + i ; /* 1 */
for (i=0 ; i<3 ; i++) printf ("%d ", t[i]) ; /* 2 */
printf ("n") ;
for (i=0 ; i<3 ; i++) printf ("%d ", *(t+i)) ; /* 3 */
printf ("n") ;
for (adt = t ; adt < t+3 ; adt++) printf ("%d ", *adt) ; /* 4 */
printf ("n") ;
for (adt = t+2 ; adt>=t ; adt--) printf ("%d ", *adt) ; /* 5 */
50 Exe rcices en langage C
printf ("n") ;
}
_______________________________________________________________
Solution
/*1*/rem plitle tableau avec les valeurs 0 (0+ 0), 2 (1+ 1) et4 (2+ 2);on obtiendraitplus sim plem entle m ê m e résultat
avec l'expression 2*i.
/*2 */affich e "classiquem ent" les valeurs du tableau t, dans l'ordre "naturel".
/*3 */ fait la m ê m e ch ose, en utilisant le form alism e pointeur au lieu du form alism e tableau. Ainsi, *(t+ i) est
parfaitem entéquivalentà t[i].
/*4 */ faitla m ê m e ch ose, en utilisantla "lvalue" adt(à laquelle on a affecté initialem entl'adresse tdu tableau) eten
"l'incrém entant" pour parcourir les différentes adresses des 4 élém ents du tableau.
/*5 */ affich e les valeurs de t, à l'envers, en utilisantle m ê m e form alism e pointeur que dans 4. On auraitpu écrire, de
façon équivalente :
for (i=2 ; i>=0 ; i--) printf ("%d ", t[i]) ;
Voici les résultats fournis par ce program m e :
0 2 4
0 2 4
0 2 4
4 2 0
Exe rcice V.2
___________________________________________________________________________
V. Table aux e tpointe urs 51
Enoncé
Ecrire, de deux façons différentes, un program m e qui lit10 nom bres entiers dans un tableau avantd'en rech erch er le plus
grand etle plus petit:
a)en utilisantuniquem entle "form alism e tableau",
b)en utilisantle "form alism e pointeur", à ch aque fois que cela estpossible
_______________________________________________________________
Solution
a)La program m ation est, ici, "classique". Nous avons sim plem entdéfini un sym bole NVALdestiné à contenir le nom bre
de valeurs du tableau. Notez bien que la déclaration int t[NVAL] est acceptée puisque NVAL est une "expression
constante". En revanch e, elle ne l'auraitpas été si nous avions défini ce sym bole NVALpar une "constante sym bolique"
(constintNVAL=10).
#include <stdio.h>
#define NVAL 10 /* nombre de valeurs du tableau */
main()
{ int i, min, max ;
int t[NVAL] ;
printf ("donnez %d valeursn", NVAL) ;
for (i=0 ; i<NVAL ; i++) scanf ("%d", &t[i]) ;
max = min = t[0] ;
for (i=1 ; i<NVAL ; i++)
{ if (t[i] > max) max = t[i] ; /* ou max = t[i]>max ? t[i] : max */
if (t[i] < min) min = t[i] ; /* ou min = t[i]<min ? t[i] : min */
}
printf ("valeur max : %dn", max) ;
printf ("valeur min : %dn", min) ;
}
b)On peut rem placer systém atiquem ent, t[i] par *(t+ i)./ De plus, dans scanf, on peut rem placer & t[i] par t+ i. Voici
finalem entle program m e obtenu :
#include <stdio.h>
#define NVAL 10 /* nombre de valeurs du tableau */
main()
{ int i, min, max ;
52 Exe rcices en langage C
int t[NVAL] ;
printf ("donnez %d valeursn", NVAL) ;
for (i=0 ; i<NVAL ; i++) scanf ("%d", t+i) ; /* attention t+i et non *(t+i) */
max = min = *t ;
for (i=1 ; i<NVAL ; i++)
{ if (*(t+i) > max) max = *(t+i) ;
if (*(t+i) < min) min = *(t+i) ;
}
printf ("valeur max : %dn", max) ;
printf ("valeur min : %dn", min) ;
}
Exe rcice V.3
___________________________________________________________________________
Enoncé
Soientdeux tableaux t1 ett2 déclarés ainsi :
float t1[10], t2[10] ;
Ecrire les instructions perm ettantde recopier, dans t1, tous les élém ents positifs de t2, en com plétantéventuellem entt1
par des zéros. Ici, on ne ch erch era pas à fournir un program m e com pleteton utilisera systém atiquem entle form alism e
tableau.
_______________________________________________________________
Solution
On peutcom m encer par rem plir t1 de zéros, avantd'y recopier les élém ents positifs de t2 :
int i, j ;
for (i=0 ; i<10 ; i++) t1[i] = 0 ;
/* i sert à pointer dans t1 et j dans t2 */
for (i=0, j=0 ; j<10 ; j++)
V. Table aux e tpointe urs 53
if (t2[j] > 0) t1[i++] = t2[j] ;
M ais, on peut recopier d'abord dans t1 les élém ents positifs de t2, avant de com pléter éventuellem ent par des zéros.
Cette deuxiè m e form ulation, m oins sim ple que la précédente, se révéleraittoutefois plus efficace sur de grands tableaux :
int i, j ;
for (i=0, j=0 ; j<10 ; j++)
if (t2[j] > 0) t1[i++] = t2[j] ;
for (j=i ; j<10 ; j++) t1[j] = 0 ;
Exe rcice V.4
___________________________________________________________________________
Enoncé
Quels résultats fournira ce program m e :
#include <stdio.h>
main()
{ int t[4] = {10, 20, 30, 40} ;
int * ad [4] ;
int i ;
for (i=0 ; i<4 ; i++) ad[i] = t+i ; /* 1 */
for (i=0 ; i<4 ; i++) printf ("%d ", * ad[i]) ; /* 2 */
printf ("n") ;
printf ("%d %d n", * (ad[1] + 1), * ad[1] + 1) ; /* 3 */
}
_______________________________________________________________
Solution
Le tableau ad est un tableau de 4 élém ents ;ch acun de ces élém ents est un pointeur sur un int. L'instruction /*1 */
rem plitle tableau ad avec les adresses des 4 élém ents du tableau t. L'instruction /*2 */affich e finalem entles 4 élém ents
du tableau t;en effet, *ad[i]représente la valeur située à l'adresse ad[i]. /*2 */estéquivalente ici à :
for (i=0 ; i<4 ; i++) printf ("%d", t[i]) ;
54 Exe rcices en langage C
Enfin, dans l'instruction /*3 */, *(ad[1]+ 1)représente la valeur située à l'entier suivantcelui d'adresse ad[1];ils'agit
donc de t[2]. En revanch e, *ad[1]+ 1 représente la valeur située à l'adresse ad[1]augm entée de 1, autrem entditt[1]+
1.
Voici, en définitive, les résultats fournis par ce program m e :
10 20 30 40
30 21
Exe rcice V.5
___________________________________________________________________________
Enoncé
Soitle tableau tdéclaré ainsi :
float t[3] [4] ;
Ecrire les (seules)instructions perm ettantde calculer, dans une variable nom m ée som , la som m e des élém ents de t:
a)en utilisantle "form alism e usueldes tableaux à deux indices",
b)en utilisantle "form alism e pointeur".
_______________________________________________________________
Solution
a)La prem iè re solution ne pose aucun problè m e particulier :
int i, j ;
som = 0 ;
for (i=0 ; i<3 ; i++)
for (j=0 ; j<4 ; j++)
som += t[i] [j] ;
b)Le form alism e pointeur estici m oins facile à appliquer que dans le cas des tableaux à un indice. En effet, avec, par
exem ple, float t[4], t est de type int*et ilcorrespond à un pointeur sur le prem ier élém ent du tableau. Ilsuffit donc
d'incrém enter convenablem enttpour parcourir tous les élém ents du tableau.
V. Table aux e tpointe urs 55
En revanch e, avec notre tableau floatt[3][4], testdu type pointeur sur des tableaux de 4 flottants(type : float[4]*). La
notation *(t+ i) est généralem ent inutilisable sous cette form e puisque, d'une part, elle correspond à des valeurs de
tableaux de 4 flottants et que, d'autre part, l'incrém ent i porte, non plus sur des flottants, m ais sur des blocs de 4
flottants ;par exem ple, t+ 2 représente l'adresse du h uitiè m e flottant, com pté à partir de celui d'adresse t.
Une solution consiste à "convertir" la valeur de t en un pointeur de type float*. On pourrait se contenter de procéder
ainsi :
float * adt ;
.....
adt = t ;
En effet, dans ce cas, l'affectation entraîne une conversion forcée de t en float *, ce qui ne ch ange pas l'adresse
correspondante1 (seule la nature du pointeur a ch angé).
Généralem ent, on y gagnera en lisibilité en explicitant la conversion m ise en oeuvre à l'aide de l'opérateur de "cast".
Notez que, d'une part, cela peutéviter certains m essages d'avertissem ent("w arnings")de la partdu com pilateur.
Voici finalem entce que pourraientê tre les instructions dem andées :
int i ;
int * adt ;
som = 0 ;
adt = (float *) t ;
for (i=0 ; i<12 ; i++)
som += * (adt+i);
Exe rcice V.6
___________________________________________________________________________
Enoncé
Ecrire une fonction qui fourniten valeur de retour la som m e des élém ents d'un tableau de flottants transm is, ainsi que sa
dim ension, en argum ent.
Ecrire un petitprogram m e d'essai.
1 Attention, cela n'estvrai que parce que l'on passe de pointeurs sur des groupes d'élém ents à un pointeur sur ces élém ents. Autrem entdit, aucune
"contrainte d'alignem ent" ne risque de nuire ici. Iln'en iraitpas de m ê m e, par exem ple, pour des conversions de ch ar *en int*.
56 Exe rcices en langage C
_______________________________________________________________
Solution
En ce qui concerne le tableau de flottants reçu en argum ent, ilne peutê tre transm is que par adresse. Quantau nom bre
d'élém ent(de type int), nous le transm ettrons classiquem entpar valeur. L'en-tê te de notre fonction pourra se présenter
sous l'une des form es suivantes :
float somme (float t[], int n)
float somme (float * t, int n)
float somme (float t[5], int n) /* déconseillé car laisse croire que t */
/* est de dimension fixe 5 */
En effet, la dim ension réelle de tn'a aucune incidence sur les instructions de la fonction elle-m ê m e (elle n'intervientpas
dans le calculde l'adresse d'un élém entdu tableau2 etelle ne sertpas à "allouer" un em placem entpuisque le tableau en
question aura été alloué dans la fonction appelantsom m e ).
Voici ce que pourraitê tre la fonction dem andée :
float somme (float t[], int n) /* on pourrait écrire somme (float * t, ... */
/* ou encore somme (float t[4], ... */
/* mais pas somme (float t[n], ... */
{ int i ;
float s = 0 ;
for (i=0 ; i<n ; i++)
s += t[i] ; /* on pourrait écrire s += * (t+i) ; */
return s ;
}
Pour ce qui est du program m e d'utilisation de la fonction som m e , on peut, là encore, écrire le "prototype" sous
différentes form es :
float somme (float [], int ) ;
float somme (float * , int ) ;
float somme (float [5], int ) ; /* déconseillé car laisse croire que t */
/* est de dimension fixe 5 */
Voici un exem ple d'un telprogram m e :
#include <stdio.h>
main()
2Iln'en iraitpas de m ê m e pour des tableaux à plusieurs indices.
V. Table aux e tpointe urs 57
{
float somme (float *, int) ;
float t[4] = {3, 2.5, 5.1, 3.5} ;
printf ("somme de t : %fn", somme (t, 4) ) ;
}
Exe rcice V.7
___________________________________________________________________________
Enoncé
Ecrire une fonction qui ne renvoie aucune valeur etqui déterm ine la valeur m axim ale etla valeur m inim ale d'un tableau
d'entiers (à un indice)de taille quelconque. Ilfaudra donc prévoir 4 argum ents : le tableau, sa dim ension, le m axim um et
le m inim um .
Ecrire un petitprogram m e d'essai.
_______________________________________________________________
Solution
En langage C, un tableau ne peutê tre transm is que par adresse (en toute rigueur, C n'autorise que la transm ission par
valeur m ais, dans le cas d'un tableau, on transm et une valeur de type pointeur qui n'est rien d'autre que l'adresse du
tableau!). En ce qui concerne son nom bre d'élém ents, on peutindifférem m enten transm ettre l'adresse (sous form e d'un
pointeur de type int*), ou la valeur ;ici, la seconde solution estla plus norm ale.
En revanch e, en ce qui concerne le m axim um et le m inim um , ils ne peuvent pas ê tre transm is par valeur, puisqu'ils
doiventprécisém entê tre déterm inés par la fonction. Ilfautdonc obligatoirem entprévoir de passer des pointeurs sur des
float. L'en-tê te de notre fonction pourra donc se présenter ainsi (nous ne donnons plus toutes les écritures possibles):
void maxmin (int t[], int n, int * admax, int * admin)
L'algorith m e de rech erch e de m axim um etde m inim um peutê tre calqué sur celui de l'exercice V.2, en rem plaçantm ax
par *adm ax etm in par *adm in. Cela nous conduità la fonction suivante :
void maxmin (int t[], int n, int * admax, int * admin)
58 Exe rcices en langage C
{
int i ;
*admax = t[1] ;
*admin = t[1] ;
for (i=1 ; i<n ; i++)
{ if (t[i] > *admax) *admax = t[i] ;
if (t[i] < *admin) *admin = t[i] ;
}
}
Si l'on souh aite éviter les "indirections" qui apparaissentsystém atiquem entdans les instructions de com paraison, on peut
"travailler" tem porairem ent sur des variables locales à la fonction (nom m ées ici m ax et m in). Cela nous conduit à une
fonction de la form e suivante :
void maxmin (int t[], int n, int * admax, int * admin)
{
int i, max, min ;
max = t[1] ;
min = t[1] ;
for (i=1 ; i<n ; i++)
{ if (t[i] > max) max = t[i] ;
if (t[i] < min) min = t[i] ;
}
*admax = max ;
*admin = min ;
}
Voici un petitexem ple de program m e d'utilisation de notre fonction :
#include <stdio.h>
main()
{
void maxmin (int [], int, int *, int *) ;
int t[8] = { 2, 5, 7, 2, 9, 3, 9, 4} ;
int max, min ;
maxmin (t, 8, &max, &min) ;
printf ("valeur maxi : %dn", max) ;
printf ("valeur mini : %dn", min) ;
}
V. Table aux e tpointe urs 59
Exe rcice V.8
___________________________________________________________________________
Enoncé
Ecrire une fonction qui fournit en retour la som m e des valeurs d'un tableau de flottants à deux indices dont les
dim ensions sontfournies en argum ent.
_______________________________________________________________
Solution
Par analogie avec ce que nous avions faitdans l'exercice V.6, nous pourrions songer à déclarer le tableau concerné dans
l'en-tê te de la fonction sous la form e t[][]. M ais, cela n'estplus possible car, cette fois, pour déterm iner l'adresse d'un
élém entt[i][j]d'un teltableau, le com pilateur doiten connaître la deuxiè m e dim ension.
Une solution consiste à considérer qu'on reçoitun pointeur (de type float*)sur le débutdu tableau etd'en parcourir tous
les élém ents (au nom bre de n*p si n etp désignentles dim ensions du tableau) com m e si l'on avaitaffaire à un tableau à
une dim ension.
Cela nous conduità cette fonction :
float somme (float * adt, int n, int p)
{
int i ;
float s ;
for (i=0 ; i<n*p ; i++) s += adt[i] ; /* ou s += *(adt+i) */
return s ;
}
Pour utiliser une telle fonction, la seule difficulté consiste à lui transm ettre effectivem entl'adresse de débutdu tableau,
sous la form e d'un pointeur de type int*. Or, avec, par exem ple t[3][4], t, s'ilcorrrespond bien à la bonne adresse, est
du type "pointeur sur des tableaux de 4 flottants". A priori, toutefois, com pte tenu de la présence du prototype, la
conversion voulue sera m ise en oeuvre autom atiquem entpar le com pilateur. Toutefois, com m e nous l'avons déjà ditdans
l'exercice V.5, on y gagnera en lisibilité (et en éventuels m essages d'avertissem ent!) en faisant appelà l'opérateur de
"cast".
Voici finalem entun exem ple d'un telprogram m e d'utilisation de notre fonction :
#include <stdio.h>
main()
{
60 Exe rcices en langage C
float somme (float *, int, int) ;
float t[3] [4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} } ;
printf ("somme : %fn", somme ((float *)t, 3, 4) ) ;
}
VI: LES CH AINES D E
CARACTERES
Exe rcice VI.1
___________________________________________________________________________
Enoncé
Quels résultats fournira ce program m e :
#include <stdio.h>
main()
{
char * ad1 ;
ad1 = "bonjour" ;
printf ("%sn", ad1) ;
ad1 = "monsieur" ;
printf ("%sn", ad1) ;
}
_______________________________________________________________
Solution
L'instruction ad1 = "bonjour" place dans la variable ad1 l'adresse de la ch aîne constante "bonjour". L'instruction printf
("%sn", ad1) se contente d'affich er la valeur de la ch aîne dontl'adresse figure dans ad1, c'est-à -dire, en l'occurrence
"bonjour". De m aniè re com parable, l'instruction ad1 = "m onsie ur" place l'adresse de la ch aîne constante "m onsieur"
62 Exe rcices en langage C
dans ad1 ;l'instruction printf("%sn", ad1)affich e la valeur de la ch aîne ayantm aintenantl'adresse contenue dans ad1,
c'est-à -dire m aintenant"m onsieur".
Finalem ent, ce program m e affich e toutsim plem ent:
bonjour
monsieur
On auraitobtenu plus sim plem entle m ê m e résultaten écrivant:
printf ("bonjournmonsieurn") ;
Exe rcice VI.2
___________________________________________________________________________
Enoncé
Quels résultats fournira ce program m e :
#include <stdio.h>
main()
{
char * adr = "bonjour" ; /* 1 */
int i ;
for (i=0 ; i<3 ; i++) putchar (adr[i]) ; /* 2 */
printf ("n") ;
i = 0 ;
while (adr[i]) putchar (adr[i++]) ; /* 3 */
}
_______________________________________________________________
Solution
La déclaration /*1 */ place dans la variable adr, l'adresse de la ch aîne constante bonjour. L'instruction /*2 */ affich e
les caractè res adr[0], adr[1]etadr[2], c'est-à -dire les 3 prem iers caractè res de cette ch aîne. L'instruction /*3 */ affich e
tous les caractè res à partir de celui d'adresse adr, tant que l'on a pas affaire à un caractè re nul;com m e notre ch aîne
VI. Les ch aînes de caractè res 63
"bonjour" est précisém ent term inée par un telcaractè re nul, cette instruction affich e finalem ent, un par un, tous les
caractè res de "bonjour".
En définitive, le program m e fournitsim plem entles résultats suivants :
bon
bonjour
Exe rcice VI.3
___________________________________________________________________________
Enoncé
Ecrire le program m e précédent(Exercice VI.2), sans utiliser le "form alism e tableau" (ilexiste plusieurs solutions).
_______________________________________________________________
Solution
Voici deux solutions possibles :
a)On peutrem placer systém atiquem entla notation adr[i]par *(adr+ i), ce qui conduità ce program m e :
#include <stdio.h>
main()
{
char * adr = "bonjour" ;
int i ;
for (i=0 ; i<3 ; i++) putchar (*(adr+i)) ;
printf ("n") ;
i = 0 ;
while (adr[i]) putchar (*(adr+i++)) ;
}
b)On peutégalem entparcourir notre ch aîne, non plus à l'aide d'un "indice" i, m ais en incrém entantun pointeur de type
ch ar *: ilpourraits'agir toutsim plem entde adr, m ais généralem ent, on préférera ne pas détruire cette inform ation eten
em ployer une copie :
64 Exe rcices en langage C
#include <stdio.h>
main()
{
char * adr = "bonjour" ;
char * adb ;
for (adb=adr ; adb<adr+3 ; adb++) putchar (*adb) ;
printf ("n") ;
adb = adr ;
while (*adb) putchar (*(adb++)) ;
}
Notez bien que si nous incrém entions directem entadr dans la prem iè re instruction d'affich age, nous ne disposerions plus
de la "bonne adresse" pour la deuxiè m e instruction d'affich age.
Exe rcice VI.4
___________________________________________________________________________
Enoncé
Ecrire un program m e qui dem ande à l'utilisateur de lui fournir un nom bre entier entre 1 et7 etqui affich e le nom du jour
de la sem aine ayantle num éro indiqué (lundi pour 1, m ardi pour 2, ... dim anch e pour 7).
_______________________________________________________________
Solution
Une dém arch e consiste à créer un "tableau de 7 pointeurs sur des ch aînes", correspondantch acune au nom d'un jour de
la sem aine. Com m e ces ch aînes sontici constantes, ilestpossible de créer un teltableau par une déclaration com portant
une intialisation de la form e :
char * jour [7] = { "lundi", "mardi", ...
N'oubliez pas alors que jour[0] contiendra l'adresse de la prem iè re ch aîne, c'est-à -dire l'adresse de la ch aîne constante
"lundi" ;jour[1]contiendra l'adresse de "m ardi", ...
Pour affich er la valeur de la ch aîne de rang i, ilsuffitde rem arquer que son adresse estsim plem entjour[i-1].
D'où le program m e dem andé :
VI. Les ch aînes de caractè res 65
#include <stdio.h>
main()
{
char * jour [7] = { "lundi", "mardi", "mercredi", "jeudi",
"vendredi", "samedi", "dimanche"
} ;
int i ;
do
{ printf ("donnez un nombre entier entre 1 et 7 : ") ;
scanf ("%d", &i) ;
}
while ( i<=0 || i>7) ;
printf ("le jour numéro %d de la semaine est %s", i, jour[i-1]) ;
}
Exe rcice VI.5
___________________________________________________________________________
Enoncé
Ecrire un program m e qui litdeux nom bres entiers fournis obligatoirem entsur une m ê m e ligne. Le program m e ne devra
pas "se planter" en cas de réponse incorrecte (caractè res invalides) com m e le ferait scanf ("%d %d", ...) m ais
sim plem entaffich er un m essage etredem ander une autre réponse. Ildevra en aller de m ê m e lorsque la réponse fournie
ne com porte pas assez d'inform ations. En revanch e, lorsque la réponse com portera trop d'inform ations, les derniè res
devrontê tre ignorées.
Le traitem ent(dem ande de 2 nom bres etaffich age)devra se poursuivre jusqu'à ce que le prem ier nom bre fourni soit0.
Voici un exem ple d'exécution d'un telprogram m e :
--- donnez deux entiers : é
réponse erronée - redonnez-la : 2 15
merci pour 2 15
--- donnez deux entiers : 5
réponse erronée - redonnez-la : 4 12
merci pour 4 12
--- donnez deux entiers : 4 8 6 9
merci pour 4 8
--- donnez deux entiers : 5 é3
66 Exe rcices en langage C
réponse erronée - redonnez-la : 5 23
merci pour 5 23
--- donnez deux entiers : 0 0
merci pour 0 0
Rem arque : on peututiliser les fonctions ge ts etsscanf.
_______________________________________________________________
Solution
Com m e le suggè re la rem arque de l'énoncé, on peutrésoudre les problè m es posés en effectuanten deux tem ps la lecture
d'un couple d'entiers :
- lecture d'une ch aîne de caractè res (c'est-à -dire une suite de caractè res absolum ent quelconques, validée par
"return")avec la fonction ge ts,
- "décodage" de cette ch aîne avec sscanf, suivant un "form at", d'une m aniè re com parable à ce que ferait scanf, à
partir de son "tam pon d'entrée".
Rappelons que sscanf, tout com m e scanf, fournit en retour le nom bre d'inform ations correctem ent lues, de sorte qu'il
suffitde répéter les deux opérations précédentes jusqu'à ce que la valeur fournie par sscanfsoitégale à 2.
L'énoncé ne faitaucune h ypoth è se sur le nom bre m axim alde caractè res que l'utilisateur pourra ê tre am ené à fournir. Ici,
nous avons supposé qu'au plus 128 caractè res seraientfournis ;ils'agitlà d'une h ypoth è se qui, dans la pratique, s'avè re
réaliste, dans la m esure où on risque rarem ent de frapper des lignes plus longues ;de surcroît, ils'agit m ê m e d'une
lim itation "naturelle" de certains environnem ents (DOS, en particulier).
Voici le program m e dem andé :
#include <stdio.h>
#define LG 128 /* longueur maximale d'une ligne */
main()
{
int n1, n2 ; /* entiers à lire en donnée */
int compte ; /* pour la valeur de retour de sscanf */
char ligne [LG+1] ; /* pour lire une ligne (+1 pour 0) */
/* boucle de lecture des différents couples de valeurs */
do
{ /* boucle de lecture d'un couple de valeur jusqu'à OK */
printf ("--- donnez deux entiers : ") ;
do
{ gets (ligne) ;
compte = sscanf (ligne, "%d %d", &n1, &n2) ;
VI. Les ch aînes de caractè res 67
if (compte<2) printf ("réponse erronée - redonnez-la : ") ;
}
while (compte < 2) ;
printf ("merci pour %d %dn", n1, n2) ;
}
while (n1) ;
}
Rem arques
1)Si l'utilisateur fournit plus de caractè res qu'iln'en faut pour form er 2 nom bres entiers, ces caractè res (lus dans
ligne ) ne serontpas utilisés par sscanf;m algré tout, ils ne serontpas exploités ultérieurem entpuisque, lorsque l'on
redem andera 2 nouveaux entiers, on relira une nouvelle ch aîne par ge ts.
2)Si l'on souh aite absolum ent pouvoir lim iter la longueur de la ch aîne lue au clavier, en utilisant des instructions
"portables", ilfautse tourner vers la fonction fge ts1 destinée à lire une ch aîne dans un fich ier, etl'appliquer à stdin.
On rem placera l'instruction ge ts (ligne )par fge ts (ligne , LG, stdin) qui lim itera à LG le nom bre de caractè res pris en
com pte. Notez toutefois que, dans ce cas, les caractè res excédentaires (et donc non "vus" par fge ts) resteront
disponibles pour une proch aine lecture (ce qui n'est pas pire que dans la situation actuelle où ces caractè res
viendraientécraser des em placem ents m ém oire situés au-delà du tableau ligne !).
Dans certaines im plém entations (Turbo/Borland C/C+ + et Quick C/C M icrosoft), ilexiste une fonction (non
portable, puisque non prévue par la norm e ANSI) nom m ée cge ts qui, utilisée à la place de ge ts (ou fge ts) perm etde
régler le problè m e évoqué. En effet, cge ts perm et de lire une ch aîne, en lim itant le nom bre de caractè res
effectivem entfournis au clavier : iln'estpas possible à l'utilisateur d'en frapper plus que prévu, de sorte que le risque
de caractè res excédentaires n'existe plus!
Exe rcice VI.6
___________________________________________________________________________
1 M ais, si vous réalisez ces exercices en accom pagnem entd'un cours de langage C, ilestprobable que vous n'aurez pas encore étudié la fonction
fgets (en général, elle estintroduite dans le ch apitre relatif au traitem entdes fich iers). Certains exercices de la seconde partie de cetouvrage feront
appelà fgets, et/ou à sscanf.
68 Exe rcices en langage C
Enoncé
Ecrire un program m e déterm inant le nom bre de lettres e (m inuscule) contenues dans un texte fourni en donnée sous
form e d'une seule ligne ne dépassant pas 128 caractè res. On ch erch era, ici, à n'utiliser aucune des fonctions de
traitem entde ch aîne.
_______________________________________________________________
Solution
Com pte tenu des contraintes im posées par l'énoncé, nous ne pouvons pas faire appelà la fonction strle n. Pour "explorer"
notre ch aîne, nous utiliserons le faitqu'elle estterm inée par un caractè re nul(0]. D'où le program m e proposé :
#define LG_LIG 128
#include <stdio.h>
main()
{
char ligne [LG_LIG+1] ; /* pour lire une ligne au clavier +1 pour 0 */
int i ; /* pour explorer les différents caractères de ligne */
int ne ; /* pour compter le nombre de 'e' */
printf ("donnez un texte de moins d'une ligne : n") ;
gets (ligne) ;
ne = 0 ;
i = 0 ;
while (ligne[i]) if (ligne[i++] == 'e') ne++ ;
printf ("votre texte comporte %d lettres e", ne) ;
}
Exe rcice VI.7
___________________________________________________________________________
Enoncé
Ecrire un program m e qui lit, en donnée, un verbe du prem ier groupe et qui en affich e la conjugaison au présent de
l'indicatif, sous la form e :
VI. Les ch aînes de caractè res 69
je chante
tu chantes
il chante
nous chantons
vous chantez
ils chantent
On s'assurera que le m ot fourni se term ine bien par "er". On supposera qu'ils'agitd'un verbe régulier ;autrem entdit,
on adm ettra que l'utilisateur ne fournira pas un verbe telque m anger (le program m e affich eraitalors : nous m angons!).
_______________________________________________________________
Solution
On lira "classiquem ent" un m ot, sous form e d'une ch aîne à l'aide de la fonction ge ts. Pour vérifier sa term inaison par
"er", on com parera avec la ch aîne constante "er", la ch aîne ayantcom m e adresse l'adresse de fin du m ot, dim inuée de 2.
L'adresse de fin se déduira de l'adresse de débutetde la longueur de la ch aîne (obtenue par la fonction strle n).
Quant à la com paraison voulue, elle se fera à l'aide de la fonction strcm p ;rappelons que cette derniè re reçoit en
argum ent 2 pointeurs sur des ch aînes et qu'elle fournit en retour une valeur nulle lorsque les deux ch aînes
correspondantes sontégales etune valeur non nulle dans tous les autres cas.
Les différentes personnes du verbe s'obtiennenten rem plaçant, dans la ch aîne en question, la term inaison "er" par une
term inaison appropriée. On peut, pour cela, utiliser la fonction strcpy qui recopie une ch aîne donnée (ici la term inaison)
à une adresse donnée (ici, celle déjà utilisée dans strcm p pour vérifier que le verbe se term ine bien par "er").
Les différentes term inaisons possibles seront rangées dans un tableau de ch aînes constantes (plus précisém ent, dans un
tableau de pointeurs sur des ch aînes constantes). Nous ferons de m ê m e pour les différents sujets (je, tu...);en revanch e,
ici, nous ne ch erch erons pas à les "concaténer" au verbe conjugué ;nous nous contentons de les écrire, au m om ent
opportun.
Voici finalem entle program m e dem andé :
#include <stdio.h>
#include <string.h>
#define LG_VERBE 30 /* longueur maximale du verbe fourni en donnée */
main()
{ char verbe [LG_VERBE+1] ; /* verbe à conjuguer +1 pour 0 */
char * sujet [6] = { "je", "tu", "il", "nous", "vous", "ils"} ; /* sujets */
char * term [6] = { "e", "es", "e", "ons", "ez", "ent" } ;/* terminaisons */
int i ;
char * adterm ; /* pointeur sur la terminaison du verbe */
70 Exe rcices en langage C
do
{ printf ("donnez un verbe régulier du premier groupe : ") ;
gets (verbe) ;
adterm = verbe + strlen(verbe) - 2 ;
}
while (strcmp (adterm, "er") ) ;
printf ("conjugaison à l'indicatif présent :n") ;
for (i=0 ; i<6 ; i++)
{ strcpy (adterm, term[i]) ;
printf ("%s %sn", sujet[i], verbe) ;
}
}
Rem arque : rappelons que strcpy recopie (sans aucun contrôle)la ch aîne dontl'adresse estfournie en prem ier argum ent
(c'est-à -dire, en fait, tous les caractè res à partir de cette adresse, jusqu'à ce que l'on rencontre un 0) à l'adresse fournie
en second argum ent;de plus, elle com plè te bien le toutavec un caractè re nulde fin de ch aîne.
Exe rcice VI.8
___________________________________________________________________________
Enoncé
Ecrire un program m e qui supprim e toutes les lettres e (m inuscule)d'un texte de m oins d'une ligne (ne dépassantpas 128
caractè res) fourni en donnée. On s'arrangera pour que le texte ainsi m odifié soit créé en m ém oire, à la place de
l'ancien.
N.B. on pourra utiliser la fonction strch r.
_______________________________________________________________
Solution
La fonction strch r perm et de trouver un caractè re donné dans une ch aîne. Elle est donc tout à fait appropriée pour
localiser les 'e' ;ilfaut toutefois noter que, pour localiser tous les 'e', ilest nécessaire de répéter l'appelde cette
VI. Les ch aînes de caractè res 71
fonction, en m odifiantà ch aque fois l'adresse de débutde la ch aîne concernée (ilfautéviter de boucler sur la rech erch e
du m ê m e caractè re 'e').
La fonction strch r fournitl'adresse à laquelle on a trouvé le prem ier caractè re indiqué (ou la valeur 0 si ce caractè re
n'existe pas). La suppression du 'e' trouvé peut se faire en recopiant le "reste" de la ch aîne à l'adresse où l'on a
trouvé le 'e'.
Voici une solution possible :
#include <stdio.h>
#include <string.h>
#define LG_LIG 128 /* longueur maximum d'une ligne de données */
#define CAR 'e' /* caractère à supprimer */
main()
{
char ligne [LG_LIG+1] ; /* pour lire une ligne +1 pour 0 */
char * adr ; /* pointeur à l'intérieur de la ligne */
printf ("donnez un texte de moins d'une ligne : n") ;
gets (ligne) ;
adr = ligne ;
while (adr = strchr (adr,'e') ) strcpy (adr, adr+1) ;
printf ("voici votre texte, privé des caractères %c :n") ;
puts (ligne) ;
}
VII: LES STRUCTURES
Exe rcice VII.1
___________________________________________________________________________
Enoncé
Soitle m odè le (type)de structure suivant:
struct s_point
{ char c ;
int x, y ;
} ;
Ecrire une fonction qui reçoiten argum entune structure de type s_pointetqui en affich e le contenu sous la form e :
point B de coordonnées 10 12
a)En transm ettanten argum entla valeur de la structure concernée,
b)En transm ettanten argum entl'adresse de la structure concernée.
Dans les deux cas, on écrira un petitprogram m e d'essai de la fonction ainsi réalisée.
_______________________________________________________________
Solution
a)Voici la fonction dem andée :
#include <stdio.h>
74 Exe rcices en langage C
void affiche (struct s_point p)
{ printf ("point %c de coordonnées %d %dn", p.c, p.x, p.y) ;
}
Notez que sa com pilation nécessite obligatoirem entla déclaration du type s_point, c'est-à -dire les instructions :
struct s_point
{ char c ;
int x, y ;
} ;
Voici un petitprogram m e qui affecte les valeurs 'A', 10 et 12 aux différents ch am ps d'une structure nom m ée s, avant
d'en affich er les valeurs à l'aide de la fonction précédente :
main()
{ void affiche (struct s_point) ; // déclaration (prototype) de affiche
struct s_point s ;
s.c = 'A' ;
s.x = 10 ;
s.y = 12 ;
affiche (s) ;
}
Naturellem ent, la rem arque précédente s'applique égalem ent ici. En pratique, la déclaration de la structure s_point
figurera dans un fich ier d'extension h q ue l'on se contentera d'incorporer par #include au m om entde la com pilation. De
m ê m e, ilestnécessaire d'inclure stdio.h .
b)Voici la nouvelle fonction dem andée :
#include <stdio.h>
void affiche (struct s_point * adp)
{ printf ("point %c de coordonnées %d %dn", adp->c, adp->x, adp->y) ;
}
Notez que l'on doit, cette fois, faire appelà l'opérateur -> , à la place de l'opérateur point(.), puisque l'on "travaille"
sur un pointeur sur une structure, etnon plus sur la valeur de la structure elle-m ê m e. Toutefois l'usage de -> n'estpas
totalem entindispensable, dans la m esure où, par exem ple, adp-> x estéquivalentà (*adp).x.
Voici l'adaptation du program m e d'essai précédent:
main()
{
VII. Les structures 75
void affiche (struct s_point *) ;
struct s_point s ;
s.c = 'A' ;
s.x = 10 ;
s.y = 12 ;
affiche (&s) ;
}
Rem arque :
Au lieu d'affecter des valeurs aux ch am ps c, x et y de notre structure s (dans les deux program m es d'essai), nous
pourrions (ici)utiliser les possibilités d'initialisation offertes par le langage C, en écrivant:
struct s_point s = {'A', 10, 12} ;
Exe rcice VII.2
___________________________________________________________________________
Enoncé
Ecrire une fonction qui "m et à zéro" les différents ch am ps d'une structure du type s_point (défini dans l'exercice
précédent)qui lui esttransm ise en argum ent. La fonction ne com portera pas de valeur de retour.
_______________________________________________________________
Solution
Ici, bien que l'énoncé ne le précise pas, ilestnécessaire de transm ettre à la fonction concernée, non pas la valeur, m ais
l'adresse de la structure à "rem ettre à zéro". Voici la fonction dem andée (ici, nous avons reproduit la déclaration de
s_point):
#include <stdio.h>
struct s_point
{ char c ;
int x, y ;
} ;
void raz (struct s_point * adr)
76 Exe rcices en langage C
{ adr->c = 0 ;
adr->x = 0 ;
adr->y = 0 ;
}
Voici, à titre indicatif, un petitprogram m e d'essai (sa com pilation nécessite la déclaration de s_point, ainsi que le fich ier
stdio.h ):
main()
{ struct s_point p ;
void raz (struct s_point *) ; // déclaration de raz
raz (&p) ;
/* on écrit c en %d pour voir son code */
printf ("après : %d %d %d", p.c, p.x, p.y) ;
}
Exe rcice VII.3
___________________________________________________________________________
Enoncé
Ecrire une fonction qui reçoiten argum entl'adresse d'une structure du type s_point(défini dans l'exercice VII.1) etqui
renvoie en résultatune structure de m ê m e type correspondantà un pointde m ê m e nom (c)etde coordonnées opposées.
Ecrire un petitprogram m e d'essai.
_______________________________________________________________
Solution
Bien que l'énoncé ne précise rien, le résultatde notre fonction ne peutê tre transm is que par valeur. En effet, ce résultat
doitê tre créé au sein de la fonction elle-m ê m e ;cela signifie qu'ilsera détruitdè s la sortie de la fonction ;en transm ettre
l'adresse reviendraità renvoyer l'adresse de quelque ch ose destiné à disparaître...
Voici ce que pourraitê tre notre fonction (ici, encore, nous avons reproduitla déclaration de s_point):
#include <stdio.h>
struct s_point
{ char c ;
VII. Les structures 77
int x, y ;
} ;
struct s_point sym (struct s_point * adr)
{ struct s_point res ;
res.c = adr->c ;
res.x = - adr->x ;
res.y = - adr->y ;
return res ;
}
Notez la "dissym étrie" d'instructions telles que res.c = adr-> c ;on y faitappelà l'opérateur . à gauch e età l'opérateur
-> à droite (on pourraitcependantécrire res.c = (*adr).c.
Voici un exem ple d'essai de notre fonction (ici, nous avons utilisé les possibilités d'initialisation d'une structure pour
donner des valeurs à p1):
main()
{
struct s_point sym (struct s_point *) ;
struct s_point p1 = {'P', 5, 8} ;
struct s_point p2 ;
p2 = sym (&p1) ;
printf ("p1 = %c %d %dn", p1.c, p1.x, p1.y) ;
printf ("p2 = %c %d %dn", p2.c, p2.x, p2.y) ;
}
Exe rcice VII.4
___________________________________________________________________________
Enoncé
Soitla structure suivante, représentantun pointd'un plan :
struct s_point
{ char c ;
int x, y ;
} ;
1)Ecrire la déclaration d'un tableau (nom m é courbe )de NPpoints (NPsupposé défini par une instruction #de fine )
2)Ecrire une fonction (nom m ée affich e ) qui affich e les valeurs des différents "points" du tableau courbe , transm is en
argum ent, sous la form e :
78 Exe rcices en langage C
point D de coordonnées 10 2
3)Ecrire un program m e qui :
- lit en données des valeurs pour le tableau courbe ;on utilisera de préférence les fonctions ge ts et sscanf, de
préférence à scanf(voir éventuellem entl'exercice VI.5);on supposera qu'une ligne de donnée ne peutpas dépasser
128 caractè res,
- faitappelà la fonction précédente pour les affich er.
_______________________________________________________________
Solution
1)Ilsuffitde déclarer un tableau de structures :
struct s_point courbe [NP] ;
2)Com m e courbe est un tableau, on ne peut qu'en transm ettre l'adresse en argum ent de affich e . Ilest préférable de
prévoir égalem enten argum entle nom bre de points. Voici ce que pourraitê tre notre fonction :
void affiche (struct s_point courbe [], int np)
/* courbe : adresse de la première structure du tableau */
/* (on pourrait écrire struct s_point * courbe) */
/* np : nombre de points de la courbe */
{
int i ;
for (i=0 ; i<np ; i++)
printf ("point %c de coordonnées %d %dn", courbe[i].c,
courbe[i].x, courbe[i].x) ;
}
Com m e pour n'im porte queltableau à une dim ension transm is en argum ent, ilest possible de ne pas en m entionner la
dim ension dans l'en-tê te de la fonction. Bien entendu, com m e, en fait, l'identificateur courbe n'est qu'un pointeur de
type s_point*(pointeur sur la prem iè re structure du tableau), nous aurions pu égalem entécrire s_point*courbe .
Notez que, com m e à l'accoutum ée, le "form alism e tableau" et le "form alism e pointeur" peuvent ê tre indifférem m ent
utilisés (voire com binés). Par exem ple, notre fonction auraitpu égalem ents'écrire :
void affiche (struct s_point * courbe, int np)
{
struct s_point * adp ;
int i ;
VII. Les structures 79
for (i=0, adp=courbe ; i<np ; i++, adp++)
printf ("point %c de coordonnées %d %d", courbe->c, courbe->x, courbe->y) ;
}
3)Com m e nous avons appris à le faire dans l'exercice VI.5, nous lirons les inform ations relatives aux différents points à
l'aide des deux fonctions :
- ge ts, pour lire, sous form e d'une ch aîne, une ligne d'inform ation,
- sscanf, pour décoder suivantun form atle contenu de la ch aîne ainsi lue.
Voici ce que pourraitle program m e dem andé (ici, nous avons reproduit, à la fois la déclaration de s_pointetla fonction
affich e précédente):
#include <stdio.h>
struct s_point
{ char c ;
int x, y ;
} ;
#define NP 10 /* nombre de points d'une courbe */
#define LG_LIG 128 /* longueur maximale d'une ligne de donnée */
main()
{ struct s_point courbe [NP] ;
int i ;
char ligne [LG_LIG+1] ;
void affiche (struct s_point [], int) ;
/* lecture des différents points de la courbe */
for (i=0 ; i<NP ; i++)
{ printf ("nom (1 caractère) et coordonnées point %d : ", i+1) ;
gets (ligne) ;
sscanf (ligne, "%c %d %d", &courbe[i].c, &courbe[i].x, &courbe[i].y) ;
}
affiche (courbe, NP) ;
}
void affiche (struct s_point courbe [], int np)
{
int i ;
for (i=0 ; i<np ; i++)
printf ("point %c de coordonnées %d %dn", courbe[i].c,
courbe[i].x, courbe[i].x) ;
}
80 Exe rcices en langage C
Exe rcice VII.5
___________________________________________________________________________
Enoncé
Ecrire le program m e de la question 3 de l'exercice précédent, sans utiliser de structures. On prévoira toujours une
fonction pour lire les inform ations relatives à un point.
_______________________________________________________________
Solution
Ici, ilnous fautobligatoirem entprévoir 3 tableaux différents de m ê m e taille : un pour les nom s de points, un pour leurs
abscisses etun pour leurs ordonnées. Le program m e ne présente pas de difficultés particuliè res (son principalintérê test
d'ê tre com paré au précédent!).
#include <stdio.h>
#define NP 10 /* nombre de points d'une courbe */
#define LG_LIG 128 /* longueur maximale d'une ligne de donnée */
main()
{
char c [NP] ; /* noms des différents points */
int x [NP] ; /* abscisses des différents points */
int y [NP] ; /* ordonnées des différents points */
int i ;
char ligne [LG_LIG+1] ;
void affiche (char [], int[], int[], int) ;
/* lecture des différents points de la courbe */
for (i=0 ; i<NP ; i++)
{ printf ("nom (1 caractère) et coordonnées point %d : ", i+1) ;
gets (ligne) ;
sscanf (ligne, "%c %d %d", &c[i], &x[i], &y[i]) ;
}
affiche (c, x, y, NP) ;
}
VII. Les structures 81
void affiche (char c[], int x[], int y[], int np)
{
int i ;
for (i=0 ; i<np ; i++)
printf ("point %c de coordonnées %d %dn", c[i], x[i], x[i]) ;
}
Exe rcice VII.6
___________________________________________________________________________
Enoncé
Soientles deux m odè les de structure date etpe rsonne déclarés ainsi :
#define LG_NOM 30
struct date
{ int jour ;
int mois ;
int annee ;
} ;
struct personne
{ char nom [LG_NOM+1] ; /* chaîne de caractères représentant le nom */
struct date date_embauche ;
struct date date_poste ;
} ;
Ecrire une fonction qui reçoiten argum entune structure de type pe rsonne etqui en rem plitles différents ch am ps avec un
dialogue se présentantsous l'une des 2 form es suivantes :
nom : DUPONT
date embauche (jj mm aa) : 16 1 75
date poste = date embauche ? (O/N) : O
nom : DUPONT
date embauche (jj mm aa) : 10 3 81
date poste = date embauche ? (O/N) : N
date poste (jj mm aa) : 23 8 91
82 Exe rcices en langage C
_______________________________________________________________
Solution
Notre fonction doit m odifier le contenu d'une structure de type pe rsonne ;ilest donc nécessaire qu'elle en reçoive
l'adresse en argum ent. Ici, l'énoncé n'im posant aucune protection particuliè re concernant les lectures au clavier, nous
lirons "classiquem ent" le nom par ge ts etles trois autres inform ations num ériques par scanf. Voici ce que pourraitê tre la
fonction dem andée :
void remplit (struct personne * adp)
{
char rep ; /* pour lire une réponse de type O/N */
printf ("nom : ") ;
gets (adp->nom) ; /* attention, pas de contrôle de longueur */
printf ("date embauche (jj mm aa) : ") ;
scanf ("%d %d %d", &adp->date_embauche.jour,
&adp->date_embauche.mois,
&adp->date_embauche.annee) ;
printf ("date poste = date embauche ? (O/N) : ") ;
getchar () ; rep = getchar () ; /* premier getchar pour sauter n */
if (rep == 'O') adp->date_poste = adp->date_embauche ;
else { printf ("date poste (jj mm aa) : ") ;
scanf ("%d %d %d", &adp->date_poste.jour,
&adp->date_poste.mois,
&adp->date_poste.annee) ;
}
}
Notez que, com m e à l'accoutum ée, dè s lors qu'une lecture de valeurs num ériques (ici par scanf) estsuivie d'une lecture
d'un caractè re (ici par ge tch ar, m ais le m ê m e problè m e se poserait avec scanf et le code %c), ilest nécessaire de
"sauter" artificiellem entle caractè re ayantservi à la validation de la derniè re inform ation num érique ;en effet, dans le
cas contraire, c'estprécisém entce caractè re (n)qui estpris en com pte.
En toute rigueur, la dém arch e ainsi utilisée n'estpas infaillible : si l'utilisateur fournitdes inform ations supplém entaires
aprè s la derniè re valeur num érique (ne serait-ce qu'un sim ple espace), le caractè re lu ultérieurem ent ne sera pas celui
attendu. Toutefois, ils'agitalors des "problè m es h abituels" liés à la fourniture d'inform ations excédentaires. Ils peuvent
ê tre résolus par différentes tech niques dontnous avons parlé, notam m ent, dans l'exercice VI.5.
VII. Les structures 83
Voici, à titre indicatif, un petit program m e d'essai de notre fonction (sa com pilation nécessite les déclarations des
structures date etpe rsonne ):
main()
{
struct personne bloc ;
remplit (&bloc) ;
printf ("nom : %s n date embauche : %d %d %d n date poste : %d %d %d",
bloc.nom,
bloc.date_embauche.jour, bloc.date_embauche.mois, bloc.date_embauche.annee,
bloc.date_poste.jour, bloc.date_poste.mois, bloc.date_poste.annee ) ;
}
D EUXIEM E PARTIE :
EXERCICES TH EMATIQUES
INTRO D UCTIO N
A LA DEUXIEM E PARTIE
Ce ch apitre vous fournitquelques explications concernantla m aniè re dontsontconçus les problè m es proposés dans cette
deuxiè m e partie de l'ouvrage et les quelques rè gles que nous nous som m es fixées pour la rédaction des program m es
correspondants.
1 - Cane vas com m un à ch aq ue e xe rcice
Pour ch aque exercice, nous avons adopté le m ê m e canevas.
a)L'e xpos é du problè m e
Ilestconstitué d'un énoncé accom pagné d'un exem ple. Cetensem ble constitue ce qu'ilestindispensable de lire avantde
tenter de résoudre le problè m e. Certes, l'exem ple perm et d'illustrer et de concrétiser l'énoncé m ais, de plus, ille
précise, en particulier en explicitantla m aniè re dontle program m e dialogue avec l'utilisateur. On notera que cetexem ple
correspond exactem entà une im age d'écran obtenue avec le program m e proposé en solution.
b)L'analyse
Elle spécifie (ou précise)les algorith m es à m ettre en oeuvre pour aboutir à une solution. Elle garde un caractè re général;
notam m ent, elle évite de s'intéresser à certains détails de program m ation dontle ch oix estrejeté au m om entde l'écriture
du program m e. A priori, elle faitdéjà partie de la solution ;toutefois, si vous séch ez sur l'énoncé lui-m ê m e, rien ne vous
em pê ch e, aprè s la lecture de cette analyse, de tenter d'écrire le program m e correspondant. En effet, un telexercice, bien
86 Exe rcices en langage C
que lim ité à la sim ple traduction d'un algorith m e dans un langage, n'en possè de pas m oins un intérê t propre en ce qui
concerne l'apprentissage du langage lui-m ê m e.
c)Le program m e
Bien qu'ilsuive exactem ent l'analyse proposée, iln'en reste pas m oins qu'ilfaille le considérer com m e une rédaction
possible parm i beaucoup d'autres. N'oubliez pas qu'à ce niveau ilestbien difficile de porter un jugem entde valeur sur
les qualités ou les défauts de telle ou telle rédaction, tantque l'on n'a pas précisé les critè res retenus (vitesse d'exécution,
taille m ém oire, clarté de la rédaction, respectde certaines rè gles de style, ...);cela estd'autantplus vrai que certains de
ces critè res peuvents'avérer incom patibles entre eux. Ces rem arques s'appliquentd'ailleurs déjà aux exercices proposés
précédem m entdans la prem iè re partie de cetouvrage m ais avec m oins d'accuité.
d)Le s com m e ntaire s
Ils fournissentcertaines explications que nous avons jugées utiles à la com préh ension du program m e lui-m ê m e. Ilpeut,
par exem ple, s'agir :
- de rappels concernantune instruction ou une fonction peu usuelle,
- de justifications de certains ch oix réalisés uniquem entau m om entde la rédaction du program m e,
- de m ise en évidence de certaines particularités ou originalités du langage,
- etc.
e )La discussion
Elle constitue une sorte d'ouve rture fondée sur une réflexion de caractè re généralqui peutporter sur :
- les insuffisances éventuelles du program m e proposé, notam m ent en ce qui concerne son com portem ent face à des
erreurs de la partde l'utilisateur,
- les am éliorations qu'ilestpossible de lui apporter,
- une généralisation du problè m e posé,
- etc.
Introduction à la de uxiè m e partie 87
2 - Prote ction de s program m e s par rapport aux donné e s
Com m e beaucoup d'autres langages, les instructions usuelles de lecture au clavier du langage C ne sont pas totalem ent
protégées d'éventuelles réponses incorrectes de la part de l'utilisateur. Celles-ci peuvent entraîner un com portem ent
anorm aldu program m e.
D'une m aniè re générale, ce problè m e de contrôle des données peut ê tre résolu par l'em ploi de tech niques appropriées
telles que celles que nous avons rencontrées dans l'exercice VI.5 de la prem iè re partie. Toutefois, celles-ci présentent
l'inconvénientd'alourdir le texte du program m e. C'estpourquoi nous avons évité d'introduire systém atiquem entde telles
protections dans tous nos exem ples, ce qui auraitm anifestem entm asqué l'objectif essentielde l'exercice (bien entendu,
ces protections pourraientdevenir indispensables dans un program m e réel). Notez toutefois que certains exercices, de par
leur nature m ê m e, requiè rentune telle protection ;celle-ci sera alors clairem entdem andée dans l'énoncé lui-m ê m e.
3 - A propos des structure s de boucle
En principe, lorsque l'analyse d'un problè m e faitintervenir une répétition, ilfaudrait, pour ê tre com plet, en préciser le
type :
- répétition définie (ou ave c com pte ur): elle estréalisée en C avec l'instruction for,
- répétition tant que, dans laquelle le test de poursuite a lieu en début de boucle : elle est réalisée en C avec
l'instruction w h ile ,
- répétition jusqu'à dans laquelle le testd'arrê ta lieu en fin de boucle : elle estréalisée en C avec l'instruction do ...
w h ile .
En fait, ilexiste plusieurs raisons de ne pas toujours spécifier le ch oix du type d'une répétition au niveau de l'analyse et
de le reporter au niveau de l'écriture du program m e :
- d'une part, le ch oix d'un type de boucle n'estpas toujours dicté im pérativem entpar le problè m e : par exem ple, un
algorith m e utilisant une répétition de type jusqu'à peut toujours ê tre transform é en un algorith m e utilisant une
répétition de type tantque,
- d'autre part, com m e nous l'avons déjà entrevu dans le ch apitre III de la prem iè re partie, le langage C autorise des
form es de répétition plus variées que les trois que nous venons d'évoquer (etqui sontcelles proposées classiquem ent
par la "program m ation structurée"): ainsi, par exem ple :
*grâ ce à la notion d'opérateur séquentiel, on peut réaliser, à l'aide de l'instruction w h ile , des boucles dans
lesquelles le testde poursuite a lieu, non plus en début, m ais en cours de boucle,
*l'instruction bre ak autorise des boucles à sorties m ultiples.
88 Exe rcices en langage C
Certes, on peut objecter que ce sont là des possibilités qui sont contraires à l'esprit de la program m ation structurée.
Cependant, utilisées à bon escient, elles peuventam éliorer la concision etle tem ps d'exécution des program m es. Com pte
tenu de l'orientation du langage C, ilne nous a pas paru opportun de nous priver totalem entde ces facilités.
En définitive, ilnous arrivera souvent, au cours de l'analyse, de nous contenter de préciser la (ou les)condition(s)d'arrê t
d'une itération etde reporter au niveau de la program m ation m ê m e le ch oix des instructions à utiliser. On notera qu'en
procédantainsi un effortde réflexion logique peutrester nécessaire au m om entde la rédaction du program m e, laquelle,
dans ce cas, se trouve ê tre plus qu'une sim ple traduction littérale!
4 - A propos des fonctions
a) Com m e nous l'avons déjà rem arqué dans l'avant-propos, la norm e ANSI accepte deux form es de définition de
fonctions. Voici, par exem ple, deux façons d'écrire l'en-tê te d'une fonction fct recevant deux argum ents de type int et
ch aretrenvoyantune valeur de type double :
double fct (int x, char * p)
double fct (x, p)
int x ;
char * p ;
Ilne s'agit là que de sim ples différences de rédaction, sans aucune incidence sur le plan fonctionnel. Ici, nous avons
systém atiquem entem ployé la prem iè re form e (on la nom m e parfois form e "m oderne"), dans la m esure où elle a tendance
à se généraliser etoù, de plus, ils'agitde la seule form e acceptée par le C+ + .
b)Les fonctions onttoujours été déclarées dans les fonctions les utilisantbien qu'a priori :
- cela ne soitpas obligatoire pour les fonctions fournissantun résultatde type int,
- cela ne soitpas obligatoire lorsqu'une fonction a été définie, dans le m ê m e source, avantd'ê tre utilisée.
c) Dans les déclarations des fonctions, nous avons utilisé la form e prototype autorisée par le standard ANSI. Celle-ci se
révè le surtout fort précieuse lorsque l'on exploite les possibilités de com pilation séparée et que l'on a donc affaire à
plusieurs fich iers source différents. Certes, ce n'est pas le cas ici, m ais, com pte tenu de ce qu'elle est pratiquem ent
acceptée de tous les com pilateurs actuels etque, de plus, elle estestobligatoire en C+ + , ilnous a paru judicieux d'en
faire une h abitude.
I: VARIATIO NS ALGO RITH M IQUES
SUR LES INSTRUCTIO NS
D E BASE
Ce ch apitre vous propose des problè m es ne faisantappelqu'aux notions de base du langage C, à savoir :
- entrées-sorties conversationnelles (ge tch ar, scanf, ge ts, putch ar, printf),
- instructions de contrôle,
- tableaux,
- ch aînes,
- fonctions.
I-1 Triangle de Pascal
______________________________________________________________________________
Enoncé
Affich er un "triangle de Pascal" dontle nom bre de lignes estfourni en donnée. Nous vous rappelons que les "cases" d'un
teltriangle contiennentles valeurs des coefficients du binom e C
n,p
(ou nom bre de com binaisons de n élém ents pris p à p).
Cette valeur est placée dans la case correspondant à l'intersection de la ligne de rang n et la colonne de rang p (la
num érotation com m ençantà 0).
On évitera de calculer ch aque term e séparém ent ;au contraire, on ch erch era à exploiter la relation de récurrence :
9 0 Exe rcices en langage C
C
i,j
= C
i-1, j
+ C
i-1,j-1
On lim itera à 15 le nom bre de lignes dem andées par l'utilisateur eton respectera la présentation proposée dans l'exem ple
ci-dessous.
Exe m ple
combien de lignes voulez vous ? 12
p 0 1 2 3 4 5 6 7 8 9 10 11
n
-----------------------------------------------------------------
0 -- 1
1 -- 1 1
2 -- 1 2 1
3 -- 1 3 3 1
4 -- 1 4 6 4 1
5 -- 1 5 10 10 5 1
6 -- 1 6 15 20 15 6 1
7 -- 1 7 21 35 35 21 7 1
8 -- 1 8 28 56 70 56 28 8 1
9 -- 1 9 36 84 126 126 84 36 9 1
10 -- 1 10 45 120 210 252 210 120 45 10 1
11 -- 1 11 55 165 330 462 462 330 165 55 11 1
______________________________________________________________________________
ANALYSE
A priori, nous pourrions utiliser un tableau t à deux dim ensions com portant 15x15 élém ents et décider (arbitrairem ent)
que le prem ier indice correspond au rang d'une ligne du triangle, le second à celui d'une colonne. Nous rem plirions
alors partiellem ent ce tableau avec les valeurs C
i,j
voulues (i varierait de 0 à n-1 si n représente le nom bre de lignes
dem andées et, pour ch aque valeur de i, jvarieraitde 0 à i).
Pour exploiter la récurrence proposée, ilnous suffiraitalors de procéder com m e suit:
- placer la valeur 1 en t(0,0)(ce qui constitue la prem iè re ligne),
- pour ch aque ligne de rang i, à partir de i=1, procéder ainsi :
*placer la valeur 1 en t(i,0)ett(i,i)(extrém ités de la ligne de rang i),
*pour jvariantde 1 à i-1, faire :
t(i,j)=t(i-1,j)+ t(i-1,j-1)
I. Variations algorith m iques sur les instructions de base 9 1
En fait, ilest possible de n'utiliser qu'un tableau à une seule dim ension, dans lequelon vient calculer successive m e nt
ch acune des lignes du triangle (ilfautalors, bien sûr, affich er ch aque ligne dè s qu'elle a été déterm inée).
Supposons, en effet, qu'à un instant donné, nous disposions dans ce tableau t des i+1 valeurs de la ligne de rang i et
voyons com m entdéterm iner celles de la ligne de rang i+1. Nous constatons que la récurrence proposée perm etde définir
la nouvelle valeur d'un élém entde ten fonction de son ancienne valeur etde l'ancienne valeur de l'élém entprécédent.
Certes, si nous répétions une affectation de la form e :
t(j)=t(j)+ t(j-1)
en faisantvarier jde 1 à i-1, nous n'aboutirions pas au résultatescom pté puisqu'alors la valeur de t(j) dépendraitde la
nouvelle valeur préalablem entattribuée à t(j-1).
M ais, ilestfacile de m ontrer qu'en explorantla ligne de droite à gauch e, c'est-à -dire en répétantl'affectation ci-dessus
en faisantdécroître jde i-1 à 0, le problè m e ne se pose plus.
Voici finalem entl'algorith m e que nous utiliserons :
Faire varier i de 0 à n-1. Pour ch aque valeur de i :
- répéter, en faisantdécroître jde i-1 à 1 :
t(j)=t(j)+ t(j-1)
- placer la valeur 1 dans t(i).
Rem arques :
1)Telque l'algorith m e vientd'ê tre énoncé, nous constatons que pour i=0, jdoitdécroître de -1 à 1! Nous adm ettrons
que cela signifie en faitqu'aucun traitem entn'està réaliser dans ce cas (ce qui estnorm alpuisque alors notre ligne
estréduite à la seule valeur 1, laquelle sera placée par l'affectation t(i)=1). Ilen va de m ê m e pour i=1, jdevantalors
décroître de 0 à 1. On notera qu'en langage C la boucle for perm etde tenir com pte de ces cas particuliers (le testde
poursuite de boucle étantréalisé en début). Ce n'esttoutefois pas là une rè gle généralisable à tous les langages.
2)Avec les précautions que nous venons d'évoquer, l'algorith m e "s'initialise" de lui-m ê m e.
Program m e
#include <stdio.h>
#define NMAX 15 /* nombre maximal de lignes */
main()
{ int t [NMAX], /* tableau représentant une ligne du triangle */
9 2 Exe rcices en langage C
nl, /* nombre de lignes souhaitées */
i, /* indice de la ligne courante */
j ; /* indice courant de colonne */
/* lecture nombre de lignes souhaitées et affichage titres */
printf ("combien de lignes voulez vous ? ") ;
scanf ("%d", &nl) ;
if (nl > NMAX) nl = NMAX ;
printf ("nn p ") ;
for (i=0 ; i<nl ;i++)
printf ("%5d", i) ;
printf ("n nn") ;
for (i=0 ; i<=nl ; i++)
printf ("-----") ;
printf ("n") ;
/* création et affichage de chaque ligne */
for (i=0 ; i<nl ;i++)
{ t[i] = 1 ;
for (j=i-1 ; j>0 ; j--)
t[j] = t[j-1] + t[j] ;
printf ("%2d --", i) ;
for (j=0 ; j<=i ; j++)
printf ("%5d", t[j]) ;
printf ("n") ;
}
}
Com m e ntaire s
*En langage C, les indices d'un tableau com m encent à 0. Ici, cette particularité s'avè re intéressante puisque nos
num éros de lignes ou de colonnes doiventaussi com m encer à 0.
*Plutôtque d'utiliser directem entla constante 15 dans notre program m e, nous avons préféré faire appelà l'instruction
#de fine du préprocesseur pour définir un sym bole NMAX possédantcette valeur. Ilestainsi beaucoup plus facile, le cas
éch éant, de m odifier cette valeur (puisqu'ilsuffitalors d'intervenir en un seulendroit du program m e). Notez que nous
n'aurions pas pu utiliser la déclaration de constante sym bolique (constintNM AX = 15), car, dans ce cas, NM AX n'aurait
pas été une "expression constante", etnous n'aurions pas pu l'utiliser com m e dim ension d'un tableau.
*Ne pas oublier que t[NMAX]réserve NMAX élém ents (c'est-à -dire 15), dontles indices varientde 0 à 14.
I. Variations algorith m iques sur les instructions de base 9 3
*Si l'utilisateur dem ande un nom bre de lignes supérieur à NMAX, le program m e se contente de lim iter cette dem ande à
la valeur NMAX.
D ISCUSSIO N
*Nous aurions pu tenir com pte de la sym étrie de ch aque ligne par rapport à son centre ;quelques instructions
supplém entaires nous auraientalors perm is une légè re réduction du tem ps de calcul.
*L'énoncé lim itait à 15 le nom bre de lignes de notre triangle. En effet, au-delà , iln'est généralem ent plus possible
d'affich er toutes les valeurs sur une seule ligne d'écran.
*Notre program m e n'estpas protégé dans le cas où l'utilisateur fournitune réponse non num érique à la question posée.
Dans ce cas, toutefois, la situation n'est pas trè s grave ;en effet, la valeur de nlest, certes, aléatoire m ais, de toute
façon, elle sera lim itée à 15 par le program m e.
Si vous souh aitiez quand m ê m e traiter ce type d'anom alie, ilvous suffiraitd'exam iner le code de retour de la fonction
scanf(ilfournitle nom bre de valeurs convenablem entlues)etde vérifier qu'ilestbien égalà 1.
I-2 Crible d'Eratosth è ne
________________________________________________________________________________________
Ilexiste une m éth ode de déterm ination de nom bres prem iers connue sous le nom de "crible d'Erastoth è ne". Elle perm et
d'obtenir tous les nom bres prem iers inférieurs à une valeur donnée n.
La m éth ode (m anuelle) consiste à dresser une liste des nom bres considérés (de 1 à n) et à y rayer tous les nom bres
m ultiples d'autres entiers (de tels nom bres sontnécessairem entnon prem iers). Plus précisém ent, on procè de ainsi :
1 - on raye le 1 (qui, par définition, n'estpas un nom bre prem ier).
2 - on rech erch e, à partir du dernier nom bre prem ier considéré (la prem iè re fois, on convient qu'ils'agit du 1), le
prem ier nom bre non rayé (on peutm ontrer qu'ilestprem ier). Ildevient, à son tour, le dernier nom bre prem ier considéré
eton raye tous ses m ultiples.
3 - on répè te le point2 jusqu'à ce que le nom bre prem ier considéré soitsupérieur à la racine carrée de n. On peutalors
m ontrer que tous les nom bres non prem iers ontété rayés de la liste.
9 4 Exe rcices en langage C
Enoncé
Ecrire un program m e basé sur cette m éth ode rech erch anttous les nom bres prem iers com pris entre 1 etn (la valeur de n
étantfixée dans le program m e)
Exe m ple
entre 1 et 1000, les nombres premiers sont :
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
________________________________________________________________________________________
ANALYSE
La m éth ode m anuelle suggè re d'utiliser un tableau. Toutefois, devons-nous, par analogie, y ranger les nom bres entiers de
1 à n?En fait, cela ne seraitguè re utile puisque alors ch aque nom bre seraitégalà son rang dans le tableau (du m oins, à
une unité prè s, suivantles conventions que l'on adopteraitpour l'indice du prem ier élém ent).
En réalité, le bon déroulem entde l'algorith m e nous im pose seulem entd'ê tre en m esure de faire correspondre à ch aque
entier entre 1 etn, une inform ation précisant, à ch aque instant, s'ilestrayé ou non (cette inform ation pouvantévoluer au
fildu déroulem ent du program m e). Ils'agit là tout naturellem ent d'une inform ation de type "logique" (vrai ou faux).
Com m e ce type n'existe pas en tantque telen langage C, nous le sim ulerons à l'aide de deux constantes entiè res : VRAI
de valeur 1, FAUX de valeur 0. Notez que le ch oix de la valeur 0 pour FAUX estim posé par la m aniè re dontle langage
I. Variations algorith m iques sur les instructions de base 9 5
C considè re une expression num érique apparaissant dans une condition ;la valeur 1, par contre, pourrait ê tre, sans
inconvénient, rem placée par n'im porte quelle valeur non nulle.
Notons raye un teltableau et supposons que raye [i] correspond à l'entier i (ce qui, com pte tenu des conventions du
langage C, signifie que raye [0]estinutilisé). Notre algorith m e nous im pose de garder la trace du dernier nom bre prem ier
considéré. Nous le nom m erons pre m . La dém arch e m anuelle se transpose alors com m e suit:
*Initialisation :
- m ettre à FAUX tous les élém ents du tableau raye ,
- m ettre à FAUX le prem ier élém entde raye , etfaire :
prem = 1
*Itération :
- rech erch er, à partir de pre m , le prem ier nom bre non encore rayé, c'est-à -dire incrém enter la valeur de pre m
jusqu'à ce que t[pre m ] soit FAUX (en toute rigueur, ilfaut se dem ander s'ilexiste encore un telnom bre dans
notre tableau, etdonc lim iter l'incrém entation de pre m à N).
- rayer tous les m ultiples de pre m , dans le cas où un telnom bre a été trouvé.
*L'itération proposée peut ê tre répétée, indifférem m ent(les deux form ulations étant équivalentes dè s que N est
supérieur ou égalà 1):
- jusqu'à ce que la valeur de pre m soitsupérieure à la racine carrée de N,
- ou tantque la valeur de pre m estinférieure ou égale à la racine carrée de N.
Program m e
#include <stdio.h>
#define N 1000 /* plus grand entier à examiner */
#define VRAI 1 /* pour "simuler" des ..... */
#define FAUX 0 /* ..... valeurs logiques */
main()
{
int raye [N+1], /* tableau servant de crible */
prem, /* dernier nombre premier considéré */
na, /* compteur de nombres affichés */
i ;
/* initialisations */
for (i=1 ; i<=N ; i++) /* mise à zéro du crible */
9 6 Exe rcices en langage C
raye[i] = FAUX ;
raye[1] = VRAI ; /* on raye le nombre 1 */
/* passage au crible */
prem = 1 ;
while (prem*prem <= N)
{ while (raye[++prem] && prem<N ) {}
/* recherche premier nombre non rayé */
for (i=2*prem ; i<=N ; i+=prem) /* on raye tous ses multiples */
raye[i] = VRAI ;
}
/* affichage résultats */
printf ("entre 1 et %d, les nombres premiers sont :n", N) ;
na = 0 ;
for (i=1 ; i<=N ; i++)
if ( !raye[i] )
{ printf ("%7d",i) ;
na++ ;
if ( na%10 == 0) printf ("n") ; /* 10 nombres par ligne */
}
}
Com m e ntaire s
*La rech erch e du prem ier nom bre non encore rayé estréalisée par la seule instruction :
while (raye[++prem] && prem<N) {}
Notez bien la pré-incrém entation de pre m ;une post-incrém entation :
while (t[prem++] && prem<N) {}
auraitconduità une boucle infinie sur le prem ier nom bre prem ier trouvé, c'est-à -dire 2 (du m oins si N estsupérieur ou
égalà 2). Ilsuffiraittoutefois d'incrém enter pre m une fois avantd'entrer dans la boucle pour que cela fonctionne.
*Nous avons conservé le garde-fou :
prem < N
I. Variations algorith m iques sur les instructions de base 9 7
On pourraittoutefois dém ontrer que, dè s que N estsupérieur ou égalà 2, on esttoujours assuré de trouver au m oins un
nom bre non rayé avantla fin du tableau (com pte tenu de ce que l'on com m ence l'exploration avec un nom bre inférieur
ou égalà la racine carrée de N).
*Nous avons prévu d'affich er nos nom bres prem iers, à raison de 10 par ligne, ch aque nom bre occupant 7 caractè res.
Pour ce faire, nous utilisons une variable nom m ée na nous perm ettantde com ptabiliser le nom bre de nom bres affich és. A
ch aque fois que na estm ultiple de 10, nous provoquons un sautde ligne.
D ISCUSSIO N
*Telqu'ilest proposé ici, le program m e traite le cas n=1000. Pour le faire fonctionner avec d'autres valeurs, ilest
nécessaire d'intervenir au niveau du program m e lui-m ê m e etde le recom piler. Si vous souh aitez que la valeur de n puisse
ê tre fournie en donnée, ilfautlui fixer une valeur m axim ale, afin de prévoir la réservation du tableau correspondant.
Notez toutefois que les possibilités de gestion dynam ique du langage C offrentune solution plus agréable à ce problè m e
de dim e nsions variables. Vous en trouverez certains exem ples dans le ch apitre consacré à la gestion dynam ique.
*Le tableau raye , ainsi que les variables pre m eti, ontété déclarés de type int, ce qui, dans certaines im plém entations,
peut lim iter à 32767 les valeurs qu'ilest ainsi possible d'exam iner. On peut toujours faire m ieux, en utilisant le type
unsigne d int, ou m ieux le type long ou unsigne d long. Toutefois, dans ce cas, on s'assurera que l'on n'estpas soum is à
des contraintes sur la taille des différents m odules objets, sur la taille de la pile ou, encore, toutsim plem ent, sur la taille
des différents objets qu'ilestpossible de m anipuler. Iln'estpas rare, en effet, que l'on rencontre des lim itations à 64 KO
(c'estle cas, actuellem ent, des com pilateurs Borland/Turbo C/C+ + utilisés dans l'environnem entDOS).
I-3 Le ttre s com m une s à de ux m ots (1)
________________________________________________________________________________________
Enoncé
Réaliser un program m e qui affich e les lettres com m unes à deux m ots fournis au clavier. On prévoira d'affich er plusieurs
fois une lettre qui apparaîtà plusieurs reprises dans ch acun des deux m ots.
9 8 Exe rcices en langage C
On supposera que ces m ots ne peuventpas com porter plus de 26 caractè res eton les lira à l'aide de la fonctions ge ts.
Exe m ple s
donnez un premier mot : monsieur
donnez un deuxième mot : bonjour
la lettre o est commune aux deux mots
la lettre n est commune aux deux mots
la lettre u est commune aux deux mots
la lettre r est commune aux deux mots
_________________
donnez un premier mot : barbara
donnez un deuxième mot : ravage
la lettre a est commune aux deux mots
la lettre r est commune aux deux mots
la lettre a est commune aux deux mots
________________________________________________________________________________________
ANALYSE
L'énoncé nous im pose d'utiliser ge ts, donc de représenter nos m ots sous form e de ch aînes de caractè res (suites de
caractè res term inées par le caractè re nul, noté en C : 0). Nous utiliserons à cet effet des tableaux de caractè res de
dim ension 27 (pour 26 lettres m axim um etun caractè re de fin).
La rech erch e des lettres com m unes aux deux m ots peutse faire en com parantch acun des caractè res de la prem iè re ch aîne
à ch acun des caractè res de la seconde. Cela nous conduit naturellem ent à l'utilisation de deux boucles avec com pteur
(instructions for)im briquées.
Toutefois, nous devons tenir com pte de ce qu'une m ê m e lettre peut figurer plusieurs fois dans un m ê m e m ot. Dans ces
conditions, ilfautéviter :
*qu'une m ê m e lettre du prem ier m ot ne puisse ê tre trouvée en deux endroits différents du second. Par exem ple,
avec :
m onsieur
et
bonjour
I. Variations algorith m iques sur les instructions de base 9 9
aprè s avoir trouvé que le o de m onsie ur figurait en position 2 de bonjour, ilfaut éviter de signaler une nouvelle
coïncidence entre ce m ê m e o de m onsie ur etle second o de bonjour.
Ilestdonc nécessaire d'interrom pre la com paraison entre une lettre du prem ier m otavec toutes celles du second m ot,
dè s qu'une coïncidence a été détectée.
*qu'une m ê m e lettre du second m otne puisse coïncider avec deux lettres différentes du second. Par exem ple, avec
(attention à l'ordre des m ots):
bonjour
et
m onsieur
ilfaut éviter de trouver une coïncidence entre le prem ier o de bonjour et l'unique o de m onsie ur et une autre
coïncidence entre le second o de bonjour etle m ê m e o de m onsie ur.
Pour ce faire, une dém arch e (parm i d'autres) consiste à élim iner dans le second m otla lettre ayant faitl'objetd'une
coïncidence. Plus précisém ent, ilsuffitde rem placer une telle lettre par un caractè re donton estsûr qu'iln'apparaîtra
pas dans un m ot. Ici, nous avons ch oisi l'espace puisque nous som m es censés travailler avec des m ots.
Program m e
#include <stdio.h>
#include <string.h>
#define LMAX 26
main()
{
char mot1 [LMAX+1], /* premier mot */
mot2 [LMAX+1] ; /* deuxième mot */
int i, j ;
/* lecture des deux mots */
printf ("donnez un premier mot : ") ;
gets (mot1) ;
printf ("donnez un deuxième mot : ") ;
gets (mot2) ;
/* comparaison */
for (i=0 ; i<strlen(mot1) ; i++)
for (j=0 ; j<strlen(mot2) ; j++)
if (mot1[i] == mot2[j])
100 Exe rcices en langage C
{ printf ("la lettre %c est commune aux deux motsn", mot1[i]) ;
mot2[j] = ' ' ;
break ;
}
}
Com m e ntaire s
*Nous avons utilisé le sym bole LMAX pour représenter la longueur m axim ale d'un m ot. Notez bien que les tableaux
m ot1 et m ot2 ont dû ê tre prévus de dim ension LMAX+1, afin de tenir com pte de la présence du caractè re de fin de
ch aîne.
*Nous aurions pu utiliser, à la place de la seconde boucle avec com pteur (en j), une boucle tantque (w h ile ). Certes, la
program m ation eûtété plus structurée m ais, néanm oins, m oins concise.
D ISCUSSIO N
Ce program m e n'est pas protégé contre des réponses de plus de 26 caractè res. Dans ce cas, en effet, les caractè res
superflus irontécraser les données se trouvantau-delà de l'un des tableaux m ot1 ou m ot2. Les conséquences peuventê tre
assez variées (vous pouvez expérim enter le présent program m e dans diverses situations et tenter d'expliquer les
com portem ents observés).
Ilexiste différentes façons d'éviter ce risque. Citons, par exem ple :
- lire (toujours par ge ts), une ch aîne com portantun nom bre de caractè res suffisam m entélevé pour que l'utilisateur ne
risque pas (trop!) d'en fournir plus. On pourrait ch oisir, par exem ple 80 ou 128 caractè res (dans certaines
im plém entations, iln'estjam ais possible de taper des lignes de plus de 128 caractè res).
- lim iter autom atiquem entla longueur de la ch aîne lue, en utilisantla fonction fge ts ;par exem ple, avec fge ts (m ot1,
LM AX, stdin), on lim ite à LM AX le nom bre de caractè res lus sur stdin.
- utiliser, dans certaines im plém entations (Turbo/Borland C/C+ + , C/Quick C M icrosoft), une fonction (non
portable!)nom m ée cge ts.
I. Variations algorith m iques sur les instructions de base 101
I-4 Le ttre s com m une s à de ux m ots (2)
________________________________________________________________________________________
Enoncé
Réaliser un program m e qui affich e les lettres com m unes à deux m ots fournis en donnée. Cette fois, on n'im posera pas de
lim ite à la taille des m ots fournis par l'utilisateur, m ais on ne prendra en com pte que les 26 prem iers caractè res. Quelque
soit le nom bre de caractè res effectivem ent frappés, l'utilisateur devra toujours valider sa réponse par la frappe de la
touch e re turn.
Là encore, on prévoira d'affich er plusieurs fois une lettre qui apparaîtà plusieurs reprises dans ch acun des m ots.
On s'astreindra à n'utiliser pour la lecture au clavier que la seule fonction getch ar. De plus, on réalisera une fonction
destinée à lire un m otdans un tableau qu'on lui transm ettra en argum ent;elle fournira, en retour, la longueur effective
du m otainsi lu.
Exe m ple s
Voir ceux de l'exercice précédent
________________________________________________________________________________________
ANALYSE
L'énoncé nous im pose l'em ploi de ge tch ar, ce qui signifie que ch acun des deux m ots devra ê tre lu caractè re par
caractè re. Dans ces conditions, nous pouvons ch oisir de représenter nos m ots :
- soitsous form e d'une ch aîne de caractè res. Ilnous faudra alors introduire nous-m ê m es le caractè re de fin de ch aîne
(0), ce que faisaitautom atiquem entge ts.
- soitsous form e d'une sim ple suite de caractè res (c'est-à -dire sans ce caractè re de fin). Dans ce cas, ilnous faudra
alors prévoir d'en déterm iner la "longueur".
Com m e l'énoncé nous im pose que la fonction de lecture d'un m ot en restitue la longueur, nous ch oisirons la seconde
solution.
La lecture d'un m otconsiste donc à lire des caractè res au clavier jusqu'à ce que l'on rencontre une validation (n)ou que
l'on ait obtenu 26 caractè res ;de plus, dans le cas où l'on a obtenu 26 caractè res, ilfaut poursuivre la lecture de
caractè res au clavier, sans les prendre en com pte, jusqu'à ce que l'on rencontre une validation.
102 Exe rcices en langage C
Program m e
#include <stdio.h>
#define LMAX 26 /* longueur maximale d'un mot */
main()
{
int lire(char []) ; /* déclaration (prototype) fonction lecture d'un mot */
char mot1 [LMAX], /* premier mot (sans '0') */
mot2 [LMAX] ; /* deuxième mot (sans '0') */
int l1, /* longueur premier mot */
l2, /* longueur deuxième mot */
i, j ;
/* lecture des deux mots */
printf ("donnez un premier mot : ") ;
l1 = lire (mot1) ;
printf ("donnez un deuxième mot : ") ;
l2 = lire (mot2) ;
/* comparaison */
for (i=0 ; i<l1 ; i++)
for (j=0 ; j<l2 ; j++)
if (mot1[i] == mot2[j])
{ printf ("la lettre %c est commune aux deux motsn", mot1[i]) ;
mot2[j] = ' ' ;
break ;
}
}
/* Fonction de lecture d'un mot */
int lire (char mot [LMAX])
{
int i ; /* rang du prochain caractère à lire */
char c ;
i = 0 ;
while ( (c=getchar()) != 'n' && i<=LMAX )
mot[i++] = c ;
/* ici, soit on a lu n, soit on a lu LMAX caractères */
/* dans tous les cas, c contient le premier caractère */
/* non pris en compte */
if (c != 'n')
I. Variations algorith m iques sur les instructions de base 103
while (getchar() != 'n') {} /* recherche 'n' */
return(i) ;
}
Com m e ntaire s
*Là encore, nous avons utilisé le sym bole LMAX pour représenter la taille m axim ale d'un m ot. Par contre, cette fois, la
dim ension des tableaux m ot1 etm ot2 estégale à LMAX (etnon plus LMAX+ 1), puisque nous n'avons pas à y introduire
le caractè re supplém entaire de fin de ch aîne.
*En ce qui concerne la fonction (nom m ée lire )de lecture d'un m otau clavier, vous constatez que nous l'avons déclarée
dans le program m e principal(m ain), bien que cela soitfacultatif, dans la m esure où elle fournitun résultatde type int(en
effet, toute fonction qui n'estpas explicitem entdéclarée estsupposée produire un résultatde type int).
D'autre part, com m e nous l'avons expliqué dans l'introduction de cette seconde partie, nous avons utilisé, dans cette
déclaration, la form e "prototype" autorisée par la norm e ANSI (ce prototype assure les contrôles de types d'argum ents et
m eten place d'éventuelles conversions).
Par ailleurs, l'en-tê te de notre fonction lire a été écrit suivant la form e "m oderne". La norm e ANSI aurait autorisé le
rem placem entde note en-tê te par :
int lire (mot)
char mot [LMAX] ;
*Le tableau de caractè res représentantl'unique argum entde lire doitobligatoirem entê tre transm is par adresse puisque
cette fonction doitê tre en m esure d'en m odifier le contenu. N'oubliez pas cependantqu'en langage C un nom de tableau
est interprété (par le com pilateur) com m e un pointeur (constant) sur son prem ier élém ent. C'est ce qui justifie la
présence, dans les appels à la fonction lire , de m ot1 ou m ot2, etnon de & m ot1 ou & m ot2.
*La déclaration de l'argum entde lire , dans son en-tê te :
char mot [LMAX]
auraitpu égalem ents'écrire :
char mot []
ou m ê m e :
char * mot
104 Exe rcices en langage C
Dans le prem ier cas, on continue de spécifier (au lecteur du program m e plus qu'au com pilateur) que m otestun tableau
de caractè res m ais que sa dim ension n'a pas besoin d'ê tre connue au sein de lire . Dans le second cas, on exprim e plus
clairem ent que, finalem ent, l'argum ent reçu par lire n'est rien d'autre qu'un pointeur sur des caractè res. Ces
form ulations sonttotalem entéquivalentes pour le com pilateur et, dans tous les cas (m ê m e le dernier), ilreste possible de
faire appelau "form alism e" tableau au sein de lire , en utilisantune notation telle que :
mot [i++]
D'ailleurs, pour le com pilateur, cette derniè re estéquivalente à :
* (mot + i++)
Les m ê m e reflexions s'appliquentà l'écriture du prototype de lire .
D ISCUSSIO N
*Le sym bole LMAX est défini pour l'ensem ble du source contenant, ici, le program m e principalet la fonction lire .
M ais, si cette fonction devait ê tre com pilée séparém ent du reste, ilserait alors nécessaire de faire figurer la définition
(#de fine )dans les deux sources, ce qui com porte un risque d'erreur. Dans une situation "réelle", on pourraitavoir intérê t
à faire appelà l'une des dém arch es suivantes :
- transm ettre la valeur de LMAX en argum entde la fonction lire .
- regrouper les définitions de sym boles com m uns à plusieurs sources dans un fich ier séparé que l'on appelle par
#include dans ch acun des sources concernés.
*Contrairem entau program m e de l'exercice précédent, celui-ci se trouve protégé de réponses trop longues de la partde
l'utilisateur.
I-5 Com ptage de le ttre s
________________________________________________________________________________________
I. Variations algorith m iques sur les instructions de base 105
Enoncé
Réaliser un program m e qui com pte le nom bre de ch acune des lettres de l'alph abet d'un texte entré au clavier. Pour
sim plifier, on ne tiendra com pte que des m inuscules, m ais on com ptera le nom bre des caractè res non reconnus com m e
tels (quels qu'ils soient: m ajuscules, ponctuation, ch iffres,...).
Le program m e devra accepter un nom bre quelconque de lignes. L'utilisateur tapera une "ligne vide" pour signaler qu'ila
term iné la frappe de son texte (ce qui revient à dire qu'ilfrappera donc deux fois de suite la touch e re turn, aprè s la
frappe de sa derniè re ligne).
On supposera que les lignes frappées au clavier ne peuvent jam ais dépasser 127 caractè res. Par ailleurs, on fera
l'h ypoth è se (peu restrictive en pratique) que les "codes" des lettres m inuscules a à z sontconsécutifs (ce qui estle cas,
notam m ent, avec le code ASCII).
Exe m ple
donnez votre texte, en le terminant par une ligne vide
je me figure ce zouave qui joue
du xylophone en buvant du whisky
votre texte comporte 63 caractères dont :
2 fois la lettre a
1 fois la lettre b
1 fois la lettre c
2 fois la lettre d
8 fois la lettre e
1 fois la lettre f
......
......
7 fois la lettre u
2 fois la lettre v
1 fois la lettre w
1 fois la lettre x
2 fois la lettre y
1 fois la lettre z
et 11 autres caractères
________________________________________________________________________________________
106 Exe rcices en langage C
ANALYSE
Ilnous faut donc utiliser un tableau de 26 entiers perm ettant de com ptabiliser le nom bre de fois où l'on a rencontré
ch acune des 26 lettres (m inuscules) de l'alph abet. Nous le nom m erons com pte . Nous utiliserons égalem entun com pteur
nom m é ntot pour le nom bre totalde caractè res et un autre nom m é nautres pour les caractè res différents d'une lettre
m inuscule.
En ce qui concerne le com ptage proprem entdit, ilnous fautexam iner ch acune des lettres du texte. Pour ce faire, ilexiste
(au m oins)deux dém arch es possibles :
- effectuer une répétition du traitem entd'un caractè re,
- effectuer une répétition du traitem entd'une ligne, lui-m ê m e constitué de la répétition du traitem ent de ch acun des
caractè res qu'elle contient.
a)La prem iè re dém arch e aboutità une sim ple boucle avec com pteur. Elle ne dem ande d'accéder qu'à un seulcaractè re à
la fois (par exem ple, par ge tch ar). Elle nécessite, par contre, l'élim ination des caractè res de fin de ligne n (qui sont
transm is com m e les autres par ge tch ar), puisqu'ils ne fontpas vraim entpartie du texte.
De surcroît, la détection de la fin du texte oblige à conserver en perm anence le "caractè re précédent". Lorsque ce
caractè re, ainsi que le caractè re courant, sont égaux à n, c'est que l'on a atteint la fin du texte. Ilsuffit d'initialiser
artificiellem ent ce caractè re précédent à une valeur quelconque (autre que n) pour éviter de devoir effectuer un
traitem entparticulier pour le prem ier caractè re.
b)La seconde dém arch e aboutità deux boucles im briquées. Elle perm etde lire directem entch aque ligne par ge ts. Elle
rè gle de m aniè re naturelle les problè m es de fin de ligne etde fin de texte.
Nous vous proposons ici deux program m es, correspondantà ch acune de ces deux dém arch es.
Program m e bas é s ur la ré pé tition du traite m e ntd'un caractè re
#include <stdio.h>
main()
{
char c, /* pour lire un caractère frappé au clavier */
cprec ; /* caractère précédent */
int compte[26] ; /* pour compter les différentes lettres */
int numl, /* rang lettre courante dans l'alphabet */
ntot, /* nombre de caractères du texte */
nautres, /* nb caractères autres qu'une lettre minuscule */
i ;
/* initialisations */
I. Variations algorith m iques sur les instructions de base 107
cprec = ' ' ;
ntot = 0 ; nautres = 0 ;
for (i=0 ; i<26 ; i++) compte[i]=0 ;
/* lecture texte et comptages */
printf ("donnez votre texte, en le terminant par une ligne viden") ;
while ( (c=getchar()) != 'n' || cprec != 'n' )
{ if (c != 'n')
{ numl = c - 'a' ; /* on donne le rang 0 à la lettre 'a' */
if (numl >=0 && numl < 26) compte[numl]++ ;
else nautres++ ;
ntot++ ;
}
cprec = c ;
}
/* affichage résultats */
printf ("nnvotre texte comporte %d caractères dont :n", ntot) ;
for (i=0; i<26 ; i++)
printf ("%d fois la lettre %cn", compte[i], 'a'+i) ;
printf ("net %d autres caractèresn", nautres) ;
}
Com m e ntaire s
*L'expression :
c - 'a'
perm etd'obtenir le "rang" dans l'alph abetdu caractè re contenu dans c. N'oubliez pas que le langage C considè re le type
ch ar com m e num érique. Plus précisém ent, dans le cas présent, les valeurs de c et de 'a' sont converties en int (ce qui
fournitla valeur num érique de leur code) avantque ne soitévaluée l'expression c-'a'. Com m e nous avons supposé que
les codes des m inuscules sontconsécutifs, nous obtenons bien le résultatescom pté.
*Les instructions :
if (c != 'n')
{ numl = c - 'a' ;
if (numl >=0 && numl < 26) compte[numl]++ ;
else nautres++ ;
ntot++ ;
108 Exe rcices en langage C
}
cprec = c;
pourraientse condenser en :
if ( (cprec=c) != 'n')
{ numl = c - 'a' ;
if (numl >=0 && numl < 26) compte[numl]++ ;
else nautres++ ;
ntot++ ;
}
Program m e bas é s ur la ré pé tition du traite m e ntd'une ligne
#include <stdio.h>
#include <string.h>
main()
{ char ligne[128] ; /* pour lire une ligne frappée au clavier */
int compte[26] ; /* pour compter les différentes lettres */
int numl, /* rang lettre courante dans l'alphabet */
ntot, /* nombre de caractères du texte */
nautres, /* nombre de caractères autres qu'une lettre minuscule */
i ;
/* initialisations */
ntot = 0 ; nautres = 0 ;
for (i=0 ; i<26 ; i++) compte[i]=0 ;
/* lecture texte et comptages */
printf ("donnez votre texte, en le terminant par une ligne viden") ;
do
{ gets(ligne) ;
for (i=0 ; i<strlen(ligne) ; i++, ntot++)
{ numl = ligne[i] - 'a' ;/* on donne le rang 0 à la lettre 'a' */
if (numl >=0 && numl < 26) compte[numl]++ ;
else nautres++ ;
}
}
while (strlen(ligne)) ;
/* affichage résultats */
I. Variations algorith m iques sur les instructions de base 109
printf ("nnvotre texte comporte %d caractères dont :n", ntot) ;
for (i=0; i<26 ; i++)
printf ("%d fois la lettre %cn", compte[i], 'a'+i) ;
printf ("net %d autres caractèresn", nautres) ;
}
D ISCUSSIO N
*Aucun des deux program m es proposés ne pose de problè m e de protection vis-à -vis des réponses fournies par
l'utilisateur.
I-6 Com ptage de m ots
________________________________________________________________________________________
Enoncé
Ecrire un program m e perm ettantde com pter le nom bre de m ots contenus dans un texte fourni au clavier. Le texte pourra
com porter plusieurs lignes etl'utilisateur tapera une ligne "vide" pour signaler qu'ilen a term iné la frappe (ce qui revient
à dire qu'ilfrappera deux fois de suite la touch e re turn aprè s avoir fourni la derniè re ligne).
On adm ettra que deux m ots sonttoujours séparés par un ou plusieurs des caractè res suivants :
- fin de ligne
- espace
- ponctuation : : . , ;?!
- parenth è ses : ( )
- guillem ets : "
- apostroph e : '
On adm ettra égalem ent, pour sim plifier, qu'aucun m ot ne peut ê tre com m encé sur une ligne et se poursuivre sur la
suivante.
On prévoira une fonction perm ettant de décider si un caractè re donné transm is en argum ent est un des séparateurs
m entionnés ci-dessus. Elle fournira la valeur 1 lorsque le caractè re estun séparateur etla valeur 0 dans le cas contraire.
110 Exe rcices en langage C
Exe m ple
donnez votre texte, en le terminant par une ligne vide
Le langage C a été conçu en 1972 par Denis Ritchie avec un objectif
très précis : écrire un "système d'exploitation" (UNIX). A cet effet,
il s'est inspiré du langage B (créé par K. Thompson) qu'il a haussé
au niveau de langage évolué, notamment en l'enrichissant de structures
et de types, et ceci tout en lui conservant ses aptitudes de
programmation proche de la machine.
votre texte comporte 68 mots
_______________________________________________________________________________________
ANALYSE
Com m e dans l'exercice précédent, ilexiste (au m oins)deux dém arch es possibles :
- effectuer une répétition du traitem entd'un caractè re,
- effectuer une répétition du traitem entd'une ligne, lui-m ê m e constitué de la répétition du traitem ent de ch acun des
caractè res qu'elle contient.
La prem iè re dém arch e aboutit à une sim ple boucle avec com pteur. Elle dem ande sim plem ent d'accéder à un seul
caractè re (par exem ple par ge tch ar).
La seconde dém arch e aboutit à deux boucles im briquées. Elle dem ande d'effectuer une lecture ligne par ligne (par
exem ple par ge ts).
Là encore, nous exam inerons les deux dém arch es et nous proposerons un program m e correspondant à ch acune d'entre
elles.
Dans les deux dém arch es, tous les caractè res séparateurs jouentle m ê m e rôle, à condition d'y inclure n (si l'on travaille
avec ge tch ar) ou 0 (si l'on travaille avec ge ts). On peutalors dire que l'on a progressé d'un m otdans le texte, ch aque
fois que l'on a réalisé la séquence suivante :
- rech erch e du prem ier caractè re différentd'un séparateur,
- rech erch e du prem ier caractè re égalà un séparateur.
I. Variations algorith m iques sur les instructions de base 111
On pourraitrépéter successivem entces deux opérations, tantque que l'on n'estpas arrivé à la fin du texte. En fait, ilest
plus sim ple d'utiliser un "indicateur logique" (nous l'appellerons m ot_e n_cours)etd'effectuer pour ch aque caractè re le
traitem entsuivant:
- si le caractè re est un séparateur et si m ot_e n_cours est vrai, augm enter de un le com pteur de m ots et rem ettre
m ot_e n_cours à faux.
- si le caractè re n'estpas un séparateur etsi m ot_e n_cours estfaux, m ettre m ot_e n_cours à vrai.
Quantà la condition d'arrê t, elle s'exprim e différem m entsuivantla dém arch e adoptée :
- deux caractè res consécutifs égaux à n pour la prem iè re, ce qui im pose de conserver en perm anence la valeur du
"caractè re précédent" ;ce dernier sera initialisé à une valeur quelconque différente de n pour éviter un traitem ent
particulier du prem ier caractè re du texte.
- ligne vide pour la seconde.
Program m e bas é s ur la ré pé tition du traite m e ntd'un caractè re
#include <stdio.h>
#define VRAI 1 /* pour "simuler" des ..... */
#define FAUX 0 /* ..... valeurs logiques */
main()
{ int sep(char) ; /* prototype fonction test "caractère séparateur?" */
char c, /* pour lire un caractère frappé au clavier */
cprec ; /* caractère précédent */
int nmots, /* compteur du nombre de mots */
fin_texte, /* indicateurs logiques : - fin texte atteinte */
mot_en_cours ; /* - mot trouvé */
cprec = ' ' ;
fin_texte = FAUX ;
mot_en_cours = FAUX ;
nmots = 0 ;
printf ("donnez votre texte, en le terminant par une ligne viden") ;
while (!fin_texte)
{ if ( sep(c=getchar()) )
{ if (mot_en_cours)
{ nmots++ ;
mot_en_cours = FAUX ;
}
112 Exe rcices en langage C
}
else mot_en_cours = VRAI ;
if ( c=='n' && cprec=='n') fin_texte = VRAI ;
cprec = c ;
}
printf ("nnvotre texte comporte %d mots :n", nmots) ;
}
/*******************************************/
/* fonction d'examen d'un caractère */
/*******************************************/
int sep (char c)
{
char sep[12] = {'n', /* fin de ligne */
' ', /* espace */
',', ';', ':', '.', '?', '!', /* ponctuation */
'(', ')', /* parenthèses */
'"', ''' } ; /* guillemets, apostrophe*/
int nsep=12, /* nombre de séparateurs */
i ;
i = 0 ;
while ( c!=sep[i] && i++<nsep-1 ) ;
if (i == nsep) return (0) ;
else return (1) ;
}
Com m e ntaire s
*Nous avons introduit une variable "logique" nom m ée fin_te xte qui nous facilite la détection de la fin du texte. Nous
aurions pu nous en passer en introduisantune instruction bre ak au sein d'une boucle do ... w h ile {1}(boucle infinie).
*Dans le traitem entde ch aque caractè re, nous n'avons pas respecté "à la lettre" l'algorith m e proposé lors de l'analyse.
En effet, nous exécutons l'instruction :
mot_en_cours = VRAI
m ê m e si l'indicateur m ot_e n_cours a déjà la valeur VRAI ;cela nous évite un test supplém entaire, sans m odifier le
com portem entdu program m e (puisque la m odification ainsi apportée consiste à m ettre à VRAI l'indicateur alors qu'ily
estdéjà ).
I. Variations algorith m iques sur les instructions de base 113
*Dans la fonction sep, la seule instruction :
while ( c!=sep[i] && i++<nsep-1 ) ;
perm et de savoir si le caractè re c est un séparateur. En effet, ilne faut pas oublier que l'opérateur & & n'évalue son
second opérande que lorsque cela estnécessaire. Autrem entdit, si la prem iè re condition estfausse (c estdonc égalà un
séparateur), l'expression i++<nsep-1 n'estpas évaluée eti n'estdonc pas incrém entée. Si, par contre, cette prem iè re
condition estvérifiée alors qu'on a exploré la totalité des séparateurs (i=11), la seconde condition estévaluée etelle est
trouvée fausse, m ais en m ê m e tem ps, i se trouve incrém entée (à 12).
En définitive, on voitqu'à la fin de cette instruction, lorsque i vaut12, cela signifie que c ne figure pas dans la liste des
séparateurs.
Program m e bas é s ur la ré pé tition du traite m e ntd'une ligne
#include <stdio.h>
#include <string.h>
#define VRAI 1 /* pour "simuler" des ..... */
#define FAUX 0 /* ..... valeurs logiques */
main()
{
int sep(char) ; /* prototype fonction test "caractère séparateur?" */
char ligne[128] ; /* pour lire une ligne frappée au clavier */
int nmots, /* compteur du nombre de mots */
mot_en_cours, /* indicateur logique : mot trouvé */
i ;
nmots = 0 ;
mot_en_cours = FAUX ;
printf ("donnez votre texte, en le terminant par une ligne viden") ;
do
{ gets(ligne) ;
for (i=0 ; i<=strlen(ligne) ; i++) /* on traite aussi le '0' */
if ( sep(ligne[i]) )
{ if (mot_en_cours)
{ nmots++ ;
mot_en_cours = FAUX ;
}
}
else mot_en_cours = VRAI ;
}
while (strlen(ligne)) ;
114 Exe rcices en langage C
printf ("nnvotre texte comporte %d mots :n", nmots) ;
}
/********************************************/
/* fonction d'examen d'un caractère */
/********************************************/
int sep (char c)
{
char sep[12] = {'0', /* fin de ligne (chaîne) */
' ', /* espace */
',', ';', ':', '.', '?', '!', /* ponctuation */
'(', ')', /* parenthèses */
'"', ''' } ; /* guillemets, apostrophe*/
int nsep=12, /* nombre de séparateurs */
i ;
i = 0 ;
while ( c!=sep[i] && i++<nsep-1 ) ;
if (i == nsep) return (0) ;
else return (1) ;
}
Com m e ntaire s
Nous avons dû :
- d'une part, au sein de la fonction sep, rem placer le séparateur n par 0,
- d'autre part, dans la boucle de traitem entdes caractè res d'une ligne, traiter com m e les autres ce caractè re de fin de
ligne (c'est-à -dire faire varier i de 0 à strle n(ligne ) etnon strle n(ligne )-1), afin d'éviter de com pter pour un seulm ot
le dernier m otd'une ligne (non suivi d'un séparateur)etle prem ier de la suivante.
D iscussion
*En ce qui concerne la fonction d'exam en d'un caractè re (nom m ée sep), vous constatez (dans les deux versions
proposées)que nous l'avons déclarée dans le program m e principal(m ain), bien que cela soitfacultatif, dans la m esure où
elle fournitun résultatde type int.
*Aucun des deux program m es proposés ne pose de problè m e de protection vis-à -vis des réponses fournies par
l'utilisateur.
II: UTILISATIO N
D E STRUCTURES
Le ch apitre I vous a proposé des exercices faisant appelaux instructions de base du langage C. Les exercices de ce
ch apitre fontintervenir, en plus, la notion de structure sous des form es diverses (en particulier les tableaux de structures
etleur initialisation).
II-1 Signe du zodiaq ue
________________________________________________________________________________________
Enoncé
Affich er le signe du zodiaque correspondantà une date de naissance fournie en donnée, sous la form e :
jour m ois
Les deux inform ations serontséparées par au m oins un espace. La prem iè re sera fournie sous form e num érique, tandis
que la seconde le sera sous form e d'une ch aîne de caractè res.
Nous vous rappelons que les périodes correspondantà ch aque signe sontles suivantes :
Capricorne 23 décembre - 19 janvier
Verseau 20 janvier - 19 février
Poisson 20 février - 20 mars
116 Exe rcices en langage C
Bélier 21 mars - 19 avril
Taureau 20 avril - 20 mai
Gémeau 21 mai - 20 juin
Cancer 21 juin - 21 juillet
Lion 22 juillet - 22 août
Vierge 23 août - 22 septembre
Balance 23 septembre - 22 octobre
Scorpion 23 octobre - 21 novembre
Sagittaire 22 novembre - 22 décembre
Exe m ple s
donnez votre jour et votre mois (sans accent) de naissance ?
11 july
*** erreur de nom de mois ***
_______________________
donnez votre jour et votre mois de naissance ?
16 janvier
vous êtes né sous le signe suivant : Capricorne
________________________________________________________________________________________
ANALYSE
Le program m e doit ê tre en m esure d'établir une correspondance entre le nom d'un signe et les deux dates lim ites
correspondantes. On peutdéjà noter que la date de fin d'un signe estla veille de celle de début du suivant. Nous nous
contenterons donc de ne conserver qu'une seule de ces deux inform ations, par exem ple la date de fin.
La correspondance souh aitée peutê tre réalisée :
- par plusieurs tableaux (jour, m ois, signe)reliés par une valeur com m une d'indice.
- par un seultableau dans lequelch aque élém entestune structure com portantun num éro de jour, un nom de m ois et
un nom de signe.
Nous ch oisirons la seconde solution car elle perm et de m ieux m ettre en évidence la correspondance entre les
inform ations, au m om entde l'initialisation au sein du program m e.
La rech erch e du signe correspondantà une date donnée se faitalors de la m aniè re suivante :
- On ch erch e toutd'abord l'élém ent(nous le nom m erons x)appartenantà notre tableau de structures, dontle nom de
m ois correspond à celui proposé en donnée. S'iln'existe pas, on le signale par un m essage approprié.
II. Utilisation de structures 117
- On regarde ensuite si le num éro du jour proposé estinférieur ou égalà celui de l'élém entx.
Dans l'affirm ative, on peuten conclure que la date proposée estantérieure à la date de fin du signe figurantdans
l'élém entx, ce qui fournitla réponse voulue.
Dans le cas contraire, on en conclutque la date proposée estpostérieure à la date de débutdu signe figurantdans
l'élém ent x ;ilsuffit donc d'exam iner l'élém ent suivant pour obtenir la réponse voulue. Toutefois, si x est le
dernier élém entde notre tableau, ilfaudra considérer que son suivantesten faitle prem ier élém entdu tableau.
On rem arquera que l'algorith m e proposé fonctionne effectivem entparce que ch acun des 12 m ois de l'année ne com porte
qu'un seulch angem entde signe. Si cela n'avaitpas été le cas, ilauraitfallu "encadrer" la date proposée par deux dates
d'élém ents consécutifs de notre tableau.
Program m e
#include <stdio.h>
#include <conio.h>
#include <string.h>
main()
{
struct s_date { int jour ;
char mois [10] ;
char signe [11] ;
} ;
struct s_date date [12] = { 23, "decembre", "Sagittaire",
20, "janvier", "Capricorne",
20, "fevrier", "Verseau",
21, "mars", "Poisson",
20, "avril", "Bélier",
21, "mai", "Taureau",
21, "juin", "Gémeau",
22, "juillet", "Cancer",
23, "aout", "Lion",
23, "septembre", "Vierge",
23, "octobre", "Balance",
22, "novembre", "Scorpion"
} ;
int jour_n ; /* jour de naissance */
char mois_n [10] ; /* mois de naissance */
int nbv, i ;
118 Exe rcices en langage C
/* lecture date de naissance */
printf ("donnez votre jour et votre mois de naissance ?n") ;
scanf ("%d %s", &jour_n, mois_n) ;
/* recherche et affichage du signe correspondant */
i = 0 ;
while ( stricmp(date[i].mois, mois_n) && i++<11 ) { }
if (i<12)
{ printf ("vous êtes né sous le signe suivant : ") ;
if (jour_n >= date[i].jour) i = (i+1)%12 ;
printf ("%s", date[i].signe) ;
}
else printf ("*** erreur de nom de mois ***") ;
}
Com m e ntaire s
*Nous avons défini ici un m odè le de structure nom m é s_date , dans lequelnous trouvons un num éro de jour, un nom de
m ois et le signe correspondant. Nous avons prévu 10 caractè res pour le nom de m ois, ce qui autorise des ch aînes de
longueur inférieure ou égale à 9 (com pte tenu du 0 de fin);de m ê m e, nous avons prévu 11 caractè res pour le signe.
Le tableau nom m é date est un tableau de 12 élém ents ayant ch acun le type s_date . Nous l'avons initialisé dans sa
déclaration, ce qui perm etde m ettre facilem enten parallè le ch aque signe etsa date de fin.
*En ce qui concerne la lecture de la date au clavier, nous n'avons pas prévu, ici, de protection vis-à -vis d'éventuelles
erreurs de frappe de l'utilisateur (cela n'étaitpas dem andé par l'énoncé).
*Rappelons que la fonction stricm p com pare, sans tenir com pte de la distinction m ajuscules/m inuscules, les deux ch aînes
donton lui fournitl'adresse en argum ent. Elle restitue une valeur non nulle (qu'on peutinterpréter com m e vrai) lorsque
les deux ch aînes sontdifférentes etune valeur nulle (faux)lorsqu'elles sontégales.
*La rech erch e du nom de m ois estréalisée par la seule instruction :
while ( stricmp(date[i].mois, mois_n) && i++<11 ) {}
Celle-ci possè de un double avantage ;tout d'abord, celui de la concision ;ensuite, celui de nous perm ettre de savoir
directem entsi la rech erch e a été fructueuse ou non.
II. Utilisation de structures 119
En effet, ilne faut pas oublier que l'opérateur & & n'évalue son second opérande que lorsque cela est nécessaire.
Autrem entdit, si la prem iè re condition estfausse (ily a donc égalité des deux ch aînes), l'expression i++<11 n'estpas
évaluée eti n'estdonc pas incrém entée. La valeur de i désigne alors l'élém entvoulu.
Si, par contre, cette prem iè re condition estvérifiée (iln'y a donc pas égalité des deux ch aînes) alors qu'on estarrivé en
fin de table (i=11), la seconde condition est évaluée et elle est trouvée fausse, m ais en m ê m e tem ps i se trouve
incrém entée (à 12).
En définitive, on voitque, à la fin de cette instruction, lorsque i vaut12, cela signifie que l'élém entch erch é ne figure pas
dans la table. Dans le cas contraire (i<12), i désigne l'élém entch erch é.
Bien entendu, cette "rech erch e en table" pouvait se program m er de beaucoup d'autres m aniè res. Par exem ple, nous
aurions pu écrire :
while ( stricmp(date[i].mois, mois_n) && i<11 ) i++ ;
Toutefois, cette instruction n'estpas équivalente à la précédente. En effet, lorsque i vaut11, cela peutsignifier :
- soitque l'élém entch erch é esten position 11 (prem ier testsatisfait),
- soitque l'élém entch erch é ne figure pas dans la table (second testsatisfait).
Pour tranch er, ilestdonc nécessaire, dans ce cas, d'effectuer une com paraison supplém entaire.
Notez que, par contre, une instruction telle que :
while ( stricmp(date[i].mois, mois_n) && i++ <= 11) {}
seraitquelque peu erronée. En effet, dans le cas où l'élém entch erch é ne figureraitpas dans le tableau, on seraitam ené à
évaluer l'expression :
date[i].mois
avec une valeur i égale à 12, c'est-à -dire désignant un élém ent situé en deh ors du tableau. Certes, en général, cela ne
seraitguè re visible dans le com portem entdu program m e, dans la m esure où ilestbien peu probable que cette valeur soit
égale au nom de m ois voulu...
*Notez l'em ploi de l'opérateur arith m étique % qui perm et de régler le problè m e du signe suivant le dernier signe du
tableau.
D ISCUSSIO N
*Telqu'ila été prévu, notre program m e accepte des nom s de m ois écrits en m inuscules ou en m ajuscules m ais sans
accent. Dans un program m e réel, ilseraitsouh aitable de faire preuve de plus de tolérance.
120 Exe rcices en langage C
*Notre rech erch e du nom de m ois a été réalisée ici par un algorith m e ditde rech erch e séquentielle en table (algorith m e
qui, com m e nous l'avons vu, peut se program m er en C à l'aide d'une seule instruction). D'autres algorith m es plus
rapides existent, en particulier celui ditde rech erch e dich otom ique. L'exercice IV-5 vous en proposera un exem ple.
II-2 Codage m ors e
________________________________________________________________________________________
Enoncé
Ecrire un program m e affich antle codage en m orse d'un texte fourni au clavier et ne dépassant pas une "ligne" de 127
caractè res. Les caractè res susceptibles d'ê tre codés en m orse sont:
- les 26 lettres de l'alph abet(supposées tapées en m ajuscules),
- les 10 ch iffres de 0 à 9 ,
- le point,
Si le texte contient d'autres caractè res que ceux-ci, le program m e affich era sim plem ent des points d'interrogation à la
place du code m orse.
Table au de s code s m orses
A .- B -... C -.-. D -.. E .
F ..-. G --. H .... I .. J .---
K -.- L .-.. M -- N -. O ---
P .--. Q --.- R .-. S ... T -
U ..- V ...- W .-- X -..- Y -.--
Z --.. . .-.-.-
0 ----- 1 .---- 2 ..--- 3 ...-- 4 ....-
5 ..... 6 -.... 7 --... 8 ---.. 9 ----.
Exe m ple
donnez votre message (1 ligne maxi) :
LE LANGAGE C, CONCU EN 1972, EST L'OEUVRE DE DENIS RITCHIE.
voici la traduction de votre message
II. Utilisation de structures 121
.-.. . ?????? .-.. .- -. --. .- --. .
?????? -.-. ?????? ?????? -.-. --- -. -.-. ..- ??????
. -. ?????? .---- ----. --... ..--- ?????? ?????? .
... - ?????? .-.. ?????? --- . ..- ...- .-.
. ?????? -.. . ?????? -.. . -. .. ...
?????? .-. .. - -.-. .... .. . .-.-.-
________________________________________________________________________________________
ANALYSE
Le program m e doitdonc ê tre en m esure d'établir une correspondance entre un caractè re etson code m orse. Là encore,
nous pourrions utiliser deux tableaux reliés par une valeur com m une d'un indice. M ais l'em ploi d'un tableau de
structures perm etde m ieux m ettre en évidence la correspondance entre les inform ations, lors de l'initialisation. Ch aque
élém ent(structure)du tableau contiendra :
- un caractè re,
- le code m orse correspondant, exprim é sous form e d'une ch aîne.
Le codage d'un caractè re se fera alors sim plem entpar sa localisation dans le tableau.
Program m e
#include <stdio.h>
#include <string.h>
#define NL 37 /* nombre de caractères codés */
main()
{
struct code { char lettre ;
char * morse ;
} ;
struct code table[NL] = /* code morse */
{ 'A', ".-", 'B', "-...", 'C', "-.-.",
'D', "-..", 'E', ".", 'F', "..-.",
'G', "--.", 'H', "....", 'I', "..",
'J', ".---", 'K', "-.-", 'L', ".-..",
'M', "--", 'N', "-.", 'O', "---",
'P', ".--.", 'Q', "--.-", 'R',".-.",
'S', "...", 'T', "-", 'U', "..-",
'V', "...-", 'W', ".--", 'X', "-..-",
122 Exe rcices en langage C
'Y', "-.--", 'Z', "--..",
'.', ".-.-.-",
'0', "-----", '1', ".----", '2', "..---",
'3', "...--", '4', "....-", '5', ".....",
'6', "-....", '7', "--...", '8', "---..",
'9', "----."
} ;
char ligne[128] ; /* pour lire une ligne au clavier */
int i, j ;
/* lecture message à traduire */
printf ("donnez votre message (1 ligne maxi) : n") ;
gets (ligne) ;
printf ("nn voici la traduction de votre messagen") ;
/* traduction lettre par lettre */
for (i=0 ; i<strlen(ligne) ; i++)
{ j=0 ;
while (ligne[i] != table[j].lettre && j++<NL-1) ;
if (j<NL) printf ("%7s", table[j].morse) ;
else printf (" ??????") ;
if ( ! ((i+1)%10) ) printf ("n") ; /* 10 codes morse par ligne */
}
}
Com m e ntaire s
*Nous avons défini un m odè le de structure, nom m é code , dans lequelnous trouvons :
- un caractè re,
- un pointeur sur une ch aîne de caractè res destinée à contenir le code m orse correspondant.
Notez que, contrairem entà ce que nous avions faitdans le program m e de l'exercice précédent, nous avons prévu ici un
pointe ur sur une ch aîne etnon un table au de caractè res.
Dans ces conditions, le tableau table occupera seulem ent 37 (valeur de NL) em placem ents dont la taille sera
généralem ent de 3 ou 5 octets (1 pour le caractè re et 2 ou 4 pour le pointeur). L'em placem ent m ê m e des ch aînes
correspondantes se trouve cependantréservé à la com pilation, de par le faitque nous avons initialisé ce tableau lors de sa
déclaration. Ilne fautpas oublier, en effet, qu'une notation telle que :
".-.-."
II. Utilisation de structures 123
est interprétée par le com pilateur com m e représentant l'adresse de la ch aîne fournie, m ais qu'en m ê m e tem ps illui
réserve un em placem ent.
Cette façon de procéder peutse révè ler plus économ ique en place m ém oire que la précédente, dans la m esure où ch aque
ch aîne n'occupe que l'espace qui lui estnécessaire (ilfauttoutefois ajouter, pour ch aque ch aîne, l'espace nécessaire à un
pointeur).
Rem arque :
En toute rigueur, le tableau table est de classe autom atique (puisqu'ilapparaît au sein d'une fonction - ici le
program m e principal). Son em placem entestdonc alloué au m om entde l'exécution du program m e (c'est-à -dire, ici,
dè s le début). Les constantes ch aînes, par contre, voientleurs em placem ents définis dè s la com pilation.
Si notre tableau table avait été déclaré de m aniè re globale, ilaurait été de classe statique. Son em placem ent aurait
alors été réservé dè s la com pilation.
Une telle distinction est toutefois relativem ent form elle et elle n'a guè re d'incidence en pratique. Ilest, en effet,
généralem ent, assez tentant de considérer les variables déclarées dans le program m e principalcom m e "quasi
statiques", dans la m esure où, bien que non réservées à la com pilation, elles n'en n'occupentpas m oins de l'espace
pendanttoute la durée de l'exécution du program m e.
*La rech erch e du caractè re dans notre tableau table estréalisée par la seule instruction :
while (ligne[i] != table[j].lettre && j++<NL-1) ;
D ISCUSSIO N
Dans un program m e "réel", ilfaudraitprévoir d'accepter un m essage de plus d'une ligne, ce qui poseraitle problè m e de
sa m ém orisation. On pourraitê tre am ené, soità lui im poser une taille m axim ale, soità se tourner vers des m éth odes de
"gestion dynam ique".
II-3 D é codage m ors e
________________________________________________________________________________________
124 Exe rcices en langage C
Enoncé
Ecrire un program m e perm ettantde décoder un m essage en m orse fourni au clavier sous form e d'une suite de caractè res.
Celle-ci pourra com porter :
- des points etdes tirets représentantles codes proprem entdits,
- un ou plusieurs espaces pour séparer les différents codes (on n'im posera donc pas à l'utilisateur d'em ployer un
"gabarit" fixe pour ch aque code).
On supposera que le m essage fourni ne dépasse pas une ligne de 127 caractè res. Les codes inexistants seronttraduits par
le caractè re "?".
On utilisera le tableau des codes m orses fourni dans l'exercice précédent(II-2).
Exe m ple
donnez votre message (1 ligne maxi) :
-... --- -. .--- --- ..- .-. .-.-.- .-.-.
voici la traduction de votre message
B O N J O U R . ?
________________________________________________________________________________________
ANALYSE
Ce program m e doitdonc établir une correspondance entre un code m orse etun caractè re. Nous pouvons, pour ce faire,
utiliser la m ê m e structure que dans l'exercice précédent. Le décodage d'un caractè re se fera alors en explorant, non plus
la partie caractè re, m ais la partie ch aîne du tableau de structure. L'algorith m e de rech erch e sera donc sim ilaire, la
com paraison de caractè res étantrem placée par une com paraison de ch aînes.
En ce qui concerne le m essage en m orse, nous pouvons le lire par ge ts dans un tableau de 128 caractè res, nom m é ligne .
Le principalproblè m e qui se pose alors à nous estcelui de l'accè s à ch acun des codes m orses contenus dans ligne ;en
effet, ceux-ci sontécrits avec un gabaritvariable etséparés par un nom bre variable d'espaces.
Nous proposons de répéter le traitem entsuivant, fondé sur l'em ploi d'un pointeur de caractè res (indice) dans le tableau
ligne :
- Avancer le pointeur, tantqu'ildésigne un espace.
II. Utilisation de structures 125
- Extraire, à partir de la position indiquée par le pointeur, à l'aide de sscanf, une ch aîne de longueur m axim ale 7
(puisque aucun code m orse ne dépasse cette longueur). Pour cela, nous ferons appelau code form at %7s, lequel
interrom ptl'exploration, soitquand un séparateur estrencontré, soitlorsque la longueur indiquée (7)estatteinte.
- Incrém enter le pointeur de la longueur de la ch aîne ainsi lue (car, bien sûr, iln'aura pas été m odifié par sscanf).
Program m e
#include <stdio.h>
#include <string.h>
#define NL 37 /* nombre de caractères codés */
#define LG 127 /* longueur ligne clavier */
main()
{
struct code { char lettre ;
char * morse ;
} ;
struct code table[NL] = /* code morse */
{ 'A', ".-", 'B', "-...", 'C', "-.-.",
'D', "-..", 'E', ".", 'F', "..-.",
'G', "--.", 'H', "....", 'I', "..",
'J', ".---", 'K', "-.-", 'L', ".-..",
'M', "--", 'N', "-.", 'O', "---",
'P', ".--.", 'Q', "--.-", 'R',".-.",
'S', "...", 'T', "-", 'U', "..-",
'V', "...-", 'W', ".--", 'X', "-..-",
'Y', "-.--", 'Z', "--..",
'.', ".-.-.-",
'0', "-----", '1', ".----", '2', "..---",
'3', "...--", '4', "....-", '5', ".....",
'6', "-....", '7', "--...", '8', "---..",
'9', "----."
} ;
char ligne[LG+1] ; /* pour lire une ligne au clavier */
char code[7] ; /* code courant à traduire */
int i, j ;
/* lecture message à traduire */
printf ("donnez votre message (1 ligne maxi) : n") ;
gets (ligne) ;
printf ("nn voici la traduction de votre messagen") ;
126 Exe rcices en langage C
/* traduction code par code */
i=0 ;
while (i<strlen(ligne))
{
while (ligne[i] == ' ') i++ ; /* saut des espaces éventuels */
if ( i<strlen(ligne) ) /* si pas en fin de ligne */
{ sscanf (&ligne[i], "%6s", code); /* lecture code (6 car max) */
i += strlen(code) ; /* incrément pointeur dans ligne */
j=0 ; /* recherche code dans table */
while ( stricmp (code, table[j].morse) && j++<NL-1) ;
if (j<NL) printf ("%2c", table[j].lettre) ; /* code trouvé */
else printf (" ?") ; /* code non trouvé */
}
}
}
Com m e ntaire s
*Dans la boucle de saut des espaces éventuels, on ne risque pas d'aller au-delà de la fin de la ch aîne contenue dans
ligne , car le caractè re de fin (0), différentd'un espace, servira de "sentinelle".
*Par contre, avantd'extraire un nouveau code par sscanf, ilestnécessaire de s'assurer que l'on n'estpas parvenu en fin
de ligne. En effet, dans ce cas, sscanffourniraitune suite de caractè res constituée du caractè re 0 (qui n'estpas considéré
par cette fonction com m e un séparateur) etdes caractè res suivants (prélevés en deh ors du tableau ligne ). Notez que, en
l'absence d'un teltest, le m alne seraitpas trè s grave puisqu'ilreviendraitsim plem entà placer au plus 7 caractè res dans
code , com m ençantpar 0.
*La rech erch e du code m orse dans le tableau table estréalisée par la seule instruction :
while ( stricmp (code, table[j].morse) && j++<NL-1) ;
Les rem arques faites dans le quatriè m e com m entaire de l'exercice II-1, à propos de la rech erch e séquentielle en table,
s'appliquentégalem entici.
D ISCUSSIO N
II. Utilisation de structures 127
Notre program m e ne détecte pas le cas où l'utilisateur fournit un code m orse de plus de 6 caractè res. Dans ce cas, en
effet, ilse contente de le "découper" en tranch es de 6 caractè res (la derniè re tranch e pouvant avoir une longueur
inférieure).
Si l'on souh aitait détecter ce genre d'anom alie, ilfaudrait, aprè s ch aque exam en d'un code, s'assurer qu'ilest
effectivem entsuivi d'un espace ou d'une fin de ch aîne.
II-4 Facturation par code
________________________________________________________________________________________
Enoncé
Réaliser un program m e établissant une facture pouvant porter sur plusieurs articles. Pour ch aque article à facturer,
l'utilisateur ne fournira que la quantité etun num é ro de code à partir duquelle program m e devra retrouver à la fois le
libellé etle prix unitaire.
Le program m e devra refuser les codes inexistants. A la fin, ilaffich era un récapitulatiftenantlieu de facture.
Les inform ations relatives aux différents articles seront définies dans le source m ê m e du program m e (et non dans un
fich ier de données). Elle seronttoutefois placées à un niveau global, de m aniè re à pouvoir, le cas éch éant, faire l'objet
d'un source séparé, appelable par #include .
On prévoira deux fonctions :
- une pour rech erch er les inform ations relatives à un article, à partir de son num éro de code,
- une pour affich er la facture récapitulative.
Exe m ple
combien d'articles à facturer ? 3
code article ? 25
quantité de Centrifugeuse au prix unitaire de 370.00 ? 33
code article ? 7
** article inexistant - redonnez le code : 16
128 Exe rcices en langage C
quantité de Grille-pain au prix unitaire de 199.50 ? 12
code article ? 26
quantité de Four à raclette 6P au prix unitaire de 295.25 ? 6
FACTURE
ARTICLE NBRE P-UNIT MONTANT
Centrifugeuse 33 370.00 12210.00
Grille-pain 12 199.50 2394.00
Four raclette 6P 6 295.25 1771.50
TOTAL 16375.50
________________________________________________________________________________________
ANALYSE
L'énoncé nous précise que les codes d'articles sont num ériques, m ais ilne dit pas qu'ils sont consécutifs. Dans ces
conditions, ilest nécessaire de m ém oriser les différentes valeurs possibles de ces codes. Com m e nous devons pouvoir
associer à ch aque code un libellé (ch aîne)etun prix (réel), nous pouvons songer à utiliser un tableau de structures, dans
lequelch aque élém ent contient les inform ations relatives à un article (code, libellé, prix unitaire). Ce tableau sera,
com m e dem andé par l'énoncé, déclaré à un niveau globaletinitialisé dans sa déclaration.
Le travailde la fonction de rech erch e (nous la nom m erons re ch e rch e ) consistera à vérifier la présence du code d'article
dans le tableau de structure ainsi défini. En cas de succè s, elle en restituera le rang (ce qui sera suffisantau program m e
principalpour affich er les inform ations correspondantes). Dans le cas contraire, elle restituera la valeur -1. Notez que le
code d'article sera le seulargum entde cette fonction.
Nous voyons donc déjà com m ent, pour ch aque code (correct) fourni par l'utilisateur, affich er les inform ations
correspondantes avantd'en dem ander la quantité. M ais, com pte tenu de ce que l'édition de la facture doitê tre faite aprè s
les saisies relatives à tous les articles, nous devons obligatoirem ent, pour ch aque article à facturer, conserver :
- la quantité,
- une inform ation perm ettant d'en retrouver le libellé et le prix unitaire. Nous pourrions, certes, arch iver ces
inform ations dans un tableau. M ais, en fait, cela n'estpas nécessaire puisqu'ilestpossible de les retrouver à partir du
rang de l'article dans la structure (le code article conviendrait égalem ent, m ais ilnous faudrait alors explorer à
nouveau notre tableau de structures lors de l'édition de la facture).
Ces deux inform ations serontconservées dans deux tableaux (nom m és qte etrangart) com portantautantd'élém ents que
d'articles à facturer (on en prévoira un nom bre m axim al).
II. Utilisation de structures 129
La fonction d'édition de la facture (nom m ée facture ) se contentera alors d'explorer séquentiellem ent ces deux tableaux
pour retrouver toutes les inform ations nécessaires. Elle recevra, en argum ent, les adresses des deux tableaux (qte et
rangart), ainsi que le nom bre d'articles à facturer.
Program m e
#include <stdio.h>
/* ------ structure contenant les informations relatives aux */
/* différents articles -------------- */
#define NBART 6 /* nombre total d'articles */
typedef struct { int code ; /* code article */
char * lib ; /* libellé */
float pu ; /* prix unitaire */
} t_article ;
t_article article [NBART] =
{ 11, "Gaufrier", 268.0,
14, "Cafetière 12 T", 235.0,
16, "Grille-pain", 199.50,
19, "Balance de ménage", 278.0,
25, "Centrifugeuse", 370.0,
26, "Four raclette 6P", 295.25
} ;
/* ----------------------------------------------------------------------*/
#define NAFMAX 10 /* nombre maxi d'articles à facturer */
main()
{
int recherche(int) ; /* proto fonction de recherche d'un article */
void facture(int[], int[], int) ; /* proto fonction d'affichage de la facture */
int naf, /* nombre d'articles à facturer */
rang, /* rang courant d'un article */
codart, /* code courant d'un article */
i ;
int rangart [NAFMAX], /* rang des articles à facturer */
qte [NAFMAX] ; /* quantité de chaque article à facturer */
/* entrée du nombre d'articles à facturer */
printf ("combien d'articles à facturer ? ") ;
scanf ("%d", &naf) ;
130 Exe rcices en langage C
/* boucle de traitement de chaque article à facturer */
for (i=0 ; i<naf ; i++)
{
printf ("code article ? ") ;
do
{ scanf ("%d", &codart) ;
rang = recherche (codart) ;
if (rang == -1)
printf (" ** article inexistant - redonnez le code : ") ;
}
while (rang == -1) ;
rangart[i] = rang ;
printf ("quantité de %s au prix unitaire de %8.2f ? ",
article[rang].lib, article[rang].pu) ;
scanf ("%d", &qte[i]) ;
}
/* affichage facture */
facture (rangart, qte, naf) ;
}
/***********************************************************/
/* fonction de recherche d'un code article */
/***********************************************************/
int recherche (int codart)
{
int rang ; /* rang courant d'un article */
rang = 0 ;
while (article[rang].code != codart && rang++ < NBART-1) {} ;
if (rang <NBART) return (rang) ;
else return (-1) ;
}
/***********************************************************/
/* fonction d'affichage de la facture */
/***********************************************************/
void facture(int rangart[], int qte[], int naf)
/* rangart : tableau des rangs des codes articles */
/* qte :tableau des prix unitaires */
/* naf :nombre d'articles à facturer */
{
float somme, /* total facture */
montant ; /* montant relatif à un article */
int i ;
II. Utilisation de structures 131
printf ("nn %32snn", "FACTURE") ;
printf ("%-20s%5s%10s%12snn",
"ARTICLE", "NBRE", "P-UNIT", "MONTANT") ;
somme = 0 ;
for (i=0 ; i<naf ; i++)
{ montant = article[rangart[i]].pu * qte[i] ;
printf ("%-20s%5d%10.2f%12.2fn",
article[rangart[i]].lib, qte[i],
article[rangart[i]].pu, montant) ;
somme += montant ;
}
printf ("nn%-35s%12.2f", " TOTAL", somme) ;
}
Com m e ntaire s
*Nous avons ch oisi ici d'utiliser type de fpour définir sous le nom t_article la structure correspondantà un article. Vous
constatez que le libellé y apparaît sous la form e d'un pointeur sur une ch aîne et non d'une ch aîne ou d'un tableau de
caractè res. Dans ces conditions, le tableau article , déclaré de ce type, n'occupera que 6 em placem ents de petite taille
(généralem ent6 ou 8 octets)
*Là encore, une seule instruction perm etd'effectuer la rech erch e d'un code article dans le tableau article . Voyez, à ce
propos, les rem arques faites dans le quatriè m e com m entaire de l'exercice II-1.
*Le code form at%-20s, utilisé à deux reprises dans la fonction facture , perm etde "cadrer" une ch aîne à gauch e.
D ISCUSSIO N
*Notre program m e n'est pas protégé contre des réponses incorrectes de la part de l'utilisateur. En particulier, une
réponse non num érique peutentraîner un com portem entassez désagréable. Dans un program m e réel, ilseraitnécessaire
de régler convenablem entce type de problè m e, par exem ple en utilisantfge ts (..., stdin)etsscanf.
*De m ê m e, dans un program m e réel, ilpourraitê tre judicieux de dem ander à l'utilisateur de confirm er que le produit
ch erch é estbien celui donton vientde lui affich er les inform ations.
132 Exe rcices en langage C
*La précision offerte par le type float(6 ch iffres significatifs)peutse révéler insuffisante.
III: H ASARD ET
RECREATIO NS
Ce ch apitre vous propose un certain nom bre d'exercices correspondantà la réalisation de program m es récréatifs, basés
sur l'utilisation du h asard.
Les deux prem iers exercices sont essentiellem ent destinés à vous m ontrer com m ent générer des nom bres aléatoires en
langage C.
III-1 Tirage alé atoire
________________________________________________________________________________________
Enoncé
Ecrire une fonction fournissant un nom bre entier tiré au h asard entre 0 (inclus) et une valeur n (incluse) fournie en
argum ent.
Réaliser un program m e principalutilisantcette fonction pour exam iner la "distribution" des valeurs ainsi obtenues dans
l'intervalle [0, 10]. Le nom bre de tirages à réaliser sera lu en donnée et le program m e affich era le nom bre de fois où
ch acune de ces valeurs aura été obtenue.
Exe m ple
combien de tirages : 1100
nombre de tirages obtenus
0 : 106
1 : 95
134 Exe rcices en langage C
2 : 115
3 : 95
4 : 91
5 : 103
6 : 103
7 : 101
8 : 92
9 : 94
10 : 105
Indication : ilestnécessaire de faire appelà la fonction rand de la "biblioth è que standard".
________________________________________________________________________________________
ANALYSE
Ilfautfaire appelà la fonction rand. Celle-ci fournitun nom bre entier, tiré de façon "pseudo-aléatoire" dans l'intervalle
[0, RAND_MAX], ch aque nom bre de cetintervalle ayantquasim entla m ê m e probabilité d'ê tre tiré. Notez que la valeur
de RAND_M AX estdéfinie dans stdlib.h ;d'aprè s la norm e, elle n'estjam ais inférieure à la capacité m inim ale d'un int,
c'est-à -dire 32767.
Pour aboutir au résultat voulu, une dém arch e consiste à transform er un telnom bre en un nom bre réelappartenant à
l'intervalle [0,1[. Ilsuffitensuite de m ultiplier ce réelpar n+1 etd'en prendre la partie entiè re pour obtenir le résultat
escom pté. On peutalors m ontrer que les valeurs 0, 1, ... n-1, n sontquasi équiprobables.
Pour obtenir un telnom bre aléatoire, nous pouvons diviser le nom bre fourni par rand par la valeur RAND_M AX+ 1 (il
faut éviter de diviser par RAND_M AX, car la valeur 1 risquerait alors d'ê tre obtenue - en m oyenne une fois sur
RAND_M AX!). Là encore, on peut, de m aniè re form elle, m ontrer que si la loi de probabilité estuniform e sur [0,1[, ilen
va de m ê m e de celle du nom bre ainsi fabriqué dans l'intervalle d'entiers [0,n].
Program m e
#include <stdio.h>
#include <stdlib.h> /* pour la fonction rand */
#define N 10 /* les tirages se feront entre 0 et N */
main()
{
III. H asard e trécréations 135
int aleat (int) ; /* prototype fonction de tirage aléatoire */
int ntir, /* nombre de tirages requis */
t[N+1], /* tableau comptage tirages de chaque valeur */
i ;
printf ("combien de tirages : ") ;
scanf ("%d", &ntir) ;
for (i=0 ; i<=N ; i++)
t[i] = 0 ;
for (i=1 ; i<=ntir ; i++)
t[aleat(N)]++ ;
printf ("nombre de tirages obtenusn") ;
for (i=0 ; i<=N ; i++)
{ printf ("%4d : ", i) ;
printf ("%6dn", t[i]) ;
}
}
/********************************************************/
/* fonction de tirage aléatoire d'un nombre dans [0, n] */
/********************************************************/
int aleat (int n)
{
int i ;
i = rand() / (RAND_MAX + 1.) * (n+1) ;
return (i) ;
}
Com m e ntaire s
*Dans la fonction ale at, la division par RAND_M AX+ 1 doitbien sûr s'effectuer sur des valeurs réelles. M ais, de plus, il
fautprendre garde à ne pas écrire le diviseur sous la form e RAND_M AX + 1. En effet, celui-ci seraitévalué dans le type
intet, dans le cas (fréquent)où la valeur de RAND_M AX estexactem entla valeur m axim ale du type int, l'addition de 1 à
RAND_M AX conduiraità la valeur ... -1 (le dépassem entde capacité n'étantjam ais détecté en cas d'opérations sur des
entiers).
D ISCUSSIO N
136 Exe rcices en langage C
En général, la fonction rand fournittoujours la m ê m e suite de valeurs, d'une exécution à une autre. L'exercice suivant
vous m ontre com m entéviter ce ph énom è ne.
III-2 Tirage alé atoire variable
________________________________________________________________________________________
Enonce
Ecrire une fonction fournissantun nom bre entier tiré au h asard entre 0 etune valeur n fournie en argum ent. La suite des
valeurs restituées par cette fonction (lorsqu'on l'appelle à diverses reprises) devra ê tre différente d'une exécution à une
autre etne pas dépendre d'une quelconque inform ation fournie par l'utilisateur.
Com m e dans l'exercice précédent, on réalisera un program m e principalutilisant cette fonction pour exam iner la
"distribution" des valeurs ainsi obtenues dans l'intervalle [0,10]. Pour ce faire, on lira en données le nom bre de tirages à
réaliser etle program m e affich era le nom bre de fois où ch acune des valeurs aura été obtenue.
Suggestion : ilfaut"initialiser" convenablem entle "générateur de nom bres aléatoire", en utilisantla fonction srand. La
"graîne" nécessaire peut ê tre fabriquée à l'aide de la fonction tim e , de façon à avoir un caractè re suffisam m ent
im prévisible.
Exe m ple s
(ils'agitlà des résultats correspondantà deux exécutions différentes du m ê m e program m e)
combien de tirages : 1100
nombre de tirages obtenus
0 : 124
1 : 104
2 : 97
3 : 97
4 : 89
5 : 93
6 : 105
III. H asard e trécréations 137
7 : 109
8 : 110
9 : 89
10 : 83
___________________
combien de tirages : 1100
nombre de tirages obtenus
0 : 104
1 : 98
2 : 98
3 : 106
4 : 98
5 : 97
6 : 99
7 : 109
8 : 99
9 : 96
10 : 96
________________________________________________________________________________________
ANALYSE
En langage C, la fonction srand perm etd'initialiser le générateur de nom bres aléatoires. Ilfautcependantlui fournir une
"graîne", c'est-à -dire un nom bre entier (de type unsigne d int) qui déterm inera le prem ier nom bre tiré par rand. Cette
m éth ode perm et ainsi, si on le souh aite, d'obtenir à volonté une m ê m e suite de nom bres aléatoires ;ilfaut d'ailleurs
noter que, par défaut, tout se passe com m e si srand était appelé, en début de l'exécution d'un program m e, avec
l'argum ent1.
Ici, par contre, nous souh aitons obtenir une suite différente d'une exécution à une autre. Une solution à ce problè m e
consiste à ch oisir une "graîne" aléatoire. Bien sûr, iln'estpas question de faire appelà rand dans ce cas. Par contre, la
fonction tim e fournitune date , exprim ée en secondes. Celle-ci possè de un caractè re suffisam m entim prévisible pour ê tre
utilisée com m e graîne.
Cette initialisation du générateur de nom bres aléatoires doittoutefois n'ê tre réalisée qu'une seule fois pour une exécution
donnée. Dans le cas contraire, on risquerait, en effet, d'obtenir plusieurs fois de suite les m ê m es nom bres. Si l'on
souh aite que ce problè m e soitpris en ch arge par la fonction de tirage d'un nom bre elle-m ê m e, ilestnécessaire que cette
derniè re soit capable de le faire lors de son prem ier appel(et uniquem ent à ce m om ent-là ). Ce m écanism e passe par
l'em ploi d'une variable de classe statique.
138 Exe rcices en langage C
Program m e
#include <stdio.h>
#include <stdlib.h> /* pour la fonction rand */
#include <time.h> /* pour la fonction time */
#define N 10 /* les tirages se feront entre 0 et N */
main()
{
int aleat (int) ; /* prototype fonction de tirage aléatoire */
int ntir, /* nombre de tirages requis */
t[N+1], /* tableau comptage tirages de chaque valeur */
i ;
printf ("combien de tirages : ") ;
scanf ("%d", &ntir) ;
for (i=0 ; i<=N ; i++)
t[i] = 0 ;
for (i=1 ; i<=ntir ; i++)
t[aleat(N)]++ ;
printf ("nombre de tirages obtenusn") ;
for (i=0 ; i<=N ; i++)
{ printf ("%4d : ", i) ;
printf ("%6dn", t[i]) ;
}
}
/********************************************************/
/* fonction de tirage aléatoire d'un nombre dans [0, n] */
/********************************************************/
int aleat (int n)
{
int i ;
static int prem = 1 ; /* drapeau premier appel */
time_t date ; /* pour l'argument de time */
/* time_t est un type entier défini dans time.h */
/* initialisation générateur au premier appel */
if (prem)
{ srand (time(&date)) ;
prem = 0 ;
}
III. H asard e trécréations 139
/* génération nombre */
i = rand() / (RAND_MAX + 1.) * (n+1) ;
return (i) ;
}
Com m e ntaire s
*Le m écanism e du traitem entparticulier à effectuer au prem ier appelest réalisé grâ ce à la variable pre m , déclarée de
classe statique. Cette variable estinitialisée à un, lors de la com pilation. Dè s le prem ier appel, elle estm ise à zéro etelle
gardera ensuite cette valeur jusqu'à la fin de l'exécution du program m e. Ainsi, la fonction srand n'est effectivem ent
appelée qu'une seule fois, lors du prem ier appelde notre fonction ale at.
*La fonction tim e fourniten retour le tem ps (exprim é en secondes) écoulé depuis une certaine "origine" dépendant de
l'im plém entation. Le type de cette valeur dépend, lui aussi, de l'im plém entation ;toutefois, la norm e prévoitqu'ilexiste,
dans tim e .h , un sym bole tim e _t (défini par type de f) précisant le type effectivem ent em ployé. Ici, lorsque nous
transm ettons cette valeur à srand, ilestpossible qu'apparaisse une conversion du type tim e _tdans le type unsigne d int;
ici, cela n'a guè re d'im portance, dans la m esure où, m ê m e si cette conversion est"dégradante", la valeur obtenue restera
im prévisible pour l'utilisateur.
D'autre part, la fonction tim e ne se contente pas de fournir une "h eure" en retour ;elle range égalem ent cette m ê m e
inform ation à l'adresse qu'on lui fournit(obligatoirem ent) en argum ent;c'est ce qui justifie l'existence de la variable
date (qui n'estpas utilisée par ailleurs)etqui doit, ici, absolum entê tre déclarée dans le "bon type", sous peine de risquer
d'aboutir à un écrasem entintem pestifde données (dans le cas où on auraitdéclaré date d'un type "plus petit" que le type
effectif).
III-3 Alé a d'é toile s
________________________________________________________________________________________
140 Exe rcices en langage C
Enoncé
Affich er au h asard un certain nom bre d'étoiles (caractè re "*") à l'intérieur d'un rectangle. Le nom bre d'étoiles
souh aitées, ainsi que le nom bre de lignes etde colonnes du rectangle serontfournis en données.
Le program m e vérifiera que la zone estassez grande pour recevoir le nom bre d'étoiles requis. On évitera que plusieurs
étoiles ne soientsuperposées.
Exe m ple
combien de lignes : 10
combien de colonnes : 45
combien de points : 200
** * **** ** *** * ** *** * *** **
* * * ** * ** * * ****** * ** **
* * ** * * * ***** *** ** * *** * *
* *** * * * * * ** * * **
* * ** ** ** **** ** ** ** ** * * * *
* * ** *** * * * ** * * * * **
*** ** ** * ** * * * * **
* * * * * ***** ** ** * *
* * ***** ** *** * ** * *****
**** * * *** * ** **** * *****
________________________________________________________________________________________
ANALYSE
Nous utiliserons un tableau de caractè res à deux dim ensions, dans lequelch aque élém entreprésentera une case de notre
rectangle. Nous conviendrons que le prem ier indice représente le rang de la ligne et que le second indice représente le
rang de la colonne. Com m e l'utilisateur doit pouvoir ch oisir les dim ensions du rectangle concerné, nous prévoirons de
donner à notre tableau une taille suffisante pour couvrir tous les cas possibles (nous avons ch oisi, ici, 25 lignes de 80
caractè res);cela signifie que, la plupartdu tem ps, le program m e n'utilisera qu'une partie de ce tableau.
Au départ, nous initialiserons tous les élém ents de la "partie utile" de ce tableau avec le caractè re espace. Nous
ch oisirons ensuite au h asard les élém ents dans lesquels nous devrons placer un caractè re "*". Pour ce faire, ilnous suffira
de tirer au h asard un num éro de ligne et un num éro de colonne jusqu'à ce que l'em placem ent correspondant soit
disponible (caractè re espace). L'algorith m e de tirage au h asard d'un nom bre entier appartenant à un intervalle donné a
été exposé dans l'analyse de l'exercice III-1.
III. H asard e trécréations 141
Ilne nous restera plus qu'à affich er, par exem ple avec la fonction putch ar, les différents élém ents de notre tableau, en
prévoyantun "ch angem entde ligne" aux m om ents opportuns.
Program m e
#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* pour memset */
#include <time.h>
#define NY 25 /* nombre total de lignes de l'écran */
#define NX 80 /* nombre total de colonnes de l'écran */
main()
{
int aleat (int) ; /* prototype fonction tirage aléatoire */
int ny, /* nombre de lignes du rect. considéré */
nx, /* nombre de col. du rect. considéré */
ix, /* colonne courante */
iy, /* ligne courante */
nb_points, /* nombre de points à tirer */
i, j ;
char ecran [NX] [NY] ; /* image de l'écran */
const char blanc = ' ', /* caractère de remplissage */
point = '*' ; /* représentation d'un point */
/* entrée des dimensions du rectangle considéré ...
... et du nombre de points souhaités */
do
{ printf ("combien de lignes : ") ;
scanf ("%d", &ny) ;
}
while (ny<=0 || ny>=NY) ;
do
{ printf ("combien de colonnes : ") ;
scanf ("%d", &nx) ;
}
while (nx<=0 || nx>=NX) ;
do
{ printf ("combien de points : ") ;
scanf ("%d", &nb_points) ;
}
while (nb_points > nx*ny || nb_points < 1 ) ;
142 Exe rcices en langage C
/* remplissage aléatoire du tableau image d'écran */
memset (ecran, blanc, NX*NY) ;
for (i=1 ; i<=nb_points ; i++)
{ do
{ ix = aleat (nx-1) ;
iy = aleat (ny-1) ;
}
while ( ecran [ix] [iy] != blanc) ;
ecran [ix] [iy] = point ;
}
/* affichage du tableau image d'écran */
for (j=0 ; j<ny ; j++)
{ for (i=0 ; i<nx ; i++)
putchar ( ecran [i] [j] ) ;
printf ("n") ;
}
}
/*******************************************************/
/* fonction de tirage aléatoire d'un nombre dans [0,n] */
/*******************************************************/
int aleat (int n)
{
int i ;
static int prem = 1 ; /* drapeau premier appel */
time_t date ; /* pour l'argument de time */
/* initialisation générateur au premier appel */
if (prem)
{ srand (time(&date) ) ;
prem = 0 ;
}
/* génération nombre aléatoire */
i = rand() / (RAND_MAX + 1.) * (n+1) ;
return (i) ;
}
Com m e ntaire s
III. H asard e trécréations 143
*L'initialisation de la partie utile du tableau avec le caractè re espace auraitpu se program m er ainsi :
for (i=0 ; i<nx ; i++)
for (j=0 ; j<ny ; j++)
ecran [i][j] = ' ' ;
Ici, nous avons préféré faire appelà la fonction m e m set, d'exécution plus rapide. Toutefois, celle-ci rem plit d'un
caractè re donné une suite d'octets consécutifs ;ceci exclutdonc de lim iter l'initialisation à la partie utile du tableau. Ilne
fautpas oublier, en effet, que celle-ci n'estpas form ée de nx*ny octets consécutifs (quoique, en toute rigueur, en tenant
com pte de la m aniè re dont sont rangés en m ém oire les différents élém ents d'un tableau, ilsoit possible de lim iter
l'initialisation à nx*NY élém ents consécutifs).
*Nous avons repris la fonction ale at de l'exercice précédent. Celle-ci tire une valeur entiè re au h asard entre 0 et une
lim ite qu'on lui fourniten argum ent;de plus, lors de son prem ier appel, elle effectue une initialisation du générateur de
nom bres aléatoires.
III-4 Estim ation de pi
________________________________________________________________________________________
Enoncé
Calculer une valeur approch ée de pi, par la m éth ode suivante :
- on tire un certain nom bre de points au h asard dans un carré donné.
- on déterm ine le rapportentre le nom bre de ces points appartenantau cercle inscritdans le carré etle nom bre total
de points tirés. Ce rapportfournitune estim ation de la valeur de pi/4.
Le nom bre totalde points à tirer sera fourni en donnée.
Exe m ple
combien de points ? 10000
estimation de pi avec 10000 points : 3.164800e+000
144 Exe rcices en langage C
________________________________________________________________________________________
ANALYSE
Nous ch oisirons un carré de côté unité. Nous conviendrons de prendre son coin bas gauch e com m e origine d'un repè re
cartésien.
Nous tirerons alors au h asard le nom bre de points voulus, à l'intérieur de ce carré. Plus précisém ent, pour ch aque point,
nous déterm inerons au h asard ses deux coordonnées, en tirantdeux nom bres réels appartenantà l'intervalle [0,1]. A cet
effet, nous ferons appelà la m éth ode exposée dans l'analyse de l'exercice III-1.
Pour ch aque point, nous calculerons sa distance au centre du carré (de coordonnées : 0.5, 0.5)etnous considérerons qu'il
appartientau cercle inscritsi cette distance estinférieure à 0.5 (notez que, par souci de sim plicité, nous travaillerons en
faitavec le carré de cette distance).
Program m e
#include <stdio.h>
#include <stdlib.h>
main()
{
float caleat(void) ; /* prototype fonction de tirage valeur aléatoire */
float x, y, /* coordonnées d'un point courant */
d2, /* distance (au carré) d'un point courant au centre */
pi ; /* valeur approchée de pi */
int np, /* nombre de points à tirer */
nc, /* nombre de points à l'intérieur du cercle */
i ;
printf ("combien de points ? ") ;
scanf ("%d", &np) ;
for (nc=0, i=1 ; i<=np ; i++)
{ x = caleat() ;
y = caleat() ;
d2 = (x-0.5) * (x-0.5) + (y-0.5) * (y-0.5) ;
if (d2 <= 0.25) nc++ ;
}
pi = (4.0 * nc) / np ;
III. H asard e trécréations 145
printf ("estimation de pi avec %d points : %e", np, pi) ;
}
float caleat (void) /* fonction fournissant une valeur aléatoire */
/* appartenant à l'intervalle [0-1] */
{
return ( (float) rand() / (RAND_MAX + 1.0) ) ;
}
D ISCUSSIO N
Notre fonction de tirage aléatoire d'un entier fournit toujours la m ê m e suite de valeurs. Ce qui signifie que, pour un
nom bre donné de points, nous obtiendrons toujours la m ê m e estim ation de pi. Vous pouvez éviter ce ph énom è ne en
utilisantla fonction réalisée dans l'exercice III-2.
III-5 Je u du de vin
________________________________________________________________________________________
Enoncé
Ecrire un program m e qui ch oisit un nom bre entier au h asard entre 0 et 1000 et qui dem ande à l'utilisateur de le
"deviner". A ch aque proposition faite par le joueur, le program m e répondra en situantle nom bre proposé par rapportà
celui à deviner (plus grand, plus petitou gagné).
Lorsque le joueur aura deviné le nom bre ch oisi, ou lorsqu'un nom bre m axim alde coups (10) aura été dépassé, le
program m e affich era la récapitulation des différentes propositions.
Exe m ple
Devinez le nombre que j'ai choisi (entre 1 et 1000)
votre proposition : 500
----------- trop grand
146 Exe rcices en langage C
votre proposition : 250
----------- trop grand
votre proposition : 125
----------- trop grand
votre proposition : 64
----------- trop grand
votre proposition : 32
----------- trop grand
votre proposition : 16
----------- trop grand
votre proposition : 8
----------- trop petit
votre proposition : 12
----------- trop grand
votre proposition : 10
++++ vous avez gagné en 9 coups
---- Récapitulation des coups joués ----
500 trop grand
250 trop grand
125 trop grand
64 trop grand
32 trop grand
16 trop grand
8 trop petit
12 trop grand
10 exact
________________________________________________________________________________________
ANALYSE
Le program m e com m encera par tirer un nom bre entier au h asard, suivant la dém arch e exposée dans l'analyse de
l'exercice III-1.
Ildevra ensuite répéter l'action :
faire joue r le joue ur
jusqu'à ce que le joueur aitgagné ou qu'ilaitdépassé le nom bre m axim alde coups perm is.
L'action en question consiste sim plem entà :
III. H asard e trécréations 147
- Dem ander au joueur de proposer un nom bre.
- Conserver ce nom bre dans un tableau (pour pouvoir établir la récapitulation finale). Notez que, com pte tenu de ce
qu'un nom bre de coups m axim alestim posé, ce dernier fournira le nom bre m axim ald'élém ents de notre tableau.
- Com parer le nom bre fourni avec la valeur ch oisie par le program m e etaffich er le m essage correspondant.
Program m e
#include <stdio.h>
#include <stdlib.h>
#define NCOUPS 15 /* nombre maximal de coups autorisés */
#define NMAX 1000 /* valeur maximale du nombre à deviner */
main()
{
int aleat(int) ; /* prototype fonction de tirage d'un nombre au hasard */
int nc, /* compteur du nombre de coups joués */
ndevin, /* nombre à deviner */
n, /* nombre courant proposé par le joueur */
prop[NMAX], /* tableau récapitulatif des nombres proposés */
i ;
/* initialisations et tirage du nombre secret */
nc = 0 ;
printf ("Devinez le nombre que j'ai choisi (entre 1 et %d)n", NMAX) ;
ndevin = aleat(NMAX) ;
/* déroulement du jeu */
do
{ printf ("votre proposition : ") ;
scanf ("%d",&n) ;
prop [nc++] = n ;
if (n < ndevin) printf ("----------- trop petitn") ;
else if (n > ndevin) printf ("----------- trop grandn") ;
}
while (n != ndevin && nc < NCOUPS) ;
/* affichage résultats */
148 Exe rcices en langage C
if (n == ndevin) printf ("nn++++ vous avez gagné en %d coupsn", nc) ;
else { printf ("nn---- vous n'avez pas trouvén") ;
printf ("le nombre choisi était %dn", ndevin) ;
}
/* affichage récapitulation */
printf ("n ---- Récapitulation des coups joués ----nn") ;
for (i=0 ; i<nc ; i++)
{ printf ("%4d ", prop[i]) ;
if (prop[i] > ndevin)
printf ("trop grand n") ;
else if (prop[i] < ndevin)
printf ("trop petitn") ;
else printf ("exactn") ;
}
}
/*******************************************************/
/* fonction de tirage aléatoire d'un nombre dans [0,n] */
/*******************************************************/
int aleat(int n)
{
int i = rand() / (RAND_MAX + 1.) * (n+1) ;
return i ;
}
D ISCUSSIO N
Notre fonction de tirage aléatoire d'un nom bre entier fournittoujours la m ê m e valeur, ce qui gâ ch e quelque peu l'intérê t
du jeu. Dans la pratique, ilserait nécessaire de rem placer la fonction ale at de ce program m e par celle proposée dans
l'exercice III-2, laquelle perm etd'obtenir un nom bre différentd'une exécution à une autre.
III-6 M aste rm ind
________________________________________________________________________________________
III. H asard e trécréations 149
Enoncé
Réaliser un program m e qui ch oisit au h asard une com binaison de 5 ch iffres (com pris entre 1 et 8) et qui dem ande à
l'utilisateur de la deviner. A ch aque proposition, le program m e précisera :
- le nom bre de ch iffres exacts proposés à la bonne place,
- le nom bre de ch iffres exacts m ais proposés à la m auvaise place.
Les différentes propositions du joueur seront fournies sous la form e de 5 ch iffres consécutifs (sans aucun séparateur),
validés par re turn.
Le program m e devra traiter convenablem entle cas des réponses incorrectes : lettre à la place d'un ch iffre, réponse trop
courte ou trop longue, ch iffre incorrect(nulou supérieur à 8).
On prévoira un nom bre lim ite d'essais, au-delà duquelle program m e s'interrom pra en indiquant quelle était la
com binaison à deviner.
Exe m ple
proposition ? : 12345
2 P 0 C
proposition ? : 23456
0 P 1 C
proposition ? : 34567
0 P 1 C
proposition ? : 45678
0 P 0 C
proposition ? : 56789
** incorrect **
proposition ? : 1133é
** incorrect **
proposition ? : 11332
3 P 1 C
proposition ? : 11333
4 P 0 C
proposition ? : 11313
5 P 0 C
vous avez trouvé en 7 coups
________________________________________________________________________________________
150 Exe rcices en langage C
ANALYSE
Ilparaîtassez natureld'utiliser un tableau à 5 élém ents pour y ranger la com binaison tirée au h asard. Notez que nous
pourrions égalem ent tirer au h asard un nom bre de 5 ch iffres, m ais ilfaudrait, de toute façon, en extraire ch acun des
ch iffres ;de plus, la m éth ode seraitdifficilem entgénéralisable à un nom bre quelconque de positions.
La principale difficulté réside dans l'analyse de la proposition du joueur. Dans la com paraison des deux tableaux
(com binaison tirée par le program m e et com binaison proposée par le joueur), ilfaudra tenir com pte des rem arques
suivantes :
- Un ch iffre com pté "à sa place" ne doitpas pouvoir ê tre égalem entcom pté com m e "exact, m ais m alplacé".
- Lorsqu'un tirage com porte plusieurs ch iffres identiques, ilne faut pas qu'un m ê m e ch iffre de la proposition du
joueur puisse ê tre com pté plusieurs fois com m e exact.
- Lorsqu'une proposition com porte plusieurs ch iffres identiques, il ne faut pas les considérer tous com m e
correspondantà un m ê m e ch iffre du tirage.
Nous vous proposons la m éth ode suivante :
1 - Nous rech erch ons toutd'abord les ch iffres exacts placés en bonne position. A ch aque fois qu'une coïncidence est
relevée, nous supprim ons le ch iffre, à la fois dans la proposition du joueur et dans le tirage (en le rem plaçant, par
exem ple, par la valeur 0).
2 - Nous reprenons ensuite, un à un, ch acun des ch iffres du tirage qui n'ontpas été supprim és (c'est-à -dire qui sont
différents de 0). Nous les com parons à ch acun des ch iffres de la proposition. Là encore, si une coïncidence est
relevée, nous supprim ons les ch iffres correspondants, à la fois dans la proposition etdans le tirage. Notez bien qu'il
faut absolum ent éviter de considérer les ch iffres déjà supprim és du tirage : ils risqueraient d'ê tre trouvés égaux à
d'autres ch iffres supprim és de la proposition.
Cette m éth ode qui détruit le tirage nous oblige nécessairem ent à en faire une copie avant d'entam er l'analyse de la
proposition.
Nous avons ch oisi de réaliser trois fonctions :
- tirage : tirage au h asard de la com binaison (tableau de 5 entiers)à deviner.
- e ntre e : entrée de la proposition du joueur. Ilparaît logique que cette fonction fournisse cette proposition dans un
tableau d'entiers. Toutefois, afin de traiter convenablem entles cas de réponses incorrectes, la proposition du joueur
sera toutd'abord lue dans une ch aîne à l'aide de la fonction cge ts (son m écanism e estdécritdans l'exercice II-4).
- analyse : analyse de la proposition du joueur, suivantl'algorith m e décritprécédem m ent.
Program m e
III. H asard e trécréations 151
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NPOS 5 /* nombre de positions */
#define NCHIF 8 /* nombre de chiffres (ici, de 1 a 8) */
#define NCMAX 12 /* nombre maximal de coups */
main()
{
void tirage (int []) ; /*****************************/
int entree (int []) ; /* prototypes fonctions */
void analyse(int [], int[], int[], int []) ; /*****************************/
int tir[NPOS], /* combinaison tirée par le programme */
prop[NPOS], /* proposition du joueur */
ncoup, /* compteur de coups joués */
bpos, /* nombre de chiffres bien placés */
bchif ; /* nombre de chiffres exacts mais mal placés */
/* initialisations */
tirage (tir) ;
ncoup = 0 ;
/* déroulement du jeu */
do
{ while (printf ("proposition ? : "), entree(&prop) )
printf ("n** incorrect **n") ;
analyse (prop, tir, &bpos, &bchif) ;
printf ("n %22d P %d Cn", bpos, bchif) ;
ncoup++ ;
}
while (bpos < NPOS && ncoup < NCMAX) ;
/* affichage résultats */
if (bpos == NPOS) printf ("vous avez trouvé en %d coups", ncoup) ;
else { int i ;
printf ("vous n'avez pas trouvé en %d coupsn", NCMAX) ;
printf ("la bonne combinaison était : ") ;
for (i=0 ; i<NPOS ; i++) printf ("%d",tir[i]) ;
printf ("n") ;
}
}
152 Exe rcices en langage C
/*************************************************/
/* fonction de tirage de la combinaison secrète */
/*************************************************/
void tirage (int tir [])
{
int i ;
for (i=0 ; i<NPOS ; i++)
tir[i] = rand() / (RAND_MAX + 1.) * NCHIF + 1 ;
}
/*************************************************/
/* fonction de lecture de la proposition du joueur */
/*****************************************************/
int entree (int prop [])
{
char ch[NPOS+3] ; /* chaîne où sera lue la proposition du joueur */
int i ;
/* lecture proposition joueur dans chaîne ch */
ch[0] = NPOS+1 ; /* préparation longueur maxi chaîne lue */
cgets (ch) ;
/* contrôles */
if (strlen (&ch[2]) != NPOS) return(-1) ;
for (i=2 ; i<NPOS+2 ; i++)
if (ch[i] < '1' || ch[i] > '1'+NCHIF-1) return(-1) ;
/* extraction des chiffres choisis */
for (i=0 ; i<NPOS ; i++)
prop[i] = ch[2+i] -'0' ;
return (0) ;
}
/**************************************************/
/* fonction d'analyse de la proposition du joueur */
/**************************************************/
void analyse (int prop [], int tir [], int bpos [] , int bchif [])
{
int tirbis[NPOS], /* double de la combinaison secrète */
i, j ;
/* recopie de la combinaison secrète */
for (i=0 ; i<NPOS ; i++) tirbis[i] = tir[i] ;
III. H asard e trécréations 153
/* comptage bonnes positions */
*bpos = 0 ;
for (i=0 ; i<NPOS ; i++)
if (prop[i] == tirbis[i])
{ (*bpos)++ ;
tirbis[i] = prop[i] = 0 ;
}
/* comptage bons chiffres mal placés */
*bchif = 0 ;
for (i=0 ; i<NPOS ; i++)
for (j=0 ; j<NPOS ; j++)
if (prop[i] !=0 && prop[i] == tirbis[j])
{ (*bchif)++ ;
prop[i] = tirbis[j] = 0 ;
}
}
Com m e ntaire s
*Le nom bre de positions (NPOS) et le nom bre de ch iffres (NCH IF) ont été définis par #de fine , ce qui en facilite
l'éventuelle m odification.
*La fonction tirage faitappelà l'algorith m e de tirage au h asard d'un entier, telque nous l'avons exposé dans l'exercice
III-1. Toutefois, ici, le nom bre tiré doitappartenir à l'intervalle [1,NCH IF] etnon à l'intervalle [0,NCH IF]. C'estce qui
explique que le nom bre réeltiré dans l'intervalle [0,1[ soitm ultiplié par NCH IF etque l'on ajoute 1 au résultat.
*La fonction e ntre e lit, com m e prévu, la proposition du joueur sous form e d'une ch aîne. Elle en effectue les contrôles
requis en restituantla valeur 0 lorsque la réponse estvalide etla réponse -1 dans le cas contraire. Notez que la décision
de dem ander, en cas d'erreur, une nouvelle proposition au joueur estprise dans le program m e principalet non dans la
fonction elle-m ê m e.
*Les argum ents de la fonction analyse sonttransm is par leur adresse, afin que leur valeur puisse ê tre m odifiée. C'estce
qui justifie leur déclaration sous form e de pointeurs sur des entiers. N'oubliez pas que les nom s de tableaux
correspondentà leur adresse ;c'estce qui justifie que dans l'appelde analyse, on trouve effectivem entles sym boles prop
ettir, alors que, par ailleurs, on y trouve & bpos et& bch if.
*Dans la boucle suivante (du program m e principal):
154 Exe rcices en langage C
while (printf ("proposition ? : "), entree(&prop) )
printf ("n** incorrect **n") ;
l'expression figurantdans w h ile utilise un "opérateur séquentiel", ce qui perm etainsi de sim plifier quelque peu l'écriture.
A titre indicatif, voici deux constructions équivalentes, l'une parfaitem ent structurée, l'autre basée sur l'utilisation de
bre ak (les valeurs des sym boles VRAI etFAUX étantrespectivem ent1 et0):
ok = FAUX ;
while (!ok)
{ printf ("proposition ? : ") ;
if (entree(&prop)) ok = VRAI ;
else printf ("n** incorrect **n") ;
}
do
{ printf ("proposition ? : ") ;
if (entree(&prop)) break ;
else printf ("n** incorrect **n) ;
while(1) ;
D ISCUSSIO N
*Ici, la saisie de la proposition du joueur estparfaitem entsatisfaisante, m ê m e pour un program m e "réel". En particulier,
elle autorise les corrections, m ê m e aprè s que l'utilisateur a frappé le dernier ch iffre.
*Par contre, telqu'ilestproposé ici, ce program m e ch oisittoujours la m ê m e com binaison, ce qui enlè ve quelque intérê t
à la pratique réguliè re du jeu (m ais qui peut faciliter la m ise au pointdu program m e). Pour rém édier à cette lacune, il
suffit d'introduire, dans la fonction tirage , une initialisation du générateur de nom bres aléatoires, lors de son prem ier
appel, com m e nous l'avons faitdans l'exercice III-2.
*Le program m e supporte, sans aucune m odification, des valeurs quelconques de NPOS et des valeurs de NCH IF
inférieures à 10. Ilestfacile d'aller au-delà , en m odifiantsim plem entla fonction e ntre e .
IV: TRIS, FUSIO NS
ET RECH ERCH E EN TABLE
Nous vous proposons ici des exercices de program m ation d'algorith m es classiques ayant trait aux tris et fusions de
tableaux, ainsi qu'à la rech erch e en table.
IV-1 Tri par e xtraction sim ple
________________________________________________________________________________________
Enoncé
Réaliser un program m e de tri par valeurs décroissantes d'un tableau d'entiers, en utilisantl'algorith m e dit"par extraction
sim ple" qui se définitde la m aniè re suivante :
- On rech erch e le plus grand des n élém ents du tableau.
- On éch ange cetélém entavec le prem ier élém entdu tableau.
- Le plus petitélém entse trouve alors en prem iè re position. On peutalors appliquer les deux opérations précédentes
aux n-1 élém ents restants, puis aux n-2, ... etcela jusqu'à ce qu'ilne reste plus qu'un seulélém ent(le dernier - qui
estalors le plus petit).
Le program m e affich era tous les "résultats interm édiaires", c'est-à -dire les valeurs du tableau, aprè s ch aque éch ange de
deux élém ents.
Exe m ple
combien de valeurs à trier : 8
donnez vos valeurs à trier
156 Exe rcices en langage C
3 9 2 7 11 6 2 8
---- valeurs à trier ----
3 9 2 7 11 6 2 8
11 9 2 7 3 6 2 8
11 9 2 7 3 6 2 8
11 9 8 7 3 6 2 2
11 9 8 7 3 6 2 2
11 9 8 7 6 3 2 2
11 9 8 7 6 3 2 2
11 9 8 7 6 3 2 2
---- valeurs triées ----
11 9 8 7 6 3 2 2
________________________________________________________________________________________
ANALYSE
L'algorith m e proposé par l'énoncé peutse form aliser com m e suit, en tenantcom pte des conventions d'indices propres au
langage C :
Répéter, pour i variantde 0 à n-2 :
- rech erch er k
m
telque t(k
m
)soitle plus grand des t(k ), pour k allantde i à n-1,
- éch anger les valeurs de t(k
m
)etde t(i).
Program m e
#include <stdio.h>
#define NMAX 100 /* nombre maximal de valeurs à trier */
main()
{
int t [NMAX], /* tableau contenant les valeurs à trier */
nval, /* nombre de valeurs à trier */
kmax, /* position du maximum temporaire */
tempo, /* valeur temporaire pour échange valeurs */
IV. Tris, fusions e tre ch e rch e e n table 157
i, j, k ;
/* lecture des valeurs à trier */
printf ("combien de valeurs à trier : ") ;
scanf ("%d", &nval) ;
if (nval > NMAX) nval = NMAX ;
printf ("donnez vos valeurs à triern") ;
for (k=0 ; k<nval ; k++)
scanf ("%d", &t[k]) ;
printf ("n ---- valeurs à trier ----n") ;
for (k=0 ; k<nval ; k++)
printf ("%5d", t[k]) ;
printf ("nn") ;
/* tri des valeurs */
for (i=0 ; i<nval-1 ; i++) /* recherche maxi partiel pour chaque i */
{ kmax = i ; /* init recherche maxi partiel */
for (j=i+1 ; j<nval ; j++) /* recherche maxi partiel */
if (t[j] > t[kmax]) kmax = j ;
tempo = t[kmax] ; /* mise en place maxi partiel */
t[kmax] = t[i] ;
t[i] = tempo ;
for (k=0 ; k<nval ; k++) /* affichage intermédiaire */
printf ("%5d", t[k]) ;
printf ("n") ;
}
/* affichage valeurs triées */
printf ("n ---- valeurs triées ----n") ;
for (k=0 ; k<nval ; k++)
printf ("%5d", t[k]) ;
printf ("n") ;
}
Com m e ntaire s
Ce program m e fonctionne pour toutes les valeurs de NMAX, en particulier :
- pour NMAX inférieur ou égalà 0, ilne faitrien,
- pour NMAX = 1, illitune valeur qu'ilaffich e telle quelle.
158 Exe rcices en langage C
IV-2 Tri par pe rm utation sim ple
________________________________________________________________________________________
Enoncé
Ecrire une fonction réalisant le tri par valeurs croissantes d'un tableau d'entiers, en utilisant l'algorith m e de tri par
perm utation sim ple (ditde "la bulle"), qui se définitainsi (n représentantle nom bre d'élém ents du tableau):
On parcourtl'ensem ble du tableau, depuis sa fin jusqu'à son début, en com parantdeux élém ents consécutifs, en les
inversants'ils sontm alclassés. On se retrouve ainsi avec le plus petitélém entplacé en tê te du tableau.
On renouvelle une telle opération (dite "passe")avec les n-1 élém ents restants, puis avec les n-2 élém ents restants, et
ainsi de suite... jusqu'à ce que :
- soitl'avant-dernier élém entaitété classé (le dernier étantalors obligatoirem entà sa place),
- soitqu'aucune perm utation n'aiteu lieu pendantla derniè re passe (ce qui prouve alors que l'ensem ble du tableau
estconvenablem entordonné).
On prévoira en argum ents :
- l'adresse du tableau à trier,
- son nom bre d'élém ents,
- un indicateur précisant si l'on souh aite que la fonction affich e les valeurs du tableau aprè s ch aque perm utation (0
pour non, 1 pour oui).
Exe m ple
combien de valeurs à trier : 6
donnez vos valeurs à trier
2 8 4 7 0 8
---- valeurs à trier ----
2 8 4 7 0 8
2 8 4 0 7 8
2 8 0 4 7 8
IV. Tris, fusions e tre ch e rch e e n table 159
2 0 8 4 7 8
0 2 8 4 7 8
0 2 4 8 7 8
0 2 4 7 8 8
---- valeurs triées ----
0 2 4 7 8 8
________________________________________________________________________________________
ANALYSE
L'algorith m e nous estindiqué par l'énoncé. Nous utiliserons cependantune répétition de type tantque (instruction w h ile )
qui perm etde prendre convenablem enten com pte le cas où l'on appelle la fonction de tri en lui fournissanten argum ent
un nom bre de valeurs inférieur ou égalà 1.
Dans la m ise en oeuvre de cet algorith m e, nous ferons appelà un entier i spécifiant le rang à partir duquelle tableau
n'est pas encore trié. Initialem ent, ilfaudra prévoir qu'aucun élém ent n'est encore à sa place, ce qui conduira à
l'initialisation artificielle de i à -1 (puisque en C, le prem ier élém entd'un tableau porte le num éro 0). D'autre part, un
indicateur logique nom m é pe rm utnous servira à préciser si au m oins une perm utation a eu lieu au cours de la derniè re
passe.
Si nous notons nvalle nom bre de valeurs de notre tableau, l'algorith m e de tri peutalors s'énoncer com m e suit:
Tant que i ne désigne pas le dernier élém ent du tableau (c'est-à -dire i < nval-1) et que pe rm ut est VRAI, nous
effectuons une passe. Cette derniè re consiste en une succession de com paraisons des élém ents de rang j et j+1, j
décrivanttous les élém ents depuis l'avant-dernier jusqu'à celui de rang i+1 (autrem entdit, jdécroissantde nval-2 à
i+1). A ch aque perm utation, nous donnons à pe rm ut la valeur VRAI ;nous aurons, bien sûr, pris soin d'initialiser
pe rm utà FAUX au débutde ch aque passe.
Notez que l'utilisation d'une répétition de type tantque (dans laquelle la condition de poursuite faitintervenir l'indicateur
pe rm ut)nous oblige à initialiser artificiellem entpe rm utà VRAI, en toutdébutde travail.
Program m e
#include <stdio.h>
#define VRAI 1 /* pour "simuler" des ... */
#define FAUX 0 /* ... valeurs logiques */
#define NMAX 100 /* nombre maximal de valeurs à trier */
main()
{
160 Exe rcices en langage C
void bulle(int [], int, int ) ; /* prototype fonction de tri */
int t [NMAX], /* tableau contenant les valeurs à trier */
nval, /* nombre de valeurs à trier */
k ;
/* lecture des valeurs à trier */
printf ("combien de valeurs à trier : ") ;
scanf ("%d", &nval) ;
if (nval > NMAX) nval = NMAX ;
printf ("donnez vos valeurs à triern") ;
for (k=0 ; k<nval ; k++)
scanf ("%d", &t[k]) ;
printf ("n ---- valeurs à trier ----n") ;
for (k=0 ; k<nval ; k++)
printf ("%5d", t[k]) ;
printf ("nn") ;
/* tri des valeurs */
bulle (t, nval, 1) ;
/* affichage valeurs triées */
printf ("n ---- valeurs triées ----n") ;
for (k=0 ; k<nval ; k++)
printf ("%5d", t[k]) ;
printf ("n") ;
}
/**************************************************/
/* fonction de tri par la méthode de la bulle */
/**************************************************/
void bulle (int t[], int nval, int affich)
/* t : tableau à trier */
/* nval : nombre de valeurs à trier */
/* affich : indicateur affichages intermédiaires */
{
int i, /* rang à partir duquel le tableau n'est pas trié */
j, /* indice courant */
tempo, /* pour l'échange de 2 valeurs */
k ;
int permut ; /* indicateur logique précisant si au moins une */
/* permutation a eu lieu lors de la précédente passe */
i = -1 ;
permut = VRAI ;
while (i < nval-1 && permut)
IV. Tris, fusions e tre ch e rch e e n table 161
{ permut = FAUX ;
for (j=nval-2 ; j>i ; j--)
{ if ( t[j] > t[j+1] )
{ permut = VRAI ;
tempo = t[j] ;
t[j] = t[j+1] ;
t[j+1] = tempo ;
if (affich)
{ for (k=0 ; k<nval ; k++)
printf ("%5d", t[k]) ;
printf ("n") ;
}
}
}
i++ ;
}
}
Com m e ntaire s
Dans la fonction bullle , la déclaration :
int * t ;
estéquivalente à :
int t[] ;
D ISCUSSIO N
Les deux algorith m es proposés dans l'exercice précédent et dans celui-ci correspondent à ce que l'on appelle des
"m éth odes directes". D'une m aniè re générale, ce sont des algorith m es sim ples à program m er, m ais qui nécessitent un
nom bre de com paraisons de l'ordre de n
2
(notez qu'ilexiste une troisiè m e m éth ode directe dite "tri par insertion").
En fait, ilexiste des m éth odes dites "évoluées" qui conduisent à un nom bre de com paraisons de l'ordre de n *log n.
Celles-ci débouch entsur des program m es plus com plexes etles opérations qu'elles fontintervenir sontelles-m ê m es plus
gourm andes en tem ps que celles des m éth odes directes. Aussi, les m éth odes évoluées ne prennentvéritablem entd'intérê t
que pour des valeurs élevées de n.
162 Exe rcices en langage C
A titre indicatif, nous vous fournissons ici l'algorith m e relatif à la m éth ode évoluée la plus perform ante, nom m ée "Tri
rapide" (Quick sort), inventée par C. A. R. H oare. Cet algorith m e, délicat à program m er, est basé sur l'opération de
"segm entation" d'un tableau ;celle-ci consiste à partager un tableau en deux parties, nom m ées segm ents, telles que tout
élém ent de l'une soit inférieur ou égalà tout élém ent de l'autre. Une telle segm entation peut ê tre réalisée par
l'algorith m e suivant:
- Prendre un élém entau h asard (on peutprendre l'élém entdu m ilieu). Soitm sa valeur.
- Rech erch er, depuis le débutdu tableau, le prem ier élém entt(i)telque t(i)> m .
- Rech erch er, depuis la fin du tableau, le prem ier élém entt(j)telque t(j)<m .
- Perm uter t(i)ett(j).
- Poursuivre ce "parcours" du tableau jusqu'à ce que i etjse rencontrent.
Le tri proprem entdits'effectue en appliquantà nouveau l'opération de segm entation à ch aque segm entobtenu, puis aux
segm ents obtenus par segm entation de ces segm ents,... etainsi de suite jusqu'à ce que ch aque segm entne contienne plus
qu'un seulélém ent.
Notez qu'une telle m éth ode se prê te particuliè rem entbien à une program m ation récursive.
IV-3 Tri d'un table au de ch aîne s
________________________________________________________________________________________
Enoncé
Ecrire une fonction utilisantla m éth ode de tri par extraction sim ple (décrite dans l'exercice IV-1) pour trier un tableau de
ch aînes, par ordre alph abétique (sans distinction entre m ajuscules etm inuscules).
Cette fonction recevra, en argum ent:
- l'adresse d'un tableau de pointeurs sur les ch aînes concernées,
- le nom bre de ch aînes à trier.
Le tri proprem entditportera, non sur les valeurs des ch aînes elles-m ê m es, m ais uniquem entsur le tableau de pointeurs.
On testera cette fonction à l'aide d'un program m e principalcréantun sim ple tableau de ch aînes (ayantdonc ch acune une
longueur m axim ale donnée).
IV. Tris, fusions e tre ch e rch e e n table 163
Exe m ple
combien de chaînes à trier ? 7
donnez vos 7 chaînes (validez chacune par 'return')
C
Turbo C
Basic
Pascal
Turbo Pascal
Fortran
ADA
voici vos chaînes triées
ADA
Basic
C
Fortran
Pascal
Turbo C
Turbo Pascal
________________________________________________________________________________________
ANALYSE
La m éth ode de tri a été décrite dans l'exercice IV-1. Ilest cependant nécessaire de procéder à plusieurs sortes
d'adaptations :
- ilfauten faire une fonction,
- la relation d'ordre qui sertau tri ne porte plus sur des entiers, m ais sur des ch aînes de caractè res ;cela im plique de
recourir à la fonction stricm p (etnon strcm p, puisque l'on souh aite ne pas distinguer les m ajuscules des m inuscules),
- les élém ents à perm uter serontdes pointeurs etnon plus des entiers.
164 Exe rcices en langage C
Program m e
#include <stdio.h>
#include <string.h>
#define NCHMAX 100 /* nombre maximal de chaînes à traiter */
#define LGMAX 25 /* longueur maximale d'une chaîne (sans 0) */
main()
{
void trichaines (char * *, int ) ; /* prototype fonction de tri */
char chaines [NCHMAX] [LGMAX+1] ; /* tableau des chaînes */
char * adr [NCHMAX] ; /* tableau pointeurs sur les chaînes */
int nch, /* nombre de chaîne à trier */
i ;
/* lecture des chaînes et préparation du tableau de pointeurs */
printf ("combien de chaînes à trier ? ") ;
scanf ("%d", &nch) ;
if (nch > NCHMAX) nch = NCHMAX ;
getchar() ; /* pour forcer la lecture de fin de ligne */
printf ("donnez vos %d chaînes (validez chacune par 'return')n", nch) ;
for (i=0 ; i<nch ; i++)
{ fgets (chaines[i], LGMAX+1, stdin) ; /* lit au maximum LGMAX caractères */
adr[i] = chaines[i] ;
}
/* tri des pointeurs sur les chaînes */
trichaines (adr, nch) ;
/* affichage des chaînes après tri */
/* attention aux chaînes de longueur maximum !! */
printf ("nnvoici vos chaînes triéesn") ;
for (i=0 ; i<nch ; i++)
printf ("%s", adr[i]) ;
}
void trichaines (char * * adr, int nch)
/* adr : adresse tableau de pointeurs sur chaînes à trier */
/* nch : nombre de chaînes */
{
char * tempo ; /* pointeur temporaire pour l'échange de 2 pointeurs */
int kmax,
IV. Tris, fusions e tre ch e rch e e n table 165
i, j ;
for (i=0 ; i<nch-1 ; i++)
{ kmax = i ;
for (j=i+1 ; j<nch ; j++)
if ( stricmp (adr[kmax], adr[j]) > 0 ) kmax = j ;
tempo = adr[kmax] ;
adr[kmax] = adr[i] ;
adr[i] = tempo ;
}
}
Com m e ntaire s
*Ici, les ch aînes à trier ontété placées (par le program m e principal) dans un tableau de caractè res (nom m é ch aines) à
deux dim ensions. Notez bien qu'ilne seraitpas possible d'en inverser l'ordre des dim ensions ;ilesten effetnécessaire
que tous les caractè res d'une m ê m e ch aîne soientconsécutifs.
*Bien que cela n'ait pas été explicitem ent dem andé par l'énoncé, nous avons prévu un contrôle sur la longueur des
ch aînes fournies au clavier ;pour ce faire, nous avons fait appelà la fonction fge ts, en l'appliquant au fich ier stdin.
L'instruction :
fgets (chaines[i], LGMAX+1, stdin) ;
litau m axim um LGM AX caractè res sur stdin etles range à l'adresse ch aine [i], en com plétantle toutpar un zéro de fin de
ch aîne. Ainsi, on évite les risques de débordem entm ém oire que présente ge ts.
Toutefois un léger inonvénientapparaît. En effet, tantque le nom bre de caractè res m axim al(LGM AX) n'estpas atteint,
le caractè re n qui a servi à délim iter la ch aîne lue est rangé en m ém oire, au m ê m e titre que les autres. En revanch e,
lorsque le nom bre m axim alde caractè res a été atteint, alors précisém entque ce caractè re n n'a pas été rencontré, on ne
trouve plus ce caractè re en m ém oire (le caractè re nulde fin de ch aîne, quantà lui, estbien toujours présent).
Cetinconvénientestsurtoutsensible lorsque l'on affich e à nouveau les ch aînes par printfaprè s leur tri : les ch aînes de
longueur m axim ale ne serontpas suivies d'un ch angem entde ligne. Notez bien qu'en em ployantputs on obtiendrait, en
revanch e, 1 caractè re de ch angem ent de ligne pour les ch aînes de longueur m axim ale (transm is par la fonction puts
m ê m e)et2 caractè res de ch angem entde ligne pour les autres ch aînes (celui figurantdans la ch aîne etcelui transm is par
puts).
Dans un "program m e opérationnel", ilfaudraitgérer convenablem entcette situation, ce que nous n'avons pas faitici.
166 Exe rcices en langage C
*Rappelons que, aprè s la lecture par scanf du nom bre de ch aînes à traiter, le pointeur reste (com m e à l'accoutum ée)
positionné sur le dernier caractè re non encore utilisé ;dans le m eilleur des cas, ils'agit de n (m ais ilpeut y avoir
d'autres caractè res avant si l'utilisateur a été distrait). Dans ces conditions, la lecture ultérieure d'une ch aîne par ge ts
conduira à lire... une ch aîne vide.
Pour éviter ce problè m e, nous avons placé une instruction ge tch ar qui absorbe ce caractè re n. En toute rigueur, si l'on
souh aitait traiter correctem ent le cas où l'utilisateur a fourni trop d'inform ation pour le scanf précédent, ilserait
nécessaire d'opérer une lecture d'une ch aîne par ge ts (ilfaudraitprévoir un em placem entà ceteffet!).
*Dans la fonction trich aines, le prem ier argum entadr a été déclaré par :
char * * adr
Ils'agitd'un pointe ur sur le table au de pointe urs sur les différentes ch aînes. Nous aurions pu égalem entle déclarer par :
char * adr[]
Notez d'ailleurs que nous avons utilisé le "form alism e" tableau au sein de la fonction elle-m ê m e. Ainsi :
adr[i] = adr[j]
auraitpu se form uler :
* (adr+i) = * (adr+j)
*Nous vous rappelons que la fonction stricm p com pare les deux ch aînes dont on lui fournit les adresses et elle fournit
une valeur entiè re définie com m e étant:
- positive si la prem iè re ch aîne arrive aprè s la seconde, au sens de l'ordre défini par le code des caractè res (sans tenir
com pte de la différence entre m ajuscules etm inuscules pour les 26 lettres de l'alph abet),
- nulle si les deux ch aînes sontégales,
- négative si la prem iè re ch aîne arrive avantla seconde.
D ISCUSSIO N
D'une m aniè re générale, iln'est pas nécessaire que les ch aînes à trier soient, com m e ici, im plantées en m ém oire de
m aniè re consécutive.
De m ê m e, la fonction trich aines proposée pourraittoutaussi bien opérer sur des ch aînes dontles em placem ents auraient
été alloués "dynam iquem ent" (le ch apitre Vvous propose d'ailleurs un exercice dans ce sens).
IV. Tris, fusions e tre ch e rch e e n table 167
IV-4 Fusion de de ux table aux ordonné s
La fusion consiste à rassem bler en un seultableau ordonné les élém ents de deux tableaux, eux-m ê m es ordonnés.
________________________________________________________________________________________
Enoncé
Réaliser une fonction qui fusionne deux tableaux d'entiers ordonnés par valeurs croissantes.
On prévoira en argum ents :
- les adresses des trois tableaux concernés,
- le nom bre de valeurs de ch acun des deux tableaux à fusionner.
Pour tester cette fonction, on écrira un program m e principalqui litau clavier deux ensem bles de valeurs que l'on triera
au préalable à l'aide de la fonction bulle réalisée dans l'exercice IV-2.
Exe m ple
combien de valeurs pour le premier tableau ? 5
donnez vos valeurs
3 9 2 8 11
combien de valeurs pour le second tableau ? 7
donnez vos valeurs
12 4 6 3 1 9 6
premier tableau à fusionner
2 3 8 9 11
second tableau à fusionner
1 3 4 6 6 9 12
résultat de la fusion des deux tableaux
1 2 3 3 4 6 6 8 9 9 11 12
________________________________________________________________________________________
168 Exe rcices en langage C
ANALYSE
La dém arch e, assez sim ple, s'inspire de celle que l'on adopteraitpour résoudre "à la m ain" un telproblè m e. Ilsuffit, en
effet, d'avancer en parallè le dans ch acun des deux tableaux à fusionner (t1 et t2), en prélevant, à ch aque fois, le plus
petitdes deux élém ents eten l'introduisantdans le tableau résultantt. Plus précisém ent, nous som m es am enés à utiliser
trois indices :
- i1 : prem ier élém entde t1 non encore pris en com pte,
- i2 : prem ier élém entde t2, non encore pris en com pte,
- i : em placem entdu proch ain élém entà introduire dans t.
Nous initialisons ces trois indices à zéro (com pte tenu des conventions du C). Nous répétons alors le traitem entsuivant:
Ch oisir le plus petit des élém ents t1(i1) et t2(i2) et le placer en t(i). Incrém enter de 1 la valeur de l'indice
correspondantà l'élém entextrait(i1 ou i2), ainsi que celle de i.
Nous poursuivons ainsi jusqu'à ce que l'un des deux tableaux soit épuisé. Ilne reste plus alors qu'à recopier la fin de
l'autre tableau.
Program m e
#include <stdio.h>
#define NMAX1 100 /* nombre maximal de valeurs du premier tableau */
#define NMAX2 100 /* nombre maximal de valeurs du second tableau */
main()
{
void fusion(int [], int [], int [], int, int ) ; /* proto fonction de fusion */
void bulle(int [], int) ; /* proto fonction servant à assurer l'ordre des tableaux
*/
int t1 [NMAX1], /* premier tableau à fusionner */
t2 [NMAX2], /* second tablleau à fusionner */
t [NMAX1+NMAX2] ; /* tableau résultant de la fusion */
int nval1, /* nombre de valeurs à prélever dans t1 */
nval2, /* nombre de valeurs à prélever dans t2 */
k ;
/* lecture des valeurs des deux ensembles à fusionner */
printf ("combien de valeurs pour le premier tableau ? ") ;
scanf ("%d", &nval1) ;
IV. Tris, fusions e tre ch e rch e e n table 169
if (nval1 > NMAX1) nval1 = NMAX1 ;
printf ("donnez vos valeursn") ;
for (k=0 ; k<nval1 ; k++)
scanf ("%d", &t1[k]) ;
printf ("combien de valeurs pour le second tableau ? ") ;
scanf ("%d", &nval2) ;
if (nval2 > NMAX2) nval2 = NMAX2 ;
printf ("donnez vos valeursn") ;
for (k=0 ; k<nval2 ; k++)
scanf ("%d", &t2[k]) ;
/* tri préalable et affichage des valeurs à fusionner */
bulle (t1, nval1) ;
bulle (t2, nval2) ;
printf ("npremier tableau à fusionnern") ;
for (k=0 ; k<nval1 ; k++)
printf ("%5d", t1[k]) ;
printf ("nsecond tableau à fusionnern") ;
for (k=0 ; k<nval2 ; k++)
printf ("%5d", t2[k]) ;
/* fusion et affichage résultats */
fusion (t, t1, t2, nval1, nval2) ;
printf ("nn résultat de la fusion des deux tableauxn") ;
for (k=0 ; k<nval1+nval2 ; k++)
printf ("%5d", t[k]) ;
}
/********************************************************/
/* fonction de fusion de deux tableaux */
/********************************************************/
void fusion (int t[], int t1[], int t2[], int nval1, int nval2)
/* t1 et t2 : tableaux à fusionner */
/* t :tableau résultant */
/* nval1 : nombre de valeurs du premier tableau t1 */
/* nval2 : nombre de valeurs du second tableau t2 */
{
int i1, i2, /* indices courants dans les tableaux à fusionner */
i, /* indice courant dans le tableau résultant */
k ;
170 Exe rcices en langage C
i = 0 ; i1 = 0 ; i2 = 0 ;
while (i1 < nval1 && i2 < nval2)
{ if ( t1[i1] < t2[i2] ) t[i++] = t1[i1++] ;
else t[i++] = t2[i2++] ;
}
if (i1 == nval1)
for (k=i2 ; k<nval2 ; k++) t[i++] = t2[k] ;
else for (k=i1 ; k<nval1 ; k++) t[i++] = t1[k] ;
}
/*******************************************************/
/* fonction de tri d'un tableau (méthode de la bulle) */
/*******************************************************/
void bulle (int t[], int nval)
{
int i, j, tempo, k, permut ;
i = -1 ; permut = 1 ;
while (i < nval-1 && permut)
{ permut = 0 ;
for (j=nval-2 ; j>i ; j--)
if ( t[j] > t[j+1])
{ permut = 1 ;
tempo = t[j] ; t[j] = t[j+1] ; t[j+1] = tempo ;
}
i++ ;
}
}
Com m e ntaire s
*Pour effectuer le tri préalable des deux tableaux fournis en donnée, nous avons repris la fonction bulle réalisée dans
l'exercice IV-2. Nous en avons toutefois supprim é les instructions perm ettant d'affich er, sur dem ande, les im pressions
interm édiaires.
IV. Tris, fusions e tre ch e rch e e n table 171
IV-5 Re ch e rch e dich otom iq ue
L'exercice II-4 de facturation par code faisait intervenir un algorith m e séquentielde rech erch e en table. Nous vous
proposons ici de réaliser un algorith m e plus perform antde rech erch e par "dich otom ie".
________________________________________________________________________________________
Enoncé
Ecrire un program m e qui rech erch e, à partir d'un code d'article (num érique), l'inform ation qui lui estassociée, à savoir
un libellé (ch aîne)etun prix unitaire (réel).
Com m e dans l'exercice II-4, le program m e utilisera un tableau de structures, déclaré à un niveau global, pour conserver
les inform ations requises. Cette fois, par contre, ces derniè res serontrangées par ordre croissantdu num éro de code.
La localisation d'un num éro de code donné se fera par une rech erch e dich otom ique. Celle-ci consiste à profiter de l'ordre
du tableau pour accélérer la rech erch e en procédantcom m e suit:
- On considè re l'élém ent figurantau "m ilieu" du tableau. Si le code ch erch é lui est égal, la rech erch e est term inée.
S'illui estinférieur, on en conclutque le code rech erch é ne peutse trouver que dans la prem iè re m oitié du tableau ;
dans le cas contraire, on en conclutqu'ilse trouve dans la seconde m oitié.
- On recom m ence alors l'opération sur la "m oitié" concernée, puis sur la m oitié de cette m oitié, et ainsi de suite...
jusqu'à ce que l'une des conditions suivantes soitsatisfaite :
*on a trouvé l'élém entch erch é,
*on estsûr qu'ilne figure pas dans le tableau.
Exe m ple s
code article recherché : 24
le code 24 n'existe pas
________________
code article recherché : 19
article de code 19
libellé : Balance de ménage
prix : 278.00
________________________________________________________________________________________
172 Exe rcices en langage C
ANALYSE
L'algorith m e proposé par l'énoncé suggè re d'utiliser trois variables perm ettantde spécifier, à un instantdonné, la partie
du tableau dans laquelle s'effectue la rech erch e :
gauch e : débutde la partie restantà explorer,
droite : fin de la partie restantà explorer,
m ilie u : position ch oisie pour le "m ilieu" de cette partie restantà explorer.
Notez déjà que cette notion de m ilieu estquelque peu am biguë. Nous conviendrons qu'elle correspond à la partie entiè re
de la m oyenne des indices gauch e etdroite .
L'algorith m e de rech erch e par dich otom ie peutalors s'énoncer ainsi (t désignant le tableau, n le nom bre de codes et x
l'élém entch erch é):
- Initialiser gauch e etdroite de façon qu'ils désignentl'ensem ble du tableau.
- Répéter le traitem entsuivant:
*Déterm iner le m ilieu de la partie à explorer :
m ilieu = (gauch e + droite)/2
*Com parer l'élém entch erch é x avec t(m ilie u):
+ S'ils sontégaux, l'élém entch erch é estlocalisé en position m ilie u,
+ Si x est supérieur à t(m ilie u), l'élém ent ch erch é ne peut se situer que dans la partie droite ;on réalise
l'affectation :
debut=m ilieu + 1
+ dans le cas contraire, l'élém entch erch é ne peutse situer que dans la partie gauch e ;on réalise l'affectation :
fin = m ilieu - 1
Ilnous reste à spécifier la condition d'arrê t (ou de poursuite) de cette répétition. On peut déjà noter que, à ch aque
parcours de la boucle, soitla valeur de gauch e augm ente, soitcelle de droite dim inue. Ainsi, on estsûr qu'au boutd'un
nom bre fini de tours on aboutira à l'une des situations suivantes :
- l'élém enta été localisé.
- la valeur de gauch e estsupérieure à celle de droite .
Elles nous fournissentdonc toutnaturellem entla condition de fin de notre boucle.
IV. Tris, fusions e tre ch e rch e e n table 173
Notez que, dans un prem ier tem ps, la valeur de gauch e devient égale à celle de droite ;m ais, dans ce cas, nous ne
savons pas encore si le seulélém ent restant à exam iner est ou non égalà x ;aussi est-ilnécessaire de faire un tour
supplém entaire pour s'en assurer.
Program m e
#include <stdio.h>
/* ------ structure contenant les informations relatives aux */
/* différents articles -------------- */
#define NBART 6 /* nombre total d'articles */
typedef struct { int code ; /* code article */
char * lib ; /* libellé */
float pu ; /* prix unitaire */
} t_article ;
t_article article [NBART] =
{ 11, "Gaufrier", 268.0,
14, "Cafetière 12 T", 235.0,
16, "Grille-pain", 199.50,
19, "Balance de ménage", 278.0,
25, "Centrifugeuse", 370.0,
26, "Four raclette 6P", 295.25
} ;
/* ----------------------------------------------------------------------*/
#define VRAI 1 /* pour "simuler" des ..... */
#define FAUX 0 /* ..... valeurs logiques */
main()
{ int coderec, /* code article recherché */
codecour, /* code courant */
gauche, /* limite gauche de la recherche */
droite, /* limite droite de la recherche */
milieu, /* nouvelle limite (droite ou gauche */
trouve ; /* indicateur code trouvé/non trouvé */
printf ("code article recherché : ") ;
scanf ("%d", &coderec) ;
gauche = 0 ;
droite = NBART-1 ;
trouve = FAUX ;
174 Exe rcices en langage C
while (gauche <= droite && !trouve)
{ milieu = (gauche+droite) / 2 ;
codecour = article[milieu].code ;
if ( codecour == coderec ) trouve = VRAI ;
else if ( codecour < coderec)
gauche = milieu + 1 ;
else droite = milieu - 1 ;
}
if (trouve) printf ("article de code %dnlibellé : %snprix : %10.2f",
coderec, article[milieu].lib, article[milieu].pu) ;
else printf ("le code %d n'existe pas", coderec) ;
}
Com m e ntaire s
*Notez bien la condition régissantla boucle while :
gauche <= droite && !trouve
- D'une part, com m e nous l'avons dit dans notre analyse, nous poursuivons notre exploration, m ê m e quand les
valeurs de gauch e etdroite sontégales, de m aniè re à savoir si le seulélém entrestantà exam iner convientou non.
- D'autre part, nous y faisons intervenir un indicateur logique (trouve ). Nous aurions pu nous en passer, à condition
de placer un bre ak dans la boucle. Toutefois, dans ce cas, ilaurait fallu prévoir, en fin de boucle, un test
supplém entaire perm ettantde savoir si la rech erch e avaitété fructueuse ou non.
D ISCUSSIO N
Ilfautprendre garde, dans le déroulem entde l'algorith m e, à ne pas se contenter de prendre com m e nouvelle borne de la
partie de tableau à explorer la valeur de m ilie u, en écrivant:
debut=m ilieu
ou :
fin = m ilieu
En effet, dans ce cas, on ne peut plus prouver que la boucle s'ach è ve en un nom bre fini de tours. Certaines situations
conduisentd'ailleurs à une boucle infinie.
V: GESTIO N DYNAMIQUE
Les données d'un program m e se répartissent en trois catégories : statiques, autom atiques et dynam iques. Les données
statiques sont définies dè s la com pilation ;la gestion des données autom atiques reste transparente au program m eur et
seules les données dynam iques sontvéritablem entcréées (dans le tas)sur son initiative.
D'une m aniè re générale, l'utilisation de données dynam iques fournitdes solutions à des problè m es tels que :
- gestion de données dontl'am pleur n'estpas connue lors de la réalisation du program m e,
- m ise en oeuvre de structures dites dynam iques, telles que les listes ch aînées ou les arbres binaires.
Ce ch apitre vous en propose quelques exem ples.
V-1 Crible dynam iq ue
________________________________________________________________________________________
Enoncé
Réaliser un program m e qui déterm ine les prem iers nom bres prem iers par la m éth ode du crible d'Eratosth è ne, exposée
dans l'exercice I-2.
Cette fois, par contre, le nom bre d'entiers à considérer ne sera pas fixé par le program m e, m ais fourni en donnée. Le
program m e allouera dynam iquem ent l'em placem ent m ém oire nécessaire au déroulem ent de l'algorith m e. En cas de
m ém oire insuffisante, ildem andera à l'utilisateur de form uler une dem ande m oins im portante.
On s'astreindra ici à utiliser la fonction m alloc.
Exe m ple
combien d'entiers voulez-vous examiner : 200
176 Exe rcices en langage C
entre 1 et 200 les nombres premiers sont :
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
________________________________________________________________________________________
ANALYSE
L'algorith m e lui-m ê m e a déjà été exposé dans l'exercice I-2. La nouveauté réside ici dans l'allocation dynam ique de
l'espace im parti au tableau d'entiers. Pour ce faire, la dém arch e la plus classique consiste à faire appelà la fonction
m alloc, com m e nous le préconise l'énoncé.
Program m e
#include <stdio.h>
#include <stdlib.h>
#define VRAI 1 /* pour simuler des ...*/
#define FAUX 0 /* ... valeurs "logiques" */
main()
{
unsigned n, /* nombre d'entiers à considérer */
* raye, /* pointeur sur tableau servant de crible */
prem, /* dernier nombre premier considéré */
i ;
int na ; /* compteur de nombres premiers affichés */
/* lecture du nombre d'entiers à considérer et
allocation dynamique du tableau correspondant */
do
{ printf ("combien d'entiers voulez-vous examiner : ") ;
scanf ("%u", &n) ;
raye = (unsigned *) malloc ( (n+1)*sizeof(unsigned) ) ;
if (raye == NULL)
printf ("** mémoire insuffisante ") ;
}
while (raye == NULL) ;
V.Gestion dynam ique 177
/* initialisations du crible */
for (i=1 ; i<=n ; i++) /* mise à "zéro" du crible */
raye[i] = FAUX ;
raye[1] = VRAI ; /* on raye le nombre 1 */
/* passage au crible */
prem = 1 ;
while (prem*prem <= n)
{ while (raye[++prem] && prem<n ) {}
/* recherche premier nb prem non rayé */
for (i=2*prem ; i<=n ; i+=prem) /* on raye tous ses multiples */
raye[i] = VRAI ;
}
/* affichage résultats */
printf ("entre 1 et %u les nombres premiers sont :n", n) ;
na = 0 ;
for (i=1 ; i<=n ; i++)
if ( !raye[i] )
{ printf ("%7u", i) ;
if (++na%10 == 0) printf ("n") ; /* 10 nombres par ligne */
}
}
Com m e ntaire s
*L'allocation de l'espace m ém oire nécessaire au tableau d'entiers estréalisée par l'instruction :
raye = (unsigned *) malloc ( (n+1)*sizeof(unsigned) ) ;
dans laquelle raye estun pointeur sur des entiers non signés.
Or, le prototype de m alloc estprécisém ent:
void * malloc (size_t) ;
Le résultatfourni par m alloc estun "pointeur générique" qui peutê tre converti im plicitem enten un pointeur de n'im porte
queltype. Aussi, l'opérateur de "cast" (unsigne d *) n'estpas indispensable ici. Notre instruction d'allocation m ém oire
auraitpu s'écrire :
raye = malloc ( (n+1) * sizeof(unsigned) ) ;
178 Exe rcices en langage C
En ce qui concerne l'argum entde m alloc, celui-ci esta priori d'un type size _tdéfini (par type de f) dans stdlib.h . Le type
exactcorrespondantdépend de l'im plém entation (m ais ilest toujours non signé - en général, ils'agit de unsigne d int).
Notez que le résultatfourni par size ofestdu m ê m e type size _t.
Rappelons que m alloc fourniten résultatun pointeur sur le débutde la zone concernée lorsque l'allocation a réussi etun
pointeur nuldans le cas contraire (notez que le sym bole NULLestdéfini dans stdlib.h ).
*En ce qui concerne l'algorith m e de passage au crible, vous rem arquez que nous avons em ployé exactem entles m ê m es
instructions que dans le program m e de l'exercice I-2. Pourtant, dans ce dernier, le sym bole raye désignait un tableau
d'entiers, tandis qu'ici ildésigne un pointeur sur des entiers. Cela estpossible parce qu'en langage C, un nom de tableau
estun pointeur (constant).
D ISCUSSIO N
*Le ch oix du type unsigne d pour n estquelque peu arbitraire ;ilestguidé par le faitque m alloc adm etgénéralem entun
argum entde ce type. En supposantque telestle cas, on constate qu'alors l'expression :
(n+1) * sizeof (unsigned)
conduità des valeurs erronées dè s que la valeur de n*size of(int)dépasse la capacité du type int(n'oubliez pas qu'iln'y a
pas de détection de dépassem ent de capacité pour les opérations portant sur des entiers). Le résultat peut alors ê tre
catastroph ique car le nom bre d'octets dem andés à m alloc se trouve ê tre inférieur à celui réellem entutilisé.
Le problè m e se com plique encore un peu si l'on tientcom pte de ce que, dans certaines im plém entations, le type size _t
peu correspondre à autre ch ose que unsigne d int.
En toute rigueur, ilfaudrait donc s'assurer que le nom bre de valeurs dem andées par l'utilisateur est effectivem ent
inférieur à une certaine lim ite à fixer en fonction de l'im plém entation concernée.
V-2 Cré ation dynam iq ue de ch aîne s
Lorsqu'un program m e doittraiter un grand nom bre de ch aînes de longueur variable etque ce nom bre n'estpas connu a
priori, ilpeut s'avérer intéressant de faire allouer dynam iquem ent (par le program m e) l'espace m ém oire nécessaire au
stock age des ch aînes. C'est ce que vous propose cet exercice qui peut ê tre considéré com m e préalable à un traitem ent
ultérieur de ces ch aînes (par exem ple un tri com m e vous le proposera l'exercice V-3).
V.Gestion dynam ique 179
________________________________________________________________________________________
Enoncé
Ecrire un program m e qui lit un nom bre quelconque de ch aînes au clavier et qui les range en m ém oire dans des
em placem ents alloués dynam iquem ent au fur et à m esure des besoins. Les adresses de ch acune des ch aînes seront
conservées dans un tableau de pointeurs. Ce dernier sera réservé dans le program m e (en classe autom atique) etsa taille
(fixe)im posera donc une valeur m axim ale au nom bre de ch aînes qu'ilsera ainsi possible de traiter.
L'utilisateur signalera qu'ila fourni sa derniè re ch aîne en la faisantsuivre d'une ch aîne "vide".
Le program m e affich era ensuite les ch aînes lues, à titre de sim ple contrôle.
Rem arque : on utilisera la fonction m alloc eton supposera que les lignes lues au clavier ne peuventjam ais dépasser 127
caractè res.
Exe m ple
----- chaîne numéro 1 (return pour finir)
C
----- chaîne numéro 2 (return pour finir)
Turbo C
----- chaîne numéro 3 (return pour finir)
Basic
----- chaîne numéro 4 (return pour finir)
Pascal
----- chaîne numéro 5 (return pour finir)
Turbo Pascal
----- chaîne numéro 6 (return pour finir)
fin création
liste des chaînes créées
------- chaîne numéro 1
C
------- chaîne numéro 2
Turbo C
------- chaîne numéro 3
180 Exe rcices en langage C
Basic
------- chaîne numéro 4
Pascal
------- chaîne numéro 5
Turbo Pascal
________________________________________________________________________________________
ANALYSE
L'énoncé nous im pose donc de définir, au sein du program m e, un tableau de pointeurs destiné à contenir les adresses des
ch aînes à créer.
Ch aque ch aîne sera d'abord lue dans une zone interm édiaire (non dynam ique). On lui allouera ensuite, dynam iquem ent, à
l'aide de la fonction m alloc, un em placem entdontla taille correspond exactem entà sa longueur ;l'adresse ainsi obtenue
sera m ém orisée dans le tableau de pointeurs.
Le traitem entsera interrom pu :
- soitquand le tableau de pointeurs estplein,
- soitquand l'utilisateur fournitune ch aîne vide.
De plus, à ch aque allocation réalisée par m alloc, on s'assurera que l'espace m ém oire nécessaire a pu ê tre obtenu. Dans le
cas contraire, on prévoira d'interrom pre le program m e.
Program m e
#include <stdio.h>
#include <stdlib.h> /* pour la fonction exit */
#include <string.h>
#define NCHMAX 1000 /* nombre maximal de chaînes */
#define LGLIGNE 127 /* longueur maximale d'une ligne d'écran */
main()
{
char ligne [LGLIGNE+1], /* chaîne servant à lire une ligne écran */
* adr [NCHMAX], /* tableau de pointeurs sur les chaînes */
* ptr ; /* pointeur courant sur une chaîne */
int nch, /* compteur du nombre de chaînes */
i ;
/* mise à zéro du tableau de pointeurs */
V.Gestion dynam ique 181
for (i=0 ; i<NCHMAX ; i++)
adr[i] = NULL ;
/* boucle de création dynamique des chaînes */
nch=0 ;
while (nch < NCHMAX) /* tant que nb max chaînes non atteint */
{ printf ("----- chaîne numéro %d (return pour finir)n", nch+1) ;
gets (ligne) ;
if ( strlen(ligne) )
{ if ( (ptr = malloc (strlen(ligne)+1)) != NULL)
strcpy (adr[nch++]=ptr, ligne) ;
else
{ printf ("nn*** erreur allocation dynamique") ;
exit(-1) ; /* arrêt si erreur alloc dynam */
}
}
else break ; /* sortie boucle si réponse vide */
}
printf ("nfin créationn") ;
/* liste des chaînes ainsi créées */
printf ("nnliste des chaînes créesnn") ;
i = 0 ;
for (i=0 ; i<nch ; i++)
printf ("------- chaîne numéro %dn%sn", i+1, adr[i]) ;
}
Com m e ntaire s
*Ici, com pte tenu de ce que nous précisait l'énoncé, nous avons ch oisi de lire nos ch aînes dans un tableau de 128
caractè res, à l'aide de la fonction ge ts.
*Nous avons rem is à "zéro" le tableau de pointeurs sur nos ch aînes. Ils'agitlà d'une opération superflue m ais qui peut
s'avérer utile pendant la ph ase de m ise au point du program m e. Notez l'usage du sym bole NULL;prédéfini dans le
fich ier stdlib.h , ilcorrespond à la constante pointeur nulle.
*La création des ch aînes estréalisée par une boucle tant que (instruction w h ile ), dans laquelle nous avons prévu deux
autres sorties :
182 Exe rcices en langage C
- une sortie par bre ak , dans le cas où l'utilisateur a fourni une ch aîne vide,
- un arrê texceptionneldu program m e par e xit, dans le cas où l'allocation dynam ique a éch oué. Cette fonction (dont
le prototype figure dans stdlib.h ) requiert un argum ent;sa valeur est transm ise au systè m e et elle pourrait
éventuellem entê tre récupérée par d'autres program m es. Notez que, en l'absence de l'instruction #include relative à
stdlib.h , le com pilateur accepte un appelde e xit sans argum ent (ilest incapable de détecter l'erreur - laquelle n'a
d'ailleurs aucune incidence sur l'exécution du program m e lui-m ê m e).
Naturellem ent, beaucoup d'autres form ulations seraientpossibles.
D ISCUSSIO N
*Le faitde réserver le tableau dans le program m e (en classe autom atique)im pose une lim ite au nom bre de ch aînes qu'il
est ainsi possible de traiter ;cette lim ite est indépendante de la m ém oire réellem ent disponible. On peut am éliorer
quelque peu la situation en faisantégalem entallouer dynam iquem entl'espace nécessaire à ce tableau de pointeurs. Il
fauttoutefois en connaître la taille (ou du m oins une valeur m axim ale)lors de l'exécution du program m e. Cela peutfaire
l'objetd'une donnée fournie par l'utilisateur com m e dans l'exercice suivant.
V-3 Tri dynam iq ue de ch aîne s
________________________________________________________________________________________
Enoncé
Ecrire un program m e perm ettantde trier par ordre alph abétique des ch aînes fournies en donnée. Com m e dans l'exercice
précédent, on allouera dynam iquem ent des em placem ents m ém oire aux ch aînes, au fur et à m esure de leur lecture, et
leurs adresses seront conservées dans un tableau de pointeurs. Par contre, ici, ce dernier verra, lui aussi, son
em placem entalloué dynam iquem enten débutde program m e ;pour ce faire, on dem andera à l'utilisateur de fournir une
valeur m axim ale du nom bre de ch aînes qu'ilsera am ené à fournir.
On utilisera l'algorith m e de "tri par extraction sim ple" exposé dans l'exercice V-1 eton fera appelà la fonction m alloc.
Exe m ple
V.Gestion dynam ique 183
nombre maximal de chaînes ? 100
------- chaîne numéro 1 (return pour finir)
C
------- chaîne numéro 2 (return pour finir)
Turbo C
------- chaîne numéro 3 (return pour finir)
Basic
------- chaîne numéro 4 (return pour finir)
Pascal
------- chaîne numéro 5 (return pour finir)
Turbo Pascal
------- chaîne numéro 6 (return pour finir)
Fortran
------- chaîne numéro 7 (return pour finir)
ADA
------- chaîne numéro 8 (return pour finir)
fin création
liste triée des chaînes crées
ADA
Basic
C
Fortran
Pascal
Turbo C
Turbo Pascal
________________________________________________________________________________________
ANALYSE
Ilnous suffiten faitd'adapter le program m e de l'exercice précédent, en lui adjoignant:
- la réservation dynam ique du tableau de pointeurs,
- le tri du tableau de ch aînes ainsi créé, par réorganisation des pointeurs. Nous utiliserons pour cela l'algorith m e de tri
par extraction sim ple Celui-ci a été exposé dans l'énoncé de l'exercice V-1 etson adaptation au tri de ch aînes a été
expliquée dans l'analyse de l'exercice V-2.
184 Exe rcices en langage C
Program m e
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LGLIGNE 127 /* longueur maximale d'une ligne d'écran */
main()
{
char ligne [LGLIGNE+1], /* chaîne servant à lire une ligne écran */
* * adr, /* adresse tableau pointeurs sur les chaînes */
* ptr, /* pointeur courant sur une chaîne */
* tempo ; /* pointeur temporaire pour éch. 2 pointeurs */
unsigned nchmax, /* nombre maximal de chaînes */
nch, /* compteur du nombre de chaînes */
i, j, kmax ;
/* création et mise à zéro du tableau de pointeurs */
printf ("nombre maximum de chaînes ? ") ;
scanf ("%d", &nchmax) ;
getchar() ; /* pour sauter la validation */
if ( (adr = malloc (nchmax*sizeof(char*)) ) == NULL)
{ printf ("nn*** erreur allocation dynamique") ;
exit(-1) ; /* arrêt si erreur alloc dynam */
}
for (i=0 ; i<nchmax ; i++)
adr[i] = NULL ;
/* boucle de création dynamique des chaînes */
nch = 0 ;
while (nch < nchmax) /* tant que nb max de chaînes non atteint */
{ printf ("------- chaîne numéro %d (return pour finir)n", nch+1) ;
gets (ligne) ;
if ( strlen(ligne) )
{ if ( ( ptr = malloc (strlen(ligne)+1)) != NULL)
strcpy (adr[nch++]=ptr, ligne) ;
else
{ printf ("nn*** erreur allocation dynamique") ;
exit(-1) ; /* arrêt si erreur alloc dynam */
}
}
else break ; /* sortie boucle si réponse vide */
}
V.Gestion dynam ique 185
printf ("nfin créationn") ;
/* tri des chaînes par réarrangement des pointeurs */
for (i=0 ; i<nch-1 ; i++)
{ kmax = i ;
for (j=i+1 ; j<nch ; j++)
if ( stricmp (adr[kmax], adr[j]) > 0 ) kmax = j ;
tempo = adr[kmax] ;
adr[kmax] = adr[i] ;
adr[i] = tempo ;
}
/* liste triées des chaînes ainsi créées */
printf ("nnliste triée des chaînes créesnn") ;
for (i=0 ; i<nch ; i++)
puts ( adr[i] ) ;
}
Com m e ntaire s
*Dans le program m e de l'exercice V-2, le sym bole adr désignaitun table au de pointe urs. Ici, ce m ê m e sym bole désigne
un pointe ur sur un table au de pointe urs. Or, m algré cette différence apparente, vous constatez que nous em ployons
toujours la notation :
adr[i]
avec la m ê m e signification dans les deux cas.
En fait, dans le précédentprogram m e, adr étaitune constante pointeur dontla valeur étaitcelle de l'adresse de débutdu
tableau de pointeurs. Dans le présent program m e, adr est une variable pointeur dont la valeur est égalem ent celle de
débutdu tableau de pointeurs. Ainsi, dans les deux cas :
adr[i]
estéquivalentà :
*(adr + i)
Notez cependantque l'équivalence entre les deux program m es n'estpas totale. En effet, dans le prem ier cas, adr n'est
pas une lvalue (m ot anglais dont une traduction approch ée pourrait ê tre : valeur à gauch e);par exem ple, l'expression
adr++ seraitincorrecte. Dans le second cas, par contre, adr estbien une lvalue .
186 Exe rcices en langage C
*Nous n'avons pris aucune précaution particuliè re en ce qui concerne les lectures au clavier qui sont réalisées ici par
ge ts et scanf. Indépendam m ent des anom alies h abituelles encourues en cas de données incorrectes (ch aîne trop longue
pour ge ts, donnée non num érique pour scanf), un problè m e supplém entaire apparaît, lié au faitqu'aprè s une lecture par
scanf, le pointeur reste positionné sur le dernier caractè re non encore utilisé, à savoir ici le n (du m oins si l'utilisateur a
validé norm alem ent, sans fournir d'inform ations supplém entaires). Si la lecture suivante est, à son tour, effectuée par
scanf, aucun problè m e particulier ne se pose, le caractè re n étantsim plem entignoré. Iln'en va plus de m ê m e lorsque la
lecture suivante esteffectuée par ge ts ;dans ce cas, en effet, ce caractè re estinterprété com m e un caractè re de "fin" et
ge ts fournit... une ch aîne vide. C'estpour éviter ce ph énom è ne que nous avons dû introduire une instruction ge tch ar pour
absorber le n.
D ISCUSSIO N
Pour pouvoir allouer convenablem entl'em placem entdu tableau de pointeurs, notre program m e a besoin que l'utilisateur
lui fournisse une valeur m axim ale du nom bre de ch aînes. Si nous souh aitions qu'ilen soitautrem ent, ilseraitnécessaire
de pouvoir allouer provisoirem entun em placem entà ce tableau, quitte à l'étendre ensuite au fur età m esure des besoins
à l'aide de la fonction re alloc. Une telle extension pourraitê tre réalisée, soit à ch aque nouvelle ch aîne entrée, soit par
blocs de taille fixe (par exem ple toutes les 100 ch aînes).
V-4 Cré ation d'une liste ch aîné e
On appelle liste ch aînée ou liste liée une suite ordonnée d'élém ents dans laquelle ch aque élém ent, sauf le dernier,
com porte un pointeur sur l'élém entsuivant.
________________________________________________________________________________________
Enoncé
Ecrire un program m e qui crée une liste ch aînée d'élém ents com portantch acun :
- un nom (ch aîne)d'au m axim um 10 caractè res,
- un â ge.
Les inform ations correspondantes serontlues au clavier etl'utilisateur frappera un nom "vide" aprè s les données relatives
au dernier élém ent.
V.Gestion dynam ique 187
Le program m e affich era ensuite les inform ations contenues dans la liste ainsi créée, dans l'ordre inverse de celui dans
lequelelles aurontété fournies.
On prévoira deux fonctions : l'une pour la création, l'autre pour la liste. Elles posséderont com m e unique argum ent
l'adresse de débutde la liste (pointeur sur le prem ier élém ent).
Exe m ple
om : Laurence
age : 19
nom : Yvette
age : 35
nom : Catherine
age : 20
nom : Sebastien
age : 21
nom :
NOM AGE
Sebastien 21
Catherine 20
Yvette 35
Laurence 19
________________________________________________________________________________________
ANALYSE
Ch aque élém entde notre liste sera représenté par une structure. Nous voyons que celle-ci doitcontenir un pointeur sur un
élém ent de m ê m e type. Cela fait intervenir une certaine "récursivité" dans la déclaration correspondante, ce qui est
accepté en C.
En ce qui concerne l'algorith m e de création de la liste, deux possibilités s'offrentà nous :
- Ajouter ch aque nouvelélém entà la fin de la liste. Le parcours ultérieur de la liste se fera alors dans le m ê m e ordre
que celui dans lequelles données correspondantes ontété introduites.
- Ajouter ch aque nouvelélém enten débutde liste. Le parcours ultérieur de la liste se fera alors dans l'ordre inverse
de celui dans lequelles données correspondantes ontété introduites.
188 Exe rcices en langage C
Com pte tenu de ce que l'énoncé nous dem ande d'affich er la liste à l'envers, aprè s sa création, ilparaîtplus apportun de
ch oisir la seconde m éth ode.
Com m e dem andé, la création de la liste sera réalisée par une fonction. Le program m e principalse contentera de réserver
un pointeur (nom m é de but) destiné à désigner le prem ier élém ent de la liste. Sa valeur effective sera fournie par la
fonction de création.
L'algorith m e de création, quantà lui, consistera à répéter le traitem entd'insertion d'un nouvelélém enten débutde liste,
à savoir :
- créer dynam iquem entun em placem entpour un nouvelélém entety ranger les inform ations fournies au clavier,
- affecter au pointeur contenu dans ce nouvelélém entl'ancienne valeur de de but,
- affecter à de butl'adresse de ce nouvelélém ent.
Nous conviendrons, de plus, que le dernier élém ent de la liste possè de un pointeur nul, ce qui nous facilitera
l'initialisation de l'algorith m e ;en effet, celle-ci se ram è ne alors à l'affectation à de butd'une valeur nulle.
Program m e
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LGNOM 20 /* longueur maximale d'un nom */
typedef struct element /* définition du type élément */
{ char nom [LGNOM+1] ; /* nom */
int age ; /* age */
struct element * suivant ; /* pointeur element suivant */
} t_element ;
main()
{
void creation (t_element * *) ; /* fonction de création de la liste */
void liste (t_element *) ; /* fonction de liste de la liste */
t_element * debut ; /* pointeur sur le début de la liste */
creation (&debut) ;
liste (debut) ;
}
V.Gestion dynam ique 189
/****************************************************/
/* fonction de création d'une liste chaînée */
/****************************************************/
void creation (t_element * * adeb)
{
char nomlu [LGNOM+1] ; /* pour lire un nom au clavier */
t_element * courant ; /* pour l'échange de valeurs de pointeurs */
* adeb = NULL ; /* liste vide au départ */
while (1) /* boucle de création apparemment infinie ... */
{ /* ... mais, en fait, interrompue sur "nom vide" */
printf ("nom : ") ;
gets (nomlu) ;
if (strlen(nomlu))
{ courant = (t_element *) malloc (sizeof(t_element)) ;
strcpy (courant->nom, nomlu) ;
printf ("age : ") ;
scanf ("%d", &courant->age) ;
getchar() ; /* pour sauter le n */
courant->suivant = * adeb ;
* adeb = courant ;
}
else break ; /* sortie boucle si nom vide */
}
}
/******************************************************/
/* fonction de liste d'une liste chaînée */
/******************************************************/
void liste (t_element * debut)
{
printf ("nn NOM AGEnn") ;
while (debut)
{ printf ("%15s %3dn", debut->nom, debut->age) ;
debut = debut->suivant ;
}
}
19 0 Exe rcices en langage C
Com m e ntaire s
*Nous avons ici ch oisi de déclarer notre structure à un niveau globaletde faire appelà type de f. Cette déclaration à un
niveau globalévite de devoir décrire la m ê m e structure en différents endroits, ce qui serait, non seulem ent laborieux
m ais, de surcroît, source d'erreurs. Par contre, le recours à type de fn'apporte qu'une sim plification des déclarations des
élém ents de ce type (dans le cas contraire, ilsuffiraitde rem placer t_e le m e ntpar structe le m e nt).
Notez bien, par contre, qu'iln'estpas possible de rem placer, au sein de la définition de notre structure, l'écriture :
struct element * suivant
par :
t_element * suivant
*La fonction de création reçoiten argum entl'adresse du pointeur de but, car elle doitpouvoir lui attribuer une valeur.
La fonction de liste, quant à elle, se contente de la valeur de ce m ê m e pointeur. Cette différence se répercute
naturellem entsur la m aniè re d'utiliser cetargum entdans ch acune des deux fonctions.
Notez d'ailleurs que nous avons pu nous perm ettre, dans la fonction de liste, de m odifier la valeur ainsi reçue (le pointeur
de buty décritsuccessivem entles différents élém ents de la liste).
*Là encore, les lectures au clavier ontété réalisées par scanfetge ts, donc sans protections particuliè res. Com m e nous
l'avons déjà signalé dans le précédentexercice, l'utilisation conjointe de ces deux fonctions pose un problè m e lié au fait
que, aprè s une lecture par scanf, le pointeur reste positionné sur le dernier caractè re non encore utilisé, à savoir
(généralem ent)n. C'estce qui justifie l'introduction d'une instruction ge tch ar pour absorber ce caractè re intem pestif.
VI: RECURSIVITE
La récursivité estune notion délicate m ais qui a l'avantage de conduire souventà des program m es sim ples.
Les trois prem iers exercices de ce ch apitre sontplutôtdes "exercices d'école" destinés à vous faire explorer différentes
situations en vous forçantà écrire une fonction récursive, là où, en pratique, on ne seraitpas am ené à le faire.
VI-1 le cture ré cursive (1)
________________________________________________________________________________________
Enoncé
Ecrire une fonction récursive de lecture d'une valeur entiè re au clavier. La fonction devra s'appeler elle-m ê m e dans le
cas où l'inform ation fournie estincorrecte (non num érique).
On prévoira une fonction à un argum ent(l'adresse de la variable pour laquelle on veutlire une valeur)etsans valeur de
retour.
On pourra faire appelà fge ts etsscanfpour détecter convenablem entles réponses incorrectes.
Re m arq ue
Nous vous conseillons de com parer cetexercice au suivantdans lequelle m ê m e problè m e estrésolu par l'em ploi d'une
fonction récursive sans argum entetavec valeur de retour.
Exe m ple
19 2 Exe rcices en langage C
donnez un nombre entier : un
** réponse incorrecte - redonnez-la : 'à
** réponse incorrecte - redonnez-la : 40
-- merci pour 40
________________________________________________________________________________________
ANALYSE
Au sein de la fonction (que nous nom m erons le cture ), nous lirons la valeur attendue à l'aide de fge ts (..., stdin), associé à
sscanf, com m e nous l'avons déjà faitdans certains des exercices précédents.
Nous considérerons la réponse de l'utilisateur com m e correcte lorsque le code de retour de sscanf sera égalà 1. Si tel
n'estpas le cas, nous ferons à nouveau appelà la m ê m e fonction le cture .
Program m e
#include <stdio.h>
#define LG_LIG 20 /* longueur maxi information lue au clavier */
main()
{
void lecture (int *) ; /* prototype fonction (récursive) de lecture */
int n ; /* entier à lire */
printf ("donnez un nombre entier : ") ;
lecture (&n) ;
printf ("-- merci pour %d", n) ;
}
void lecture (int *p)
{
int compte ; /* compteur du nb de valeurs OK */
char ligne[LG_LIG+1] ; /* pour lire une ligne au clavier par fgets */
/* +1 pour tenir compte du 0 de fin */
fgets (ligne, LG_LIG, stdin) ;
compte = sscanf (ligne, "%d", p) ;
if (!compte)
{ printf ("** réponse incorrecte - redonnez la : ") ;
lecture (p) ;
VI. Récursivité 19 3
}
}
Com m e ntaire s
*Notez bien qu'au sein de la fonction le cture , au niveau de l'appelde sscanf, nous voyons apparaître p et non & p,
puisque ici p estdéjà un pointeur sur la variable donton veutlire la valeur.
*Si nous avions utilisé sim plem entge ts (com m e dans l'exercice VI.5 de la prem iè re partie) au lieu de fge ts (..., stdin),
nous aurions pu égalem entnous protéger de m auvaises réponses de l'utilisateur, m ais nous aurions dû définir une taille
m axim ale pour la ch aîne lue au clavier ;nous aurions couru le risque de "débordem ent m ém oire", dans le cas où
l'utilisateur auraitfourni une réponse trop longue.
D ISCUSSIO N
Ch aque nouvelappelde le cture entraîne l'allocation autom atique, sur la pile, d'em placem ents pour :
- l'argum entp,
- les objets locaux : com pte etligne .
Or, en fait, ne sont nécessaires que les valeurs correspondant au dernier appelde le cture (celui où la lecture s'est
convenablem ent déroulée) ;dans ces conditions, l'em pilem ent des différents em placem ents alloués au tableau ligne est
superflu. Si l'on souh aite faire quelques économ ies d'espace m ém oire à ce niveau, on peut s'arranger pour que cet
em placem entne soitréservé qu'une seule fois :
- soit dans le program m e appelant (ici le program m e principal);dans ce cas, ilfaudra en transm ettre l'adresse en
argum ent, ce qui entraîne l'em pilem entd'une variable supplém entaire.
- soiten classe globale ;dans ce cas, on peutégalem enttraiter de la sorte com pte etp (c'est-à -dire , en fait, n), ce qui
supprim e du m ê m e coup tous les argum ents etles objets locaux de le cture . Notez qu'ilrestera quand m ê m e, à ch aque
appel, une allocation autom atique d'espace pour l'adresse de re tour.
- soiten classe statique (static)au sein de la fonction. Là encore, nous pouvons traiter de la m ê m e m aniè re la variable
com pte , la variable p, quantà elle, restantsoum ise aux em pilem ents.
19 4 Exe rcices en langage C
VI-2 Le cture ré cursive (2)
________________________________________________________________________________________
Enoncé
Ecrire une fonction récursive de lecture d'une valeur entiè re au clavier. La fonction devra s'appeler elle-m ê m e dans le
cas où l'inform ation fournie estincorrecte (non num érique).
On prévoira cette fois une fonction dans laquelle la valeur de retour estla valeur lue (iln'y aura donc pas d'argum ents).
Là encore, on pourra faire appelà fge ts (..., stdin)etsscanfpour détecter convenablem entles réponses incorrectes.
Re m arq ue
Cetexercice estsurtoutdestiné à ê tre com paré au précédentdans lequelle m ê m e problè m e estrésolu par l'em ploi d'une
fonction avec argum entetsans valeur de retour.
Exe m ple
donnez un nombre entier : un
** réponse incorrecte - redonnez la : 'à
** réponse incorrecte - redonnez la : 40
-- merci pour 40
________________________________________________________________________________________
ANALYSE
Com m e précédem m ent, au sein de notre fonction (nom m ée le cture ), nous lirons la valeur attendue à l'aide de fge ts
associé à sscanf. Nous considérerons la réponse de l'utilisateur com m e correcte lorsque le code de retour de sscanfsera
égalà 1. Si cela n'estpas le cas, nous ferons de nouveau appelà la m ê m e fonction le cture .
VI. Récursivité 19 5
Program m e
#include <stdio.h>
#define LG_LIG 20 /* longueur maxi information lue au clavier */
main()
{
int lecture (void) ; /* fonction (récursive) de lecture */
int n ; /* entier à lire */
printf ("donnez un nombre entier : ") ;
n = lecture() ;
printf ("-- merci pour %d", n) ;
}
int lecture (void)
{
int compte, /* compteur du nb de valeurs OK */
p ; /* entier à lire */
char ligne[LG_LIG+1] ; /* pour lire une ligne au clavier par fgets */
fgets (ligne, LG_LIG, stdin) ;
compte = sscanf (ligne, "%d", &p) ;
if (!compte)
{ printf ("** réponse incorrecte - redonnez-la : ") ;
p = lecture() ;
}
return(p) ;
}
Com m e ntaire s
*Cette fois, on notera que p désigne une variable locale de type int, dontl'em placem entest alloué autom atiquem ent à
ch aque appelde la fonction le cture , de la m ê m e m aniè re que pour les autres objets locaux com pte etligne . Par ailleurs,
si aucun em placem entn'estalloué ici pour un quelconque argum ent, ilfauten prévoir un pour la valeur de retour. On
rem arque d'ailleurs qu'ici cette valeur se trouve "propagée" de proch e en proch e, lors du "dépilem ent" des appels.
*Prenez garde à ne pas écrire :
19 6 Exe rcices en langage C
if (!compte)
{ printf ("** réponse incorrecte - redonnez-la : ") ;
p = lecture() ;
}
else return (p) ;
car la fonction ne renverrait une valeur que lorsque la lecture se serait déroulée convenablem ent. Notez d'ailleurs que
dans ce cas, bon nom bre de com pilateurs vous préviendraitpar un m essage d'avertissem ent("w arning").
Par contre, ilseraittoutà faitcorrect(etéquivalent)d'écrire :
if (!compte)
{ printf ("** réponse incorrecte - redonnez la : ") ;
return (lecture()) ;
}
else return (p) ;
D ISCUSSIO N
Les rem arques faites dans le précédent exercice à propos des em pilem ents de ligne (et éventuellem ent com pte )
s'appliquentencore ici.
VI-3 Le cture ré cursive (3)
________________________________________________________________________________________
Enoncé
Ecrire une fonction récursive de lecture d'un entier au clavier. La fonction devra s'appeler elle-m ê m e dans le cas où
l'inform ation fournie estincorrecte.
Cette fois, la fonction possédera 3 argum ents :
- le m essage qu'elle doit im prim er avant de lire une valeur (le m essage "donnez un nom bre entier :" ne sera donc
plus affich é par le program m e principal),
- l'adresse de la variable dans laquelle on doitlire une valeur,
- le nom bre m axim ald'essais autorisés.
VI. Récursivité 19 7
Elle fournira un code de retour égalà 0 si la lecture a fini par aboutir età -1 lorsque la lecture n'a pas pu aboutir dans le
nom bre d'essais im partis.
Com m e dans les deux précédents exercices, on fera appelà fge ts associée à sscanf.
Exe m ple s
donnez un nombre entier : huit
** réponse incorrecte - redonnez-la : 8
-- merci pour 8
____________________
donnez un nombre entier : un
** réponse incorrecte - redonnez-la : deux
** réponse incorrecte - redonnez-la : trois
** réponse incorrecte - redonnez-la : quatre
** réponse incorrecte - redonnez-la : cinq
-- nombre d'essais dépassé
________________________________________________________________________________________
ANALYSE
Le m essage à im prim er sera transm is sous form e de l'adresse d'une ch aîne. La fonction affich era ce m essage dè s son
appel. Son contenu devra donc ê tre :
donne z un nom bre e ntie r :
dans l'appelinitialde la fonction (réalisé dans le program m e principal), et:
**réponse incorre cte - re donne z -a :
dans l'appelde la fonction par elle-m ê m e en cas de réponse incorrecte.
En ce qui concerne le nom bre m axim ald'appels, on le transm ettra par valeur et on s'arrangera pour faire décroître sa
valeur de 1 à ch aque appel.
La récursivité des appels cessera lorsque l'une des deux conditions suivantes sera satisfaite :
- valeur lue correcte - on fournira alors 0 com m e valeur de retour,
19 8 Exe rcices en langage C
- nom bre m axim ald'appels dépassé - on fournira alors -1 com m e valeur de retour.
Program m e
#include <stdio.h>
#define LG_LIG 20 /* longueur maxi information lue au clavier */
main()
{
int lecture (char *, int *, int) ; /* proto fonction (récursive) de lecture */
int n ; /* entier à lire */
const nessais = 5 ; /* nombre d'essais autorisés */
if ( lecture ("donnez un nombre entier : ", &n, nessais) != -1)
printf ("-- merci pour %d", n) ;
else printf ("-- nombre d'essais dépassés") ;
}
int lecture (char * mes, int * p, int nmax)
/* mes : adresse message à afficher avant lecture */
/* p : adresse de la valeur à lire */
/* nmax : nombre d'essais autorisés */
{
int compte ; /* compteur du nb de valeurs OK */
char ligne [LG_LIG] ; /* pour lire une ligne au clavier par fgets */
printf ("%s", mes) ;
fgets (ligne, LG_LIG, stdin) ;
compte = sscanf (ligne, "%d", p) ;
if (!compte)
if (--nmax)
return (lecture ("** réponse incorrecte - redonnez la : ",
p, nmax) ) ;
else return (-1) ;
else return (0) ;
}
Com m e ntaire s
*Nous avons ch oisi ici de faire affich er le m essage :
nom bre d'essais dépassé
VI. Récursivité 19 9
dans le program m e principal. Ils'agitlà d'un ch oix arbitraire puisque nous aurions toutaussi bien pu le faire affich er par
la fonction elle-m ê m e.
VI-4 Puissance e ntiè re
________________________________________________________________________________________
Enoncé
Ecrire une fonction récursive perm ettantde calculer la valeur de x
k
pour x réelquelconque etk entier relatifquelconque.
On exploitera les propriétés suivantes :
x
0
= 1,
x
k
= x pour k = 1,
x
-k
= 1 /x
k
pour k positif,
x
k
= (x
k -1
)x pour k positifim pair,
x
k
= (x
k /2
)x pour k positifpair.
On testera cette fonction à l'aide d'un program m e principalperm ettantà l'utilisateur de fournir en données les valeurs de
x etde k .
Exe m ple s
donnez une valeur réelle : 4
donnez une puissance entière : -2
4.000000e+000 à la puissance -2 = 6.250000e-002
_______________________
donnez une valeur réelle : 5.2
200 Exe rcices en langage C
donnez une puissance entière : 3
5.200000e+000 à la puissance 3 = 1.406080e+002
________________________________________________________________________________________
ANALYSE
L'énoncé fournitles "définitions récursives" à em ployer.
Program m e
#include <stdio.h>
main()
{
double puissance(double, int) ; /* proto fonction d'élévation à la puissance */
double x ; /* valeur dont on cherche la puissance neme */
int n ; /* puissance à laquelle on veut élever x */
printf ("donnez une valeur réelle : ") ;
scanf ("%le", &x) ;
printf ("donnez une puissance entière : ") ;
scanf ("%d", &n) ;
printf ("%le à la puissance %d = %le", x, n, puissance (x, n) ) ;
}
double puissance (double x, int n)
{
double z ;
if (n < 0) return (puissance (1.0/x, -n) ) ; /* puissance négative */
else if (n == 0) return (1) ; /* x puissance 0 égale 1 */
else if (n == 1) return (x) ; /* x puissance 1 égale x */
else if (n%2 == 0)
{ z = puissance (x, n/2) ; /* puissance paire */
return (z*z) ;
}
else return (x * puissance (x, n-1) ) ; /* puissance impaire */
VI. Récursivité 201
}
Com m e ntaire s
Ilestpréférable d'écrire :
z = puissance (x, n/2) ;
return (z*z) ;
plutôtque :
return (puissance (x,n/2) * puissance (x,n/2) ) ;
qui produiraitdeux fois plus d'appels de la fonction puissance .
VI-5 Fonction d'Ack e rm ann
________________________________________________________________________________________
Enoncé
Ecrire une fonction récursive calculant la valeur de la fonction d'Ack erm ann, définie pour m et n, entiers positifs ou
nuls, par :
A(m ,n)=A(m -1, A(m ,n-1)) pour m > 0 etn> 0,
A(0,n)=n+ 1 pour n> 0,
A(m ,0)=A(m -1,1) pour m > 0.
Cette fonction possédera en argum entles valeurs de m etde n etfournira en résultatla valeur de A correspondante.
On visualisera l'em pilem ent des appels et leur dépilem ent en affich ant un m essage accom pagné de la valeur des deux
argum ents lors de ch aque entrée dans la fonction ainsi que juste avant sa sortie (dans ce dernier cas, on affich era
égalem entla valeur que la fonction s'apprê te à retourner).
On testera cette fonction à l'aide d'un program m e principalauquelon fournira en données les valeurs de m etde n.
202 Exe rcices en langage C
Exe m ple
valeurs de m et n ? : 1 1
** entrée Acker (1, 1)
** entrée Acker (1, 0)
** entrée Acker (0, 1)
-- sortie Acker (0, 1) = 2
-- sortie Acker (1, 0) = 2
** entrée Acker (0, 2)
-- sortie Acker (0, 2) = 3
-- sortie Acker (1, 1) = 3
Acker (1, 1) = 3
________________________________________________________________________________________
Program m e
#include <stdio.h>
main()
{
int m, n, a ;
int acker (int, int) ; /* prototype fonction de calcul fonction d'Ackermann */
printf ("valeurs de m et n ? : ") ;
scanf ("%d %d", &m, &n) ;
a = acker (m, n) ;
printf ("nnAcker (%d, %d) = %d", m, n, a) ;
}
/***********************************************************/
/* fonction récursive de calcul de la fonction d'Ackermann */
/***********************************************************/
int acker (int m, int n)
{
int a ; /* valeur de la fonction */
printf ("** entrée Acker (%d, %d)n", m, n) ;
VI. Récursivité 203
if (m<0 || n<0)
a = -1 ; /* cas arguments incorrects */
else if (m == 0)
a = n+1 ;
else if (n == 0)
a = acker (m-1, 1) ;
else
a = acker (m-1, acker(m, n-1) ) ;
printf ("-- sortie Acker (%d, %d) = %dn", m, n, a) ;
return (a) ;
}
VI-6 Tours de H anoi
________________________________________________________________________________________
Enoncé
Réaliser une fonction récursive proposantune solution au problè m e ditdes tours de H anoi, lequels'énonce ainsi :
On dispose de trois piquets, num érotés 1, 2 et 3 et de n disques de tailles différentes. Au départ, ces disques sont
em pilés par taille décroissante sur le piquetnum éro 1. Le butdu jeu estde déplacer ces n disques du piquetnum éro 1
sur le piquetnum éro 3, en respectantles contraintes suivantes :
- on ne déplace qu'un seuldisque à la fois (d'un piquetà un autre),
- un disque ne doitjam ais ê tre placé au-dessus d'un disque plus petitque lui.
On testera cette fonction avec un program m e principalperm ettant de ch oisir, en donnée, le nom bre totalde disques à
déplacer (n).
Si vous n'ê tes pas fam iliarisé avec ce type de problè m e, nous vous conseillons de tenter tout d'abord de le résoudre
m anuellem entavantde ch erch er à program m er la fonction dem andée.
Exe m ple
combien de disques ? 4
déplacer un disque de 1 en 2
204 Exe rcices en langage C
déplacer un disque de 1 en 3
déplacer un disque de 2 en 3
déplacer un disque de 1 en 2
déplacer un disque de 3 en 1
déplacer un disque de 3 en 2
déplacer un disque de 1 en 2
déplacer un disque de 1 en 3
déplacer un disque de 2 en 3
déplacer un disque de 2 en 1
déplacer un disque de 3 en 1
déplacer un disque de 2 en 3
déplacer un disque de 1 en 2
déplacer un disque de 1 en 3
déplacer un disque de 2 en 3
________________________________________________________________________________________
ANALYSE
Pour n=1, la solution estévidente ;ilsuffitde déplacer l'unique disque du piquetnum éro 1 au piquetnum éro 3.
Dè s que n est supérieur à 1, on rem arque qu'ilest nécessaire d'utiliser le piquet num éro 2 pour des stock ages
interm édiaires. On peut considérer que le problè m e consiste à déplace r n disques du piquet num é ro 1 ve rs le piquet
num é ro 3, e n utilisant le piquet num é ro 2 com m e piquet inte rm é diaire . On peut m ontrer que cette opération se
décom pose en trois opérations plus sim ples :
- déplacer les n-1 disques supérieurs du piquet num éro 1 vers le piquet num éro 2 ;pendant cette ph ase, on peut
utiliser le piquetnum éro 3 com m e piquetinterm édiaire,
- déplacer les n-1 disques du piquetnum éro 2 vers le piquetnum éro 3 ;là encore, on peututiliser le piquetnum éro 1
com m e piquet interm édiaire (le disque initialem ent présent sur ce piquet étant plus grand que tous les disques à
déplacer).
Cela nous conduità la réalisation d'une fonction récursive possédantcom m e argum ents :
- le nom bre de disques à déplacer,
- le num éro du piquet"de départ",
- le num éro du piquet"d'arrivée",
- le num éro du piquet"interm édiaire".
VI. Récursivité 205
Program m e
#include <stdio.h>
main()
{
void hanoi (int, int, int, int) ;
int nd ; /* nombre total de disques */
printf ("combien de disques ? ") ;
scanf ("%d", &nd) ;
hanoi (nd, 1, 3, 2) ;
}
/***********************************************/
/* fonction résolvant le pb des tours de hanoi */
/***********************************************/
void hanoi (int n, int depart, int but, int inter)
/* n : nombre de disques à déplacer */
/* depart : tour d'où l'on part */
/* but : tour où l'on arrive */
/* inter : tour intermédiaire */
{
if (n>0)
{ hanoi (n-1, depart, inter, but) ;
printf ("déplacer un disque de %d en %dn", depart, but) ;
hanoi (n-1, inter, but, depart) ;
}
}
VII: TRAITEM ENT D E
FICH IERS
Les exercices de ce ch apitre vous fournissentdes exem ples classiques de traitem entde fich iers correspondantà différents
aspects :
- traitem entséquentiel,
- accè s direct,
- fich iers de type texte.
VII-1 Cré ation s é q ue ntie lle de fich ie r
________________________________________________________________________________________
Enoncé
Ecrire un program m e de création séquentielle d'un fich ier com portant, pour un certain nom bre de personnes, les
inform ations suivantes, fournies au clavier :
- nom (au m axim um 20 caractè res),
- â ge,
- nom bre d'enfants,
- â ge de ch acun des différents enfants ;on ne dem andera (etdonc on n'enregistrera)que l'â ge des 15 prem iers enfants
(m ais le nom bre figurantdans le ch am p précédentpourra ê tre supérieur à 15).
L'utilisateur fournira un nom "vide" pour signaler qu'iln'a plus de personnes à enregistrer.
208 Exe rcices en langage C
On ne prévoira aucun contrôle particulier au niveau de la saisie des données
Exe m ple
donnez le nom du fichier à créer : person
----- pour terminer la saisie, donnez un nom 'vide' ---
nom : dubois
age : 32
nombre enfants : 1
age enfant no 1 : 7
nom : dunoyer
age : 29
nombre enfants : 0
nom : dutronc
age : 45
nombre enfants : 3
age enfant no 1 : 21
age enfant no 2 : 18
age enfant no 3 : 17
nom :
-------- FIN CREATION FICHIER ----------
________________________________________________________________________________________
ANALYSE
La structure de ch aque enregistrem ent du fich ier découle de l'énoncé. Cependant, en ce qui concerne la m aniè re de
représenter le nom des personnes, nous devons décider de la présence ou de l'absence du caractè re de fin de ch aîne (0).
Ici, nous avons ch oisi, par facilité, d'introduire ce caractè re, ce qui im plique que la zone correspondante soitde longueur
21.
Pour créer notre fich ier, nous utiliserons les fonctions de niveau 2, c'est-à -dire ici fope n etfw rite . Rappelons que celles-
ci travaillentavec un pointeur sur une structure de type FILE (prédéfini dans stdio.h ). La valeur de ce pointeur nous est
fournie par fope n ;cette fonction restitue un pointeur nulen cas d'erreur d'ouverture.
La création du fich ier consiste sim plem entà répéter les actions :
VII. Traite m e ntde fich ie rs 209
- lecture d'inform ations au clavier,
- écriture de ces inform ations dans le fich ier.
Cette répétition doitê tre interrom pue à la rencontre d'un nom vide.
Program m e
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define LGNOM 20 /* longueur maxi d'un nom */
#define NBENFMAX 15 /* nombre maxi d'enfants */
#define LNOMFICH 20 /* longueur maxi nom de fichier */
main()
{ char nomfich [LNOMFICH+1] ; /* nom du fichier à créer */
FILE * sortie ; /* descripteur fichier (niveau 2) */
struct { char nom [LGNOM+1] ;
int age ; /* description d'un enregistrement */
int nbenf ; /* du fichier */
int agenf [NBENFMAX] ;
} bloc ;
int i ;
/* ouverture fichier à créer */
/* attention : mode d'ouverture w au lieu de wb dans certains cas */
printf ("donnez le nom du fichier à créer : ") ;
gets (nomfich) ;
if ( (sortie = fopen (nomfich, "w")) == NULL )
{ printf ("***** erreur ouverture - abandon programme") ;
exit(-1) ;
}
/* création du fichier à partir d'informations */
/* fournies au clavier */
printf ("----- pour terminer la saisie, donnez un nom 'vide' ---n") ;
do
{ printf ("nom : ") ; /* saisie nom */
gets (bloc.nom) ;
if ( strlen(bloc.nom) == 0) break ; /* sortie boucle si nom vide */
printf ("age : ") ;
scanf ("%d", &bloc.age) ; /* saisie age */
210 Exe rcices en langage C
printf ("nombre enfants : ") ;
scanf ("%d", &bloc.nbenf) ; /* saisie nb enfants */
for (i=0 ; i < bloc.nbenf && i < NBENFMAX ; i++)
{ printf ("age enfant no %d : ", i+1) ; /* saisie age des */
scanf ("%d", &bloc.agenf[i]) ; /* différents enfants */
}
getchar() ; /* pour éliminer n */
printf ("n") ;
fwrite (&bloc, sizeof(bloc), 1, sortie) ; /* écriture fichier */
}
while (1) ;
/* fin création */
fclose(sortie) ;
printf ("n -------- FIN CREATION FICHIER ----------") ;
}
Com m e ntaire s
*Notez le "m ode d'ouverture" w b :
w : ouverture en écriture ;si le fich ier n'existe pas, ilestcréé. S'ilexiste, son ancien contenu estperdu.
b : m ode dit"binaire" ou "non translaté".
En fait, l'indication b ne se justifie que dans les im plém entations qui distinguentles fich iers de texte des autres. Une telle
distinction estm otivée par le faitque le caractè re de fin de ligne (n) possè de, sur certains systè m es, une représentation
particuliè re obtenue par la succession de deux caractè res. La présence de b évite le risque que le fich ier concerné soit
considéré com m e un fich ier de type texte, ce qui am è nerait une interprétation non souh aitée des couples de caractè res
représentantune fin de ligne.
*Ici, nous avons faitappelà la fonction e xit(son prototype figure dans stdlib.h ) pour interrom pre le program m e en cas
d'erreur d'ouverture du fich ier. Ils'agitlà d'un ch oix arbitraire. Nous aurions pu dem ander à l'utilisateur de proposer un
autre nom de fich ier.
*En ce qui concerne la boucle de création du fich ier, nous avons ch oisi de la program m er sous form e d'une boucle
infinie :
do
.......
.......
VII. Traite m e ntde fich ie rs 211
while (1) ;
que nous interrom pons au m om ent opportun par bre ak . Nous aurions pu égalem ent ch oisir d'introduire les prem iè res
instructions de la boucle dans l'expression conditionnantune instruction w h ile , de cette m aniè re :
while (printf("nom : "), gets(bloc.nom), strlen(bloc.mot) )
*Com m e prévu par l'énoncé, aucun contrôle particulier n'est effectué sur les données qui sont donc lues par scanf et
ge ts. Là encore se pose le problè m e d'ignorer le n qui subsiste aprè s une lecture par scanf, ce qui im pose d'introduire
artificiellem entune instruction ge tch ar (pour plus de détails sur ce problè m e, voyez les com m entaires de l'exercice V-3).
*Rappelons que la fonction d'écriture dans le fich ier (fw rite )possè de 4 argum ents :
- L'adresse de débutd'un ensem ble de blocs à écrire (notez bien la notation & bloc et non sim plem ent bloc, dans la
m esure où le nom d'une structure désigne sa valeur etnon son adresse, com m e cela estle cas pour un tableau).
- La taille d'un bloc. Notez qu'ici nous avons utilisé la fonction size of, ce qui assure la portabilité du program m e.
- Le nom bre de blocs de cette taille à écrire (ici, 1).
- L'adresse de la structure décrivantle fich ier (elle a été fournie par fope n).
D ISCUSSIO N
*Ce program m e n'exam ine pas le code de retour de fw rite , lequelprécise le nom bre de blocs réellem entécrits dans le
fich ier (ce nom bre étantinférieur au nom bre souh aité en cas d'erreur d'écriture). Ilfauttoutefois noter, à ce propos, que,
généralem ent, un certain nom bre d'erreurs sont "récupérées" par le systè m e qui affich e alors lui-m ê m e son propre
m essage.
*Com m e le prévoyait l'énoncé, ce program m e n'est pas protégé d'éventuelles erreurs dans les réponses fournies par
l'utilisateur. A titre indicatif, voici quelques situations que l'on peutrencontrer :
- Si l'utilisateur fournitun nom de fich ier de plus de 20 caractè res, ily aura écrasem entd'inform ations en m ém oire.
Ici, ilserait toutefois assez facile de rem édier à ce problè m e en attribuant au sym bole LNO M FICH une valeur
supérieure au nom bre de caractè res que l'on peut frapper au clavier dans l'im plém entation concernée. On pourrait
égalem entlire un nom bre de caractè res lim ités en utilisant, au lieu de ge ts (nom fich ), l'instruction :
fgets (nomfich, LNOMFICH, stdin) ;
Notez toutefois que, dans ce cas, les caractè res supplém entaires frappés éventuellem entpar l'utilisateur sur la m ê m e
"ligne" seraientpris en com pte par une proch aine instruction de lecture sur l'entrée standard.
212 Exe rcices en langage C
Dans certaines im plém entations (notam m ent Turbo/Borland C et C/Quick C M icrosoft), ilest possible de régler
com plè tem ent le problè m e en utilisant l'instruction cge ts qui a le m érite de lim iter, non seulem ent le nom bre de
caractè res pris en com pte, m ais égalem entceux effectivem entfrappés au clavier.
- Si l'utilisateur fournit plus de caractè res que n'en attend scanf, ceux-ci seront utilisés (avec plus ou m oins de
bonh eur) par une lecture suivante. Là encore, le problè m e ne peut ê tre convenablem ent réglé que d'une façon
dépendant de l'im plém entation, par exem ple avec la fonction cge ts (associée, cette fois, à sscanf) citée
précédem m ent.
- Si l'utilisateur fournit des caractè res non num ériques là où scanf attend des ch iffres, le résultat de la lecture sera
arbitraire ;le program m e ne s'en apercevra pas puisqu'ilne teste pas le code de retour de scanf(qui fournitle nom bre
de valeurs effectivem entlues). De plus, là encore, les caractè res non traités serontrepris par une lecture ultérieure.
Le prem ier point peut, là encore, ê tre résolu par l'em ploi de sscanf, associé à fge ts (..., stdin). Là encore, dans
certaines im plém entations, cge ts (associée à sscanf)perm etde régler totalem entle problè m e.
VII-2 Liste s é q ue ntie lle d'un fich ie r
________________________________________________________________________________________
Enoncé
Réaliser un program m e perm ettant d'affich er successivem ent ch acun des enregistrem ents d'un fich ier analogue à ceux
créés par le program m e précédent. Le program m e présentera un seulenregistrem entà la fois, accom pagné d'un num éro
précisant son rang dans le fich ier (on attribuera le num éro 1 au prem ier enregistrem ent);ilattendra que l'utilisateur
frappe la touch e re turn avantde passer à l'enregistrem entsuivant.
L'affich age des inform ations sera réalisé par une fonction à laquelle on transm ettra en argum ent l'enregistrem ent à
affich er etson num éro. Le m odè le m ê m e de la structure correspondante sera, quantà lui, défini à un niveau global.
Le program m e devra s'assurer de l'existence du fich ier à lister.
Exe m ple
donnez le nom du fichier à lister : person
enregistrement numéro : 1
NOM : dubois
AGE : 32
NOMBRE D'ENFANTS : 1
VII. Traite m e ntde fich ie rs 213
AGE ENFANT 1 : 7
enregistrement numéro : 2
NOM : dunoyer
AGE : 29
NOMBRE D'ENFANTS : 0
enregistrement numéro : 3
NOM : dutronc
AGE : 45
NOMBRE D'ENFANTS : 3
AGE ENFANT 1 : 21
AGE ENFANT 2 : 18
AGE ENFANT 3 : 17
-------- FIN LISTE FICHIER ----------
________________________________________________________________________________________
Program m e
#include <stdio.h>
#include <string.h>
#define LGNOM 20 /* longueur maxi d'un nom */
#define NBENFMAX 15 /* nombre maxi d'enfants */
#define LNOMFICH 20 /* longueur maxi nom de fichier */
struct enreg { char nom [LGNOM+1] ;
int age ;
int nbenf ;
int agenf [NBENFMAX] ;
} ;
214 Exe rcices en langage C
main()
{
void affiche (struct enreg *, int) ; /* fonction d'affichage */
char nomfich [LNOMFICH+1] ; /* nom du fichier à lister */
FILE * entree ; /* descripteur fichier (niveau 2) */
struct enreg bloc ; /* enregistrement fichier */
int num ; /* numéro d'enregistrement */
/* ouverture fichier à lister */
/* attention : mode d'ouverture : r au lieu de rb dans certains cas */
do
{ printf ("donnez le nom du fichier à lister : ") ;
gets (nomfich) ;
if ( (entree = fopen (nomfich, "rb")) == 0 )
printf ("fichier non trouvén") ;
}
while (!entree) ;
/* liste du fichier */
num = 1 ;
while (fread(&bloc, sizeof(bloc), 1, entree), ! feof(entree) )
{ affiche (&bloc, num++) ;
getchar() ; /* attente frappe "return" */
}
/* fin liste */
fclose(entree) ;
printf ("nn -------- FIN LISTE FICHIER ----------") ;
}
/*************************************************/
/* fonction d'affichage d'un enregistrement */
/*************************************************/
void affiche (struct enreg * bloc, int num)
{
int i ;
printf ("nnenregistrement numéro : %dnn", num) ;
printf ("NOM : %sn", bloc->nom) ;
printf ("AGE : %dn", bloc->age) ;
printf ("NOMBRE D'ENFANTS : %dn", bloc->nbenf) ;
for (i=0 ; i < bloc->nbenf && i < NBENFMAX ; i++)
VII. Traite m e ntde fich ie rs 215
printf ("AGE ENFANT %2d : %2dn", i+1, bloc->agenf[i]) ;
}
Com m e ntaire s
*Notez le m ode d'ouverture rb :
r : ouverture en lecture. Si le fich ier n'existe pas, fope n fournitun pointeur nul.
b : ouverture en m ode "binaire" ou "non translaté" (pour plus d'inform ations sur la différence entre les m odes
translaté etnon translaté, voyez les com m entaires de l'exercice VII-1).
*Rappelons que la fonction de lecture fre ad possè de 4 argum ents, com parables à ceux de fw rite :
- l'adresse de débutd'un ensem ble de blocs à lire,
- la taille d'un bloc (en octets),
- le nom bre de blocs de cette taille à lire,
- l'adresse de la structure décrivantle fich ier (elle a été fournie par fope n).
*La fonction fe ofprend la valeur vrai (1) lorsque la fin de fich ier a été effectivem ent rencontrée. Autrem ent dit, ilne
suffitpas, pour détecter la fin d'un fich ier, d'avoir sim plem ent lu son dernier octet;ilest, de plus, nécessaire d'avoir
tenté de lire au-delà . C'estce qui justifie que cette condition soitexam inée aprè s fre ad etnon avant.
*Voyez la façon dontnous avons program m é la boucle de lecture des différents enregistrem ents du fich ier. Cela nous
évite une sortie en cours de boucle par bre ak , com m e dans :
do
{ fread (&bloc, sizeof(bloc), 1, entree) ;
if (feof(entree)) break ;
affiche (&bloc, num++) ;
getchar() ;
}
while (1) ;
ou un testsupplém entaire dans la boucle com m e dans :
216 Exe rcices en langage C
do
{ fread (&bloc, sizeof(bloc), 1, entree) ;
if (!feof(entree))
{ affiche (&bloc, num++) ;
getchar ;
}
}
while (!feof(entree)) ;
D ISCUSSIO N
*Ce program m e n'exam ine pas le code de retour de fre ad (celui-ci précise le nom bre de blocs réellem entlus).
*Notre program m e n'estpas protégé contre la fourniture par l'utilisateur d'un nom de fich ier de plus de 20 caractè res.
Voyez la discussion de l'exercice précédent.
*Le passage à l'enregistrem entsuivantestdéclench é par la frappe de re turn. M ais si l'utilisateur frappe un ou plusieurs
caractè res (validés par return), ilverra défiler plusieurs enregistrem ents de suite. La solution à ce problè m e dépend, ici
encore, de l'im plém entation. Par exem ple, dans un environnem ent DOS, avec Turbo/Borland C/C+ + ou Quick C/C
M icrosoft, ilsuffira de "vider le tam pon du systè m e" par :
while (kbhit()) getch ;
avantch aque attente.
VII-3 Corre ction de fich ie r
________________________________________________________________________________________
Enoncé
Réaliser un program m e perm ettantd'effectuer des corrections sur un fich ier analogue à ceux créés par le program m e de
l'exercice VII-1.
VII. Traite m e ntde fich ie rs 217
L'utilisateur désignera un enregistrem ent par son num éro d'ordre dans le fich ier. Le program m e s'assurera de son
existence et l'affich era d'abord telquelavant de dem ander les m odifications à lui apporter. Ces derniè res seront
effectuées ch am p par ch am p. Pour ch aque ch am p, le program m e en affich era à nouveau la valeur, puis ildem andera à
l'utilisateur d'entrer une éventuelle valeur de rem placem ent. Si aucune m odification n'est souh aitée, ilsuffira à ce
dernier de répondre directem entpar la frappe de re turn.
On prévoira deux fonctions :
- une pour l'affich age d'un enregistrem ent(on pourra reprendre la fonction affich e de l'exercice précédent),
- une pour la m odification d'un enregistrem ent.
Exe m ple
donnez le nom du fichier à modifier : person
numéro enregistrement à modifier (0 pour fin) : 14
numéro enregistrement à modifier (0 pour fin) : 2
enregistrement numéro : 2
NOM : dunoyer
AGE : 29
NOMBRE D'ENFANTS : 0
entrez vos nouvelles infos (return si pas de modifs)
NOM : Dunoyer
AGE :
NOMBRE D'ENFANTS : 1
AGE ENFANT 1 : 15
numéro enregistrement à modifier (0 pour fin) : 0
-------- FIN MODIFICATIONS FICHIER ----------
________________________________________________________________________________________
218 Exe rcices en langage C
ANALYSE
A partir du m om entoù l'on souh aite retrouver un enregistrem entpar son rang dans le fich ier, ilparaîtlogique de réaliser
un "accè s direct". Rappelons qu'en langage C celui-ci s'obtienten agissantsur la valeur d'un pointeur dans le fich ier à
l'aide de la fonction fseek. La lecture et l'écriture, quant à elles, restent toujours réalisées par les fonctions fre ad et
fw rite .
L'énoncé ne nous im pose pas de contrôle sur l'inform ation lue au clavier. Néanm oins, nous devons ê tre en m esure
d'accepter etde reconnaître com m e telle une "réponse vide". Dans ces conditions, nous ne pouvons pas em ployer scanf
qui risqueraitde conduire à un bouclage sur le caractè re n.
Une solution à un telproblè m e consiste à lire tout d'abord la réponse de l'utilisateur sous form e d'une ch aîne, ce qui
perm etde déceler convenablem entles réponses vides. Si l'on souh aite une solution dépendante de l'im plém entation, cela
peutse faire soitavec ge ts, soit(si l'on souh aite lim iter le nom bre de caractè res pris en com pte)avec fge ts (..., stdin).Ici,
nous utiliserons la prem iè re possibilité, en faisant appel à une zone de 128 caractè res (dans bon nom bre
d'im plém entations, on ne peutpas frapper au clavier de "lignes" plus longues!).
Lorsqu'une inform ation num érique estattendue, ilnous suffitalors de "décoder" le contenu de cette ch aîne. Cela peutse
faire, soitavec la fonction sscanfassortie (ici)d'un form at%d, soitavec la fonction standard atoi. Par souci de diversité,
nous avons ch oisi ici la seconde.
Program m e
#include <stdio.h>
#include <string.h>
#define VRAI 1 /* pour simuler ..... */
#define FAUX 0 /* ..... des booléens */
#define LGNOM 20 /* longueur maxi d'un nom */
#define NBENFMAX 15 /* nombre maxi d'enfants */
#define LNOMFICH 20 /* longueur maxi nom de fichier */
struct enreg { char nom [LGNOM+1] ;
int age ;
int nbenf ;
int agenf [NBENFMAX] ;
} ;
main()
{
VII. Traite m e ntde fich ie rs 219
void affiche (struct enreg *, int) ; /* fonction d'affichage */
void modifie (struct enreg *) ; /* fonction de modif d'un enreg */
char nomfich [LNOMFICH+1] ; /* nom du fichier à lister */
FILE * fichier ; /* descripteur fichier (niveau 2) */
struct enreg bloc ; /* enregistrement fichier */
int num, /* numéro d'enregistrement */
horsfich ; /* indicateur "logique" */
long nb_enreg, /* nbre d'enregistrements du fichier */
pos ; /* position courante (octets) dans fich */
/* ouverture (en mise à jour) fichier à modifier et calcul de sa taille */
/* attention, mode d'ouverture r+ au lieu de r+b dans certains cas */
do
{ printf ("donnez le nom du fichier à modifier : ") ;
gets (nomfich) ;
if ( (fichier = fopen (nomfich, "r+b")) == 0 )
printf ("fichier non trouvén") ;
}
while (! fichier) ;
fseek (fichier, 0, 2) ;
nb_enreg = ftell (fichier) / sizeof(bloc) ;
/* boucle de corrections d'enregistrements */
/* jusqu'à demande d'arrêt */
do
{ do
{ printf ("nnuméro enregistrement à modifier (0 pour fin) : ");
scanf ("%d", &num) ;
getchar() ; /* pour sauter le dernier n" */
horsfich = num < 0 || num > nb_enreg ;
}
while (horsfich) ;
if (num == 0 ) break ; /* sortie boucle si demande arrêt */
pos = (num-1) * sizeof(bloc) ; /* calcul position courante */
fseek (fichier, pos, 0) ; /* positionnement fichier */
fread (&bloc, sizeof(bloc), 1, fichier) ; /* lecture enreg */
affiche (&bloc, num) ; /* affichage enreg */
modifie (&bloc) ; /* modif enreg */
fseek (fichier, pos, 0) ; /* repositionnement fichier */
fwrite (&bloc, sizeof(bloc), 1, fichier) ; /* réécriture enreg */
220 Exe rcices en langage C
}
while (1) ;
/* fin modifications */
fclose(fichier) ;
printf ("nn -------- FIN MODIFICATIONS FICHIER ----------n") ;
}
/*************************************************/
/* fonction d'affichage d'un enregistrement */
/*************************************************/
void affiche (struct enreg * bloc, int num)
{
int i ;
printf ("nnenregistrement numéro : %dnn", num) ;
printf ("NOM : %sn", bloc->nom) ;
printf ("AGE : %dn", bloc->age) ;
printf ("NOMBRE D'ENFANTS : %dn", bloc->nbenf) ;
for (i=0 ; i < bloc->nbenf && i < NBENFMAX ; i++)
printf ("AGE ENFANT %2d : %2dn", i+1, bloc->agenf[i]) ;
}
/***************************************************/
/* fonction de modification d'un enregistrement */
/***************************************************/
void modifie (struct enreg * bloc)
{
char ligne[127] ; /* chaîne de lecture d'une ligne d'écran */
int i ;
printf ("nnentrez vos nouvelles infos (return si pas de modifs)n") ;
printf ("NOM : ") ;
gets (ligne) ;
if (strlen(ligne)) strcpy (bloc->nom, ligne) ;
printf ("AGE : ") ;
gets (ligne) ;
if (strlen(ligne)) bloc->age = atoi(ligne) ;
printf ("NOMBRE D'ENFANTS : ") ;
VII. Traite m e ntde fich ie rs 221
gets (ligne) ;
if (strlen(ligne)) bloc->nbenf = atoi(ligne) ;
for (i=0 ; i < bloc->nbenf && i < NBENFMAX ; i++)
{ printf ("AGE ENFANT %2d : ", i+1) ;
gets (ligne) ;
if (strlen(ligne)) bloc->agenf[i] = atoi(ligne) ;
}
}
Com m e ntaire s
*Nous avons ouvertle fich ier dans le m ode r+ b, lequelautorise la m ise à jour (lecture etécriture en un em placem ent
quelconque du fich ier). Notez qu'ilfaut éviter d'écrire ici rb+ , ce qui ne provoquerait généralem ent pas d'erreur
d'ouverture, m ais qui em pê ch erait toute écriture dans le fich ier (ici, notre program m e ne s'apercevrait pas de cette
anom alie puisqu'ilne teste pas le code de retour de fw rite ). En ce qui concerne l'indication b, rappelons que celle-ci
n'est indispensable que dans les im plém entations qui distinguent les fich iers de type texte des autres. Revoyez
éventuellem entles com m entaires de l'exercice VII.1.
*Aprè s l'ouverture du fich ier, nous en déterm inons la taille (dans la variable nb_e nre g) à l'aide des fonctions fseek et
fte ll. Plus précisém ent:
fseek (fichier, 0, 2)
nous place à 0 octetde la fin (code 2)du fich ier et:
ftell (fichier)
nous donne la position courante du pointeur associé au fich ier (qui pointe ici sur la fin). Ilnous est alors facile de la
transform er en un nom bre d'enregistrem ents, en la divisantpar la taille d'un enregistrem ent.
*N'oubliez pas qu'aprè s avoir lu un enregistrem ent, ilestnécessaire, avantde le réécrire, de positionner à nouveau le
pointeur dans le fich ier.
D ISCUSSIO N
222 Exe rcices en langage C
*Com m e dans les précédents program m es, nous n'avons pas introduitde protections particuliè res vis-à -vis des réponses
fournies par l'utilisateur (voyez les discussions des précédents program m es). Toutefois, ici, la m aniè re m ê m e dontnous
avons program m é la saisie des corrections, iln'existe pas, à ce niveau, de risque de "plangage" consécutif à une
m auvaise réponse puisque nous n'avons pas faitappelà scanf.
VII-4 Com ptage de le ttre s e tm ots d'un fich ie r te xte
________________________________________________________________________________________
Enoncé
Ecrire un program m e qui, à partir d'un fich ier texte, déterm ine :
- le nom bre de caractè res qu'ilcontient,
- le nom bre de ch acune des lettres de l'alph abet(on ne considérera que les m inuscules),
- le nom bre de m ots,
- le nom bre de lignes.
Les fins de lignes ne devront pas ê tre com ptabilisées dans les caractè res. On adm ettra que deux m ots sont toujours
séparés par un ou plusieurs des caractè res suivants :
- fin de ligne
- espace
- ponctuation : : . , ;?!
- parenth è ses : ( )
- guillem ets : "
- apostroph e : '
On adm ettra égalem ent, pour sim plifier, qu'aucun m ot ne peut ê tre com m encé sur une ligne et se poursuivre sur la
suivante.
Ilest conseillé de réaliser une fonction perm ettant de décider si un caractè re donné, transm is en argum ent, est un des
séparateurs m entionnés ci-dessus. Elle restituera la valeur 1 lorsque le caractè re estun séparateur etla valeur 0 dans le
cas contraire.
VII. Traite m e ntde fich ie rs 223
Exe m ple
donnez le nom du fichier à examiner : b:letfic.c
votre fichier contient 87 lignes, 371 mots
et 3186 caractères dont :
69 fois la lettre a
6 fois la lettre b
74 fois la lettre c
36 fois la lettre d
163 fois la lettre e
........
110 fois la lettre t
63 fois la lettre u
7 fois la lettre v
3 fois la lettre w
6 fois la lettre x
0 fois la lettre y
1 fois la lettre z
et 1979 autres caractères
________________________________________________________________________________________
ANALYSE
Com m e nous avons déjà eu l'occasion de le voir dans les exercices I-5 etI-6, ce type de problè m e peutê tre résolu d'au
m oins deux m aniè res :
- en effectuantune répétition du traitem entd'un caractè re,
- en effectuantune répétition du traitem entd'une ligne, lui-m ê m e constitué de la répétition du traitem ent de ch acun
des caractè res qu'elle contient.
Toutefois, ici, nous avons à faire à un fich ier dans lequella longueur m axim ale d'une ligne n'estpas connue a priori, ce
qui rend la seconde m éth ode difficile à m ettre en oeuvre. Nous ch oisirons donc la prem iè re ;ch aque caractè re du fich ier
sera donc lu par fge tc.
Rappelons que certaines im plém entations distinguent les fich iers de type texte des autres. Dans ce cas, une telle
distinction n'estpas liée au contenu m ê m e du fich ier (en fait, on peuttoujours considérer qu'un fich ier, quelque soitson
contenu, estform é d'une suite d'octets, donc, finalem ent, d'une suite de caractè res). Elle a sim plem entpour objectif de
224 Exe rcices en langage C
faire en sorte que, pour le program m e, les "fins de ligne" apparaissenttoujours m atérialisées par un caractè re unique, à
savoir n (alors que, précisém ent, certaines im plém entations, DOS notam m ent, représentent une fin de ligne par un
"coupe" de caractè res). Lorsqu'une telle distinction estnécessaire, ilestprévu d'introduire l'indication t, au niveau du
m ode d'ouverture du fich ier (de m ê m e qu'on y introduisaitl'indication b pour signaler qu'ilne s'agissaitpas d'un fich ier
de type texte).
Bien entendu, ici, nous avons toutintérê tà profiter de cette possibilité, de m aniè re à nous faciliter la détection des fins
de ligne et, surtout, à obtenir un program m e portable (à l'exception, éventuellem ent, de l'indication t).
Les com ptages à effectuer au niveau des caractè res (nom bre de caractè res, nom bre de ch acune des m inuscules) peuvent
ê tre réalisés de façon naturelle, à condition toutefois de ne pas com ptabiliser n com m e un caractè re (au contraire, à sa
rencontre, ilfaudra incrém enter le com pteur de lignes).
En ce qui concerne les com ptages de m ots, nous procéderons com m e dans le prem ier program m e de l'exercice I-6 en
em ployant:
- une fonction perm ettantde tester si un caractè re estun séparateur,
- un indicateur logique : m ot_e n_cours.
Program m e
#include <stdio.h>
#define LNOMFICH 20 /* longueur maximale d'un nom de fichier */
#define VRAI 1 /* pour "simuler" des ..... */
#define FAUX 0 /* ..... valeurs logiques */
main()
{
int sep (char) ; /* fonction test "caractère séparateur?" */
char nomfich [LNOMFICH+1] ; /* nom du fichier à examiner */
FILE * entree ; /* descripteur du fichier à examiner */
char c ; /* caractère courant */
int compte [26], /* pour compter les différentes lettres */
numl, /* rang lettre courante dans l'alphabet */
ntot, /* compteur nombre total de caractères */
nautres, /* compteur carac autres que minuscules */
nmots, /* compteur du nombre de mots */
nlignes, /* compteur du nombre de lignes */
mot_en_cours, /* indicateur logique : mot trouvé */
i ;
VII. Traite m e ntde fich ie rs 225
/* entrée du nom de fichier à examiner et ouverture */
/* attention, mode r au lieu de rt, dans certains cas */
do
{ printf ("donnez le nom du fichier à examiner : ") ;
gets (nomfich) ;
if ( (entree = fopen (nomfich, "rt")) == NULL)
printf ("***** fichier non trouvén") ;
}
while (entree == NULL) ;
/* initialisations */
for (i=0 ; i<26 ; i++)
compte[i] = 0 ;
ntot = 0 ; nautres = 0 ;
nmots = 0 ;
nlignes = 0 ;
mot_en_cours = FAUX ;
/* boucle d'examen de chacun des caractères du fichier */
while ( c = fgetc (entree), ! feof (entree) )
{
if (c == 'n') nlignes++ ; /* comptages au niveau caractères */
else
{ ntot++ ;
numl = c -'a' ;
if (numl >= 0 && numl < 26) compte[numl]++ ;
else nautres++ ;
}
if (sep(c)) /* comptages au niveau mots */
{ if (mot_en_cours)
{ nmots++ ;
mot_en_cours = FAUX ;
}
}
else mot_en_cours = VRAI ;
}
/* affichage résultats */
printf ("nvotre fichier contient %d lignes, %d motsn",
nlignes, nmots) ;
226 Exe rcices en langage C
printf ("et %d caractères dont :n", ntot) ;
for (i=0 ; i<26 ; i++)
printf ("%d fois la lettre %cn", compte[i], 'a'+i) ;
printf ("net %d autres caractèresn", nautres) ;
}
/*********************************************************/
/* fonction de test "caractère séparateur" */
/*********************************************************/
int sep (char c)
{
char sep[12] = {'n', /* fin de ligne */
' ', /* espace */
',', ';', ':', '.', '?', '!', /* ponctuation */
'(', ')', /* parenthèses */
'"', ''' } ; /* guillemets, apostr*/
int nsep=12, /* nbre séparateurs */
i ;
i = 0 ;
while ( c!=sep[i] && i<nsep ) i++ ;
if (i == nsep) return (0) ;
else return (1) ;
}
Com m e ntaire s
Le fich ier a été ouverten m ode rt:
r : ouverture en lecture. Si le fich ier n'existe pas, fope n fournitun pointeur nul.
t: ouverture en m ode translaté (voyez à ce propos, le prem ier com m entaire de l'exercice VII-1).
Notez que le ch oix du m ode translaté n'est jam ais absolum ent indispensable. Toutefois, com m e nous l'avons dit dans
l'analyse, ilnous facilite la détection de fin de ligne et, de plus, ilrend le program m e transportable (par exem ple sous
UNIX, où une fin de ligne estreprésentée par n).
VII. Traite m e ntde fich ie rs 227
D ISCUSSIO N
Nous avons supposé (im plicitem ent)que notre program m e traitaitun véritable fich ier texte, autrem entditque ce dernier
se term inaitpar une fin de ligne. Si cela n'étaitpas le cas :
- la derniè re ligne ne seraitpas com ptabilisée,
- le dernier m otne seraitpas com ptabilisé, à m oins d'ê tre suivi d'au m oins un séparateur.
VIII: ANALYSE
NUM ERIQUE
Ce ch apitre vous propose quelques applications du langage C à l'analyse num érique. Nous avons ch erch é à y introduire
les tech niques de program m ation qui interviennentfréquem m entdans ce dom aine. Citons, par exem ple :
- la représentation etles m anipulations de m atrices,
- la représentation de nom bres com plexes,
- la réalisation de m odules susceptibles de travailler avec une fonction quelconque ou avec des tableaux de dim ensions
quelconques.
VIII-1 Produitde m atrice s ré e lle s
________________________________________________________________________________________
Enoncé
Ecrire une fonction calculantle produitde deux m atrices réelles. On supposera que le prem ier indice de ch aque tableau
représentantune m atrice correspond à une ligne.
On prévoira en argum ents :
- les adresses des deux m atrices à m ultiplier etcelle de la m atrice produit,
- le nom bre de lignes etle nom bre de colonnes de la prem iè re m atrice,
- le nom bre de colonnes de la seconde m atrice (son nom bre de lignes étant obligatoirem ent égalau nom bre de
colonnes de la prem iè re).
Un program m e principalperm ettra de tester cette fonction.
230 Exe rcices en langage C
Exe m ple
MATRICE A
0 1 2 3
1 2 3 4
2 3 4 5
3 4 5 6
4 5 6 7
MATRICE B
0 1 2
1 2 3
2 3 4
3 4 5
PRODUIT A x B
14 20 26
20 30 40
26 40 54
32 50 68
38 60 82
ANALYSE
Rappelons que si A estune m atrice n, p (n lignes etp colonnes)etsi B estune m atrice p, q, la m atrice produit:
C = A x B
estune m atrice n, q définie par :
c
ij
= a
ik
b
k j
Program m e
#define N 5
#define P 4
#define Q 3
VIII. Analyse num é rique 231
main()
{
void prod_mat(double *, double *, double *, int, int, int) ;
double a[N][P], b[P][Q], c[N][Q] ;
int i, j ;
/* initialisation matrice a */
for (i=0 ; i<N ; i++)
for (j=0 ; j<P ; j++)
a[i][j] = i+j ;
/* initialisation matrice b */
for (i=0 ; i<P ; i++)
for (j=0 ; j<Q ; j++)
b[i][j] = i+ j ;
/* calcul produit a x b */
/* les "cast" (int *) sont facultatifs */
prod_mat ( (double *) a, (double *) b, (double *) c, N, P, Q) ;
/* affichage matrice a */
printf (" MATRICE An") ;
for (i=0 ; i<N ; i++)
{ for (j=0 ; j<P ; j++)
printf ("%4.0f", a[i][j]) ;
printf ("n") ;
}
printf ("n") ;
/* affichage matrice b */
printf (" MATRICE Bn") ;
for (i=0 ; i<P ; i++)
{ for (j=0 ; j<Q ; j++)
printf ("%4.0f", b[i][j]) ;
printf ("n") ;
}
printf ("n") ;
/* affichage produit */
printf (" PRODUIT A x Bn") ;
for (i=0 ; i<N ; i++)
{ for (j=0 ; j<Q ; j++)
printf ("%4.0f", c[i][j]) ;
printf ("n") ;
}
232 Exe rcices en langage C
}
void prod_mat ( double * a, double * b, double * c,
int n, int p, int q)
{
int i, j, k ;
double s ;
double *aik, *bkj, *cij ;
cij = c ;
for (i=0 ; i<n ; i++)
for (j=0 ; j<q ; j++)
{ aik = a + i*p ;
bkj = b + j ;
s = 0 ;
for (k=0 ; k<p ; k++)
{ s += *aik * *bkj ;
aik++ ;
bkj += q ;
}
* (cij++) = s ;
}
}
Com m e ntaire s
*Dans la fonction prod_m at, nous n'avons pas pu utiliser le "form alism e" des tableaux pour les m atrices a, b et c car
celles-ci possè dentdeux dim ensions non connues lors de la com pilation du program m e. Rappelons qu'un telproblè m e ne
se pose pas lorsqu'ils'agitde tableaux à une seule dim ension (car une notation telle que t[i] a toujours un sens, quelle
que soitla taille de t)ou lorsqu'ils'agitd'un tableau à plusieurs dim ensions dontseule la prem iè re estinconnue (com pte
tenu de la m aniè re dontles élém ents d'un tableau sontrangés en m ém oire).
Dans ces conditions, nous som m es obligé de faire appelau form alism e des pointeurs pour repérer un élém entquelconque
de nos m atrices. Pour ce faire, nous transm ettons à la fonction prodm atl'adresse de débutdes trois m atrices concernées.
Notez qu'en toute rigueur (du m oins d'aprè s la norm e ANSI), dans le program m e m ain, un sym bole telque a estdu type
(double [P]) *(c'est-à -dire qu'ilreprésente un pointeur sur des blocs de P élém ents de type double ), et non pas
sim plem entdu type double *. Ildoitdonc ê tre converti dans le type double *, cette conversion ne m odifiantpas, en fait,
l'adresse correspondante (revoyez éventuellem ent les com m entaires de l'exercice V.5 de la prem iè re partie de cet
ouvrage). Cette conversion quelque peu fictive peutsoitê tre m ise en place autom atiquem entpar le com pilateur, au vu du
VIII. Analyse num é rique 233
prototype, soitê tre explicitée à l'aide d'un opérateur de "cast" ;cette derniè re façon de faire a souventle m érite d'éviter
des m essages d'avertissem entintem pestifs ("w arnings").
*Notez que, dans la définition de la fonction prodm at, nous avons dû tenir com pte de la m aniè re dontle langage C range
en m ém oire les élém ents d'un tableau à deux dim ensions (suivantce qu'on nom m e abusivem entles "lignes" du tableau,
c'est-à -dire suivantl'ordre obtenu en faisantvarier en prem ier le dernier indice). Plus précisém ent:
- Le sym bole aik représente un pointeur courant sur les élém ents a
ik
. Pour ch aque valeur de i, aik est initialisé à
l'adresse du prem ier élém entde la ligne i de la m atrice a (a+i*p)etilestincrém enté d'une colonne , en m ê m e tem ps
que l'indice k (d'où la présence de aik ++ dans la boucle en k ).
- De m ê m e, bk j représente un pointeur courant sur les élém ents b
k j
. Pour ch aque valeur de j, bk j est initialisé à
l'adresse du prem ier élém entde la colonne jde la m atrice b (b+j) etilestincrém enté d'une ligne en m ê m e tem ps
que l'indice k (d'où la présence de bkj=bkj+q dans la boucle en k ).
- Enfin, cij représente un pointeur courant sur les élém ents c
ij
. Ilest initialisé à l'adresse du prem ier élém ent de la
m atrice c. Ilprogresse de 1 à ch aque tour de la boucle la plus interne en j(notez qu'iln'en auraitpas été ainsi si nous
avions inversé les deux boucles en i etj).
D ISCUSSIO N
*On a souventtendance à dire qu'une fonction com m e prod_m attravaille sur des m atrices de dim ensions variables. En
fait, le term e estquelque peu am bigu. Ainsi, dans notre exem ple, les m atrices donton transm etl'adresse en argum entà
prod_m atontune taille bien déterm inée dans le program m e principal. Iln'en reste pas m oins que :
- d'une part, la m ê m e fonction peuttravailler sur des m atrices de tailles différentes,
- d'autre part, rien n'em pê ch eraitqu'au sein du program m e principal, les m atrices a, b etc voientleur taille définie
uniquem entlors de l'exécution etleurs em placem ents alloués dynam iquem ent.
*Au sein d'une fonction com m e prod_m at, ilestpossible d'em ployer le form alism e des tableaux à la place de celui des
pointeurs en faisantappelà un artifice. Celui-ci consiste à créer, pour ch aque m atrice, un tableau de pointeurs contenant
l'adresse de débutde ch aque ligne. Ainsi, par exem ple, pour la m atrice a, on pourrait réserver un tableau nom m é ada
par :
double * * ada ;
Ilseraitrem pli de la m aniè re suivante :
for (i=1 ; i<n ; i++)
ada[i] = a + i*p ;
234 Exe rcices en langage C
Dans ces conditions, en effet, la notation ada [i] [j] correspondrait(com pte tenu de l'associativité de gauch e à droite de
l'opérateur [])à :
(ada [i]) [j]
c'est-à -dire à :
* (ada [i] + j)
Autrem entdit, cette notation ada [i][j]désigneraitsim plem entla valeur de l'élém entsitué à l'intersection de la ligne i et
de la colonne jde la m atrice a.
On notera que pour que cet artifice soit utilisable au sein d'une fonction com m e prod_m at, censée travailler sur des
m atrices de taille quelconque, ilestnécessaire que les em placem ents des tableaux de pointeurs tels que ada soientalloués
dynam iquem ent.
VIII-2 Arith m é tiq ue com ple xe
________________________________________________________________________________________
Enoncé
Ecrire deux fonctions calculantla som m e etle produitde deux nom bres com plexes. Ces derniers serontreprésentés par
une structure com portantdeux élém ents de type double , correspondantà la partie réelle età la partie im aginaire.
Ch acune de ces fonctions com portera trois argum ents :
- l'adresse des deux nom bres com plexes (structures)concernés,
- l'adresse du résultat(structure).
Un program m e principalperm ettra de tester ces deux fonctions avec les valeurs com plexes :
0,5 + i
1 + i
VIII. Analyse num é rique 235
Exe m ple
0.500000 + 1.000000 i et 1.000000 + 1.000000 i
ont pour somme 1.500000 + 2.000000 i
et pour produit -0.500000 + 1.500000 i
________________________________________________________________________________________
ANALYSE
Soitdeux nom bres com plexes :
x = a + ib
y = c + id
On saitque :
x + y = (a+ c)+ i (b+ d)
etque :
x y = (ac - bd)+ i (ad + bc)
Program m e
typedef struct
{ double reel ;
double imag ;
} complexe ;
main()
{
void somme (complexe *, complexe *, complexe *) ;
void produit (complexe *, complexe *, complexe *) ;
complexe z1, z2, s, p ;
z1.reel = 0.5 ; z1.imag = 1.0 ;
z2.reel = 1.0 ; z2.imag = 1.0 ;
somme (&z1, &z2, &s) ;
produit (&z1 ,&z2, &p) ;
printf ("%lf + %lf i et %lf + %lf i n",
236 Exe rcices en langage C
z1.reel, z1.imag, z2.reel, z2.imag) ;
printf ("ont pour somme %lf + %lf i n", s.reel, s.imag) ;
printf ("et pour produit %lf + %lf i n", p.reel, p.imag) ;
}
void somme (complexe * x, complexe * y, complexe * som)
{
som->reel = x->reel + y->reel ;
som->imag = x->imag + y->imag ;
}
void produit (complexe * x, complexe * y, complexe * prod)
{
prod->reel = x->reel * y->reel - x->imag * y->imag ;
prod->imag = x->reel * y->imag + x->imag * y->reel ;
}
Com m e ntaire s
*Nous avons défini, à un niveau global, un m odè le de structure nom m é com ple xe .
*Notez bien que, dans le program m e principal, l'accè s à une structure se faitpar l'opérateur "." (com m e dans z1.re e l)
car z1 désigne ici la valeur d'une structure ;par contre, dans les fonctions, ilse faitpar l'opérateur -> (com m e dans x-
>re e l) car x désigne alors l'adresse d'une structure. On peut toutefois éviter l'em ploi de cet opérateur, en rem arquant
que x-> re e lestéquivalentà (*x).re e l.
*En toute rigueur, d'aprè s la norm e ANSI, ilestpossible de transm ettre, en argum ent d'une fonction, la valeur d'une
structure. Aussi, aurions-nous pu prévoir que som m e et produit reçoivent les valeurs des com plexes sur lesquels porte
l'opération. En revanch e, le résultat devrait toujours ê tre transm is par valeur puisque déterm iné par la fonction elle-
m ê m e. Par exem ple, la définition de som m e auraitpu ê tre :
void somme (complexe x, complexe y, complexe * som)
{
prod->reel = x.reel + y.reel ;
prod->imag = x.imag + y.imag ;
}
VIII. Analyse num é rique 237
D ISCUSSIO N
Dans la pratique, les fonctions som m e etproduitseraientcom pilées séparém entdes fonctions les utilisant. Pour ce faire,
ilestnécessaire qu'elles disposentde la description de la structure com ple xe . On voitqu'on risque alors d'ê tre am ené à
décrire une m ê m e structure à différentes reprises. Certes, ici la ch ose n'est pas bien grave, dans la m esure où cette
définition estsim ple. D'une m aniè re générale, toutefois, on a toutintérê tà régler ce type de problè m e en plaçantune fois
pour toutes une telle définition dans un fich ier (d'extension h , par exem ple) qu'on incorpore par #include dans tous les
program m es en ayantbesoin.
VIII-3 Produitde m atrice s com ple xe s
________________________________________________________________________________________
Enoncé
Ecrire une fonction calculantle produit de deux m atrices com plexes. Ch aque m atrice sera définie com m e un tableau à
deux dim ensions dans lequelch aque élém ent sera une structure représentant un nom bre com plexe ;cette structure sera
constituée de deux élém ents de type double correspondant à la partie réelle et à la partie im aginaire du nom bre. On
supposera que le prem ier indice du tableau représentantune m atrice correspond à une ligne.
On prévoira en argum ents :
- les adresses des deux m atrices à m ultiplier,
- l'adresse de la m atrice produit,
- le nom bre de lignes etde colonnes de la prem iè re m atrice,
- le nom bre de colonnes de la deuxiè m e m atrice (son nom bre de lignes étant obligatoirem ent égalau nom bre de
colonnes de la prem iè re).
On réalisera un program m e principalperm ettantde tester cette fonction.
On pourra éventuellem entfaire appelaux fonctions som m e etproduitréalisées dans l'exercice VIII-2 pour calculer la
som m e etle produitde deux nom bres com plexes.
Exe m ple
MATRICE A
0+ 0i 1+ 2i 2+ 4i 3+ 6i
238 Exe rcices en langage C
1+ 1i 2+ 3i 3+ 5i 4+ 7i
2+ 2i 3+ 4i 4+ 6i 5+ 8i
3+ 3i 4+ 5i 5+ 7i 6+ 9i
4+ 4i 5+ 6i 6+ 8i 7+ 10i
MATRICE B
0+ 0i 1+ 2i 2+ 4i
1+ 1i 2+ 3i 3+ 5i
2+ 2i 3+ 4i 4+ 6i
3+ 3i 4+ 5i 5+ 7i
PRODUIT A x B
-14+ 42i -32+ 66i -50+ 90i
-14+ 54i -36+ 90i -58+ 126i
-14+ 66i -40+ 114i -66+ 162i
-14+ 78i -44+ 138i -74+ 198i
-14+ 90i -48+ 162i -82+ 234i
________________________________________________________________________________________
ANALYSE
Les form ules de définition du produitde m atrices com plexes restentcelles proposées dans l'analyse de l'exercice VIII-1
pour les m atrices réelles ;ilsuffitd'y rem placer les opérations + etx portantsur des réels par les opérations som m e et
produitde deux com plexes (les rè gles de ces deux opérations ontété exposées dans l'analyse de l'exercice VIII-2).
Program m e
#define N 5
#define P 4
#define Q 3
typedef struct
{ double reel ;
double imag ;
} complexe ;
VIII. Analyse num é rique 239
main()
{
void prod_mat (complexe *, complexe *, complexe *, int, int, int) ;
complexe a[N][P], b[P][Q], c[N][Q] ;
int i, j ;
/* initialisation matrice a */
for (i=0 ; i<N ; i++)
for (j=0 ; j<P ; j++)
{ a[i][j].reel = i+j ;
a[i][j].imag = i+2*j ;
}
/* initialisation matrice b */
for (i=0 ; i<P ; i++)
for (j=0 ; j<Q ; j++)
{ b[i][j].reel = i+j ;
b[i][j].imag = i+2*j ;
}
/* calcul produit a x b */
/* les "cast" (complexe *) sont facultatifs */
prod_mat ((complexe *) &a, (complexe *) &b, (complexe *) &c, N, P, Q) ;
/* affichage matrice a */
printf (" MATRICE An") ;
for (i=0 ; i<N ; i++)
{ for (j=0 ; j<P ; j++)
printf ("%4.0lf+%4.0lfi ", a[i][j].reel, a[i][j].imag) ;
printf ("n") ;
}
printf ("n") ;
/* affichage matrice b */
printf (" MATRICE Bn") ;
for (i=0 ; i<P ; i++)
{ for (j=0 ; j<Q ; j++)
printf ("%4.0lf+%4.0lfi ", b[i][j].reel, b[i][j].imag) ;
printf ("n") ;
}
printf ("n") ;
240 Exe rcices en langage C
/* affichage produit */
printf (" PRODUIT A x Bn") ;
for (i=0 ; i<N ; i++)
{ for (j=0 ; j<Q ; j++)
printf ("%4.0lf+%4.0lfi ", c[i][j].reel, c[i][j].imag) ;
printf ("n") ;
}
}
/*********************************************************/
/* fonction de calcul de produit de 2 matrices complexes */
/*********************************************************/
void prod_mat ( complexe * a, complexe * b, complexe * c,
int n, int p, int q)
{
void produit() ;
int i, j, k ;
complexe s, pr ;
complexe *aik, *bkj, *cij ;
cij = c ;
for (i=0 ; i<n ; i++)
for (j=0 ; j<q ; j++)
{ aik = a + i*p ;
bkj = b + j ;
s.reel = 0 ; s.imag = 0 ;
for (k=0 ; k<p ; k++)
{ produit (aik, bkj, &pr) ;
s.reel += pr.reel ; s.imag += pr.imag ;
aik++ ;
bkj += q ;
}
* (cij++) = s ;
}
}
void produit (x, y, prod)
complexe *x, *y, *prod ;
{
prod->reel = x->reel * y->reel - x->imag * y->imag ;
prod->imag = x->reel * y->imag + x->imag * y->reel ;
}
VIII. Analyse num é rique 241
Com m e ntaire s
La fonction prod_m at peut ê tre considérée com m e une adaptation de la fonction prod_m at de l'exercice VIII-1. Cette
fois, les sym boles aik , bk j et cij désignent, non plus des pointeurs sur des réels, m ais des pointeurs sur une structure
représentantun nom bre com plexe. La souplesse du langage C en m atiè re d'opérations arith m étiques sur les pointeurs fait
que les instructions d'incrém entation de ces quantités restent les m ê m es (l'unité étant ici la structure com ple xe - soit 2
élém ents de type double , au lieu d'une valeur de type double ).
D ISCUSSIO N
Les rem arques faites dans l'exercice VIII-2, à propos de la description de la structure com ple xe restent naturellem ent
valables ici.
VIII-4 Re ch e rch e de zé ro d'une fonction par dich otom ie
________________________________________________________________________________________
Enoncé
Ecrire une fonction déterm inant, par dich otom ie, le zéro d'une fonction quelconque (réelle d'une variable réelle et
continue). On supposera connu un intervalle [a,b]sur lequella fonction ch ange de signe, c'est-à -dire telque f(a).f(b)soit
négatif.
On prévoira en argum ents :
- les valeurs des bornes a etb (de type double )de l'intervalle de départ,
- l'adresse d'une fonction perm ettant de calculer la valeur de f pour une valeur quelconque de la variable. On
supposera que l'en-tê te de cette fonction estde la form e :
double fct (x)
double x ;
242 Exe rcices en langage C
- l'adresse d'une variable de type double destinée à recueillir la valeur approch ée du zéro de f,
- la valeur de la précision (absolue)souh aitée (de type double ).
Le code de retour de la fonction sera de -1 lorsque l'intervalle fourni en argum entne convientpas, c'est-à -dire :
- soitlorsque la condition a<b n'estpas satisfaite,
- soitlorsque la condition f(a).f(b)<0 n'estpas satisfaite.
Dans le cas contraire, le code de retour sera égalà 0.
Un program m e principalperm ettra de tester cette fonction.
Exe m ple
zéro de la fonction sin entre -1 et 1 à 1e-2 près = 0.000000e+000
zéro de la fonction sin entre -1 et 2 à 1e-2 près = 1.953125e-003
zéro de la fonction sin entre -1 et 2 à 1e-12 près = -2.273737e-013
________________________________________________________________________________________
ANALYSE
La dém arch e consiste donc, aprè s avoir vérifié que l'intervalle reçu en argum entétaitconvenable, à répéter le traitem ent
suivant:
- prendre le m ilieu m de [a,b]: m = (a+ b)/2
- calculer f(m ),
- si f(m )=0, le zéro esten m ,
- si f(a).f(m )<0, ilexiste un zéro sur [a,m ];on rem place donc l'intervalle [a,b]par [a,m ]en faisant:
b = m
- si f(a).f(m )> 0, ilexiste un zéro sur [b,m ];on rem place donc l'intervalle [a,b]par [b,m ], en faisant:
a = m
Le traitem ent est interrom pu soit lorsque l'intervalle [a,b] aura été suffisam m ent réduit, c'est-à -dire lorsque |b-a| est
inférieur à la précision souh aitée, soitlorsque le zéro a été localisé exactem ent(f(m )=0).
VIII. Analyse num é rique 243
Program m e
#include <stdio.h>
#include <math.h> /* pour la fonction sin */
main()
{ /* fonction de recherche d'un zéro par dichotomie */
int dichoto ( double (*(double)(), double, double, double *, double) ;
double z, /* zéro recherché */
a, b, /* bornes de l'intervalle de recherche */
eps ; /* précision souhaitée */
dichoto (sin, -1.0, 1.0, &z, 1.0e-2) ;
printf ("zéro de la fonction sin entre -1 et 1 à 1e-2 près = %len",z);
dichoto (sin, -1.0, 2.0, &z, 1.0e-2) ;
printf ("zéro de la fonction sin entre -1 et 2 à 1e-2 près = %len",z);
dichoto (sin, -1.0, 2.0, &z, 1.0e-12) ;
printf ("zéro de la fonction sin entre -1 et 2 à 1e-12 près = %len",z);
}
/*************************************************************/
/* fonction de recherhce dichotomique du zéro d'une fonction */
/*************************************************************/
int dichoto ( double (* f)(double), double a, double b, double * zero, double eps)
/* f : fonction dont on cherche le zéro */
/* a, b : bornes de l'intervalle de recherche */
/* zero : zéro estimé */
/* eps : précision souhaitée) */
{
double m, /* milieu de l'intervalle courant */
fm, /* valeur de f(m) */
fa, fb ; /* valeurs de f(a) et de f(b) */
fa = (*f)(a) ;
fb = (*f)(b) ;
if (fa*fb >= 0 || a >= b) return (-1) ; /* intervalle incorrect */
while (b-a > eps)
{ m = (b+a) / 2.0 ;
244 Exe rcices en langage C
fm = (*f)(m) ;
if (fm == 0) break ; /* zéro atteint */
if (fa*fm < 0) { b = m ;
fb = fm ;
}
else { a = m ;
fa = fm ;
}
}
* zero = m ;
return (0) ;
}
Com m e ntaire s
*Notez, dans la fonction dich oto :
- la déclaration de l'argum entcorrespondantà l'adresse de la fonction donton ch erch e le zéro :
double (*f)(double)
Celle-ci s'interprè te com m e suit:
(*f)estune fonction recevantun argum entde type double etfournissantun résultatde type double ,
*festdonc une fonction recevantun argum entde type double etfournissantun résultatde type double ,
f est donc un pointeur sur une fonction recevant un argum ent de type double et fournissant un résultat de type
double .
- l'utilisation du sym bole f;ainsi (*f)(a) représente la valeur de la fonction (*f) (fonction d'adresse f), à laquelle on
fournitl'argum enta.
Les m ê m es réflexions s'appliquentau prototype servantà déclarer dich oto.
*La fonction dich oto recevanten argum entles valeurs des argum ents a etb (etnon des adresses), nous pouvons nous
perm ettre de les m odifier au sein de la fonction, sans que cela ait d'incidence sur les valeurs effectives des bornes
définies dans le program m e principal.
*Voyez com m ent, dans le program m e principal, un sym bole com m e sin est interprété par le com pilateur com m e
l'adresse d'une fonction prédéfinie ;ilest toutefois nécessaire d'avoir incorporé son prototype (situé dans m ath .h );en
VIII. Analyse num é rique 245
l'absence de l'instruction #include correspondante, le com pilateur détecterait un erreur puisque alors le sym bole sin ne
seraitpas défini.
D ISCUSSIO N
En th éorie, la m éth ode de dich otom ie conduittoujours à une solution, avec une précision aussi grande qu'on le désire, à
partir du m om ent où la fonction ch ange effectivem ent de signe sur l'intervalle de départ. En pratique, toutefois, les
ch oses ne sontpas toujours aussi idylliques, com pte tenu de la lim itation de la précision des calculs.
Toutd'abord, si on im pose une précision trop faible par rapportà la précision de l'ordinateur, on peutaboutir à ce que :
m = (a+b)/2
soitégalà l'une des deux bornes a ou b. Ilestalors facile de m ontrer que l'algorith m e peutboucler indéfinim ent.
D'autre part, les valeurs de f(a) etde f(b) sontnécessairem entévaluées de m aniè re approch ée. Dans le cas de form ules
quelque peu com plexes, on peuttrè s bien aboutir à une situation dans laquelle f(a).f(b)estpositif.
La prem iè re situation est assez facile à éviter : ilsuffit de ch oisir une précision relative (attention, ici, notre fonction
travaille avec une précision absolue) inférieure à celle de l'ordinateur. Iln'en va pas de m ê m e pour la seconde dans la
m esure où iln'estpas toujours possible de m aîtriser la précision des calculs des valeurs de f.

Exercices en langage c

  • 1.
    Exercices en langageC C. Delannoy
  • 2.
    2 Exe rcicesen langage C PREM IERE PARTIE : EXERCICES D 'APPLICATIO N Cette prem iè re partie vous propose des exercices, à résoudre, de préférence, pendantla ph ase d'étude du langage C lui- m ê m e. Elle épouse la structure d'un cours "classique"1, sous la form e de 7 ch apitres : types de base, opérateurs et expressions ;entrées-sorties conversationnelles ;instructions de contrôle ;les fonctions ;les tableaux et les pointeurs ; les ch aînes de caractè res ;les structures. Ch aque ch apitre com porte : - des exercices d'application im m édiate destinés à faciliter l'assim ilation du cours correspondant, - des exercices, sans grande difficulté algorith m ique m ettant en oeuvre les différentes notions acquises au cours des précédents ch apitres. Notez que l'utilisation des fich iers, ainsi que la gestion dynam ique ne sontpas abordés dans cette prem iè re partie ;ces deux points ferontch acun l'objetd'un ch apitre approprié dans la seconde partie de l'ouvrage. 1 Un telcours vous est proposé, par exem ple, dans "Apprendre à program m er en Turbo C" ou dans "C norm e ANSI - Guide com plet de program m ation" du m ê m e auteur, égalem entaux éditions Eyrolles.
  • 3.
    I: TYPES DE BASE, O PERATEURS ET EXPRESSIO NS Exe rcice I.1 ___________________________________________________________________________ Enoncé Elim iner les parenth è ses superflues dans les expressions suivantes : a = (x+5) /* expression 1 */ a = (x=y) + 2 /* expression 2 */ a = (x==y) /* expression 3 */ (a<b) && (c<d) /* expression 4 */ (i++) * (n+p) /* expression 5 */ ___________________________________________________________________________ Solution a = x+5 /* expression 1 */ L'opérateur + estprioritaire sur l'opérateur d'affectation =. a = (x=y) + 2 /* expression 2 */ Ici, l'opérateur + étantprioritaire sur =, les parenth è ses sontindispensables. a = x==y /* expression 3 */
  • 4.
    4 Exe rcicesen langage C L'opérateur == estprioritaire sur =. a<b && c<d /* expression 4 */ L'opérateur & & estprioritaire sur l'opérateur <. i++ * (n+p) /* expression 5 */ L'opérateur + + estprioritaire sur *;en revanch e, *estprioritaire sur + ;de sorte qu'on ne peutélim iner les derniè res parenth è ses. Exe rcice I.2 ___________________________________________________________________________ Enoncé Soientles déclarations : char c = 'x01' ; short int p = 10 ; Quels sontle type etla valeur de ch acune des expressions suivantes : p + 3 /* 1 */ c + 1 /* 2 */ p + c /* 3 */ 3 * p + 5 * c /* 4 */ ___________________________________________________________________________ Solution 1)p estd'abord soum is à la conversion "systém atique" sh ort-> int, avantd'ê tre ajouté à la valeur 3 (int). Le résultat13 estde type int. 2)c estd'abord soum is à la conversion "systém atique" ch ar -> int(ce qui aboutità la valeur 1), avantd'ê tre ajouté à la valeur 1 (int). Le résultat2 estde type int.
  • 5.
    I. Types debase, opérate urs e te xpressions 5 3)p estd'abord soum is à la conversion systém atique sh ort-> int, tandis que c estsoum is à la conversion systém atique ch ar -> int;les résultats sontalors additionnés pour aboutir à la valeur 11 de type int. 4)p etc sontd'abord aux m ê m es conversions systém atiques que ci-dessus ;le résultat35 estde type int. Exe rcice I.3 ___________________________________________________________________________ Enoncé Soientles déclarations : char c = 'x05' ; int n = 5 ; long p = 1000 ; float x = 1.25 ; double z = 5.5 ; Quels sontle type etla valeur de ch acune des expressions suivantes : n + c + p /* 1 */ 2 * x + c /* 2 */ (char) n + c /* 3 */ (float) z + n / 2 /* 4 */ ___________________________________________________________________________ Solution 1)c esttoutd'abord converti en int, avantd'ê tre ajouté à n. Le résultat(10), de type int, estalors converti en long, avant d'ê tre ajouté à p. On obtientfinalem entla valeur 1010, de type long. 2)On évalue d'abord la valeur de 2*x, en convertissant2 (int) en float, ce qui fournitla valeur 2.5 (de type float). Par ailleurs, c estconverti en int(conversion systém atique). On évalue ensuite la valeur de 2*x, en convertissant2 (int) en float, ce qui fournitla valeur 2.5 (de type float). Pour effectuer l'addition, on convertitalors la valeur entiè re 5 (c) en float, avantde l'ajouter au résultatprécédent. On obtientfinalem entla valeur 7.75, de type float.
  • 6.
    6 Exe rcicesen langage C 3)n est tout d'abord converti en ch ar (à cause de l'opérateur de "cast"), tandis que c est converti (conversion systém atique) en int. Puis, pour procéder à l'addition, ilest nécessaire de reconvertir la valeur de (ch ar) n en int. Finalem ent, on obtientla valeur 10, de type int. 4)z estd'abord converti en float, ce qui fournitla valeur 5.5 (approxim ative, car, en fait, on obtientune valeur un peu m oins précise que ne le serait5.5 exprim é en double ). Par ailleurs, on procè de à la division entiè re de n par 2, ce qui fournit la valeur entiè re 2. Cette derniè re est ensuite convertie en float, avant d'ê tre ajoutée à 5.5, ce qui fournit le résultat7.5, de type float. Rem arque : Dans la prem iè re définition de Kernigh an et Ritch ie, les valeurs de type float étaient, elles aussi, soum ises à une conversion systém atique en double . Dans ce cas, les expressions 3 et4 étaientalors de type double . Exe rcice I.4 ___________________________________________________________________________ Enoncé Soientles déclarations suivantes : int n = 5, p = 9 ; int q ; float x ; Quelle estla valeur affectée aux différentes variables concernées par ch acune des instructions suivantes : q = n < p ; /* 1 */ q = n == p ; /* 2 */ q = p % n + p > n ; /* 3 */ x = p / n ; /* 4 */ x = (float) p / n ; /* 5 */ x = (p + 0.5) / n ; /* 6 */ x = (int) (p + 0.5) / n ; /* 7 */ q = n * (p > n ? n : p) ; /* 8 */ q = n * (p < n ? n : p) ; /* 9 *: ___________________________________________________________________________
  • 7.
    I. Types debase, opérate urs e te xpressions 7 Solution 1)1 2)0 3)5 (p%n vaut4, tandis que p> n vaut1) 4)1 (p/n estd'abord évalué en int, ce qui fournit1 ;puis le résultatestconverti en float, avantd'ê tre affecté à x). 5)1.8 (p estconverti en float, avantd'ê tre divisé par le résultatde la conversion de n en float). 6)1.9 (p est converti en float, avant d'ê tre ajouté à 0.5 ;le résultat est divisé par le résultat de la conversion de n en float). 7)1 (p estconverti en float, avantd'ê tre ajouté à 0.5 ;le résultat(5.5)estalors converti en intavantd'ê tre divisé par n). 8)25 9 )45 Exe rcice I.5 ___________________________________________________________________________ Enoncé Quels résultats fournitle program m e suivant: #include <stdio.h> main () { int i, j, n ; i = 0 ; n = i++ ; printf ("A : i = %d n = %d n", i, n ) ; i = 10 ; n = ++ i ; printf ("B : i = %d n = %d n", i, n ) ;
  • 8.
    8 Exe rcicesen langage C i = 20 ; j = 5 ; n = i++ * ++ j ; printf ("C : i = %d j = %d n = %d n", i, j, n ) ; i = 15 ; n = i += 3 ; printf ("D : i = %d n = %d n", i, n) ; i = 3 ; j = 5 ; n = i *= --j ; printf ("E : i = %d j = %d n = %d n", i, n) ; } ___________________________________________________________________________ Solution A : i = 1 n = 0 B : i = 11 n = 11 C : i = 21 j = 6 n = 120 D : i = 18 n = 18 E : i = 12 j = 12 n = 6 Exe rcice I.6 ___________________________________________________________________________ Enoncé Quels résultats fournira ce program m e : #include <stdio.h> main() { int n=10, p=5, q=10, r ; r = n == (p = q) ; printf ("A : n = %d p = %d q = %d r = %dn", n, p, q, r) ; n = p = q = 5 ; n += p += q ; printf ("B : n = %d p = %d q = %dn", n, p, q) ;
  • 9.
    I. Types debase, opérate urs e te xpressions 9 q = n < p ? n++ : p++ ; printf ("C : n = %d p = %d q = %dn", n, p, q) ; q = n > p ? n++ : p++ ; printf ("D : n = %d p = %d q = %dn", n, p, q) ; } ___________________________________________________________________________ Solution A : n = 10 p = 10 q = 10 r = 1 B : n = 15 p = 10 q = 5 C : n = 15 p = 11 q = 10 D : n = 16 p = 11 q = 15 Exe rcice I.7 ___________________________________________________________________________ Enoncé Quels résultats fournira ce program m e : #include <stdio.h> main() { int n, p, q ; n = 5 ; p = 2 ; /* cas 1 */ q = n++ >p || p++ != 3 ; printf ("A : n = %d p = %d q = %dn", n, p, q) ; n = 5 ; p = 2 ; /* cas 2 */ q = n++<p || p++ != 3 ; printf ("B : n = %d p = %d q = %dn", n, p, q) ; n = 5 ; p = 2 ; /* cas 3 */
  • 10.
    10 Exe rcicesen langage C q = ++n == 3 && ++p == 3 ; printf ("C : n = %d p = %d q = %dn", n, p, q) ; n = 5 ; p = 2 ; /* cas 4 */ q = ++n == 6 && ++p == 3 ; printf ("D : n = %d p = %d q = %dn", n, p, q) ; } ___________________________________________________________________________ Solution Ilne faut pas oublier que les opérateurs & & et || n'évaluent leur deuxiè m e opérande que lorsque cela est nécessaire. Ainsi, ici, iln'estpas évalué dans les cas 1 et3. Voici les résultats fournis par le program m e : A : n = 6 p = 2 q = 1 B : n = 6 p = 3 q = 1 C : n = 6 p = 2 q = 0 D : n = 6 p = 3 q = 1
  • 11.
    II: LES ENTREES-SORTIES CO NVERSATIO NNELLES Exe rcice II.1 ___________________________________________________________________________ Enoncé Quels serontles résultats fournis par ce program m e : #include <stdio.h> main () { int n = 543 ; int p = 5 ; float x = 34.5678; printf ("A : %d %fn", n, x) ; printf ("B : %4d %10fn", n, x) ; printf ("C : %2d %3fn", n, x) ; printf ("D : %10.3f %10.3en", x, x) ; printf ("E : %-5d %fn", n, x) ; printf ("F : %*dn", p, n) ; printf ("G : %*.*fn", 12, 5, x) ; printf ("H : %x : %8x :n", n, n) ; printf ("I : %o : %8o :n", n, n) ; } _______________________________________________________________ Solution A : 543 34.567799 B : 543 34.567799
  • 12.
    12 Exe rcicesen langage C C : 543 34.567799 D : 34.568 3.457e+01 E : 543 34.567799 F : 543 G : 34.56780 H : 21f : 21f : I : 1037 : 1037 : Exe rcice II.2 ___________________________________________________________________________ Enoncé Quels serontles résultats fournis par ce program m e : #include <stdio.h> main() { char c ; int n ; c = 'S' ; printf ("A : %cn", c) ; n = c ; printf ("B : %cn", n) ; printf ("C : %d %dn", c, n) ; printf ("D : %x %xn", c, n) ; } _______________________________________________________________ Solution A : S B : S C : 83 83 D : 53 53
  • 13.
    II. Les entrées-sortiesconve rsationne lles 13 Exe rcice II.3 ___________________________________________________________________________ Enoncé Quelles serontles valeurs lues dans les variables n etp (de type int), par l'instruction suivante : scanf ("%d %d", &n, &p) ; lorsqu'on lui fournit les données suivantes (le sym bole ^ représente un espace et le sym bole @ représente une fin de ligne, c'est-à -dire une "validation"): a) 253^45@ b) ^253^@ ^^ 4 ^ 5 @ _______________________________________________________________ Solution a)n = 243, p = 45 b)n = 253, p = 4 (les derniers caractè res de la deuxiè m e ligne pourrontéventuellem entê tre utilisés par une instruction de lecture ultérieure). Exe rcice II.4 ___________________________________________________________________________ Enoncé Quelles serontles valeurs lues dans les variables n etp (de type int), par l'instruction suivante : scanf ("%4d %2d", &n, &p) ; lorsqu'on lui fournit les données suivantes (le sym bole ^ représente un espace et le sym bole @ représente une fin de ligne, c'est-à -dire une "validation"):
  • 14.
    14 Exe rcicesen langage C a) 12^45@ b) 123456@ c) 123456^7@ d) 1^458@ e) ^^^4567^^8912@ _______________________________________________________________ Solution Rappelons que lorsqu'une indication de longueur estprésente dans le code form atfourni à scanf(com m e, par exem ple, le 4 de %4d), scanf interrom pt son exploration si le nom bre correspondant de caractè res a été exploré, sans qu'un séparateur (ou "espace blanc") n'aitété trouvé. Notez bien, cependant, que les éventuels caractè res séparateurs "sautés" auparavantne sontpas considérés dans ce com pte. Voici les résultats obtenus : a)n=12, p=45 b)n=1234, p=56 c) n=1234, p=56 d)n=1, p=45 e)n=4567, p=89 En a, on obtiendrait exactem ent les m ê m es résultats sans indication de longueur (c'est-à -dire avec %d %d). En b, en revanch e, sans l'indication de longueur 4, les résultats seraientdifférents (n vaudrait123456, tandis qu'ilm anqueraitdes inform ations pour p). En c, les inform ations ^ et7 ne sontpas prises en com pte par scanf(elles le serontéventuellem ent par une proch aine lecture!) ;sans la prem iè re indication de longueur, les résultats seraientdifférents : 123456 pour n (en supposantque cela ne conduise pas à une valeur non représentable dans le type int) et 7 pour p. En d, cette fois, c'est l'indication de longueur 2 qui a de l'im portance ;en son abscence, n vaudrait effectivem ent 1, m ais p vaudrait 458. Enfin, en e, les deux indications de longueur sont im portantes ;notez bien que les trois espaces placés avant les caractè res pris en com pte pour n, ainsi que les 2 espaces placés avantles caractè res pris en com pte pour p ne sont pas com ptabilisés dans la longueur im posée. Exe rcice II.5 ___________________________________________________________________________
  • 15.
    II. Les entrées-sortiesconve rsationne lles 15 Enoncé Soitle program m e suivant: #include <stdio.h> main() { int n, p ; do { printf ("donnez 2 entiers (0 pour finir) : ") ; scanf("%4d%2d", &n, &p) ; printf ("merci pour : %d %dn", n, p) ; } while (n) ; } Quels résultats fournira-t-il, en supposantqu'on lui entre les données suivantes (attention, on supposera que les données sont frappées au clavier et les résultats affich és à l'écran, ce qui signifie qu'ily aura "m ixage" entre ces deux sortes d'inform ations): 1 2 3 4 123456 78901234 5 6 7 8 9 10 0 0 12 _______________________________________________________________ Solution Ici, on retrouve le m écanism e lié à l'indication d'une longueur m axim ale dans le code form at, com m e dans l'exercice précédent. De plus, on exploite le fait que les inform ations d'une ligne qui n'ont pas été prises en com pte lors d'une lecture restent disponibles pour la lecture suivante. Enfin, rappelons que, tant que scanf n'a pas reçu suffisam m ent d'inform ation, com pte tenu des différents codes form atspécifiés (et non pas des variables indiquées), elle en attend de nouvelles. Voici finalem entles résultats obtenus : donnez 2 entiers (0 pour finir) 1 2 merci pour : 1 2
  • 16.
    16 Exe rcicesen langage C donnez 2 entiers (0 pour finir) 3 4 merci pour : 3 4 donnez 2 entiers (0 pour finir) 123456 merci pour : 1234 56 donnez 2 entiers (0 pour finir) 78901234 5 merci pour : 7890 12 donnez 2 entiers (0 pour finir) merci pour : 34 5 donnez 2 entiers (0 pour finir) 6 7 8 9 10 merci pour : 6 7 donnez 2 entiers (0 pour finir) merci pour : 8 9 donnez 2 entiers (0 pour finir) 0 merci pour : 10 0 donnez 2 entiers (0 pour finir) 0 12 merci pour : 0 12
  • 17.
    III: LES INSTRUCTIONS D E CO NTRO LE Exe rcice III.1 ___________________________________________________________________________ Enoncé Quelles erreurs ontété com m ises dans ch acun des groupes d'instructions suivants : 1) if (a<b) printf ("ascendant") else printf ("non ascendant") ; 2) int n ; ... switch (2*n+1) { case 1 : printf ("petit") ; case n : printf ("moyen") ; } 3) #define LIMITE 100 int n ; ... switch (n) { case LIMITE-1 : printf ("un peu moins") ; case LIMITE : printf ("juste") ; case LIMITE+1 : printf ("un peu plus") ; } 4) const int LIMITE=100 int n ;
  • 18.
    18 Exe rcicesen langage C ... switch (n) { case LIMITE-1 : printf ("un peu moins") ; case LIMITE : printf ("juste") ; case LIMITE+1 : printf ("un peu plus") ; } _______________________________________________________________ Solution 1)Ilm anque un point-virgule à la fin du prem ier printf: if (a<b) printf ("ascendant") ; else printf ("non ascendant") ; 2)Les valeurs suivantle m otcase doiventobligatoirem entê tre des "expressions constantes", c'est-à -dire des expressions calculables par le com pilateur lui-m ê m e. Ce n'estpas le cas de n. 3)Aucune erreur, les expressions telles que LIM ITE-1 étantbien des expressions constantes. 4) Ici, les expressions suivant le m ot case ne sont plus des expressions constantes, car le sym bole LIM ITE a été défini sous form e d'une "constante sym bolique" (en C+ + , cependant, ces instructions serontcorrectes). Exe rcice III.2 ___________________________________________________________________________ Enoncé Soitle program m e suivant: #include <stdio.h> main() { int n ; scanf ("%d", &n) ; switch (n) { case 0 : printf ("Nuln") ; case 1 :
  • 19.
    III. Les instructionsde contrôle 19 case 2 : printf ("Petitn") ; break ; case 3 : case 4 : case 5 : printf ("Moyenn") ; default : printf ("Grandn") ; } } Quels résultats affich e-t-illorsqu'on lui fourniten donnée : a)0 b)1 c) 4 d)10 e)-5 ___________________________________________________________________________ Solution a) Nul Petit b) Petit c) Moyen Grand d) Grand e) Grand Exe rcice III.3 ___________________________________________________________________________
  • 20.
    20 Exe rcicesen langage C Enoncé Quelles erreurs ontété com m ises dans ch acune des instructions suivantes : a) do c = getchar() while (c != 'n') ; b) do while ( (c = getchar()) != 'n') ; c) do {} while (1) ; ___________________________________________________________________________ Solution a)Ilm anque un point-virgule : do c = getchar() ; while (c != 'n') ; b)Ilm anque une instruction (éventuellem ent"vide")aprè s le m otdo. On pourraitécrire, par exem ple : do {} while ( (c = getchar()) != 'n') ; ou : do ; while ( (c = getchar()) != 'n') ; c) Iln'y aura pas d'erreur de com pilation ;toutefois, ils'agitd'une "boucle infinie". Exe rcice III.4 ___________________________________________________________________________ Enoncé Ecrire plus lisiblem ent: do {} while (printf("donnez un nombre >0 "), scanf ("%d", &n), n<=0) ; ___________________________________________________________________________
  • 21.
    III. Les instructionsde contrôle 21 Solution Plusieurs possibilités existent, puisqu'il"suffit" de reporter, dans le corps de la boucle, des instructions figurant "artificiellem ent" sous form e d'expressions dans la condition de poursuite : do printf("donnez un nombre >0 ") ; while (scanf ("%d", &n), n<=0) ; ou, m ieux : do { printf("donnez un nombre >0 ") ; scanf ("%d", &n) ; } while (n<=0) ; Exe rcice III.5 ___________________________________________________________________________ Enoncé Soitle petitprogram m e suivant: #include <stdio.h> main() { int i, n, som ; som = 0 ; for (i=0 ; i<4 ; i++) { printf ("donnez un entier ") ; scanf ("%d", &n) ; som += n ; } printf ("Somme : %dn", som) ; } Ecrire un program m e réalisantexactem entla m ê m e ch ose, en em ployant, à la place de l'instruction for :
  • 22.
    22 Exe rcicesen langage C a)une instruction w h ile , b)une instruction do ... w h ile . ___________________________________________________________________________ Solution a) #include <stdio.h> main() { int i, n, som ; som = 0 ; i = 0 ; /* ne pas oublier cette "initialisation" */ while (i<4) { printf ("donnez un entier ") ; scanf ("%d", &n) ; som += n ; i++ ; /* ni cette "incrémentation" */ } printf ("Somme : %dn", som) ; } b) #include <stdio.h> main() { int i, n, som ; som = 0 ; i = 0 ; /* ne pas oublier cette "initialisation" */ do { printf ("donnez un entier ") ; scanf ("%d", &n) ; som += n ; i++ ; /* ni cette "incrémentation" */ } while (i<4) ; /* attention, ici, toujours <4 */ printf ("Somme : %dn", som) ; }
  • 23.
    III. Les instructionsde contrôle 23 Exe rcice III.6 ___________________________________________________________________________ Enoncé Quels résultats fournitle program m e suivant: #include <stdio.h> main() { int n=0 ; do { if (n%2==0) { printf ("%d est pairn", n) ; n += 3 ; continue ; } if (n%3==0) { printf ("%d est multiple de 3n", n) ; n += 5 ; } if (n%5==0) { printf ("%d est multiple de 5n", n) ; break ; } n += 1 ; } while (1) ; } ___________________________________________________________________________ Solution 0 est pair 3 est multiple de 3 9 est multiple de 3 15 est multiple de 3 20 est multiple de 5
  • 24.
    24 Exe rcicesen langage C Exe rcice III.7 ___________________________________________________________________________ Enoncé Quels résultats fournitle program m e suivant: #include <stdio.h> main() { int n, p ; n=0 ; while (n<=5) n++ ; printf ("A : n = %dn", n) ; n=p=0 ; while (n<=8) n += p++ ; printf ("B : n = %dn", n) ; n=p=0 ; while (n<=8) n += ++p ; printf ("C : n = %dn", n) ; n=p=0 ; while (p<=5) n+= p++ ; printf ("D : n = %dn", n) ; n=p=0 ; while (p<=5) n+= ++p ; printf ("D : n = %dn", n) ; } ___________________________________________________________________________ Solution A : n = 6 B : n = 10 C : n = 10 D : n = 15
  • 25.
    III. Les instructionsde contrôle 25 D : n = 21 Exe rcice III.8 ___________________________________________________________________________ Enoncé Quels résultats fournitle program m e suivant: #include <stdio.h> main() { int n, p ; n=p=0 ; while (n<5) n+=2 ; p++ ; printf ("A : n = %d, p = %d n", n, p) ; n=p=0 ; while (n<5) { n+=2 ; p++ ; } printf ("B : n = %d, p = %d n", n, p) ; } ___________________________________________________________________________ Solution A : n = 6, p = 1 B : n = 6, p = 3 Exe rcice III.9 ___________________________________________________________________________
  • 26.
    26 Exe rcicesen langage C Enoncé Quels résultats fournitle program m e suivant: #include <stdio.h> main() { int i, n ; for (i=0, n=0 ; i<5 ; i++) n++ ; printf ("A : i = %d, n = %dn", i, n) ; for (i=0, n=0 ; i<5 ; i++, n++) {} printf ("B : i = %d, n = %dn", i, n) ; for (i=0, n=50 ; n>10 ; i++, n-= i ) {} printf ("C : i = %d, n = %dn", i, n) ; for (i=0, n=0 ; i<3 ; i++, n+=i, printf ("D : i = %d, n = %dn", i, n) ) ; printf ("E : i = %d, n = %dn", i, n) ; } ___________________________________________________________________________ Solution A : i = 5, n = 5 B : i = 5, n = 5 C : i = 9, n = 5 D : i = 1, n = 1 D : i = 2, n = 3 D : i = 3, n = 6 E : i = 3, n = 6
  • 27.
    III. Les instructionsde contrôle 27 Exe rcice III.10 ___________________________________________________________________________ Enoncé Ecrire un program m e qui calcule les racines carrées de nom bres fournis en donnée. Ils'arrê tera lorqu'on lui fournira la valeur 0. Ilrefusera les valeurs négatives. Son exécution se présentera ainsi : donnez un nombre positif : 2 sa racine carrée est : 1.414214e+00 donnez un nombre positif : -1 svp positif donnez un nombre positif : 5 sa racine carrée est : 2.236068e+00 donnez un nombre positif : 0 Rappelons que la fonction sqrtfournitla racine carrée (double )de la valeur (double )qu'on lui fourniten argum ent. ___________________________________________________________________________ Solution Ilexiste beaucoup de "rédactions possibles" ;en voici 3 : #include <stdio.h> #include <math.h> /* indispensable pour sqrt (qui fourni un résultat */ /* de type double */ main() { double x ; do { printf ("donnez un nombre positif : ") ; scanf ("%le", &x) ; if (x < 0) printf ("svp positif n") ; if (x <=0) continue ; printf ("sa racine carrée est : %len", sqrt (x) ) ; } while (x) ; }
  • 28.
    28 Exe rcicesen langage C #include <stdio.h> #include <math.h> main() { double x ; do { printf ("donnez un nombre positif : ") ; scanf ("%le", &x) ; if (x < 0) { printf ("svp positif n") ; continue ; } if (x>0) printf ("sa racine carrée est : %len", sqrt (x) ) ; } while (x) ; } #include <stdio.h> #include <math.h> main() { double x ; do { printf ("donnez un nombre positif : ") ; scanf ("%le", &x) ; if (x < 0) { printf ("svp positif n") ; continue ; } if (x>0) printf ("sa racine carrée est : %len", sqrt (x) ) ; if (x==0) break ; } while (1) ; } Rem arque : Ilne fautsurtoutpas oublier #include < m ath .h > car, sinon, le com pilateur considè re (en l'abscence du prototype) que sqrtfournitun résultatde type int.
  • 29.
    III. Les instructionsde contrôle 29 Exe rcice III.11 ___________________________________________________________________________ Enoncé Calculer la som m e des n prem iers term es de la "série h arm onique", c'est-à -dire la som m e : 1 + 1/2 + 1/3 + 1/4 + ..... + 1/n La valeur de n sera lue en donnée. ___________________________________________________________________________ Solution #include <stdio.h> main() { int nt ; /* nombre de termes de la série harmonique */ float som ; /* pour la somme de la série */ int i ; do { printf ("combien de termes : ") ; scanf ("%d", &nt) ; } while (nt<1) ; for (i=1, som=0 ; i<=nt ; i++) som += (float)1/i ; printf ("Somme des %d premiers termes = %f", nt, som) ; } Rem arques : 1)Rappelons que dans : som += (float)1/i l'expression de droite estévaluée en convertissantd'abord 1 eti en float.
  • 30.
    30 Exe rcicesen langage C Ilfautéviter d'écrire : som += 1/i auquelcas, les valeurs de 1/i seraient toujours nulles (sauf pour i=1) puique l'opérateur /, lorsqu'ilporte sur des entiers, correspond à la division entiè re. De m ê m e, en écrivant: som += (float) (1/i) le résultatne seraitpas plus satisfaisantpuisque la conversion en flottantn'auraitlieu qu'aprè s la division (en entier). En revanch e, on pourraitécrire : som += 1.0/i ; 2)Si l'on ch erch aità exécuter ce program m e pour des valeurs élevées de n (en prévoyantalors une variable de type floatou double ), on constateraitque la valeur de la som m e sem ble "converger" vers une lim ite (bien qu'en th éorie la série h arm onique "diverge"). Cela provient tout sim plem ent de ce que, dè s que la valeur de 1/i est "petite" devant som , le résultat de l'addition de 1/i et de som est exactem ent som . On pourrait toutefois am éliorer le résultat en effectuantla som m e "à l'envers" (en effet, dans ce cas, le rapportentre la valeur à ajouter etla som m e courante serait plus faible que précédem m ent).. Exe rcice III.12 ___________________________________________________________________________ Enoncé Affich er un triangle isocè le form é d'étoiles. La h auteur du triangle (c'est-à -dire le nom bre de lignes) sera fourni en donnée, com m e dans l'exem ple ci-dessous. On s'arrangera pour que la derniè re ligne du triangle s'affich e sur le bord gauch e de l'écran. combien de lignes ? 10 * *** ***** ******* ********* *********** ************* ***************
  • 31.
    III. Les instructionsde contrôle 31 ***************** ******************* ___________________________________________________________________________ Solution #include <stdio.h> #define car '*' /* caractère de remplissage */ main() { int nlignes ; /* nombre total de lignes */ int nl ; /* compteur de ligne */ int nesp ; /* nombre d'espaces précédent une étoile */ int j ; printf ("combien de lignes ? ") ; scanf ("%d", &nlignes) ; for (nl=0 ; nl<nlignes ; nl++) { nesp = nlignes - nl - 1 ; for (j=0 ; j<nesp ; j++) putchar (' ') ; for (j=0 ; j<2*nl+1 ; j++) putchar (car) ; putchar ('n') ; } } Exe rcice III.13 ___________________________________________________________________________ Enoncé Affich er toutes les m aniè res possibles d'obtenir un franc avec des piè ces de 2 centim es, 5 centim es et10 centim es. Dire com bien de possibilités ontété ainsi trouvées. Les résultats serontaffich és com m e suit: 1 F = 50 X 2c
  • 32.
    32 Exe rcicesen langage C 1 F = 45 X 2c 2 X 5c 1 F = 40 X 2c 4 X 5c 1 F = 35 X 2c 6 X 5c 1 F = 30 X 2c 8 X 5c 1 F = 25 X 2c 10 X 5c 1 F = 20 X 2c 12 X 5c 1 F = 15 X 2c 14 X 5c 1 F = 10 X 2c 16 X 5c 1 F = 5 X 2c 18 X 5c 1 F = 20 X 5c 1 F = 45 X 2c 1 X 10c 1 F = 40 X 2c 2 X 5c 1 X 10c 1 F = 35 X 2c 4 X 5c 1 X 10c 1 F = 10 X 2c 2 X 5c 7 X 10c 1 F = 5 X 2c 4 X 5c 7 X 10c 1 F = 6 X 5c 7 X 10c 1 F = 10 X 2c 8 X 10c 1 F = 5 X 2c 2 X 5c 8 X 10c 1 F = 4 X 5c 8 X 10c 1 F = 5 X 2c 9 X 10c 1 F = 2 X 5c 9 X 10c 1 F = 10 X 10c En tout, il y a 66 façons de faire 1 F ___________________________________________________________________________ Solution #include <stdio.h> main() { int nbf ; /* compteur du nombre de façons de faire 1 F */ int n10 ; /* nombre de pièces de 10 centimes */ int n5 ; /* nombre de pièces de 5 centimes */ int n2 ; /* nombre de pièces de 2 centimes */ nbf = 0 ; for (n10=0 ; n10<=10 ; n10++) for (n5=0 ; n5<=20 ; n5++) for (n2=0 ; n2<=50 ; n2++)
  • 33.
    III. Les instructionsde contrôle 33 if ( 2*n2 + 5*n5 + 10*n10 == 100) { nbf ++ ; printf ("1 F = ") ; if (n2) printf ("%2d X 2c ", n2 ) ; if (n5) printf ("%2d X 5c ", n5 ) ; if (n10) printf ("%2d X 10c", n10) ; printf ("n") ; } printf ("nEn tout, il y a %d façons de faire 1 Fn", nbf) ; } Exe rcice III.14 ___________________________________________________________________________ Enoncé Ecrire un program m e qui déterm ine la niem e valeur un (n étant fourni en donnée) de la "suite de Fibonacci" définie com m e suit: u1 = 1 u2 = 1 un = un-1 + un-2 pour n> 2 _______________________________________________________________ Solution #include <stdio.h> main() { int u1, u2, u3 ; /* pour "parcourir" la suite */ int n ; /* rang du terme demandé */ int i ; /* compteur */
  • 34.
    34 Exe rcicesen langage C do { printf ("rang du terme demandé (au moins 3) ? ") ; scanf ("%d", &n) ; } while (n<3) ; u2 = u1 = 1 ; /* les deux premiers termes */ i = 2 ; while (i++ < n) /* attention, l'algorithme ne fonctionne */ { u3 = u1 + u2 ; /* que pour n > 2 */ u1 = u2 ; u2 = u3 ; } /* autre formulation possible : */ /* for (i=3 ; i<=n ; i++, u1=u2, u2=u3) u3 = u1 + u2 ; */ printf ("Valeur du terme de rang %d : %d", n, u3) ; } Notez que, com m e à l'accoutum ée en C, beaucoup de form ulations sont possibles. Nous en avons d'ailleurs placé une seconde en com m entaire de notre program m e. Exe rcice III.15 ___________________________________________________________________________ Enoncé Ecrire un program m e qui trouve la plus grande etla plus petite valeur d'une succession de notes (nom bres entiers entre 0 et20) fournies en données, ainsi que le nom bre de fois où ce m axim um etce m inim um ontété attribués. On supposera que les notes, en nom bre non connu à l'avance, serontterm inées par une valeur négative. On s'astreindra à ne pas utiliser de "tableau". L'exécution du program m e pourra se présenter ainsi : donnez une note (-1 pour finir) : 12 donnez une note (-1 pour finir) : 8 donnez une note (-1 pour finir) : 13 donnez une note (-1 pour finir) : 7
  • 35.
    III. Les instructionsde contrôle 35 donnez une note (-1 pour finir) : 11 donnez une note (-1 pour finir) : 12 donnez une note (-1 pour finir) : 7 donnez une note (-1 pour finir) : 9 donnez une note (-1 pour finir) : -1 note maximale : 13 attribuée 1 fois note minimale : 7 attribuée 2 fois _______________________________________________________________ Solution #include <stdio.h> main() { int note ; /* note "courante" */ int max ; /* note maxi */ int min ; /* note mini */ int nmax ; /* nombre de fois où la note maxi a été trouvée */ int nmin ; /* nombre de fois où la note mini a été trouvée */ max = -1 ; /* initialisation max (possible car toutes notes >=0 */ min = 21 ; /* initialisation min (possible car toutes notes < 21) */ while (printf ("donnez une note (-1 pour finir) : "), scanf ("%d", &note), note >=0) { if (note == max) nmax++ ; if (note > max) { max = note ; nmax = 1 ; } if (note == min) nmin++ ; if (note < min) { min = note ; nmin = 1 ; } } /* attention, si aucune note (cad si max<0) */ /* les résultats sont sans signification */ if (max >= 0) { printf ("nnote maximale : %d attribuée %d foisn", max, nmax) ;
  • 36.
    36 Exe rcicesen langage C printf ("note minimale : %d attribuée %d foisn", min, nmin) ; } } Exe rcice III.16 ___________________________________________________________________________ Enoncé Ecrire un program m e qui affich e la "table de m ultiplication" des nom bres de 1 à 10, sous la form e suivante : I 1 2 3 4 5 6 7 8 9 10 ----------------------------------------------- 1 I 1 2 3 4 5 6 7 8 9 10 2 I 2 4 6 8 10 12 14 16 18 20 3 I 3 6 9 12 15 18 21 24 27 30 4 I 4 8 12 16 20 24 28 32 36 40 5 I 5 10 15 20 25 30 35 40 45 50 6 I 6 12 18 24 30 36 42 48 54 60 7 I 7 14 21 28 35 42 49 56 63 70 8 I 8 16 24 32 40 48 56 64 72 80 9 I 9 18 27 36 45 54 63 72 81 90 10 I 10 20 30 40 50 60 70 80 90 100 _______________________________________________________________ Solution #include <stdio.h> #define NMAX 10 /* nombre de valeurs */ main() { int i, j ; /* affichage ligne en-tête */ printf (" I") ; for (j=1 ; j<=NMAX ; j++) printf ("%4d", j) ;
  • 37.
    III. Les instructionsde contrôle 37 printf ("n") ; printf ("-------") ; for (j=1 ; j<=NMAX ; j++) printf ("----") ; printf ("n") ; /* affichage des différentes lignes */ for (i=1 ; i<=NMAX ; i++) { printf ("%4d I", i) ; for (j=1 ; j<=NMAX ; j++) printf ("%4d", i*j) ; printf ("n") ; }
  • 39.
    IV: LES FONCTIO NS N.B. Ici, on ne trouvera aucun exercice faisant intervenir des pointeurs, et par conséquent aucun exercice m ettant en oeuvre une transm ission d'argum ents par adresse. De tels exercices apparaîtrontdans le ch apitre suivant. Exe rcice IV.1 ___________________________________________________________________________ Enoncé a)Que fournitle program m e suivant: #include <stdio.h> main() { int n, p=5 ; n = fct (p) ; printf ("p = %d, n = %dn", p, n) ; } int fct (int r) { return 2*r ; } b)Ajouter une déclaration convenable de la fonction fct: - sous la form e la plus brè ve possible (suivantla norm e ANSI),
  • 40.
    40 Exe rcicesen langage C - sous form e d'un "prototype". _______________________________________________________________ Solution a)Bien qu'ilne possè de pas de déclaration de la fonction fct, le program m e m ain estcorrect. En effet, la norm e ANSI autorise qu'une fonction ne soitpas déclarée, auquelcas elle est considérée com m e fournissant un résultat de type int. Cette facilité esttoutefois fortem entdéconseillée (etelle ne sera plus acceptée de C+ + ). Voici les résultats fournis par le program m e : p = 5, n = 10 b)La déclaration la plus brè ve sera : int fct () ; La déclaration (vivem entconseillée), sous form e de prototype sera : int fct (int) ; ou, éventuellem ent, sous form e d'un prototype "com plet" : int fct (int r) ; Dans ce dernier cas, le nom r n'a aucune signification : on utilise souventle m ê m e nom (lorsqu'on le connaît!) que dans l'en-tê te de la fonction, m ais ilpourraits'agir de n'im porte quelautre nom de variable). Exe rcice IV.2 ___________________________________________________________________________ Enoncé Ecrire :
  • 41.
    IV. Les fonctions41 - une fonction, nom m ée f1, se contentant d'affich er "bonjour" (elle ne possédera aucun argum ent, ni valeur de retour), - une fonction, nom m ée f2, qui affich e "bonjour" un nom bre de fois égalà la valeur reçue en argum ent(int)etqui ne renvoie aucune valeur, - une fonction, nom m ée f3, qui faitla m ê m e ch ose que f2, m ais qui, de plus, renvoie la valeur (int)0. Ecrire un petitprogram m e appelantsuccessivem entch acune de ces 3 fonctions, aprè s les avoir convenablem entdéclarées sous form e d'un prototype. _______________________________________________________________ Solution #include <stdio.h> void f1 (void) { printf ("bonjourn") ; } void f2 (int n) { int i ; for (i=0 ; i<n ; i++) printf ("bonjourn") ; } int f3 (int n) { int i ; for (i=0 ; i<n ; i++) printf ("bonjourn") ; return 0 ; } main() { void f1 (void) ; void f2 (int) ; int f3 (int) ; f1 () ; f2 (3) ; f3 (3) ;
  • 42.
    42 Exe rcicesen langage C } Exe rcice IV.3 ___________________________________________________________________________ Enoncé Quels résultats fournira ce program m e : #include <stdio.h> int n=10, q=2 ; main() { int fct (int) ; void f (void) ; int n=0, p=5 ; n = fct(p) ; printf ("A : dans main, n = %d, p = %d, q = %dn", n, p, q) ; f() ; } int fct (int p) { int q ; q = 2 * p + n ; printf ("B : dans fct, n = %d, p = %d, q = %dn", n, p, q) ; return q ; } void f (void) { int p = q * n ; printf ("C : dans f, n = %d, p = %d, q = %dn", n, p, q) ; } _______________________________________________________________
  • 43.
    IV. Les fonctions43 Solution B : dans fct, n = 10, p = 5, q = 20 A : dans main, n = 20, p = 5, q = 2 C : dans f, n = 10, p = 20, q = 2 Exe rcice IV.4 ___________________________________________________________________________ Enoncé Ecrire une fonction qui reçoiten argum ents 2 nom bres flottants etun caractè re etqui fournitun résultatcorrespondantà l'une des 4 opérations appliquées à ses deux prem iers argum ents, en fonction de la valeur du dernier, à savoir : addition pour le caractè re + , soustraction pour -, m ultiplication pour *etdivision pour /(toutautre caractè re que l'un des 4 cités sera interprété com m e une addition). On ne tiendra pas com pte des risques de division par zéro. Ecrire un petit program m e (m ain) utilisant cette fonction pour effectuer les 4 opérations sur deux nom bres fournis en donnée. _______________________________________________________________ Solution #include <stdio.h> float oper (float v1, float v2, char op) { float res ; switch (op) { case '+' : res = v1 + v2 ; break ; case '-' : res = v1 - v2 ; break ; case '*' : res = v1 * v2 ; break ; case '/' : res = v1 / v2 ;
  • 44.
    44 Exe rcicesen langage C break ; default : res = v1 + v2 ; } return res ; } main() { float oper (float, float, char) ; /* prototype de oper */ float x, y ; printf ("donnez deux nombres réels : ") ; scanf ("%e %e", &x, &y) ; printf ("leur somme est : %en", oper (x, y, '+') ) ; printf ("leur différence est : %en", oper (x, y, '-') ) ; printf ("leur produit est : %en", oper (x, y, '*') ) ; printf ("leur quotient est : %en", oper (x, y, '/') ) ; } Exe rcice IV.5 ___________________________________________________________________________ Enoncé Transform er le program m e (fonction + m ain)écritdans l'exercice précédentde m aniè re à ce que la fonction ne dispose plus que de 2 argum ents, le caractè re indiquantla nature de l'opération à effectuer étantprécisé, cette fois, à l'aide d'une variable globale. _______________________________________________________________ Solution #include <stdio.h>
  • 45.
    IV. Les fonctions45 char op ; /* variable globale pour la nature de l'opération */ /* attention : doit être déclarée avant d'être utilisée */ float oper (float v1, float v2) { float res ; switch (op) { case '+' : res = v1 + v2 ; break ; case '-' : res = v1 - v2 ; break ; case '*' : res = v1 * v2 ; break ; case '/' : res = v1 / v2 ; break ; default : res = v1 + v2 ; } return res ; } main() { float oper (float, float) ; /* prototype de oper */ float x, y ; printf ("donnez deux nombres réels : ") ; scanf ("%e %e", &x, &y) ; op = '+' ; printf ("leur somme est : %en", oper (x, y) ) ; op = '-' ; printf ("leur différence est : %en", oper (x, y) ) ; op = '*' ; printf ("leur produit est : %en", oper (x, y) ) ; op = '/' ; printf ("leur quotient est : %en", oper (x, y) ) ; } Rem arque : Ils'agissait ici d'un exercice d'"école" destiné à forcer l'utilisation d'une variable globale. Dans la pratique, on évitera le plus possible ce genre de program m ation qui favorise trop largem entles risques d'"effets de bord".
  • 46.
    46 Exe rcicesen langage C Exe rcice IV.6 ___________________________________________________________________________ Enoncé Ecrire une fonction, sans argum entni valeur de retour, qui se contente d'affich er, à ch aque appel, le nom bre totalde fois où elle a été appelée sous la form e : appel numéro 3 _______________________________________________________________ Solution La m eilleure solution consiste à prévoir, au sein de la fonction en question, une variable de classe statique. Elle sera initialisée une seule fois à zéro (ou à toute autre valeur éventuellem entexplicitée)au débutde l'exécution du program m e. Ici, nous avons, de plus, prévu un petitprogram m e d'essai. #include <stdio.h> void fcompte (void) { static int i ; /* il est inutile, mais pas défendu, d'écrire i=0 */ i++ ; printf ("appel numéro %dn", i) ; } /* petit programme d'essai de fcompte */ main() { void fcompte (void) ; int i ; for (i=0 ; i<3 ; i++) fcompte () ; } Là encore, la dém arch e consistantà utiliser com m e com pteur d'appels une variable globale (qui devraitalors ê tre connue du program m e utilisateur)està proscrire.
  • 47.
    IV. Les fonctions47 Exe rcice IV.7 ___________________________________________________________________________ Enoncé Ecrire 2 fonctions à un argum ent entier et une valeur de retour entiè re perm ettant de préciser si l'argum ent reçu est m ultiple de 2 (pour la prem iè re fonction)ou m ultiple de 3 (pour la seconde fonction). Utiliser ces deux fonctions dans un petit program m e qui lit un nom bre entier et qui précise s'ilest pair, m ultiple de 3 et/ou m ultiple de 6, com m e dans cetexem ple (ily a deux exécutions): donnez un entier : 9 il est multiple de 3 _______________ donnez un entier : 12 il est pair il est multiple de 3 il est divisible par 6 _______________________________________________________________ Solution #include <stdio.h> int mul2 (int n) { if (n%2) return 0 ; else return 1 ; } int mul3 (int n) { if (n%3) return 0 ; else return 1 ; } main() { int mul2 (int) ;
  • 48.
    48 Exe rcicesen langage C int mul3 (int) ; int n ; printf ("donnez un entier : ") ; scanf ("%d", &n) ; if (mul2(n)) printf ("il est pairn") ; if (mul3(n)) printf ("il est multiple de 3n") ; if (mul2(n) && mul3(n)) printf ("il est divisible par 6n") ; }
  • 49.
    V: TABLEAUX ET POINTEURS Exe rcice V.1 ___________________________________________________________________________ Enoncé Quels résultats fournira ce program m e : #include <stdio.h> main() { int t [3] ; int i, j ; int * adt ; for (i=0, j=0 ; i<3 ; i++) t[i] = j++ + i ; /* 1 */ for (i=0 ; i<3 ; i++) printf ("%d ", t[i]) ; /* 2 */ printf ("n") ; for (i=0 ; i<3 ; i++) printf ("%d ", *(t+i)) ; /* 3 */ printf ("n") ; for (adt = t ; adt < t+3 ; adt++) printf ("%d ", *adt) ; /* 4 */ printf ("n") ; for (adt = t+2 ; adt>=t ; adt--) printf ("%d ", *adt) ; /* 5 */
  • 50.
    50 Exe rcicesen langage C printf ("n") ; } _______________________________________________________________ Solution /*1*/rem plitle tableau avec les valeurs 0 (0+ 0), 2 (1+ 1) et4 (2+ 2);on obtiendraitplus sim plem entle m ê m e résultat avec l'expression 2*i. /*2 */affich e "classiquem ent" les valeurs du tableau t, dans l'ordre "naturel". /*3 */ fait la m ê m e ch ose, en utilisant le form alism e pointeur au lieu du form alism e tableau. Ainsi, *(t+ i) est parfaitem entéquivalentà t[i]. /*4 */ faitla m ê m e ch ose, en utilisantla "lvalue" adt(à laquelle on a affecté initialem entl'adresse tdu tableau) eten "l'incrém entant" pour parcourir les différentes adresses des 4 élém ents du tableau. /*5 */ affich e les valeurs de t, à l'envers, en utilisantle m ê m e form alism e pointeur que dans 4. On auraitpu écrire, de façon équivalente : for (i=2 ; i>=0 ; i--) printf ("%d ", t[i]) ; Voici les résultats fournis par ce program m e : 0 2 4 0 2 4 0 2 4 4 2 0 Exe rcice V.2 ___________________________________________________________________________
  • 51.
    V. Table auxe tpointe urs 51 Enoncé Ecrire, de deux façons différentes, un program m e qui lit10 nom bres entiers dans un tableau avantd'en rech erch er le plus grand etle plus petit: a)en utilisantuniquem entle "form alism e tableau", b)en utilisantle "form alism e pointeur", à ch aque fois que cela estpossible _______________________________________________________________ Solution a)La program m ation est, ici, "classique". Nous avons sim plem entdéfini un sym bole NVALdestiné à contenir le nom bre de valeurs du tableau. Notez bien que la déclaration int t[NVAL] est acceptée puisque NVAL est une "expression constante". En revanch e, elle ne l'auraitpas été si nous avions défini ce sym bole NVALpar une "constante sym bolique" (constintNVAL=10). #include <stdio.h> #define NVAL 10 /* nombre de valeurs du tableau */ main() { int i, min, max ; int t[NVAL] ; printf ("donnez %d valeursn", NVAL) ; for (i=0 ; i<NVAL ; i++) scanf ("%d", &t[i]) ; max = min = t[0] ; for (i=1 ; i<NVAL ; i++) { if (t[i] > max) max = t[i] ; /* ou max = t[i]>max ? t[i] : max */ if (t[i] < min) min = t[i] ; /* ou min = t[i]<min ? t[i] : min */ } printf ("valeur max : %dn", max) ; printf ("valeur min : %dn", min) ; } b)On peut rem placer systém atiquem ent, t[i] par *(t+ i)./ De plus, dans scanf, on peut rem placer & t[i] par t+ i. Voici finalem entle program m e obtenu : #include <stdio.h> #define NVAL 10 /* nombre de valeurs du tableau */ main() { int i, min, max ;
  • 52.
    52 Exe rcicesen langage C int t[NVAL] ; printf ("donnez %d valeursn", NVAL) ; for (i=0 ; i<NVAL ; i++) scanf ("%d", t+i) ; /* attention t+i et non *(t+i) */ max = min = *t ; for (i=1 ; i<NVAL ; i++) { if (*(t+i) > max) max = *(t+i) ; if (*(t+i) < min) min = *(t+i) ; } printf ("valeur max : %dn", max) ; printf ("valeur min : %dn", min) ; } Exe rcice V.3 ___________________________________________________________________________ Enoncé Soientdeux tableaux t1 ett2 déclarés ainsi : float t1[10], t2[10] ; Ecrire les instructions perm ettantde recopier, dans t1, tous les élém ents positifs de t2, en com plétantéventuellem entt1 par des zéros. Ici, on ne ch erch era pas à fournir un program m e com pleteton utilisera systém atiquem entle form alism e tableau. _______________________________________________________________ Solution On peutcom m encer par rem plir t1 de zéros, avantd'y recopier les élém ents positifs de t2 : int i, j ; for (i=0 ; i<10 ; i++) t1[i] = 0 ; /* i sert à pointer dans t1 et j dans t2 */ for (i=0, j=0 ; j<10 ; j++)
  • 53.
    V. Table auxe tpointe urs 53 if (t2[j] > 0) t1[i++] = t2[j] ; M ais, on peut recopier d'abord dans t1 les élém ents positifs de t2, avant de com pléter éventuellem ent par des zéros. Cette deuxiè m e form ulation, m oins sim ple que la précédente, se révéleraittoutefois plus efficace sur de grands tableaux : int i, j ; for (i=0, j=0 ; j<10 ; j++) if (t2[j] > 0) t1[i++] = t2[j] ; for (j=i ; j<10 ; j++) t1[j] = 0 ; Exe rcice V.4 ___________________________________________________________________________ Enoncé Quels résultats fournira ce program m e : #include <stdio.h> main() { int t[4] = {10, 20, 30, 40} ; int * ad [4] ; int i ; for (i=0 ; i<4 ; i++) ad[i] = t+i ; /* 1 */ for (i=0 ; i<4 ; i++) printf ("%d ", * ad[i]) ; /* 2 */ printf ("n") ; printf ("%d %d n", * (ad[1] + 1), * ad[1] + 1) ; /* 3 */ } _______________________________________________________________ Solution Le tableau ad est un tableau de 4 élém ents ;ch acun de ces élém ents est un pointeur sur un int. L'instruction /*1 */ rem plitle tableau ad avec les adresses des 4 élém ents du tableau t. L'instruction /*2 */affich e finalem entles 4 élém ents du tableau t;en effet, *ad[i]représente la valeur située à l'adresse ad[i]. /*2 */estéquivalente ici à : for (i=0 ; i<4 ; i++) printf ("%d", t[i]) ;
  • 54.
    54 Exe rcicesen langage C Enfin, dans l'instruction /*3 */, *(ad[1]+ 1)représente la valeur située à l'entier suivantcelui d'adresse ad[1];ils'agit donc de t[2]. En revanch e, *ad[1]+ 1 représente la valeur située à l'adresse ad[1]augm entée de 1, autrem entditt[1]+ 1. Voici, en définitive, les résultats fournis par ce program m e : 10 20 30 40 30 21 Exe rcice V.5 ___________________________________________________________________________ Enoncé Soitle tableau tdéclaré ainsi : float t[3] [4] ; Ecrire les (seules)instructions perm ettantde calculer, dans une variable nom m ée som , la som m e des élém ents de t: a)en utilisantle "form alism e usueldes tableaux à deux indices", b)en utilisantle "form alism e pointeur". _______________________________________________________________ Solution a)La prem iè re solution ne pose aucun problè m e particulier : int i, j ; som = 0 ; for (i=0 ; i<3 ; i++) for (j=0 ; j<4 ; j++) som += t[i] [j] ; b)Le form alism e pointeur estici m oins facile à appliquer que dans le cas des tableaux à un indice. En effet, avec, par exem ple, float t[4], t est de type int*et ilcorrespond à un pointeur sur le prem ier élém ent du tableau. Ilsuffit donc d'incrém enter convenablem enttpour parcourir tous les élém ents du tableau.
  • 55.
    V. Table auxe tpointe urs 55 En revanch e, avec notre tableau floatt[3][4], testdu type pointeur sur des tableaux de 4 flottants(type : float[4]*). La notation *(t+ i) est généralem ent inutilisable sous cette form e puisque, d'une part, elle correspond à des valeurs de tableaux de 4 flottants et que, d'autre part, l'incrém ent i porte, non plus sur des flottants, m ais sur des blocs de 4 flottants ;par exem ple, t+ 2 représente l'adresse du h uitiè m e flottant, com pté à partir de celui d'adresse t. Une solution consiste à "convertir" la valeur de t en un pointeur de type float*. On pourrait se contenter de procéder ainsi : float * adt ; ..... adt = t ; En effet, dans ce cas, l'affectation entraîne une conversion forcée de t en float *, ce qui ne ch ange pas l'adresse correspondante1 (seule la nature du pointeur a ch angé). Généralem ent, on y gagnera en lisibilité en explicitant la conversion m ise en oeuvre à l'aide de l'opérateur de "cast". Notez que, d'une part, cela peutéviter certains m essages d'avertissem ent("w arnings")de la partdu com pilateur. Voici finalem entce que pourraientê tre les instructions dem andées : int i ; int * adt ; som = 0 ; adt = (float *) t ; for (i=0 ; i<12 ; i++) som += * (adt+i); Exe rcice V.6 ___________________________________________________________________________ Enoncé Ecrire une fonction qui fourniten valeur de retour la som m e des élém ents d'un tableau de flottants transm is, ainsi que sa dim ension, en argum ent. Ecrire un petitprogram m e d'essai. 1 Attention, cela n'estvrai que parce que l'on passe de pointeurs sur des groupes d'élém ents à un pointeur sur ces élém ents. Autrem entdit, aucune "contrainte d'alignem ent" ne risque de nuire ici. Iln'en iraitpas de m ê m e, par exem ple, pour des conversions de ch ar *en int*.
  • 56.
    56 Exe rcicesen langage C _______________________________________________________________ Solution En ce qui concerne le tableau de flottants reçu en argum ent, ilne peutê tre transm is que par adresse. Quantau nom bre d'élém ent(de type int), nous le transm ettrons classiquem entpar valeur. L'en-tê te de notre fonction pourra se présenter sous l'une des form es suivantes : float somme (float t[], int n) float somme (float * t, int n) float somme (float t[5], int n) /* déconseillé car laisse croire que t */ /* est de dimension fixe 5 */ En effet, la dim ension réelle de tn'a aucune incidence sur les instructions de la fonction elle-m ê m e (elle n'intervientpas dans le calculde l'adresse d'un élém entdu tableau2 etelle ne sertpas à "allouer" un em placem entpuisque le tableau en question aura été alloué dans la fonction appelantsom m e ). Voici ce que pourraitê tre la fonction dem andée : float somme (float t[], int n) /* on pourrait écrire somme (float * t, ... */ /* ou encore somme (float t[4], ... */ /* mais pas somme (float t[n], ... */ { int i ; float s = 0 ; for (i=0 ; i<n ; i++) s += t[i] ; /* on pourrait écrire s += * (t+i) ; */ return s ; } Pour ce qui est du program m e d'utilisation de la fonction som m e , on peut, là encore, écrire le "prototype" sous différentes form es : float somme (float [], int ) ; float somme (float * , int ) ; float somme (float [5], int ) ; /* déconseillé car laisse croire que t */ /* est de dimension fixe 5 */ Voici un exem ple d'un telprogram m e : #include <stdio.h> main() 2Iln'en iraitpas de m ê m e pour des tableaux à plusieurs indices.
  • 57.
    V. Table auxe tpointe urs 57 { float somme (float *, int) ; float t[4] = {3, 2.5, 5.1, 3.5} ; printf ("somme de t : %fn", somme (t, 4) ) ; } Exe rcice V.7 ___________________________________________________________________________ Enoncé Ecrire une fonction qui ne renvoie aucune valeur etqui déterm ine la valeur m axim ale etla valeur m inim ale d'un tableau d'entiers (à un indice)de taille quelconque. Ilfaudra donc prévoir 4 argum ents : le tableau, sa dim ension, le m axim um et le m inim um . Ecrire un petitprogram m e d'essai. _______________________________________________________________ Solution En langage C, un tableau ne peutê tre transm is que par adresse (en toute rigueur, C n'autorise que la transm ission par valeur m ais, dans le cas d'un tableau, on transm et une valeur de type pointeur qui n'est rien d'autre que l'adresse du tableau!). En ce qui concerne son nom bre d'élém ents, on peutindifférem m enten transm ettre l'adresse (sous form e d'un pointeur de type int*), ou la valeur ;ici, la seconde solution estla plus norm ale. En revanch e, en ce qui concerne le m axim um et le m inim um , ils ne peuvent pas ê tre transm is par valeur, puisqu'ils doiventprécisém entê tre déterm inés par la fonction. Ilfautdonc obligatoirem entprévoir de passer des pointeurs sur des float. L'en-tê te de notre fonction pourra donc se présenter ainsi (nous ne donnons plus toutes les écritures possibles): void maxmin (int t[], int n, int * admax, int * admin) L'algorith m e de rech erch e de m axim um etde m inim um peutê tre calqué sur celui de l'exercice V.2, en rem plaçantm ax par *adm ax etm in par *adm in. Cela nous conduità la fonction suivante : void maxmin (int t[], int n, int * admax, int * admin)
  • 58.
    58 Exe rcicesen langage C { int i ; *admax = t[1] ; *admin = t[1] ; for (i=1 ; i<n ; i++) { if (t[i] > *admax) *admax = t[i] ; if (t[i] < *admin) *admin = t[i] ; } } Si l'on souh aite éviter les "indirections" qui apparaissentsystém atiquem entdans les instructions de com paraison, on peut "travailler" tem porairem ent sur des variables locales à la fonction (nom m ées ici m ax et m in). Cela nous conduit à une fonction de la form e suivante : void maxmin (int t[], int n, int * admax, int * admin) { int i, max, min ; max = t[1] ; min = t[1] ; for (i=1 ; i<n ; i++) { if (t[i] > max) max = t[i] ; if (t[i] < min) min = t[i] ; } *admax = max ; *admin = min ; } Voici un petitexem ple de program m e d'utilisation de notre fonction : #include <stdio.h> main() { void maxmin (int [], int, int *, int *) ; int t[8] = { 2, 5, 7, 2, 9, 3, 9, 4} ; int max, min ; maxmin (t, 8, &max, &min) ; printf ("valeur maxi : %dn", max) ; printf ("valeur mini : %dn", min) ; }
  • 59.
    V. Table auxe tpointe urs 59 Exe rcice V.8 ___________________________________________________________________________ Enoncé Ecrire une fonction qui fournit en retour la som m e des valeurs d'un tableau de flottants à deux indices dont les dim ensions sontfournies en argum ent. _______________________________________________________________ Solution Par analogie avec ce que nous avions faitdans l'exercice V.6, nous pourrions songer à déclarer le tableau concerné dans l'en-tê te de la fonction sous la form e t[][]. M ais, cela n'estplus possible car, cette fois, pour déterm iner l'adresse d'un élém entt[i][j]d'un teltableau, le com pilateur doiten connaître la deuxiè m e dim ension. Une solution consiste à considérer qu'on reçoitun pointeur (de type float*)sur le débutdu tableau etd'en parcourir tous les élém ents (au nom bre de n*p si n etp désignentles dim ensions du tableau) com m e si l'on avaitaffaire à un tableau à une dim ension. Cela nous conduità cette fonction : float somme (float * adt, int n, int p) { int i ; float s ; for (i=0 ; i<n*p ; i++) s += adt[i] ; /* ou s += *(adt+i) */ return s ; } Pour utiliser une telle fonction, la seule difficulté consiste à lui transm ettre effectivem entl'adresse de débutdu tableau, sous la form e d'un pointeur de type int*. Or, avec, par exem ple t[3][4], t, s'ilcorrrespond bien à la bonne adresse, est du type "pointeur sur des tableaux de 4 flottants". A priori, toutefois, com pte tenu de la présence du prototype, la conversion voulue sera m ise en oeuvre autom atiquem entpar le com pilateur. Toutefois, com m e nous l'avons déjà ditdans l'exercice V.5, on y gagnera en lisibilité (et en éventuels m essages d'avertissem ent!) en faisant appelà l'opérateur de "cast". Voici finalem entun exem ple d'un telprogram m e d'utilisation de notre fonction : #include <stdio.h> main() {
  • 60.
    60 Exe rcicesen langage C float somme (float *, int, int) ; float t[3] [4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} } ; printf ("somme : %fn", somme ((float *)t, 3, 4) ) ; }
  • 61.
    VI: LES CHAINES D E CARACTERES Exe rcice VI.1 ___________________________________________________________________________ Enoncé Quels résultats fournira ce program m e : #include <stdio.h> main() { char * ad1 ; ad1 = "bonjour" ; printf ("%sn", ad1) ; ad1 = "monsieur" ; printf ("%sn", ad1) ; } _______________________________________________________________ Solution L'instruction ad1 = "bonjour" place dans la variable ad1 l'adresse de la ch aîne constante "bonjour". L'instruction printf ("%sn", ad1) se contente d'affich er la valeur de la ch aîne dontl'adresse figure dans ad1, c'est-à -dire, en l'occurrence "bonjour". De m aniè re com parable, l'instruction ad1 = "m onsie ur" place l'adresse de la ch aîne constante "m onsieur"
  • 62.
    62 Exe rcicesen langage C dans ad1 ;l'instruction printf("%sn", ad1)affich e la valeur de la ch aîne ayantm aintenantl'adresse contenue dans ad1, c'est-à -dire m aintenant"m onsieur". Finalem ent, ce program m e affich e toutsim plem ent: bonjour monsieur On auraitobtenu plus sim plem entle m ê m e résultaten écrivant: printf ("bonjournmonsieurn") ; Exe rcice VI.2 ___________________________________________________________________________ Enoncé Quels résultats fournira ce program m e : #include <stdio.h> main() { char * adr = "bonjour" ; /* 1 */ int i ; for (i=0 ; i<3 ; i++) putchar (adr[i]) ; /* 2 */ printf ("n") ; i = 0 ; while (adr[i]) putchar (adr[i++]) ; /* 3 */ } _______________________________________________________________ Solution La déclaration /*1 */ place dans la variable adr, l'adresse de la ch aîne constante bonjour. L'instruction /*2 */ affich e les caractè res adr[0], adr[1]etadr[2], c'est-à -dire les 3 prem iers caractè res de cette ch aîne. L'instruction /*3 */ affich e tous les caractè res à partir de celui d'adresse adr, tant que l'on a pas affaire à un caractè re nul;com m e notre ch aîne
  • 63.
    VI. Les chaînes de caractè res 63 "bonjour" est précisém ent term inée par un telcaractè re nul, cette instruction affich e finalem ent, un par un, tous les caractè res de "bonjour". En définitive, le program m e fournitsim plem entles résultats suivants : bon bonjour Exe rcice VI.3 ___________________________________________________________________________ Enoncé Ecrire le program m e précédent(Exercice VI.2), sans utiliser le "form alism e tableau" (ilexiste plusieurs solutions). _______________________________________________________________ Solution Voici deux solutions possibles : a)On peutrem placer systém atiquem entla notation adr[i]par *(adr+ i), ce qui conduità ce program m e : #include <stdio.h> main() { char * adr = "bonjour" ; int i ; for (i=0 ; i<3 ; i++) putchar (*(adr+i)) ; printf ("n") ; i = 0 ; while (adr[i]) putchar (*(adr+i++)) ; } b)On peutégalem entparcourir notre ch aîne, non plus à l'aide d'un "indice" i, m ais en incrém entantun pointeur de type ch ar *: ilpourraits'agir toutsim plem entde adr, m ais généralem ent, on préférera ne pas détruire cette inform ation eten em ployer une copie :
  • 64.
    64 Exe rcicesen langage C #include <stdio.h> main() { char * adr = "bonjour" ; char * adb ; for (adb=adr ; adb<adr+3 ; adb++) putchar (*adb) ; printf ("n") ; adb = adr ; while (*adb) putchar (*(adb++)) ; } Notez bien que si nous incrém entions directem entadr dans la prem iè re instruction d'affich age, nous ne disposerions plus de la "bonne adresse" pour la deuxiè m e instruction d'affich age. Exe rcice VI.4 ___________________________________________________________________________ Enoncé Ecrire un program m e qui dem ande à l'utilisateur de lui fournir un nom bre entier entre 1 et7 etqui affich e le nom du jour de la sem aine ayantle num éro indiqué (lundi pour 1, m ardi pour 2, ... dim anch e pour 7). _______________________________________________________________ Solution Une dém arch e consiste à créer un "tableau de 7 pointeurs sur des ch aînes", correspondantch acune au nom d'un jour de la sem aine. Com m e ces ch aînes sontici constantes, ilestpossible de créer un teltableau par une déclaration com portant une intialisation de la form e : char * jour [7] = { "lundi", "mardi", ... N'oubliez pas alors que jour[0] contiendra l'adresse de la prem iè re ch aîne, c'est-à -dire l'adresse de la ch aîne constante "lundi" ;jour[1]contiendra l'adresse de "m ardi", ... Pour affich er la valeur de la ch aîne de rang i, ilsuffitde rem arquer que son adresse estsim plem entjour[i-1]. D'où le program m e dem andé :
  • 65.
    VI. Les chaînes de caractè res 65 #include <stdio.h> main() { char * jour [7] = { "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche" } ; int i ; do { printf ("donnez un nombre entier entre 1 et 7 : ") ; scanf ("%d", &i) ; } while ( i<=0 || i>7) ; printf ("le jour numéro %d de la semaine est %s", i, jour[i-1]) ; } Exe rcice VI.5 ___________________________________________________________________________ Enoncé Ecrire un program m e qui litdeux nom bres entiers fournis obligatoirem entsur une m ê m e ligne. Le program m e ne devra pas "se planter" en cas de réponse incorrecte (caractè res invalides) com m e le ferait scanf ("%d %d", ...) m ais sim plem entaffich er un m essage etredem ander une autre réponse. Ildevra en aller de m ê m e lorsque la réponse fournie ne com porte pas assez d'inform ations. En revanch e, lorsque la réponse com portera trop d'inform ations, les derniè res devrontê tre ignorées. Le traitem ent(dem ande de 2 nom bres etaffich age)devra se poursuivre jusqu'à ce que le prem ier nom bre fourni soit0. Voici un exem ple d'exécution d'un telprogram m e : --- donnez deux entiers : é réponse erronée - redonnez-la : 2 15 merci pour 2 15 --- donnez deux entiers : 5 réponse erronée - redonnez-la : 4 12 merci pour 4 12 --- donnez deux entiers : 4 8 6 9 merci pour 4 8 --- donnez deux entiers : 5 é3
  • 66.
    66 Exe rcicesen langage C réponse erronée - redonnez-la : 5 23 merci pour 5 23 --- donnez deux entiers : 0 0 merci pour 0 0 Rem arque : on peututiliser les fonctions ge ts etsscanf. _______________________________________________________________ Solution Com m e le suggè re la rem arque de l'énoncé, on peutrésoudre les problè m es posés en effectuanten deux tem ps la lecture d'un couple d'entiers : - lecture d'une ch aîne de caractè res (c'est-à -dire une suite de caractè res absolum ent quelconques, validée par "return")avec la fonction ge ts, - "décodage" de cette ch aîne avec sscanf, suivant un "form at", d'une m aniè re com parable à ce que ferait scanf, à partir de son "tam pon d'entrée". Rappelons que sscanf, tout com m e scanf, fournit en retour le nom bre d'inform ations correctem ent lues, de sorte qu'il suffitde répéter les deux opérations précédentes jusqu'à ce que la valeur fournie par sscanfsoitégale à 2. L'énoncé ne faitaucune h ypoth è se sur le nom bre m axim alde caractè res que l'utilisateur pourra ê tre am ené à fournir. Ici, nous avons supposé qu'au plus 128 caractè res seraientfournis ;ils'agitlà d'une h ypoth è se qui, dans la pratique, s'avè re réaliste, dans la m esure où on risque rarem ent de frapper des lignes plus longues ;de surcroît, ils'agit m ê m e d'une lim itation "naturelle" de certains environnem ents (DOS, en particulier). Voici le program m e dem andé : #include <stdio.h> #define LG 128 /* longueur maximale d'une ligne */ main() { int n1, n2 ; /* entiers à lire en donnée */ int compte ; /* pour la valeur de retour de sscanf */ char ligne [LG+1] ; /* pour lire une ligne (+1 pour 0) */ /* boucle de lecture des différents couples de valeurs */ do { /* boucle de lecture d'un couple de valeur jusqu'à OK */ printf ("--- donnez deux entiers : ") ; do { gets (ligne) ; compte = sscanf (ligne, "%d %d", &n1, &n2) ;
  • 67.
    VI. Les chaînes de caractè res 67 if (compte<2) printf ("réponse erronée - redonnez-la : ") ; } while (compte < 2) ; printf ("merci pour %d %dn", n1, n2) ; } while (n1) ; } Rem arques 1)Si l'utilisateur fournit plus de caractè res qu'iln'en faut pour form er 2 nom bres entiers, ces caractè res (lus dans ligne ) ne serontpas utilisés par sscanf;m algré tout, ils ne serontpas exploités ultérieurem entpuisque, lorsque l'on redem andera 2 nouveaux entiers, on relira une nouvelle ch aîne par ge ts. 2)Si l'on souh aite absolum ent pouvoir lim iter la longueur de la ch aîne lue au clavier, en utilisant des instructions "portables", ilfautse tourner vers la fonction fge ts1 destinée à lire une ch aîne dans un fich ier, etl'appliquer à stdin. On rem placera l'instruction ge ts (ligne )par fge ts (ligne , LG, stdin) qui lim itera à LG le nom bre de caractè res pris en com pte. Notez toutefois que, dans ce cas, les caractè res excédentaires (et donc non "vus" par fge ts) resteront disponibles pour une proch aine lecture (ce qui n'est pas pire que dans la situation actuelle où ces caractè res viendraientécraser des em placem ents m ém oire situés au-delà du tableau ligne !). Dans certaines im plém entations (Turbo/Borland C/C+ + et Quick C/C M icrosoft), ilexiste une fonction (non portable, puisque non prévue par la norm e ANSI) nom m ée cge ts qui, utilisée à la place de ge ts (ou fge ts) perm etde régler le problè m e évoqué. En effet, cge ts perm et de lire une ch aîne, en lim itant le nom bre de caractè res effectivem entfournis au clavier : iln'estpas possible à l'utilisateur d'en frapper plus que prévu, de sorte que le risque de caractè res excédentaires n'existe plus! Exe rcice VI.6 ___________________________________________________________________________ 1 M ais, si vous réalisez ces exercices en accom pagnem entd'un cours de langage C, ilestprobable que vous n'aurez pas encore étudié la fonction fgets (en général, elle estintroduite dans le ch apitre relatif au traitem entdes fich iers). Certains exercices de la seconde partie de cetouvrage feront appelà fgets, et/ou à sscanf.
  • 68.
    68 Exe rcicesen langage C Enoncé Ecrire un program m e déterm inant le nom bre de lettres e (m inuscule) contenues dans un texte fourni en donnée sous form e d'une seule ligne ne dépassant pas 128 caractè res. On ch erch era, ici, à n'utiliser aucune des fonctions de traitem entde ch aîne. _______________________________________________________________ Solution Com pte tenu des contraintes im posées par l'énoncé, nous ne pouvons pas faire appelà la fonction strle n. Pour "explorer" notre ch aîne, nous utiliserons le faitqu'elle estterm inée par un caractè re nul(0]. D'où le program m e proposé : #define LG_LIG 128 #include <stdio.h> main() { char ligne [LG_LIG+1] ; /* pour lire une ligne au clavier +1 pour 0 */ int i ; /* pour explorer les différents caractères de ligne */ int ne ; /* pour compter le nombre de 'e' */ printf ("donnez un texte de moins d'une ligne : n") ; gets (ligne) ; ne = 0 ; i = 0 ; while (ligne[i]) if (ligne[i++] == 'e') ne++ ; printf ("votre texte comporte %d lettres e", ne) ; } Exe rcice VI.7 ___________________________________________________________________________ Enoncé Ecrire un program m e qui lit, en donnée, un verbe du prem ier groupe et qui en affich e la conjugaison au présent de l'indicatif, sous la form e :
  • 69.
    VI. Les chaînes de caractè res 69 je chante tu chantes il chante nous chantons vous chantez ils chantent On s'assurera que le m ot fourni se term ine bien par "er". On supposera qu'ils'agitd'un verbe régulier ;autrem entdit, on adm ettra que l'utilisateur ne fournira pas un verbe telque m anger (le program m e affich eraitalors : nous m angons!). _______________________________________________________________ Solution On lira "classiquem ent" un m ot, sous form e d'une ch aîne à l'aide de la fonction ge ts. Pour vérifier sa term inaison par "er", on com parera avec la ch aîne constante "er", la ch aîne ayantcom m e adresse l'adresse de fin du m ot, dim inuée de 2. L'adresse de fin se déduira de l'adresse de débutetde la longueur de la ch aîne (obtenue par la fonction strle n). Quant à la com paraison voulue, elle se fera à l'aide de la fonction strcm p ;rappelons que cette derniè re reçoit en argum ent 2 pointeurs sur des ch aînes et qu'elle fournit en retour une valeur nulle lorsque les deux ch aînes correspondantes sontégales etune valeur non nulle dans tous les autres cas. Les différentes personnes du verbe s'obtiennenten rem plaçant, dans la ch aîne en question, la term inaison "er" par une term inaison appropriée. On peut, pour cela, utiliser la fonction strcpy qui recopie une ch aîne donnée (ici la term inaison) à une adresse donnée (ici, celle déjà utilisée dans strcm p pour vérifier que le verbe se term ine bien par "er"). Les différentes term inaisons possibles seront rangées dans un tableau de ch aînes constantes (plus précisém ent, dans un tableau de pointeurs sur des ch aînes constantes). Nous ferons de m ê m e pour les différents sujets (je, tu...);en revanch e, ici, nous ne ch erch erons pas à les "concaténer" au verbe conjugué ;nous nous contentons de les écrire, au m om ent opportun. Voici finalem entle program m e dem andé : #include <stdio.h> #include <string.h> #define LG_VERBE 30 /* longueur maximale du verbe fourni en donnée */ main() { char verbe [LG_VERBE+1] ; /* verbe à conjuguer +1 pour 0 */ char * sujet [6] = { "je", "tu", "il", "nous", "vous", "ils"} ; /* sujets */ char * term [6] = { "e", "es", "e", "ons", "ez", "ent" } ;/* terminaisons */ int i ; char * adterm ; /* pointeur sur la terminaison du verbe */
  • 70.
    70 Exe rcicesen langage C do { printf ("donnez un verbe régulier du premier groupe : ") ; gets (verbe) ; adterm = verbe + strlen(verbe) - 2 ; } while (strcmp (adterm, "er") ) ; printf ("conjugaison à l'indicatif présent :n") ; for (i=0 ; i<6 ; i++) { strcpy (adterm, term[i]) ; printf ("%s %sn", sujet[i], verbe) ; } } Rem arque : rappelons que strcpy recopie (sans aucun contrôle)la ch aîne dontl'adresse estfournie en prem ier argum ent (c'est-à -dire, en fait, tous les caractè res à partir de cette adresse, jusqu'à ce que l'on rencontre un 0) à l'adresse fournie en second argum ent;de plus, elle com plè te bien le toutavec un caractè re nulde fin de ch aîne. Exe rcice VI.8 ___________________________________________________________________________ Enoncé Ecrire un program m e qui supprim e toutes les lettres e (m inuscule)d'un texte de m oins d'une ligne (ne dépassantpas 128 caractè res) fourni en donnée. On s'arrangera pour que le texte ainsi m odifié soit créé en m ém oire, à la place de l'ancien. N.B. on pourra utiliser la fonction strch r. _______________________________________________________________ Solution La fonction strch r perm et de trouver un caractè re donné dans une ch aîne. Elle est donc tout à fait appropriée pour localiser les 'e' ;ilfaut toutefois noter que, pour localiser tous les 'e', ilest nécessaire de répéter l'appelde cette
  • 71.
    VI. Les chaînes de caractè res 71 fonction, en m odifiantà ch aque fois l'adresse de débutde la ch aîne concernée (ilfautéviter de boucler sur la rech erch e du m ê m e caractè re 'e'). La fonction strch r fournitl'adresse à laquelle on a trouvé le prem ier caractè re indiqué (ou la valeur 0 si ce caractè re n'existe pas). La suppression du 'e' trouvé peut se faire en recopiant le "reste" de la ch aîne à l'adresse où l'on a trouvé le 'e'. Voici une solution possible : #include <stdio.h> #include <string.h> #define LG_LIG 128 /* longueur maximum d'une ligne de données */ #define CAR 'e' /* caractère à supprimer */ main() { char ligne [LG_LIG+1] ; /* pour lire une ligne +1 pour 0 */ char * adr ; /* pointeur à l'intérieur de la ligne */ printf ("donnez un texte de moins d'une ligne : n") ; gets (ligne) ; adr = ligne ; while (adr = strchr (adr,'e') ) strcpy (adr, adr+1) ; printf ("voici votre texte, privé des caractères %c :n") ; puts (ligne) ; }
  • 73.
    VII: LES STRUCTURES Exercice VII.1 ___________________________________________________________________________ Enoncé Soitle m odè le (type)de structure suivant: struct s_point { char c ; int x, y ; } ; Ecrire une fonction qui reçoiten argum entune structure de type s_pointetqui en affich e le contenu sous la form e : point B de coordonnées 10 12 a)En transm ettanten argum entla valeur de la structure concernée, b)En transm ettanten argum entl'adresse de la structure concernée. Dans les deux cas, on écrira un petitprogram m e d'essai de la fonction ainsi réalisée. _______________________________________________________________ Solution a)Voici la fonction dem andée : #include <stdio.h>
  • 74.
    74 Exe rcicesen langage C void affiche (struct s_point p) { printf ("point %c de coordonnées %d %dn", p.c, p.x, p.y) ; } Notez que sa com pilation nécessite obligatoirem entla déclaration du type s_point, c'est-à -dire les instructions : struct s_point { char c ; int x, y ; } ; Voici un petitprogram m e qui affecte les valeurs 'A', 10 et 12 aux différents ch am ps d'une structure nom m ée s, avant d'en affich er les valeurs à l'aide de la fonction précédente : main() { void affiche (struct s_point) ; // déclaration (prototype) de affiche struct s_point s ; s.c = 'A' ; s.x = 10 ; s.y = 12 ; affiche (s) ; } Naturellem ent, la rem arque précédente s'applique égalem ent ici. En pratique, la déclaration de la structure s_point figurera dans un fich ier d'extension h q ue l'on se contentera d'incorporer par #include au m om entde la com pilation. De m ê m e, ilestnécessaire d'inclure stdio.h . b)Voici la nouvelle fonction dem andée : #include <stdio.h> void affiche (struct s_point * adp) { printf ("point %c de coordonnées %d %dn", adp->c, adp->x, adp->y) ; } Notez que l'on doit, cette fois, faire appelà l'opérateur -> , à la place de l'opérateur point(.), puisque l'on "travaille" sur un pointeur sur une structure, etnon plus sur la valeur de la structure elle-m ê m e. Toutefois l'usage de -> n'estpas totalem entindispensable, dans la m esure où, par exem ple, adp-> x estéquivalentà (*adp).x. Voici l'adaptation du program m e d'essai précédent: main() {
  • 75.
    VII. Les structures75 void affiche (struct s_point *) ; struct s_point s ; s.c = 'A' ; s.x = 10 ; s.y = 12 ; affiche (&s) ; } Rem arque : Au lieu d'affecter des valeurs aux ch am ps c, x et y de notre structure s (dans les deux program m es d'essai), nous pourrions (ici)utiliser les possibilités d'initialisation offertes par le langage C, en écrivant: struct s_point s = {'A', 10, 12} ; Exe rcice VII.2 ___________________________________________________________________________ Enoncé Ecrire une fonction qui "m et à zéro" les différents ch am ps d'une structure du type s_point (défini dans l'exercice précédent)qui lui esttransm ise en argum ent. La fonction ne com portera pas de valeur de retour. _______________________________________________________________ Solution Ici, bien que l'énoncé ne le précise pas, ilestnécessaire de transm ettre à la fonction concernée, non pas la valeur, m ais l'adresse de la structure à "rem ettre à zéro". Voici la fonction dem andée (ici, nous avons reproduit la déclaration de s_point): #include <stdio.h> struct s_point { char c ; int x, y ; } ; void raz (struct s_point * adr)
  • 76.
    76 Exe rcicesen langage C { adr->c = 0 ; adr->x = 0 ; adr->y = 0 ; } Voici, à titre indicatif, un petitprogram m e d'essai (sa com pilation nécessite la déclaration de s_point, ainsi que le fich ier stdio.h ): main() { struct s_point p ; void raz (struct s_point *) ; // déclaration de raz raz (&p) ; /* on écrit c en %d pour voir son code */ printf ("après : %d %d %d", p.c, p.x, p.y) ; } Exe rcice VII.3 ___________________________________________________________________________ Enoncé Ecrire une fonction qui reçoiten argum entl'adresse d'une structure du type s_point(défini dans l'exercice VII.1) etqui renvoie en résultatune structure de m ê m e type correspondantà un pointde m ê m e nom (c)etde coordonnées opposées. Ecrire un petitprogram m e d'essai. _______________________________________________________________ Solution Bien que l'énoncé ne précise rien, le résultatde notre fonction ne peutê tre transm is que par valeur. En effet, ce résultat doitê tre créé au sein de la fonction elle-m ê m e ;cela signifie qu'ilsera détruitdè s la sortie de la fonction ;en transm ettre l'adresse reviendraità renvoyer l'adresse de quelque ch ose destiné à disparaître... Voici ce que pourraitê tre notre fonction (ici, encore, nous avons reproduitla déclaration de s_point): #include <stdio.h> struct s_point { char c ;
  • 77.
    VII. Les structures77 int x, y ; } ; struct s_point sym (struct s_point * adr) { struct s_point res ; res.c = adr->c ; res.x = - adr->x ; res.y = - adr->y ; return res ; } Notez la "dissym étrie" d'instructions telles que res.c = adr-> c ;on y faitappelà l'opérateur . à gauch e età l'opérateur -> à droite (on pourraitcependantécrire res.c = (*adr).c. Voici un exem ple d'essai de notre fonction (ici, nous avons utilisé les possibilités d'initialisation d'une structure pour donner des valeurs à p1): main() { struct s_point sym (struct s_point *) ; struct s_point p1 = {'P', 5, 8} ; struct s_point p2 ; p2 = sym (&p1) ; printf ("p1 = %c %d %dn", p1.c, p1.x, p1.y) ; printf ("p2 = %c %d %dn", p2.c, p2.x, p2.y) ; } Exe rcice VII.4 ___________________________________________________________________________ Enoncé Soitla structure suivante, représentantun pointd'un plan : struct s_point { char c ; int x, y ; } ; 1)Ecrire la déclaration d'un tableau (nom m é courbe )de NPpoints (NPsupposé défini par une instruction #de fine ) 2)Ecrire une fonction (nom m ée affich e ) qui affich e les valeurs des différents "points" du tableau courbe , transm is en argum ent, sous la form e :
  • 78.
    78 Exe rcicesen langage C point D de coordonnées 10 2 3)Ecrire un program m e qui : - lit en données des valeurs pour le tableau courbe ;on utilisera de préférence les fonctions ge ts et sscanf, de préférence à scanf(voir éventuellem entl'exercice VI.5);on supposera qu'une ligne de donnée ne peutpas dépasser 128 caractè res, - faitappelà la fonction précédente pour les affich er. _______________________________________________________________ Solution 1)Ilsuffitde déclarer un tableau de structures : struct s_point courbe [NP] ; 2)Com m e courbe est un tableau, on ne peut qu'en transm ettre l'adresse en argum ent de affich e . Ilest préférable de prévoir égalem enten argum entle nom bre de points. Voici ce que pourraitê tre notre fonction : void affiche (struct s_point courbe [], int np) /* courbe : adresse de la première structure du tableau */ /* (on pourrait écrire struct s_point * courbe) */ /* np : nombre de points de la courbe */ { int i ; for (i=0 ; i<np ; i++) printf ("point %c de coordonnées %d %dn", courbe[i].c, courbe[i].x, courbe[i].x) ; } Com m e pour n'im porte queltableau à une dim ension transm is en argum ent, ilest possible de ne pas en m entionner la dim ension dans l'en-tê te de la fonction. Bien entendu, com m e, en fait, l'identificateur courbe n'est qu'un pointeur de type s_point*(pointeur sur la prem iè re structure du tableau), nous aurions pu égalem entécrire s_point*courbe . Notez que, com m e à l'accoutum ée, le "form alism e tableau" et le "form alism e pointeur" peuvent ê tre indifférem m ent utilisés (voire com binés). Par exem ple, notre fonction auraitpu égalem ents'écrire : void affiche (struct s_point * courbe, int np) { struct s_point * adp ; int i ;
  • 79.
    VII. Les structures79 for (i=0, adp=courbe ; i<np ; i++, adp++) printf ("point %c de coordonnées %d %d", courbe->c, courbe->x, courbe->y) ; } 3)Com m e nous avons appris à le faire dans l'exercice VI.5, nous lirons les inform ations relatives aux différents points à l'aide des deux fonctions : - ge ts, pour lire, sous form e d'une ch aîne, une ligne d'inform ation, - sscanf, pour décoder suivantun form atle contenu de la ch aîne ainsi lue. Voici ce que pourraitle program m e dem andé (ici, nous avons reproduit, à la fois la déclaration de s_pointetla fonction affich e précédente): #include <stdio.h> struct s_point { char c ; int x, y ; } ; #define NP 10 /* nombre de points d'une courbe */ #define LG_LIG 128 /* longueur maximale d'une ligne de donnée */ main() { struct s_point courbe [NP] ; int i ; char ligne [LG_LIG+1] ; void affiche (struct s_point [], int) ; /* lecture des différents points de la courbe */ for (i=0 ; i<NP ; i++) { printf ("nom (1 caractère) et coordonnées point %d : ", i+1) ; gets (ligne) ; sscanf (ligne, "%c %d %d", &courbe[i].c, &courbe[i].x, &courbe[i].y) ; } affiche (courbe, NP) ; } void affiche (struct s_point courbe [], int np) { int i ; for (i=0 ; i<np ; i++) printf ("point %c de coordonnées %d %dn", courbe[i].c, courbe[i].x, courbe[i].x) ; }
  • 80.
    80 Exe rcicesen langage C Exe rcice VII.5 ___________________________________________________________________________ Enoncé Ecrire le program m e de la question 3 de l'exercice précédent, sans utiliser de structures. On prévoira toujours une fonction pour lire les inform ations relatives à un point. _______________________________________________________________ Solution Ici, ilnous fautobligatoirem entprévoir 3 tableaux différents de m ê m e taille : un pour les nom s de points, un pour leurs abscisses etun pour leurs ordonnées. Le program m e ne présente pas de difficultés particuliè res (son principalintérê test d'ê tre com paré au précédent!). #include <stdio.h> #define NP 10 /* nombre de points d'une courbe */ #define LG_LIG 128 /* longueur maximale d'une ligne de donnée */ main() { char c [NP] ; /* noms des différents points */ int x [NP] ; /* abscisses des différents points */ int y [NP] ; /* ordonnées des différents points */ int i ; char ligne [LG_LIG+1] ; void affiche (char [], int[], int[], int) ; /* lecture des différents points de la courbe */ for (i=0 ; i<NP ; i++) { printf ("nom (1 caractère) et coordonnées point %d : ", i+1) ; gets (ligne) ; sscanf (ligne, "%c %d %d", &c[i], &x[i], &y[i]) ; } affiche (c, x, y, NP) ; }
  • 81.
    VII. Les structures81 void affiche (char c[], int x[], int y[], int np) { int i ; for (i=0 ; i<np ; i++) printf ("point %c de coordonnées %d %dn", c[i], x[i], x[i]) ; } Exe rcice VII.6 ___________________________________________________________________________ Enoncé Soientles deux m odè les de structure date etpe rsonne déclarés ainsi : #define LG_NOM 30 struct date { int jour ; int mois ; int annee ; } ; struct personne { char nom [LG_NOM+1] ; /* chaîne de caractères représentant le nom */ struct date date_embauche ; struct date date_poste ; } ; Ecrire une fonction qui reçoiten argum entune structure de type pe rsonne etqui en rem plitles différents ch am ps avec un dialogue se présentantsous l'une des 2 form es suivantes : nom : DUPONT date embauche (jj mm aa) : 16 1 75 date poste = date embauche ? (O/N) : O nom : DUPONT date embauche (jj mm aa) : 10 3 81 date poste = date embauche ? (O/N) : N date poste (jj mm aa) : 23 8 91
  • 82.
    82 Exe rcicesen langage C _______________________________________________________________ Solution Notre fonction doit m odifier le contenu d'une structure de type pe rsonne ;ilest donc nécessaire qu'elle en reçoive l'adresse en argum ent. Ici, l'énoncé n'im posant aucune protection particuliè re concernant les lectures au clavier, nous lirons "classiquem ent" le nom par ge ts etles trois autres inform ations num ériques par scanf. Voici ce que pourraitê tre la fonction dem andée : void remplit (struct personne * adp) { char rep ; /* pour lire une réponse de type O/N */ printf ("nom : ") ; gets (adp->nom) ; /* attention, pas de contrôle de longueur */ printf ("date embauche (jj mm aa) : ") ; scanf ("%d %d %d", &adp->date_embauche.jour, &adp->date_embauche.mois, &adp->date_embauche.annee) ; printf ("date poste = date embauche ? (O/N) : ") ; getchar () ; rep = getchar () ; /* premier getchar pour sauter n */ if (rep == 'O') adp->date_poste = adp->date_embauche ; else { printf ("date poste (jj mm aa) : ") ; scanf ("%d %d %d", &adp->date_poste.jour, &adp->date_poste.mois, &adp->date_poste.annee) ; } } Notez que, com m e à l'accoutum ée, dè s lors qu'une lecture de valeurs num ériques (ici par scanf) estsuivie d'une lecture d'un caractè re (ici par ge tch ar, m ais le m ê m e problè m e se poserait avec scanf et le code %c), ilest nécessaire de "sauter" artificiellem entle caractè re ayantservi à la validation de la derniè re inform ation num érique ;en effet, dans le cas contraire, c'estprécisém entce caractè re (n)qui estpris en com pte. En toute rigueur, la dém arch e ainsi utilisée n'estpas infaillible : si l'utilisateur fournitdes inform ations supplém entaires aprè s la derniè re valeur num érique (ne serait-ce qu'un sim ple espace), le caractè re lu ultérieurem ent ne sera pas celui attendu. Toutefois, ils'agitalors des "problè m es h abituels" liés à la fourniture d'inform ations excédentaires. Ils peuvent ê tre résolus par différentes tech niques dontnous avons parlé, notam m ent, dans l'exercice VI.5.
  • 83.
    VII. Les structures83 Voici, à titre indicatif, un petit program m e d'essai de notre fonction (sa com pilation nécessite les déclarations des structures date etpe rsonne ): main() { struct personne bloc ; remplit (&bloc) ; printf ("nom : %s n date embauche : %d %d %d n date poste : %d %d %d", bloc.nom, bloc.date_embauche.jour, bloc.date_embauche.mois, bloc.date_embauche.annee, bloc.date_poste.jour, bloc.date_poste.mois, bloc.date_poste.annee ) ; }
  • 84.
    D EUXIEM EPARTIE : EXERCICES TH EMATIQUES
  • 85.
    INTRO D UCTION A LA DEUXIEM E PARTIE Ce ch apitre vous fournitquelques explications concernantla m aniè re dontsontconçus les problè m es proposés dans cette deuxiè m e partie de l'ouvrage et les quelques rè gles que nous nous som m es fixées pour la rédaction des program m es correspondants. 1 - Cane vas com m un à ch aq ue e xe rcice Pour ch aque exercice, nous avons adopté le m ê m e canevas. a)L'e xpos é du problè m e Ilestconstitué d'un énoncé accom pagné d'un exem ple. Cetensem ble constitue ce qu'ilestindispensable de lire avantde tenter de résoudre le problè m e. Certes, l'exem ple perm et d'illustrer et de concrétiser l'énoncé m ais, de plus, ille précise, en particulier en explicitantla m aniè re dontle program m e dialogue avec l'utilisateur. On notera que cetexem ple correspond exactem entà une im age d'écran obtenue avec le program m e proposé en solution. b)L'analyse Elle spécifie (ou précise)les algorith m es à m ettre en oeuvre pour aboutir à une solution. Elle garde un caractè re général; notam m ent, elle évite de s'intéresser à certains détails de program m ation dontle ch oix estrejeté au m om entde l'écriture du program m e. A priori, elle faitdéjà partie de la solution ;toutefois, si vous séch ez sur l'énoncé lui-m ê m e, rien ne vous em pê ch e, aprè s la lecture de cette analyse, de tenter d'écrire le program m e correspondant. En effet, un telexercice, bien
  • 86.
    86 Exe rcicesen langage C que lim ité à la sim ple traduction d'un algorith m e dans un langage, n'en possè de pas m oins un intérê t propre en ce qui concerne l'apprentissage du langage lui-m ê m e. c)Le program m e Bien qu'ilsuive exactem ent l'analyse proposée, iln'en reste pas m oins qu'ilfaille le considérer com m e une rédaction possible parm i beaucoup d'autres. N'oubliez pas qu'à ce niveau ilestbien difficile de porter un jugem entde valeur sur les qualités ou les défauts de telle ou telle rédaction, tantque l'on n'a pas précisé les critè res retenus (vitesse d'exécution, taille m ém oire, clarté de la rédaction, respectde certaines rè gles de style, ...);cela estd'autantplus vrai que certains de ces critè res peuvents'avérer incom patibles entre eux. Ces rem arques s'appliquentd'ailleurs déjà aux exercices proposés précédem m entdans la prem iè re partie de cetouvrage m ais avec m oins d'accuité. d)Le s com m e ntaire s Ils fournissentcertaines explications que nous avons jugées utiles à la com préh ension du program m e lui-m ê m e. Ilpeut, par exem ple, s'agir : - de rappels concernantune instruction ou une fonction peu usuelle, - de justifications de certains ch oix réalisés uniquem entau m om entde la rédaction du program m e, - de m ise en évidence de certaines particularités ou originalités du langage, - etc. e )La discussion Elle constitue une sorte d'ouve rture fondée sur une réflexion de caractè re généralqui peutporter sur : - les insuffisances éventuelles du program m e proposé, notam m ent en ce qui concerne son com portem ent face à des erreurs de la partde l'utilisateur, - les am éliorations qu'ilestpossible de lui apporter, - une généralisation du problè m e posé, - etc.
  • 87.
    Introduction à lade uxiè m e partie 87 2 - Prote ction de s program m e s par rapport aux donné e s Com m e beaucoup d'autres langages, les instructions usuelles de lecture au clavier du langage C ne sont pas totalem ent protégées d'éventuelles réponses incorrectes de la part de l'utilisateur. Celles-ci peuvent entraîner un com portem ent anorm aldu program m e. D'une m aniè re générale, ce problè m e de contrôle des données peut ê tre résolu par l'em ploi de tech niques appropriées telles que celles que nous avons rencontrées dans l'exercice VI.5 de la prem iè re partie. Toutefois, celles-ci présentent l'inconvénientd'alourdir le texte du program m e. C'estpourquoi nous avons évité d'introduire systém atiquem entde telles protections dans tous nos exem ples, ce qui auraitm anifestem entm asqué l'objectif essentielde l'exercice (bien entendu, ces protections pourraientdevenir indispensables dans un program m e réel). Notez toutefois que certains exercices, de par leur nature m ê m e, requiè rentune telle protection ;celle-ci sera alors clairem entdem andée dans l'énoncé lui-m ê m e. 3 - A propos des structure s de boucle En principe, lorsque l'analyse d'un problè m e faitintervenir une répétition, ilfaudrait, pour ê tre com plet, en préciser le type : - répétition définie (ou ave c com pte ur): elle estréalisée en C avec l'instruction for, - répétition tant que, dans laquelle le test de poursuite a lieu en début de boucle : elle est réalisée en C avec l'instruction w h ile , - répétition jusqu'à dans laquelle le testd'arrê ta lieu en fin de boucle : elle estréalisée en C avec l'instruction do ... w h ile . En fait, ilexiste plusieurs raisons de ne pas toujours spécifier le ch oix du type d'une répétition au niveau de l'analyse et de le reporter au niveau de l'écriture du program m e : - d'une part, le ch oix d'un type de boucle n'estpas toujours dicté im pérativem entpar le problè m e : par exem ple, un algorith m e utilisant une répétition de type jusqu'à peut toujours ê tre transform é en un algorith m e utilisant une répétition de type tantque, - d'autre part, com m e nous l'avons déjà entrevu dans le ch apitre III de la prem iè re partie, le langage C autorise des form es de répétition plus variées que les trois que nous venons d'évoquer (etqui sontcelles proposées classiquem ent par la "program m ation structurée"): ainsi, par exem ple : *grâ ce à la notion d'opérateur séquentiel, on peut réaliser, à l'aide de l'instruction w h ile , des boucles dans lesquelles le testde poursuite a lieu, non plus en début, m ais en cours de boucle, *l'instruction bre ak autorise des boucles à sorties m ultiples.
  • 88.
    88 Exe rcicesen langage C Certes, on peut objecter que ce sont là des possibilités qui sont contraires à l'esprit de la program m ation structurée. Cependant, utilisées à bon escient, elles peuventam éliorer la concision etle tem ps d'exécution des program m es. Com pte tenu de l'orientation du langage C, ilne nous a pas paru opportun de nous priver totalem entde ces facilités. En définitive, ilnous arrivera souvent, au cours de l'analyse, de nous contenter de préciser la (ou les)condition(s)d'arrê t d'une itération etde reporter au niveau de la program m ation m ê m e le ch oix des instructions à utiliser. On notera qu'en procédantainsi un effortde réflexion logique peutrester nécessaire au m om entde la rédaction du program m e, laquelle, dans ce cas, se trouve ê tre plus qu'une sim ple traduction littérale! 4 - A propos des fonctions a) Com m e nous l'avons déjà rem arqué dans l'avant-propos, la norm e ANSI accepte deux form es de définition de fonctions. Voici, par exem ple, deux façons d'écrire l'en-tê te d'une fonction fct recevant deux argum ents de type int et ch aretrenvoyantune valeur de type double : double fct (int x, char * p) double fct (x, p) int x ; char * p ; Ilne s'agit là que de sim ples différences de rédaction, sans aucune incidence sur le plan fonctionnel. Ici, nous avons systém atiquem entem ployé la prem iè re form e (on la nom m e parfois form e "m oderne"), dans la m esure où elle a tendance à se généraliser etoù, de plus, ils'agitde la seule form e acceptée par le C+ + . b)Les fonctions onttoujours été déclarées dans les fonctions les utilisantbien qu'a priori : - cela ne soitpas obligatoire pour les fonctions fournissantun résultatde type int, - cela ne soitpas obligatoire lorsqu'une fonction a été définie, dans le m ê m e source, avantd'ê tre utilisée. c) Dans les déclarations des fonctions, nous avons utilisé la form e prototype autorisée par le standard ANSI. Celle-ci se révè le surtout fort précieuse lorsque l'on exploite les possibilités de com pilation séparée et que l'on a donc affaire à plusieurs fich iers source différents. Certes, ce n'est pas le cas ici, m ais, com pte tenu de ce qu'elle est pratiquem ent acceptée de tous les com pilateurs actuels etque, de plus, elle estestobligatoire en C+ + , ilnous a paru judicieux d'en faire une h abitude.
  • 89.
    I: VARIATIO NSALGO RITH M IQUES SUR LES INSTRUCTIO NS D E BASE Ce ch apitre vous propose des problè m es ne faisantappelqu'aux notions de base du langage C, à savoir : - entrées-sorties conversationnelles (ge tch ar, scanf, ge ts, putch ar, printf), - instructions de contrôle, - tableaux, - ch aînes, - fonctions. I-1 Triangle de Pascal ______________________________________________________________________________ Enoncé Affich er un "triangle de Pascal" dontle nom bre de lignes estfourni en donnée. Nous vous rappelons que les "cases" d'un teltriangle contiennentles valeurs des coefficients du binom e C n,p (ou nom bre de com binaisons de n élém ents pris p à p). Cette valeur est placée dans la case correspondant à l'intersection de la ligne de rang n et la colonne de rang p (la num érotation com m ençantà 0). On évitera de calculer ch aque term e séparém ent ;au contraire, on ch erch era à exploiter la relation de récurrence :
  • 90.
    9 0 Exercices en langage C C i,j = C i-1, j + C i-1,j-1 On lim itera à 15 le nom bre de lignes dem andées par l'utilisateur eton respectera la présentation proposée dans l'exem ple ci-dessous. Exe m ple combien de lignes voulez vous ? 12 p 0 1 2 3 4 5 6 7 8 9 10 11 n ----------------------------------------------------------------- 0 -- 1 1 -- 1 1 2 -- 1 2 1 3 -- 1 3 3 1 4 -- 1 4 6 4 1 5 -- 1 5 10 10 5 1 6 -- 1 6 15 20 15 6 1 7 -- 1 7 21 35 35 21 7 1 8 -- 1 8 28 56 70 56 28 8 1 9 -- 1 9 36 84 126 126 84 36 9 1 10 -- 1 10 45 120 210 252 210 120 45 10 1 11 -- 1 11 55 165 330 462 462 330 165 55 11 1 ______________________________________________________________________________ ANALYSE A priori, nous pourrions utiliser un tableau t à deux dim ensions com portant 15x15 élém ents et décider (arbitrairem ent) que le prem ier indice correspond au rang d'une ligne du triangle, le second à celui d'une colonne. Nous rem plirions alors partiellem ent ce tableau avec les valeurs C i,j voulues (i varierait de 0 à n-1 si n représente le nom bre de lignes dem andées et, pour ch aque valeur de i, jvarieraitde 0 à i). Pour exploiter la récurrence proposée, ilnous suffiraitalors de procéder com m e suit: - placer la valeur 1 en t(0,0)(ce qui constitue la prem iè re ligne), - pour ch aque ligne de rang i, à partir de i=1, procéder ainsi : *placer la valeur 1 en t(i,0)ett(i,i)(extrém ités de la ligne de rang i), *pour jvariantde 1 à i-1, faire : t(i,j)=t(i-1,j)+ t(i-1,j-1)
  • 91.
    I. Variations algorithm iques sur les instructions de base 9 1 En fait, ilest possible de n'utiliser qu'un tableau à une seule dim ension, dans lequelon vient calculer successive m e nt ch acune des lignes du triangle (ilfautalors, bien sûr, affich er ch aque ligne dè s qu'elle a été déterm inée). Supposons, en effet, qu'à un instant donné, nous disposions dans ce tableau t des i+1 valeurs de la ligne de rang i et voyons com m entdéterm iner celles de la ligne de rang i+1. Nous constatons que la récurrence proposée perm etde définir la nouvelle valeur d'un élém entde ten fonction de son ancienne valeur etde l'ancienne valeur de l'élém entprécédent. Certes, si nous répétions une affectation de la form e : t(j)=t(j)+ t(j-1) en faisantvarier jde 1 à i-1, nous n'aboutirions pas au résultatescom pté puisqu'alors la valeur de t(j) dépendraitde la nouvelle valeur préalablem entattribuée à t(j-1). M ais, ilestfacile de m ontrer qu'en explorantla ligne de droite à gauch e, c'est-à -dire en répétantl'affectation ci-dessus en faisantdécroître jde i-1 à 0, le problè m e ne se pose plus. Voici finalem entl'algorith m e que nous utiliserons : Faire varier i de 0 à n-1. Pour ch aque valeur de i : - répéter, en faisantdécroître jde i-1 à 1 : t(j)=t(j)+ t(j-1) - placer la valeur 1 dans t(i). Rem arques : 1)Telque l'algorith m e vientd'ê tre énoncé, nous constatons que pour i=0, jdoitdécroître de -1 à 1! Nous adm ettrons que cela signifie en faitqu'aucun traitem entn'està réaliser dans ce cas (ce qui estnorm alpuisque alors notre ligne estréduite à la seule valeur 1, laquelle sera placée par l'affectation t(i)=1). Ilen va de m ê m e pour i=1, jdevantalors décroître de 0 à 1. On notera qu'en langage C la boucle for perm etde tenir com pte de ces cas particuliers (le testde poursuite de boucle étantréalisé en début). Ce n'esttoutefois pas là une rè gle généralisable à tous les langages. 2)Avec les précautions que nous venons d'évoquer, l'algorith m e "s'initialise" de lui-m ê m e. Program m e #include <stdio.h> #define NMAX 15 /* nombre maximal de lignes */ main() { int t [NMAX], /* tableau représentant une ligne du triangle */
  • 92.
    9 2 Exercices en langage C nl, /* nombre de lignes souhaitées */ i, /* indice de la ligne courante */ j ; /* indice courant de colonne */ /* lecture nombre de lignes souhaitées et affichage titres */ printf ("combien de lignes voulez vous ? ") ; scanf ("%d", &nl) ; if (nl > NMAX) nl = NMAX ; printf ("nn p ") ; for (i=0 ; i<nl ;i++) printf ("%5d", i) ; printf ("n nn") ; for (i=0 ; i<=nl ; i++) printf ("-----") ; printf ("n") ; /* création et affichage de chaque ligne */ for (i=0 ; i<nl ;i++) { t[i] = 1 ; for (j=i-1 ; j>0 ; j--) t[j] = t[j-1] + t[j] ; printf ("%2d --", i) ; for (j=0 ; j<=i ; j++) printf ("%5d", t[j]) ; printf ("n") ; } } Com m e ntaire s *En langage C, les indices d'un tableau com m encent à 0. Ici, cette particularité s'avè re intéressante puisque nos num éros de lignes ou de colonnes doiventaussi com m encer à 0. *Plutôtque d'utiliser directem entla constante 15 dans notre program m e, nous avons préféré faire appelà l'instruction #de fine du préprocesseur pour définir un sym bole NMAX possédantcette valeur. Ilestainsi beaucoup plus facile, le cas éch éant, de m odifier cette valeur (puisqu'ilsuffitalors d'intervenir en un seulendroit du program m e). Notez que nous n'aurions pas pu utiliser la déclaration de constante sym bolique (constintNM AX = 15), car, dans ce cas, NM AX n'aurait pas été une "expression constante", etnous n'aurions pas pu l'utiliser com m e dim ension d'un tableau. *Ne pas oublier que t[NMAX]réserve NMAX élém ents (c'est-à -dire 15), dontles indices varientde 0 à 14.
  • 93.
    I. Variations algorithm iques sur les instructions de base 9 3 *Si l'utilisateur dem ande un nom bre de lignes supérieur à NMAX, le program m e se contente de lim iter cette dem ande à la valeur NMAX. D ISCUSSIO N *Nous aurions pu tenir com pte de la sym étrie de ch aque ligne par rapport à son centre ;quelques instructions supplém entaires nous auraientalors perm is une légè re réduction du tem ps de calcul. *L'énoncé lim itait à 15 le nom bre de lignes de notre triangle. En effet, au-delà , iln'est généralem ent plus possible d'affich er toutes les valeurs sur une seule ligne d'écran. *Notre program m e n'estpas protégé dans le cas où l'utilisateur fournitune réponse non num érique à la question posée. Dans ce cas, toutefois, la situation n'est pas trè s grave ;en effet, la valeur de nlest, certes, aléatoire m ais, de toute façon, elle sera lim itée à 15 par le program m e. Si vous souh aitiez quand m ê m e traiter ce type d'anom alie, ilvous suffiraitd'exam iner le code de retour de la fonction scanf(ilfournitle nom bre de valeurs convenablem entlues)etde vérifier qu'ilestbien égalà 1. I-2 Crible d'Eratosth è ne ________________________________________________________________________________________ Ilexiste une m éth ode de déterm ination de nom bres prem iers connue sous le nom de "crible d'Erastoth è ne". Elle perm et d'obtenir tous les nom bres prem iers inférieurs à une valeur donnée n. La m éth ode (m anuelle) consiste à dresser une liste des nom bres considérés (de 1 à n) et à y rayer tous les nom bres m ultiples d'autres entiers (de tels nom bres sontnécessairem entnon prem iers). Plus précisém ent, on procè de ainsi : 1 - on raye le 1 (qui, par définition, n'estpas un nom bre prem ier). 2 - on rech erch e, à partir du dernier nom bre prem ier considéré (la prem iè re fois, on convient qu'ils'agit du 1), le prem ier nom bre non rayé (on peutm ontrer qu'ilestprem ier). Ildevient, à son tour, le dernier nom bre prem ier considéré eton raye tous ses m ultiples. 3 - on répè te le point2 jusqu'à ce que le nom bre prem ier considéré soitsupérieur à la racine carrée de n. On peutalors m ontrer que tous les nom bres non prem iers ontété rayés de la liste.
  • 94.
    9 4 Exercices en langage C Enoncé Ecrire un program m e basé sur cette m éth ode rech erch anttous les nom bres prem iers com pris entre 1 etn (la valeur de n étantfixée dans le program m e) Exe m ple entre 1 et 1000, les nombres premiers sont : 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 ________________________________________________________________________________________ ANALYSE La m éth ode m anuelle suggè re d'utiliser un tableau. Toutefois, devons-nous, par analogie, y ranger les nom bres entiers de 1 à n?En fait, cela ne seraitguè re utile puisque alors ch aque nom bre seraitégalà son rang dans le tableau (du m oins, à une unité prè s, suivantles conventions que l'on adopteraitpour l'indice du prem ier élém ent). En réalité, le bon déroulem entde l'algorith m e nous im pose seulem entd'ê tre en m esure de faire correspondre à ch aque entier entre 1 etn, une inform ation précisant, à ch aque instant, s'ilestrayé ou non (cette inform ation pouvantévoluer au fildu déroulem ent du program m e). Ils'agit là tout naturellem ent d'une inform ation de type "logique" (vrai ou faux). Com m e ce type n'existe pas en tantque telen langage C, nous le sim ulerons à l'aide de deux constantes entiè res : VRAI de valeur 1, FAUX de valeur 0. Notez que le ch oix de la valeur 0 pour FAUX estim posé par la m aniè re dontle langage
  • 95.
    I. Variations algorithm iques sur les instructions de base 9 5 C considè re une expression num érique apparaissant dans une condition ;la valeur 1, par contre, pourrait ê tre, sans inconvénient, rem placée par n'im porte quelle valeur non nulle. Notons raye un teltableau et supposons que raye [i] correspond à l'entier i (ce qui, com pte tenu des conventions du langage C, signifie que raye [0]estinutilisé). Notre algorith m e nous im pose de garder la trace du dernier nom bre prem ier considéré. Nous le nom m erons pre m . La dém arch e m anuelle se transpose alors com m e suit: *Initialisation : - m ettre à FAUX tous les élém ents du tableau raye , - m ettre à FAUX le prem ier élém entde raye , etfaire : prem = 1 *Itération : - rech erch er, à partir de pre m , le prem ier nom bre non encore rayé, c'est-à -dire incrém enter la valeur de pre m jusqu'à ce que t[pre m ] soit FAUX (en toute rigueur, ilfaut se dem ander s'ilexiste encore un telnom bre dans notre tableau, etdonc lim iter l'incrém entation de pre m à N). - rayer tous les m ultiples de pre m , dans le cas où un telnom bre a été trouvé. *L'itération proposée peut ê tre répétée, indifférem m ent(les deux form ulations étant équivalentes dè s que N est supérieur ou égalà 1): - jusqu'à ce que la valeur de pre m soitsupérieure à la racine carrée de N, - ou tantque la valeur de pre m estinférieure ou égale à la racine carrée de N. Program m e #include <stdio.h> #define N 1000 /* plus grand entier à examiner */ #define VRAI 1 /* pour "simuler" des ..... */ #define FAUX 0 /* ..... valeurs logiques */ main() { int raye [N+1], /* tableau servant de crible */ prem, /* dernier nombre premier considéré */ na, /* compteur de nombres affichés */ i ; /* initialisations */ for (i=1 ; i<=N ; i++) /* mise à zéro du crible */
  • 96.
    9 6 Exercices en langage C raye[i] = FAUX ; raye[1] = VRAI ; /* on raye le nombre 1 */ /* passage au crible */ prem = 1 ; while (prem*prem <= N) { while (raye[++prem] && prem<N ) {} /* recherche premier nombre non rayé */ for (i=2*prem ; i<=N ; i+=prem) /* on raye tous ses multiples */ raye[i] = VRAI ; } /* affichage résultats */ printf ("entre 1 et %d, les nombres premiers sont :n", N) ; na = 0 ; for (i=1 ; i<=N ; i++) if ( !raye[i] ) { printf ("%7d",i) ; na++ ; if ( na%10 == 0) printf ("n") ; /* 10 nombres par ligne */ } } Com m e ntaire s *La rech erch e du prem ier nom bre non encore rayé estréalisée par la seule instruction : while (raye[++prem] && prem<N) {} Notez bien la pré-incrém entation de pre m ;une post-incrém entation : while (t[prem++] && prem<N) {} auraitconduità une boucle infinie sur le prem ier nom bre prem ier trouvé, c'est-à -dire 2 (du m oins si N estsupérieur ou égalà 2). Ilsuffiraittoutefois d'incrém enter pre m une fois avantd'entrer dans la boucle pour que cela fonctionne. *Nous avons conservé le garde-fou : prem < N
  • 97.
    I. Variations algorithm iques sur les instructions de base 9 7 On pourraittoutefois dém ontrer que, dè s que N estsupérieur ou égalà 2, on esttoujours assuré de trouver au m oins un nom bre non rayé avantla fin du tableau (com pte tenu de ce que l'on com m ence l'exploration avec un nom bre inférieur ou égalà la racine carrée de N). *Nous avons prévu d'affich er nos nom bres prem iers, à raison de 10 par ligne, ch aque nom bre occupant 7 caractè res. Pour ce faire, nous utilisons une variable nom m ée na nous perm ettantde com ptabiliser le nom bre de nom bres affich és. A ch aque fois que na estm ultiple de 10, nous provoquons un sautde ligne. D ISCUSSIO N *Telqu'ilest proposé ici, le program m e traite le cas n=1000. Pour le faire fonctionner avec d'autres valeurs, ilest nécessaire d'intervenir au niveau du program m e lui-m ê m e etde le recom piler. Si vous souh aitez que la valeur de n puisse ê tre fournie en donnée, ilfautlui fixer une valeur m axim ale, afin de prévoir la réservation du tableau correspondant. Notez toutefois que les possibilités de gestion dynam ique du langage C offrentune solution plus agréable à ce problè m e de dim e nsions variables. Vous en trouverez certains exem ples dans le ch apitre consacré à la gestion dynam ique. *Le tableau raye , ainsi que les variables pre m eti, ontété déclarés de type int, ce qui, dans certaines im plém entations, peut lim iter à 32767 les valeurs qu'ilest ainsi possible d'exam iner. On peut toujours faire m ieux, en utilisant le type unsigne d int, ou m ieux le type long ou unsigne d long. Toutefois, dans ce cas, on s'assurera que l'on n'estpas soum is à des contraintes sur la taille des différents m odules objets, sur la taille de la pile ou, encore, toutsim plem ent, sur la taille des différents objets qu'ilestpossible de m anipuler. Iln'estpas rare, en effet, que l'on rencontre des lim itations à 64 KO (c'estle cas, actuellem ent, des com pilateurs Borland/Turbo C/C+ + utilisés dans l'environnem entDOS). I-3 Le ttre s com m une s à de ux m ots (1) ________________________________________________________________________________________ Enoncé Réaliser un program m e qui affich e les lettres com m unes à deux m ots fournis au clavier. On prévoira d'affich er plusieurs fois une lettre qui apparaîtà plusieurs reprises dans ch acun des deux m ots.
  • 98.
    9 8 Exercices en langage C On supposera que ces m ots ne peuventpas com porter plus de 26 caractè res eton les lira à l'aide de la fonctions ge ts. Exe m ple s donnez un premier mot : monsieur donnez un deuxième mot : bonjour la lettre o est commune aux deux mots la lettre n est commune aux deux mots la lettre u est commune aux deux mots la lettre r est commune aux deux mots _________________ donnez un premier mot : barbara donnez un deuxième mot : ravage la lettre a est commune aux deux mots la lettre r est commune aux deux mots la lettre a est commune aux deux mots ________________________________________________________________________________________ ANALYSE L'énoncé nous im pose d'utiliser ge ts, donc de représenter nos m ots sous form e de ch aînes de caractè res (suites de caractè res term inées par le caractè re nul, noté en C : 0). Nous utiliserons à cet effet des tableaux de caractè res de dim ension 27 (pour 26 lettres m axim um etun caractè re de fin). La rech erch e des lettres com m unes aux deux m ots peutse faire en com parantch acun des caractè res de la prem iè re ch aîne à ch acun des caractè res de la seconde. Cela nous conduit naturellem ent à l'utilisation de deux boucles avec com pteur (instructions for)im briquées. Toutefois, nous devons tenir com pte de ce qu'une m ê m e lettre peut figurer plusieurs fois dans un m ê m e m ot. Dans ces conditions, ilfautéviter : *qu'une m ê m e lettre du prem ier m ot ne puisse ê tre trouvée en deux endroits différents du second. Par exem ple, avec : m onsieur et bonjour
  • 99.
    I. Variations algorithm iques sur les instructions de base 9 9 aprè s avoir trouvé que le o de m onsie ur figurait en position 2 de bonjour, ilfaut éviter de signaler une nouvelle coïncidence entre ce m ê m e o de m onsie ur etle second o de bonjour. Ilestdonc nécessaire d'interrom pre la com paraison entre une lettre du prem ier m otavec toutes celles du second m ot, dè s qu'une coïncidence a été détectée. *qu'une m ê m e lettre du second m otne puisse coïncider avec deux lettres différentes du second. Par exem ple, avec (attention à l'ordre des m ots): bonjour et m onsieur ilfaut éviter de trouver une coïncidence entre le prem ier o de bonjour et l'unique o de m onsie ur et une autre coïncidence entre le second o de bonjour etle m ê m e o de m onsie ur. Pour ce faire, une dém arch e (parm i d'autres) consiste à élim iner dans le second m otla lettre ayant faitl'objetd'une coïncidence. Plus précisém ent, ilsuffitde rem placer une telle lettre par un caractè re donton estsûr qu'iln'apparaîtra pas dans un m ot. Ici, nous avons ch oisi l'espace puisque nous som m es censés travailler avec des m ots. Program m e #include <stdio.h> #include <string.h> #define LMAX 26 main() { char mot1 [LMAX+1], /* premier mot */ mot2 [LMAX+1] ; /* deuxième mot */ int i, j ; /* lecture des deux mots */ printf ("donnez un premier mot : ") ; gets (mot1) ; printf ("donnez un deuxième mot : ") ; gets (mot2) ; /* comparaison */ for (i=0 ; i<strlen(mot1) ; i++) for (j=0 ; j<strlen(mot2) ; j++) if (mot1[i] == mot2[j])
  • 100.
    100 Exe rcicesen langage C { printf ("la lettre %c est commune aux deux motsn", mot1[i]) ; mot2[j] = ' ' ; break ; } } Com m e ntaire s *Nous avons utilisé le sym bole LMAX pour représenter la longueur m axim ale d'un m ot. Notez bien que les tableaux m ot1 et m ot2 ont dû ê tre prévus de dim ension LMAX+1, afin de tenir com pte de la présence du caractè re de fin de ch aîne. *Nous aurions pu utiliser, à la place de la seconde boucle avec com pteur (en j), une boucle tantque (w h ile ). Certes, la program m ation eûtété plus structurée m ais, néanm oins, m oins concise. D ISCUSSIO N Ce program m e n'est pas protégé contre des réponses de plus de 26 caractè res. Dans ce cas, en effet, les caractè res superflus irontécraser les données se trouvantau-delà de l'un des tableaux m ot1 ou m ot2. Les conséquences peuventê tre assez variées (vous pouvez expérim enter le présent program m e dans diverses situations et tenter d'expliquer les com portem ents observés). Ilexiste différentes façons d'éviter ce risque. Citons, par exem ple : - lire (toujours par ge ts), une ch aîne com portantun nom bre de caractè res suffisam m entélevé pour que l'utilisateur ne risque pas (trop!) d'en fournir plus. On pourrait ch oisir, par exem ple 80 ou 128 caractè res (dans certaines im plém entations, iln'estjam ais possible de taper des lignes de plus de 128 caractè res). - lim iter autom atiquem entla longueur de la ch aîne lue, en utilisantla fonction fge ts ;par exem ple, avec fge ts (m ot1, LM AX, stdin), on lim ite à LM AX le nom bre de caractè res lus sur stdin. - utiliser, dans certaines im plém entations (Turbo/Borland C/C+ + , C/Quick C M icrosoft), une fonction (non portable!)nom m ée cge ts.
  • 101.
    I. Variations algorithm iques sur les instructions de base 101 I-4 Le ttre s com m une s à de ux m ots (2) ________________________________________________________________________________________ Enoncé Réaliser un program m e qui affich e les lettres com m unes à deux m ots fournis en donnée. Cette fois, on n'im posera pas de lim ite à la taille des m ots fournis par l'utilisateur, m ais on ne prendra en com pte que les 26 prem iers caractè res. Quelque soit le nom bre de caractè res effectivem ent frappés, l'utilisateur devra toujours valider sa réponse par la frappe de la touch e re turn. Là encore, on prévoira d'affich er plusieurs fois une lettre qui apparaîtà plusieurs reprises dans ch acun des m ots. On s'astreindra à n'utiliser pour la lecture au clavier que la seule fonction getch ar. De plus, on réalisera une fonction destinée à lire un m otdans un tableau qu'on lui transm ettra en argum ent;elle fournira, en retour, la longueur effective du m otainsi lu. Exe m ple s Voir ceux de l'exercice précédent ________________________________________________________________________________________ ANALYSE L'énoncé nous im pose l'em ploi de ge tch ar, ce qui signifie que ch acun des deux m ots devra ê tre lu caractè re par caractè re. Dans ces conditions, nous pouvons ch oisir de représenter nos m ots : - soitsous form e d'une ch aîne de caractè res. Ilnous faudra alors introduire nous-m ê m es le caractè re de fin de ch aîne (0), ce que faisaitautom atiquem entge ts. - soitsous form e d'une sim ple suite de caractè res (c'est-à -dire sans ce caractè re de fin). Dans ce cas, ilnous faudra alors prévoir d'en déterm iner la "longueur". Com m e l'énoncé nous im pose que la fonction de lecture d'un m ot en restitue la longueur, nous ch oisirons la seconde solution. La lecture d'un m otconsiste donc à lire des caractè res au clavier jusqu'à ce que l'on rencontre une validation (n)ou que l'on ait obtenu 26 caractè res ;de plus, dans le cas où l'on a obtenu 26 caractè res, ilfaut poursuivre la lecture de caractè res au clavier, sans les prendre en com pte, jusqu'à ce que l'on rencontre une validation.
  • 102.
    102 Exe rcicesen langage C Program m e #include <stdio.h> #define LMAX 26 /* longueur maximale d'un mot */ main() { int lire(char []) ; /* déclaration (prototype) fonction lecture d'un mot */ char mot1 [LMAX], /* premier mot (sans '0') */ mot2 [LMAX] ; /* deuxième mot (sans '0') */ int l1, /* longueur premier mot */ l2, /* longueur deuxième mot */ i, j ; /* lecture des deux mots */ printf ("donnez un premier mot : ") ; l1 = lire (mot1) ; printf ("donnez un deuxième mot : ") ; l2 = lire (mot2) ; /* comparaison */ for (i=0 ; i<l1 ; i++) for (j=0 ; j<l2 ; j++) if (mot1[i] == mot2[j]) { printf ("la lettre %c est commune aux deux motsn", mot1[i]) ; mot2[j] = ' ' ; break ; } } /* Fonction de lecture d'un mot */ int lire (char mot [LMAX]) { int i ; /* rang du prochain caractère à lire */ char c ; i = 0 ; while ( (c=getchar()) != 'n' && i<=LMAX ) mot[i++] = c ; /* ici, soit on a lu n, soit on a lu LMAX caractères */ /* dans tous les cas, c contient le premier caractère */ /* non pris en compte */ if (c != 'n')
  • 103.
    I. Variations algorithm iques sur les instructions de base 103 while (getchar() != 'n') {} /* recherche 'n' */ return(i) ; } Com m e ntaire s *Là encore, nous avons utilisé le sym bole LMAX pour représenter la taille m axim ale d'un m ot. Par contre, cette fois, la dim ension des tableaux m ot1 etm ot2 estégale à LMAX (etnon plus LMAX+ 1), puisque nous n'avons pas à y introduire le caractè re supplém entaire de fin de ch aîne. *En ce qui concerne la fonction (nom m ée lire )de lecture d'un m otau clavier, vous constatez que nous l'avons déclarée dans le program m e principal(m ain), bien que cela soitfacultatif, dans la m esure où elle fournitun résultatde type int(en effet, toute fonction qui n'estpas explicitem entdéclarée estsupposée produire un résultatde type int). D'autre part, com m e nous l'avons expliqué dans l'introduction de cette seconde partie, nous avons utilisé, dans cette déclaration, la form e "prototype" autorisée par la norm e ANSI (ce prototype assure les contrôles de types d'argum ents et m eten place d'éventuelles conversions). Par ailleurs, l'en-tê te de notre fonction lire a été écrit suivant la form e "m oderne". La norm e ANSI aurait autorisé le rem placem entde note en-tê te par : int lire (mot) char mot [LMAX] ; *Le tableau de caractè res représentantl'unique argum entde lire doitobligatoirem entê tre transm is par adresse puisque cette fonction doitê tre en m esure d'en m odifier le contenu. N'oubliez pas cependantqu'en langage C un nom de tableau est interprété (par le com pilateur) com m e un pointeur (constant) sur son prem ier élém ent. C'est ce qui justifie la présence, dans les appels à la fonction lire , de m ot1 ou m ot2, etnon de & m ot1 ou & m ot2. *La déclaration de l'argum entde lire , dans son en-tê te : char mot [LMAX] auraitpu égalem ents'écrire : char mot [] ou m ê m e : char * mot
  • 104.
    104 Exe rcicesen langage C Dans le prem ier cas, on continue de spécifier (au lecteur du program m e plus qu'au com pilateur) que m otestun tableau de caractè res m ais que sa dim ension n'a pas besoin d'ê tre connue au sein de lire . Dans le second cas, on exprim e plus clairem ent que, finalem ent, l'argum ent reçu par lire n'est rien d'autre qu'un pointeur sur des caractè res. Ces form ulations sonttotalem entéquivalentes pour le com pilateur et, dans tous les cas (m ê m e le dernier), ilreste possible de faire appelau "form alism e" tableau au sein de lire , en utilisantune notation telle que : mot [i++] D'ailleurs, pour le com pilateur, cette derniè re estéquivalente à : * (mot + i++) Les m ê m e reflexions s'appliquentà l'écriture du prototype de lire . D ISCUSSIO N *Le sym bole LMAX est défini pour l'ensem ble du source contenant, ici, le program m e principalet la fonction lire . M ais, si cette fonction devait ê tre com pilée séparém ent du reste, ilserait alors nécessaire de faire figurer la définition (#de fine )dans les deux sources, ce qui com porte un risque d'erreur. Dans une situation "réelle", on pourraitavoir intérê t à faire appelà l'une des dém arch es suivantes : - transm ettre la valeur de LMAX en argum entde la fonction lire . - regrouper les définitions de sym boles com m uns à plusieurs sources dans un fich ier séparé que l'on appelle par #include dans ch acun des sources concernés. *Contrairem entau program m e de l'exercice précédent, celui-ci se trouve protégé de réponses trop longues de la partde l'utilisateur. I-5 Com ptage de le ttre s ________________________________________________________________________________________
  • 105.
    I. Variations algorithm iques sur les instructions de base 105 Enoncé Réaliser un program m e qui com pte le nom bre de ch acune des lettres de l'alph abet d'un texte entré au clavier. Pour sim plifier, on ne tiendra com pte que des m inuscules, m ais on com ptera le nom bre des caractè res non reconnus com m e tels (quels qu'ils soient: m ajuscules, ponctuation, ch iffres,...). Le program m e devra accepter un nom bre quelconque de lignes. L'utilisateur tapera une "ligne vide" pour signaler qu'ila term iné la frappe de son texte (ce qui revient à dire qu'ilfrappera donc deux fois de suite la touch e re turn, aprè s la frappe de sa derniè re ligne). On supposera que les lignes frappées au clavier ne peuvent jam ais dépasser 127 caractè res. Par ailleurs, on fera l'h ypoth è se (peu restrictive en pratique) que les "codes" des lettres m inuscules a à z sontconsécutifs (ce qui estle cas, notam m ent, avec le code ASCII). Exe m ple donnez votre texte, en le terminant par une ligne vide je me figure ce zouave qui joue du xylophone en buvant du whisky votre texte comporte 63 caractères dont : 2 fois la lettre a 1 fois la lettre b 1 fois la lettre c 2 fois la lettre d 8 fois la lettre e 1 fois la lettre f ...... ...... 7 fois la lettre u 2 fois la lettre v 1 fois la lettre w 1 fois la lettre x 2 fois la lettre y 1 fois la lettre z et 11 autres caractères ________________________________________________________________________________________
  • 106.
    106 Exe rcicesen langage C ANALYSE Ilnous faut donc utiliser un tableau de 26 entiers perm ettant de com ptabiliser le nom bre de fois où l'on a rencontré ch acune des 26 lettres (m inuscules) de l'alph abet. Nous le nom m erons com pte . Nous utiliserons égalem entun com pteur nom m é ntot pour le nom bre totalde caractè res et un autre nom m é nautres pour les caractè res différents d'une lettre m inuscule. En ce qui concerne le com ptage proprem entdit, ilnous fautexam iner ch acune des lettres du texte. Pour ce faire, ilexiste (au m oins)deux dém arch es possibles : - effectuer une répétition du traitem entd'un caractè re, - effectuer une répétition du traitem entd'une ligne, lui-m ê m e constitué de la répétition du traitem ent de ch acun des caractè res qu'elle contient. a)La prem iè re dém arch e aboutità une sim ple boucle avec com pteur. Elle ne dem ande d'accéder qu'à un seulcaractè re à la fois (par exem ple, par ge tch ar). Elle nécessite, par contre, l'élim ination des caractè res de fin de ligne n (qui sont transm is com m e les autres par ge tch ar), puisqu'ils ne fontpas vraim entpartie du texte. De surcroît, la détection de la fin du texte oblige à conserver en perm anence le "caractè re précédent". Lorsque ce caractè re, ainsi que le caractè re courant, sont égaux à n, c'est que l'on a atteint la fin du texte. Ilsuffit d'initialiser artificiellem ent ce caractè re précédent à une valeur quelconque (autre que n) pour éviter de devoir effectuer un traitem entparticulier pour le prem ier caractè re. b)La seconde dém arch e aboutità deux boucles im briquées. Elle perm etde lire directem entch aque ligne par ge ts. Elle rè gle de m aniè re naturelle les problè m es de fin de ligne etde fin de texte. Nous vous proposons ici deux program m es, correspondantà ch acune de ces deux dém arch es. Program m e bas é s ur la ré pé tition du traite m e ntd'un caractè re #include <stdio.h> main() { char c, /* pour lire un caractère frappé au clavier */ cprec ; /* caractère précédent */ int compte[26] ; /* pour compter les différentes lettres */ int numl, /* rang lettre courante dans l'alphabet */ ntot, /* nombre de caractères du texte */ nautres, /* nb caractères autres qu'une lettre minuscule */ i ; /* initialisations */
  • 107.
    I. Variations algorithm iques sur les instructions de base 107 cprec = ' ' ; ntot = 0 ; nautres = 0 ; for (i=0 ; i<26 ; i++) compte[i]=0 ; /* lecture texte et comptages */ printf ("donnez votre texte, en le terminant par une ligne viden") ; while ( (c=getchar()) != 'n' || cprec != 'n' ) { if (c != 'n') { numl = c - 'a' ; /* on donne le rang 0 à la lettre 'a' */ if (numl >=0 && numl < 26) compte[numl]++ ; else nautres++ ; ntot++ ; } cprec = c ; } /* affichage résultats */ printf ("nnvotre texte comporte %d caractères dont :n", ntot) ; for (i=0; i<26 ; i++) printf ("%d fois la lettre %cn", compte[i], 'a'+i) ; printf ("net %d autres caractèresn", nautres) ; } Com m e ntaire s *L'expression : c - 'a' perm etd'obtenir le "rang" dans l'alph abetdu caractè re contenu dans c. N'oubliez pas que le langage C considè re le type ch ar com m e num érique. Plus précisém ent, dans le cas présent, les valeurs de c et de 'a' sont converties en int (ce qui fournitla valeur num érique de leur code) avantque ne soitévaluée l'expression c-'a'. Com m e nous avons supposé que les codes des m inuscules sontconsécutifs, nous obtenons bien le résultatescom pté. *Les instructions : if (c != 'n') { numl = c - 'a' ; if (numl >=0 && numl < 26) compte[numl]++ ; else nautres++ ; ntot++ ;
  • 108.
    108 Exe rcicesen langage C } cprec = c; pourraientse condenser en : if ( (cprec=c) != 'n') { numl = c - 'a' ; if (numl >=0 && numl < 26) compte[numl]++ ; else nautres++ ; ntot++ ; } Program m e bas é s ur la ré pé tition du traite m e ntd'une ligne #include <stdio.h> #include <string.h> main() { char ligne[128] ; /* pour lire une ligne frappée au clavier */ int compte[26] ; /* pour compter les différentes lettres */ int numl, /* rang lettre courante dans l'alphabet */ ntot, /* nombre de caractères du texte */ nautres, /* nombre de caractères autres qu'une lettre minuscule */ i ; /* initialisations */ ntot = 0 ; nautres = 0 ; for (i=0 ; i<26 ; i++) compte[i]=0 ; /* lecture texte et comptages */ printf ("donnez votre texte, en le terminant par une ligne viden") ; do { gets(ligne) ; for (i=0 ; i<strlen(ligne) ; i++, ntot++) { numl = ligne[i] - 'a' ;/* on donne le rang 0 à la lettre 'a' */ if (numl >=0 && numl < 26) compte[numl]++ ; else nautres++ ; } } while (strlen(ligne)) ; /* affichage résultats */
  • 109.
    I. Variations algorithm iques sur les instructions de base 109 printf ("nnvotre texte comporte %d caractères dont :n", ntot) ; for (i=0; i<26 ; i++) printf ("%d fois la lettre %cn", compte[i], 'a'+i) ; printf ("net %d autres caractèresn", nautres) ; } D ISCUSSIO N *Aucun des deux program m es proposés ne pose de problè m e de protection vis-à -vis des réponses fournies par l'utilisateur. I-6 Com ptage de m ots ________________________________________________________________________________________ Enoncé Ecrire un program m e perm ettantde com pter le nom bre de m ots contenus dans un texte fourni au clavier. Le texte pourra com porter plusieurs lignes etl'utilisateur tapera une ligne "vide" pour signaler qu'ilen a term iné la frappe (ce qui revient à dire qu'ilfrappera deux fois de suite la touch e re turn aprè s avoir fourni la derniè re ligne). On adm ettra que deux m ots sonttoujours séparés par un ou plusieurs des caractè res suivants : - fin de ligne - espace - ponctuation : : . , ;?! - parenth è ses : ( ) - guillem ets : " - apostroph e : ' On adm ettra égalem ent, pour sim plifier, qu'aucun m ot ne peut ê tre com m encé sur une ligne et se poursuivre sur la suivante. On prévoira une fonction perm ettant de décider si un caractè re donné transm is en argum ent est un des séparateurs m entionnés ci-dessus. Elle fournira la valeur 1 lorsque le caractè re estun séparateur etla valeur 0 dans le cas contraire.
  • 110.
    110 Exe rcicesen langage C Exe m ple donnez votre texte, en le terminant par une ligne vide Le langage C a été conçu en 1972 par Denis Ritchie avec un objectif très précis : écrire un "système d'exploitation" (UNIX). A cet effet, il s'est inspiré du langage B (créé par K. Thompson) qu'il a haussé au niveau de langage évolué, notamment en l'enrichissant de structures et de types, et ceci tout en lui conservant ses aptitudes de programmation proche de la machine. votre texte comporte 68 mots _______________________________________________________________________________________ ANALYSE Com m e dans l'exercice précédent, ilexiste (au m oins)deux dém arch es possibles : - effectuer une répétition du traitem entd'un caractè re, - effectuer une répétition du traitem entd'une ligne, lui-m ê m e constitué de la répétition du traitem ent de ch acun des caractè res qu'elle contient. La prem iè re dém arch e aboutit à une sim ple boucle avec com pteur. Elle dem ande sim plem ent d'accéder à un seul caractè re (par exem ple par ge tch ar). La seconde dém arch e aboutit à deux boucles im briquées. Elle dem ande d'effectuer une lecture ligne par ligne (par exem ple par ge ts). Là encore, nous exam inerons les deux dém arch es et nous proposerons un program m e correspondant à ch acune d'entre elles. Dans les deux dém arch es, tous les caractè res séparateurs jouentle m ê m e rôle, à condition d'y inclure n (si l'on travaille avec ge tch ar) ou 0 (si l'on travaille avec ge ts). On peutalors dire que l'on a progressé d'un m otdans le texte, ch aque fois que l'on a réalisé la séquence suivante : - rech erch e du prem ier caractè re différentd'un séparateur, - rech erch e du prem ier caractè re égalà un séparateur.
  • 111.
    I. Variations algorithm iques sur les instructions de base 111 On pourraitrépéter successivem entces deux opérations, tantque que l'on n'estpas arrivé à la fin du texte. En fait, ilest plus sim ple d'utiliser un "indicateur logique" (nous l'appellerons m ot_e n_cours)etd'effectuer pour ch aque caractè re le traitem entsuivant: - si le caractè re est un séparateur et si m ot_e n_cours est vrai, augm enter de un le com pteur de m ots et rem ettre m ot_e n_cours à faux. - si le caractè re n'estpas un séparateur etsi m ot_e n_cours estfaux, m ettre m ot_e n_cours à vrai. Quantà la condition d'arrê t, elle s'exprim e différem m entsuivantla dém arch e adoptée : - deux caractè res consécutifs égaux à n pour la prem iè re, ce qui im pose de conserver en perm anence la valeur du "caractè re précédent" ;ce dernier sera initialisé à une valeur quelconque différente de n pour éviter un traitem ent particulier du prem ier caractè re du texte. - ligne vide pour la seconde. Program m e bas é s ur la ré pé tition du traite m e ntd'un caractè re #include <stdio.h> #define VRAI 1 /* pour "simuler" des ..... */ #define FAUX 0 /* ..... valeurs logiques */ main() { int sep(char) ; /* prototype fonction test "caractère séparateur?" */ char c, /* pour lire un caractère frappé au clavier */ cprec ; /* caractère précédent */ int nmots, /* compteur du nombre de mots */ fin_texte, /* indicateurs logiques : - fin texte atteinte */ mot_en_cours ; /* - mot trouvé */ cprec = ' ' ; fin_texte = FAUX ; mot_en_cours = FAUX ; nmots = 0 ; printf ("donnez votre texte, en le terminant par une ligne viden") ; while (!fin_texte) { if ( sep(c=getchar()) ) { if (mot_en_cours) { nmots++ ; mot_en_cours = FAUX ; }
  • 112.
    112 Exe rcicesen langage C } else mot_en_cours = VRAI ; if ( c=='n' && cprec=='n') fin_texte = VRAI ; cprec = c ; } printf ("nnvotre texte comporte %d mots :n", nmots) ; } /*******************************************/ /* fonction d'examen d'un caractère */ /*******************************************/ int sep (char c) { char sep[12] = {'n', /* fin de ligne */ ' ', /* espace */ ',', ';', ':', '.', '?', '!', /* ponctuation */ '(', ')', /* parenthèses */ '"', ''' } ; /* guillemets, apostrophe*/ int nsep=12, /* nombre de séparateurs */ i ; i = 0 ; while ( c!=sep[i] && i++<nsep-1 ) ; if (i == nsep) return (0) ; else return (1) ; } Com m e ntaire s *Nous avons introduit une variable "logique" nom m ée fin_te xte qui nous facilite la détection de la fin du texte. Nous aurions pu nous en passer en introduisantune instruction bre ak au sein d'une boucle do ... w h ile {1}(boucle infinie). *Dans le traitem entde ch aque caractè re, nous n'avons pas respecté "à la lettre" l'algorith m e proposé lors de l'analyse. En effet, nous exécutons l'instruction : mot_en_cours = VRAI m ê m e si l'indicateur m ot_e n_cours a déjà la valeur VRAI ;cela nous évite un test supplém entaire, sans m odifier le com portem entdu program m e (puisque la m odification ainsi apportée consiste à m ettre à VRAI l'indicateur alors qu'ily estdéjà ).
  • 113.
    I. Variations algorithm iques sur les instructions de base 113 *Dans la fonction sep, la seule instruction : while ( c!=sep[i] && i++<nsep-1 ) ; perm et de savoir si le caractè re c est un séparateur. En effet, ilne faut pas oublier que l'opérateur & & n'évalue son second opérande que lorsque cela estnécessaire. Autrem entdit, si la prem iè re condition estfausse (c estdonc égalà un séparateur), l'expression i++<nsep-1 n'estpas évaluée eti n'estdonc pas incrém entée. Si, par contre, cette prem iè re condition estvérifiée alors qu'on a exploré la totalité des séparateurs (i=11), la seconde condition estévaluée etelle est trouvée fausse, m ais en m ê m e tem ps, i se trouve incrém entée (à 12). En définitive, on voitqu'à la fin de cette instruction, lorsque i vaut12, cela signifie que c ne figure pas dans la liste des séparateurs. Program m e bas é s ur la ré pé tition du traite m e ntd'une ligne #include <stdio.h> #include <string.h> #define VRAI 1 /* pour "simuler" des ..... */ #define FAUX 0 /* ..... valeurs logiques */ main() { int sep(char) ; /* prototype fonction test "caractère séparateur?" */ char ligne[128] ; /* pour lire une ligne frappée au clavier */ int nmots, /* compteur du nombre de mots */ mot_en_cours, /* indicateur logique : mot trouvé */ i ; nmots = 0 ; mot_en_cours = FAUX ; printf ("donnez votre texte, en le terminant par une ligne viden") ; do { gets(ligne) ; for (i=0 ; i<=strlen(ligne) ; i++) /* on traite aussi le '0' */ if ( sep(ligne[i]) ) { if (mot_en_cours) { nmots++ ; mot_en_cours = FAUX ; } } else mot_en_cours = VRAI ; } while (strlen(ligne)) ;
  • 114.
    114 Exe rcicesen langage C printf ("nnvotre texte comporte %d mots :n", nmots) ; } /********************************************/ /* fonction d'examen d'un caractère */ /********************************************/ int sep (char c) { char sep[12] = {'0', /* fin de ligne (chaîne) */ ' ', /* espace */ ',', ';', ':', '.', '?', '!', /* ponctuation */ '(', ')', /* parenthèses */ '"', ''' } ; /* guillemets, apostrophe*/ int nsep=12, /* nombre de séparateurs */ i ; i = 0 ; while ( c!=sep[i] && i++<nsep-1 ) ; if (i == nsep) return (0) ; else return (1) ; } Com m e ntaire s Nous avons dû : - d'une part, au sein de la fonction sep, rem placer le séparateur n par 0, - d'autre part, dans la boucle de traitem entdes caractè res d'une ligne, traiter com m e les autres ce caractè re de fin de ligne (c'est-à -dire faire varier i de 0 à strle n(ligne ) etnon strle n(ligne )-1), afin d'éviter de com pter pour un seulm ot le dernier m otd'une ligne (non suivi d'un séparateur)etle prem ier de la suivante. D iscussion *En ce qui concerne la fonction d'exam en d'un caractè re (nom m ée sep), vous constatez (dans les deux versions proposées)que nous l'avons déclarée dans le program m e principal(m ain), bien que cela soitfacultatif, dans la m esure où elle fournitun résultatde type int. *Aucun des deux program m es proposés ne pose de problè m e de protection vis-à -vis des réponses fournies par l'utilisateur.
  • 115.
    II: UTILISATIO N DE STRUCTURES Le ch apitre I vous a proposé des exercices faisant appelaux instructions de base du langage C. Les exercices de ce ch apitre fontintervenir, en plus, la notion de structure sous des form es diverses (en particulier les tableaux de structures etleur initialisation). II-1 Signe du zodiaq ue ________________________________________________________________________________________ Enoncé Affich er le signe du zodiaque correspondantà une date de naissance fournie en donnée, sous la form e : jour m ois Les deux inform ations serontséparées par au m oins un espace. La prem iè re sera fournie sous form e num érique, tandis que la seconde le sera sous form e d'une ch aîne de caractè res. Nous vous rappelons que les périodes correspondantà ch aque signe sontles suivantes : Capricorne 23 décembre - 19 janvier Verseau 20 janvier - 19 février Poisson 20 février - 20 mars
  • 116.
    116 Exe rcicesen langage C Bélier 21 mars - 19 avril Taureau 20 avril - 20 mai Gémeau 21 mai - 20 juin Cancer 21 juin - 21 juillet Lion 22 juillet - 22 août Vierge 23 août - 22 septembre Balance 23 septembre - 22 octobre Scorpion 23 octobre - 21 novembre Sagittaire 22 novembre - 22 décembre Exe m ple s donnez votre jour et votre mois (sans accent) de naissance ? 11 july *** erreur de nom de mois *** _______________________ donnez votre jour et votre mois de naissance ? 16 janvier vous êtes né sous le signe suivant : Capricorne ________________________________________________________________________________________ ANALYSE Le program m e doit ê tre en m esure d'établir une correspondance entre le nom d'un signe et les deux dates lim ites correspondantes. On peutdéjà noter que la date de fin d'un signe estla veille de celle de début du suivant. Nous nous contenterons donc de ne conserver qu'une seule de ces deux inform ations, par exem ple la date de fin. La correspondance souh aitée peutê tre réalisée : - par plusieurs tableaux (jour, m ois, signe)reliés par une valeur com m une d'indice. - par un seultableau dans lequelch aque élém entestune structure com portantun num éro de jour, un nom de m ois et un nom de signe. Nous ch oisirons la seconde solution car elle perm et de m ieux m ettre en évidence la correspondance entre les inform ations, au m om entde l'initialisation au sein du program m e. La rech erch e du signe correspondantà une date donnée se faitalors de la m aniè re suivante : - On ch erch e toutd'abord l'élém ent(nous le nom m erons x)appartenantà notre tableau de structures, dontle nom de m ois correspond à celui proposé en donnée. S'iln'existe pas, on le signale par un m essage approprié.
  • 117.
    II. Utilisation destructures 117 - On regarde ensuite si le num éro du jour proposé estinférieur ou égalà celui de l'élém entx. Dans l'affirm ative, on peuten conclure que la date proposée estantérieure à la date de fin du signe figurantdans l'élém entx, ce qui fournitla réponse voulue. Dans le cas contraire, on en conclutque la date proposée estpostérieure à la date de débutdu signe figurantdans l'élém ent x ;ilsuffit donc d'exam iner l'élém ent suivant pour obtenir la réponse voulue. Toutefois, si x est le dernier élém entde notre tableau, ilfaudra considérer que son suivantesten faitle prem ier élém entdu tableau. On rem arquera que l'algorith m e proposé fonctionne effectivem entparce que ch acun des 12 m ois de l'année ne com porte qu'un seulch angem entde signe. Si cela n'avaitpas été le cas, ilauraitfallu "encadrer" la date proposée par deux dates d'élém ents consécutifs de notre tableau. Program m e #include <stdio.h> #include <conio.h> #include <string.h> main() { struct s_date { int jour ; char mois [10] ; char signe [11] ; } ; struct s_date date [12] = { 23, "decembre", "Sagittaire", 20, "janvier", "Capricorne", 20, "fevrier", "Verseau", 21, "mars", "Poisson", 20, "avril", "Bélier", 21, "mai", "Taureau", 21, "juin", "Gémeau", 22, "juillet", "Cancer", 23, "aout", "Lion", 23, "septembre", "Vierge", 23, "octobre", "Balance", 22, "novembre", "Scorpion" } ; int jour_n ; /* jour de naissance */ char mois_n [10] ; /* mois de naissance */ int nbv, i ;
  • 118.
    118 Exe rcicesen langage C /* lecture date de naissance */ printf ("donnez votre jour et votre mois de naissance ?n") ; scanf ("%d %s", &jour_n, mois_n) ; /* recherche et affichage du signe correspondant */ i = 0 ; while ( stricmp(date[i].mois, mois_n) && i++<11 ) { } if (i<12) { printf ("vous êtes né sous le signe suivant : ") ; if (jour_n >= date[i].jour) i = (i+1)%12 ; printf ("%s", date[i].signe) ; } else printf ("*** erreur de nom de mois ***") ; } Com m e ntaire s *Nous avons défini ici un m odè le de structure nom m é s_date , dans lequelnous trouvons un num éro de jour, un nom de m ois et le signe correspondant. Nous avons prévu 10 caractè res pour le nom de m ois, ce qui autorise des ch aînes de longueur inférieure ou égale à 9 (com pte tenu du 0 de fin);de m ê m e, nous avons prévu 11 caractè res pour le signe. Le tableau nom m é date est un tableau de 12 élém ents ayant ch acun le type s_date . Nous l'avons initialisé dans sa déclaration, ce qui perm etde m ettre facilem enten parallè le ch aque signe etsa date de fin. *En ce qui concerne la lecture de la date au clavier, nous n'avons pas prévu, ici, de protection vis-à -vis d'éventuelles erreurs de frappe de l'utilisateur (cela n'étaitpas dem andé par l'énoncé). *Rappelons que la fonction stricm p com pare, sans tenir com pte de la distinction m ajuscules/m inuscules, les deux ch aînes donton lui fournitl'adresse en argum ent. Elle restitue une valeur non nulle (qu'on peutinterpréter com m e vrai) lorsque les deux ch aînes sontdifférentes etune valeur nulle (faux)lorsqu'elles sontégales. *La rech erch e du nom de m ois estréalisée par la seule instruction : while ( stricmp(date[i].mois, mois_n) && i++<11 ) {} Celle-ci possè de un double avantage ;tout d'abord, celui de la concision ;ensuite, celui de nous perm ettre de savoir directem entsi la rech erch e a été fructueuse ou non.
  • 119.
    II. Utilisation destructures 119 En effet, ilne faut pas oublier que l'opérateur & & n'évalue son second opérande que lorsque cela est nécessaire. Autrem entdit, si la prem iè re condition estfausse (ily a donc égalité des deux ch aînes), l'expression i++<11 n'estpas évaluée eti n'estdonc pas incrém entée. La valeur de i désigne alors l'élém entvoulu. Si, par contre, cette prem iè re condition estvérifiée (iln'y a donc pas égalité des deux ch aînes) alors qu'on estarrivé en fin de table (i=11), la seconde condition est évaluée et elle est trouvée fausse, m ais en m ê m e tem ps i se trouve incrém entée (à 12). En définitive, on voitque, à la fin de cette instruction, lorsque i vaut12, cela signifie que l'élém entch erch é ne figure pas dans la table. Dans le cas contraire (i<12), i désigne l'élém entch erch é. Bien entendu, cette "rech erch e en table" pouvait se program m er de beaucoup d'autres m aniè res. Par exem ple, nous aurions pu écrire : while ( stricmp(date[i].mois, mois_n) && i<11 ) i++ ; Toutefois, cette instruction n'estpas équivalente à la précédente. En effet, lorsque i vaut11, cela peutsignifier : - soitque l'élém entch erch é esten position 11 (prem ier testsatisfait), - soitque l'élém entch erch é ne figure pas dans la table (second testsatisfait). Pour tranch er, ilestdonc nécessaire, dans ce cas, d'effectuer une com paraison supplém entaire. Notez que, par contre, une instruction telle que : while ( stricmp(date[i].mois, mois_n) && i++ <= 11) {} seraitquelque peu erronée. En effet, dans le cas où l'élém entch erch é ne figureraitpas dans le tableau, on seraitam ené à évaluer l'expression : date[i].mois avec une valeur i égale à 12, c'est-à -dire désignant un élém ent situé en deh ors du tableau. Certes, en général, cela ne seraitguè re visible dans le com portem entdu program m e, dans la m esure où ilestbien peu probable que cette valeur soit égale au nom de m ois voulu... *Notez l'em ploi de l'opérateur arith m étique % qui perm et de régler le problè m e du signe suivant le dernier signe du tableau. D ISCUSSIO N *Telqu'ila été prévu, notre program m e accepte des nom s de m ois écrits en m inuscules ou en m ajuscules m ais sans accent. Dans un program m e réel, ilseraitsouh aitable de faire preuve de plus de tolérance.
  • 120.
    120 Exe rcicesen langage C *Notre rech erch e du nom de m ois a été réalisée ici par un algorith m e ditde rech erch e séquentielle en table (algorith m e qui, com m e nous l'avons vu, peut se program m er en C à l'aide d'une seule instruction). D'autres algorith m es plus rapides existent, en particulier celui ditde rech erch e dich otom ique. L'exercice IV-5 vous en proposera un exem ple. II-2 Codage m ors e ________________________________________________________________________________________ Enoncé Ecrire un program m e affich antle codage en m orse d'un texte fourni au clavier et ne dépassant pas une "ligne" de 127 caractè res. Les caractè res susceptibles d'ê tre codés en m orse sont: - les 26 lettres de l'alph abet(supposées tapées en m ajuscules), - les 10 ch iffres de 0 à 9 , - le point, Si le texte contient d'autres caractè res que ceux-ci, le program m e affich era sim plem ent des points d'interrogation à la place du code m orse. Table au de s code s m orses A .- B -... C -.-. D -.. E . F ..-. G --. H .... I .. J .--- K -.- L .-.. M -- N -. O --- P .--. Q --.- R .-. S ... T - U ..- V ...- W .-- X -..- Y -.-- Z --.. . .-.-.- 0 ----- 1 .---- 2 ..--- 3 ...-- 4 ....- 5 ..... 6 -.... 7 --... 8 ---.. 9 ----. Exe m ple donnez votre message (1 ligne maxi) : LE LANGAGE C, CONCU EN 1972, EST L'OEUVRE DE DENIS RITCHIE. voici la traduction de votre message
  • 121.
    II. Utilisation destructures 121 .-.. . ?????? .-.. .- -. --. .- --. . ?????? -.-. ?????? ?????? -.-. --- -. -.-. ..- ?????? . -. ?????? .---- ----. --... ..--- ?????? ?????? . ... - ?????? .-.. ?????? --- . ..- ...- .-. . ?????? -.. . ?????? -.. . -. .. ... ?????? .-. .. - -.-. .... .. . .-.-.- ________________________________________________________________________________________ ANALYSE Le program m e doitdonc ê tre en m esure d'établir une correspondance entre un caractè re etson code m orse. Là encore, nous pourrions utiliser deux tableaux reliés par une valeur com m une d'un indice. M ais l'em ploi d'un tableau de structures perm etde m ieux m ettre en évidence la correspondance entre les inform ations, lors de l'initialisation. Ch aque élém ent(structure)du tableau contiendra : - un caractè re, - le code m orse correspondant, exprim é sous form e d'une ch aîne. Le codage d'un caractè re se fera alors sim plem entpar sa localisation dans le tableau. Program m e #include <stdio.h> #include <string.h> #define NL 37 /* nombre de caractères codés */ main() { struct code { char lettre ; char * morse ; } ; struct code table[NL] = /* code morse */ { 'A', ".-", 'B', "-...", 'C', "-.-.", 'D', "-..", 'E', ".", 'F', "..-.", 'G', "--.", 'H', "....", 'I', "..", 'J', ".---", 'K', "-.-", 'L', ".-..", 'M', "--", 'N', "-.", 'O', "---", 'P', ".--.", 'Q', "--.-", 'R',".-.", 'S', "...", 'T', "-", 'U', "..-", 'V', "...-", 'W', ".--", 'X', "-..-",
  • 122.
    122 Exe rcicesen langage C 'Y', "-.--", 'Z', "--..", '.', ".-.-.-", '0', "-----", '1', ".----", '2', "..---", '3', "...--", '4', "....-", '5', ".....", '6', "-....", '7', "--...", '8', "---..", '9', "----." } ; char ligne[128] ; /* pour lire une ligne au clavier */ int i, j ; /* lecture message à traduire */ printf ("donnez votre message (1 ligne maxi) : n") ; gets (ligne) ; printf ("nn voici la traduction de votre messagen") ; /* traduction lettre par lettre */ for (i=0 ; i<strlen(ligne) ; i++) { j=0 ; while (ligne[i] != table[j].lettre && j++<NL-1) ; if (j<NL) printf ("%7s", table[j].morse) ; else printf (" ??????") ; if ( ! ((i+1)%10) ) printf ("n") ; /* 10 codes morse par ligne */ } } Com m e ntaire s *Nous avons défini un m odè le de structure, nom m é code , dans lequelnous trouvons : - un caractè re, - un pointeur sur une ch aîne de caractè res destinée à contenir le code m orse correspondant. Notez que, contrairem entà ce que nous avions faitdans le program m e de l'exercice précédent, nous avons prévu ici un pointe ur sur une ch aîne etnon un table au de caractè res. Dans ces conditions, le tableau table occupera seulem ent 37 (valeur de NL) em placem ents dont la taille sera généralem ent de 3 ou 5 octets (1 pour le caractè re et 2 ou 4 pour le pointeur). L'em placem ent m ê m e des ch aînes correspondantes se trouve cependantréservé à la com pilation, de par le faitque nous avons initialisé ce tableau lors de sa déclaration. Ilne fautpas oublier, en effet, qu'une notation telle que : ".-.-."
  • 123.
    II. Utilisation destructures 123 est interprétée par le com pilateur com m e représentant l'adresse de la ch aîne fournie, m ais qu'en m ê m e tem ps illui réserve un em placem ent. Cette façon de procéder peutse révè ler plus économ ique en place m ém oire que la précédente, dans la m esure où ch aque ch aîne n'occupe que l'espace qui lui estnécessaire (ilfauttoutefois ajouter, pour ch aque ch aîne, l'espace nécessaire à un pointeur). Rem arque : En toute rigueur, le tableau table est de classe autom atique (puisqu'ilapparaît au sein d'une fonction - ici le program m e principal). Son em placem entestdonc alloué au m om entde l'exécution du program m e (c'est-à -dire, ici, dè s le début). Les constantes ch aînes, par contre, voientleurs em placem ents définis dè s la com pilation. Si notre tableau table avait été déclaré de m aniè re globale, ilaurait été de classe statique. Son em placem ent aurait alors été réservé dè s la com pilation. Une telle distinction est toutefois relativem ent form elle et elle n'a guè re d'incidence en pratique. Ilest, en effet, généralem ent, assez tentant de considérer les variables déclarées dans le program m e principalcom m e "quasi statiques", dans la m esure où, bien que non réservées à la com pilation, elles n'en n'occupentpas m oins de l'espace pendanttoute la durée de l'exécution du program m e. *La rech erch e du caractè re dans notre tableau table estréalisée par la seule instruction : while (ligne[i] != table[j].lettre && j++<NL-1) ; D ISCUSSIO N Dans un program m e "réel", ilfaudraitprévoir d'accepter un m essage de plus d'une ligne, ce qui poseraitle problè m e de sa m ém orisation. On pourraitê tre am ené, soità lui im poser une taille m axim ale, soità se tourner vers des m éth odes de "gestion dynam ique". II-3 D é codage m ors e ________________________________________________________________________________________
  • 124.
    124 Exe rcicesen langage C Enoncé Ecrire un program m e perm ettantde décoder un m essage en m orse fourni au clavier sous form e d'une suite de caractè res. Celle-ci pourra com porter : - des points etdes tirets représentantles codes proprem entdits, - un ou plusieurs espaces pour séparer les différents codes (on n'im posera donc pas à l'utilisateur d'em ployer un "gabarit" fixe pour ch aque code). On supposera que le m essage fourni ne dépasse pas une ligne de 127 caractè res. Les codes inexistants seronttraduits par le caractè re "?". On utilisera le tableau des codes m orses fourni dans l'exercice précédent(II-2). Exe m ple donnez votre message (1 ligne maxi) : -... --- -. .--- --- ..- .-. .-.-.- .-.-. voici la traduction de votre message B O N J O U R . ? ________________________________________________________________________________________ ANALYSE Ce program m e doitdonc établir une correspondance entre un code m orse etun caractè re. Nous pouvons, pour ce faire, utiliser la m ê m e structure que dans l'exercice précédent. Le décodage d'un caractè re se fera alors en explorant, non plus la partie caractè re, m ais la partie ch aîne du tableau de structure. L'algorith m e de rech erch e sera donc sim ilaire, la com paraison de caractè res étantrem placée par une com paraison de ch aînes. En ce qui concerne le m essage en m orse, nous pouvons le lire par ge ts dans un tableau de 128 caractè res, nom m é ligne . Le principalproblè m e qui se pose alors à nous estcelui de l'accè s à ch acun des codes m orses contenus dans ligne ;en effet, ceux-ci sontécrits avec un gabaritvariable etséparés par un nom bre variable d'espaces. Nous proposons de répéter le traitem entsuivant, fondé sur l'em ploi d'un pointeur de caractè res (indice) dans le tableau ligne : - Avancer le pointeur, tantqu'ildésigne un espace.
  • 125.
    II. Utilisation destructures 125 - Extraire, à partir de la position indiquée par le pointeur, à l'aide de sscanf, une ch aîne de longueur m axim ale 7 (puisque aucun code m orse ne dépasse cette longueur). Pour cela, nous ferons appelau code form at %7s, lequel interrom ptl'exploration, soitquand un séparateur estrencontré, soitlorsque la longueur indiquée (7)estatteinte. - Incrém enter le pointeur de la longueur de la ch aîne ainsi lue (car, bien sûr, iln'aura pas été m odifié par sscanf). Program m e #include <stdio.h> #include <string.h> #define NL 37 /* nombre de caractères codés */ #define LG 127 /* longueur ligne clavier */ main() { struct code { char lettre ; char * morse ; } ; struct code table[NL] = /* code morse */ { 'A', ".-", 'B', "-...", 'C', "-.-.", 'D', "-..", 'E', ".", 'F', "..-.", 'G', "--.", 'H', "....", 'I', "..", 'J', ".---", 'K', "-.-", 'L', ".-..", 'M', "--", 'N', "-.", 'O', "---", 'P', ".--.", 'Q', "--.-", 'R',".-.", 'S', "...", 'T', "-", 'U', "..-", 'V', "...-", 'W', ".--", 'X', "-..-", 'Y', "-.--", 'Z', "--..", '.', ".-.-.-", '0', "-----", '1', ".----", '2', "..---", '3', "...--", '4', "....-", '5', ".....", '6', "-....", '7', "--...", '8', "---..", '9', "----." } ; char ligne[LG+1] ; /* pour lire une ligne au clavier */ char code[7] ; /* code courant à traduire */ int i, j ; /* lecture message à traduire */ printf ("donnez votre message (1 ligne maxi) : n") ; gets (ligne) ; printf ("nn voici la traduction de votre messagen") ;
  • 126.
    126 Exe rcicesen langage C /* traduction code par code */ i=0 ; while (i<strlen(ligne)) { while (ligne[i] == ' ') i++ ; /* saut des espaces éventuels */ if ( i<strlen(ligne) ) /* si pas en fin de ligne */ { sscanf (&ligne[i], "%6s", code); /* lecture code (6 car max) */ i += strlen(code) ; /* incrément pointeur dans ligne */ j=0 ; /* recherche code dans table */ while ( stricmp (code, table[j].morse) && j++<NL-1) ; if (j<NL) printf ("%2c", table[j].lettre) ; /* code trouvé */ else printf (" ?") ; /* code non trouvé */ } } } Com m e ntaire s *Dans la boucle de saut des espaces éventuels, on ne risque pas d'aller au-delà de la fin de la ch aîne contenue dans ligne , car le caractè re de fin (0), différentd'un espace, servira de "sentinelle". *Par contre, avantd'extraire un nouveau code par sscanf, ilestnécessaire de s'assurer que l'on n'estpas parvenu en fin de ligne. En effet, dans ce cas, sscanffourniraitune suite de caractè res constituée du caractè re 0 (qui n'estpas considéré par cette fonction com m e un séparateur) etdes caractè res suivants (prélevés en deh ors du tableau ligne ). Notez que, en l'absence d'un teltest, le m alne seraitpas trè s grave puisqu'ilreviendraitsim plem entà placer au plus 7 caractè res dans code , com m ençantpar 0. *La rech erch e du code m orse dans le tableau table estréalisée par la seule instruction : while ( stricmp (code, table[j].morse) && j++<NL-1) ; Les rem arques faites dans le quatriè m e com m entaire de l'exercice II-1, à propos de la rech erch e séquentielle en table, s'appliquentégalem entici. D ISCUSSIO N
  • 127.
    II. Utilisation destructures 127 Notre program m e ne détecte pas le cas où l'utilisateur fournit un code m orse de plus de 6 caractè res. Dans ce cas, en effet, ilse contente de le "découper" en tranch es de 6 caractè res (la derniè re tranch e pouvant avoir une longueur inférieure). Si l'on souh aitait détecter ce genre d'anom alie, ilfaudrait, aprè s ch aque exam en d'un code, s'assurer qu'ilest effectivem entsuivi d'un espace ou d'une fin de ch aîne. II-4 Facturation par code ________________________________________________________________________________________ Enoncé Réaliser un program m e établissant une facture pouvant porter sur plusieurs articles. Pour ch aque article à facturer, l'utilisateur ne fournira que la quantité etun num é ro de code à partir duquelle program m e devra retrouver à la fois le libellé etle prix unitaire. Le program m e devra refuser les codes inexistants. A la fin, ilaffich era un récapitulatiftenantlieu de facture. Les inform ations relatives aux différents articles seront définies dans le source m ê m e du program m e (et non dans un fich ier de données). Elle seronttoutefois placées à un niveau global, de m aniè re à pouvoir, le cas éch éant, faire l'objet d'un source séparé, appelable par #include . On prévoira deux fonctions : - une pour rech erch er les inform ations relatives à un article, à partir de son num éro de code, - une pour affich er la facture récapitulative. Exe m ple combien d'articles à facturer ? 3 code article ? 25 quantité de Centrifugeuse au prix unitaire de 370.00 ? 33 code article ? 7 ** article inexistant - redonnez le code : 16
  • 128.
    128 Exe rcicesen langage C quantité de Grille-pain au prix unitaire de 199.50 ? 12 code article ? 26 quantité de Four à raclette 6P au prix unitaire de 295.25 ? 6 FACTURE ARTICLE NBRE P-UNIT MONTANT Centrifugeuse 33 370.00 12210.00 Grille-pain 12 199.50 2394.00 Four raclette 6P 6 295.25 1771.50 TOTAL 16375.50 ________________________________________________________________________________________ ANALYSE L'énoncé nous précise que les codes d'articles sont num ériques, m ais ilne dit pas qu'ils sont consécutifs. Dans ces conditions, ilest nécessaire de m ém oriser les différentes valeurs possibles de ces codes. Com m e nous devons pouvoir associer à ch aque code un libellé (ch aîne)etun prix (réel), nous pouvons songer à utiliser un tableau de structures, dans lequelch aque élém ent contient les inform ations relatives à un article (code, libellé, prix unitaire). Ce tableau sera, com m e dem andé par l'énoncé, déclaré à un niveau globaletinitialisé dans sa déclaration. Le travailde la fonction de rech erch e (nous la nom m erons re ch e rch e ) consistera à vérifier la présence du code d'article dans le tableau de structure ainsi défini. En cas de succè s, elle en restituera le rang (ce qui sera suffisantau program m e principalpour affich er les inform ations correspondantes). Dans le cas contraire, elle restituera la valeur -1. Notez que le code d'article sera le seulargum entde cette fonction. Nous voyons donc déjà com m ent, pour ch aque code (correct) fourni par l'utilisateur, affich er les inform ations correspondantes avantd'en dem ander la quantité. M ais, com pte tenu de ce que l'édition de la facture doitê tre faite aprè s les saisies relatives à tous les articles, nous devons obligatoirem ent, pour ch aque article à facturer, conserver : - la quantité, - une inform ation perm ettant d'en retrouver le libellé et le prix unitaire. Nous pourrions, certes, arch iver ces inform ations dans un tableau. M ais, en fait, cela n'estpas nécessaire puisqu'ilestpossible de les retrouver à partir du rang de l'article dans la structure (le code article conviendrait égalem ent, m ais ilnous faudrait alors explorer à nouveau notre tableau de structures lors de l'édition de la facture). Ces deux inform ations serontconservées dans deux tableaux (nom m és qte etrangart) com portantautantd'élém ents que d'articles à facturer (on en prévoira un nom bre m axim al).
  • 129.
    II. Utilisation destructures 129 La fonction d'édition de la facture (nom m ée facture ) se contentera alors d'explorer séquentiellem ent ces deux tableaux pour retrouver toutes les inform ations nécessaires. Elle recevra, en argum ent, les adresses des deux tableaux (qte et rangart), ainsi que le nom bre d'articles à facturer. Program m e #include <stdio.h> /* ------ structure contenant les informations relatives aux */ /* différents articles -------------- */ #define NBART 6 /* nombre total d'articles */ typedef struct { int code ; /* code article */ char * lib ; /* libellé */ float pu ; /* prix unitaire */ } t_article ; t_article article [NBART] = { 11, "Gaufrier", 268.0, 14, "Cafetière 12 T", 235.0, 16, "Grille-pain", 199.50, 19, "Balance de ménage", 278.0, 25, "Centrifugeuse", 370.0, 26, "Four raclette 6P", 295.25 } ; /* ----------------------------------------------------------------------*/ #define NAFMAX 10 /* nombre maxi d'articles à facturer */ main() { int recherche(int) ; /* proto fonction de recherche d'un article */ void facture(int[], int[], int) ; /* proto fonction d'affichage de la facture */ int naf, /* nombre d'articles à facturer */ rang, /* rang courant d'un article */ codart, /* code courant d'un article */ i ; int rangart [NAFMAX], /* rang des articles à facturer */ qte [NAFMAX] ; /* quantité de chaque article à facturer */ /* entrée du nombre d'articles à facturer */ printf ("combien d'articles à facturer ? ") ; scanf ("%d", &naf) ;
  • 130.
    130 Exe rcicesen langage C /* boucle de traitement de chaque article à facturer */ for (i=0 ; i<naf ; i++) { printf ("code article ? ") ; do { scanf ("%d", &codart) ; rang = recherche (codart) ; if (rang == -1) printf (" ** article inexistant - redonnez le code : ") ; } while (rang == -1) ; rangart[i] = rang ; printf ("quantité de %s au prix unitaire de %8.2f ? ", article[rang].lib, article[rang].pu) ; scanf ("%d", &qte[i]) ; } /* affichage facture */ facture (rangart, qte, naf) ; } /***********************************************************/ /* fonction de recherche d'un code article */ /***********************************************************/ int recherche (int codart) { int rang ; /* rang courant d'un article */ rang = 0 ; while (article[rang].code != codart && rang++ < NBART-1) {} ; if (rang <NBART) return (rang) ; else return (-1) ; } /***********************************************************/ /* fonction d'affichage de la facture */ /***********************************************************/ void facture(int rangart[], int qte[], int naf) /* rangart : tableau des rangs des codes articles */ /* qte :tableau des prix unitaires */ /* naf :nombre d'articles à facturer */ { float somme, /* total facture */ montant ; /* montant relatif à un article */ int i ;
  • 131.
    II. Utilisation destructures 131 printf ("nn %32snn", "FACTURE") ; printf ("%-20s%5s%10s%12snn", "ARTICLE", "NBRE", "P-UNIT", "MONTANT") ; somme = 0 ; for (i=0 ; i<naf ; i++) { montant = article[rangart[i]].pu * qte[i] ; printf ("%-20s%5d%10.2f%12.2fn", article[rangart[i]].lib, qte[i], article[rangart[i]].pu, montant) ; somme += montant ; } printf ("nn%-35s%12.2f", " TOTAL", somme) ; } Com m e ntaire s *Nous avons ch oisi ici d'utiliser type de fpour définir sous le nom t_article la structure correspondantà un article. Vous constatez que le libellé y apparaît sous la form e d'un pointeur sur une ch aîne et non d'une ch aîne ou d'un tableau de caractè res. Dans ces conditions, le tableau article , déclaré de ce type, n'occupera que 6 em placem ents de petite taille (généralem ent6 ou 8 octets) *Là encore, une seule instruction perm etd'effectuer la rech erch e d'un code article dans le tableau article . Voyez, à ce propos, les rem arques faites dans le quatriè m e com m entaire de l'exercice II-1. *Le code form at%-20s, utilisé à deux reprises dans la fonction facture , perm etde "cadrer" une ch aîne à gauch e. D ISCUSSIO N *Notre program m e n'est pas protégé contre des réponses incorrectes de la part de l'utilisateur. En particulier, une réponse non num érique peutentraîner un com portem entassez désagréable. Dans un program m e réel, ilseraitnécessaire de régler convenablem entce type de problè m e, par exem ple en utilisantfge ts (..., stdin)etsscanf. *De m ê m e, dans un program m e réel, ilpourraitê tre judicieux de dem ander à l'utilisateur de confirm er que le produit ch erch é estbien celui donton vientde lui affich er les inform ations.
  • 132.
    132 Exe rcicesen langage C *La précision offerte par le type float(6 ch iffres significatifs)peutse révéler insuffisante.
  • 133.
    III: H ASARDET RECREATIO NS Ce ch apitre vous propose un certain nom bre d'exercices correspondantà la réalisation de program m es récréatifs, basés sur l'utilisation du h asard. Les deux prem iers exercices sont essentiellem ent destinés à vous m ontrer com m ent générer des nom bres aléatoires en langage C. III-1 Tirage alé atoire ________________________________________________________________________________________ Enoncé Ecrire une fonction fournissant un nom bre entier tiré au h asard entre 0 (inclus) et une valeur n (incluse) fournie en argum ent. Réaliser un program m e principalutilisantcette fonction pour exam iner la "distribution" des valeurs ainsi obtenues dans l'intervalle [0, 10]. Le nom bre de tirages à réaliser sera lu en donnée et le program m e affich era le nom bre de fois où ch acune de ces valeurs aura été obtenue. Exe m ple combien de tirages : 1100 nombre de tirages obtenus 0 : 106 1 : 95
  • 134.
    134 Exe rcicesen langage C 2 : 115 3 : 95 4 : 91 5 : 103 6 : 103 7 : 101 8 : 92 9 : 94 10 : 105 Indication : ilestnécessaire de faire appelà la fonction rand de la "biblioth è que standard". ________________________________________________________________________________________ ANALYSE Ilfautfaire appelà la fonction rand. Celle-ci fournitun nom bre entier, tiré de façon "pseudo-aléatoire" dans l'intervalle [0, RAND_MAX], ch aque nom bre de cetintervalle ayantquasim entla m ê m e probabilité d'ê tre tiré. Notez que la valeur de RAND_M AX estdéfinie dans stdlib.h ;d'aprè s la norm e, elle n'estjam ais inférieure à la capacité m inim ale d'un int, c'est-à -dire 32767. Pour aboutir au résultat voulu, une dém arch e consiste à transform er un telnom bre en un nom bre réelappartenant à l'intervalle [0,1[. Ilsuffitensuite de m ultiplier ce réelpar n+1 etd'en prendre la partie entiè re pour obtenir le résultat escom pté. On peutalors m ontrer que les valeurs 0, 1, ... n-1, n sontquasi équiprobables. Pour obtenir un telnom bre aléatoire, nous pouvons diviser le nom bre fourni par rand par la valeur RAND_M AX+ 1 (il faut éviter de diviser par RAND_M AX, car la valeur 1 risquerait alors d'ê tre obtenue - en m oyenne une fois sur RAND_M AX!). Là encore, on peut, de m aniè re form elle, m ontrer que si la loi de probabilité estuniform e sur [0,1[, ilen va de m ê m e de celle du nom bre ainsi fabriqué dans l'intervalle d'entiers [0,n]. Program m e #include <stdio.h> #include <stdlib.h> /* pour la fonction rand */ #define N 10 /* les tirages se feront entre 0 et N */ main() {
  • 135.
    III. H asarde trécréations 135 int aleat (int) ; /* prototype fonction de tirage aléatoire */ int ntir, /* nombre de tirages requis */ t[N+1], /* tableau comptage tirages de chaque valeur */ i ; printf ("combien de tirages : ") ; scanf ("%d", &ntir) ; for (i=0 ; i<=N ; i++) t[i] = 0 ; for (i=1 ; i<=ntir ; i++) t[aleat(N)]++ ; printf ("nombre de tirages obtenusn") ; for (i=0 ; i<=N ; i++) { printf ("%4d : ", i) ; printf ("%6dn", t[i]) ; } } /********************************************************/ /* fonction de tirage aléatoire d'un nombre dans [0, n] */ /********************************************************/ int aleat (int n) { int i ; i = rand() / (RAND_MAX + 1.) * (n+1) ; return (i) ; } Com m e ntaire s *Dans la fonction ale at, la division par RAND_M AX+ 1 doitbien sûr s'effectuer sur des valeurs réelles. M ais, de plus, il fautprendre garde à ne pas écrire le diviseur sous la form e RAND_M AX + 1. En effet, celui-ci seraitévalué dans le type intet, dans le cas (fréquent)où la valeur de RAND_M AX estexactem entla valeur m axim ale du type int, l'addition de 1 à RAND_M AX conduiraità la valeur ... -1 (le dépassem entde capacité n'étantjam ais détecté en cas d'opérations sur des entiers). D ISCUSSIO N
  • 136.
    136 Exe rcicesen langage C En général, la fonction rand fournittoujours la m ê m e suite de valeurs, d'une exécution à une autre. L'exercice suivant vous m ontre com m entéviter ce ph énom è ne. III-2 Tirage alé atoire variable ________________________________________________________________________________________ Enonce Ecrire une fonction fournissantun nom bre entier tiré au h asard entre 0 etune valeur n fournie en argum ent. La suite des valeurs restituées par cette fonction (lorsqu'on l'appelle à diverses reprises) devra ê tre différente d'une exécution à une autre etne pas dépendre d'une quelconque inform ation fournie par l'utilisateur. Com m e dans l'exercice précédent, on réalisera un program m e principalutilisant cette fonction pour exam iner la "distribution" des valeurs ainsi obtenues dans l'intervalle [0,10]. Pour ce faire, on lira en données le nom bre de tirages à réaliser etle program m e affich era le nom bre de fois où ch acune des valeurs aura été obtenue. Suggestion : ilfaut"initialiser" convenablem entle "générateur de nom bres aléatoire", en utilisantla fonction srand. La "graîne" nécessaire peut ê tre fabriquée à l'aide de la fonction tim e , de façon à avoir un caractè re suffisam m ent im prévisible. Exe m ple s (ils'agitlà des résultats correspondantà deux exécutions différentes du m ê m e program m e) combien de tirages : 1100 nombre de tirages obtenus 0 : 124 1 : 104 2 : 97 3 : 97 4 : 89 5 : 93 6 : 105
  • 137.
    III. H asarde trécréations 137 7 : 109 8 : 110 9 : 89 10 : 83 ___________________ combien de tirages : 1100 nombre de tirages obtenus 0 : 104 1 : 98 2 : 98 3 : 106 4 : 98 5 : 97 6 : 99 7 : 109 8 : 99 9 : 96 10 : 96 ________________________________________________________________________________________ ANALYSE En langage C, la fonction srand perm etd'initialiser le générateur de nom bres aléatoires. Ilfautcependantlui fournir une "graîne", c'est-à -dire un nom bre entier (de type unsigne d int) qui déterm inera le prem ier nom bre tiré par rand. Cette m éth ode perm et ainsi, si on le souh aite, d'obtenir à volonté une m ê m e suite de nom bres aléatoires ;ilfaut d'ailleurs noter que, par défaut, tout se passe com m e si srand était appelé, en début de l'exécution d'un program m e, avec l'argum ent1. Ici, par contre, nous souh aitons obtenir une suite différente d'une exécution à une autre. Une solution à ce problè m e consiste à ch oisir une "graîne" aléatoire. Bien sûr, iln'estpas question de faire appelà rand dans ce cas. Par contre, la fonction tim e fournitune date , exprim ée en secondes. Celle-ci possè de un caractè re suffisam m entim prévisible pour ê tre utilisée com m e graîne. Cette initialisation du générateur de nom bres aléatoires doittoutefois n'ê tre réalisée qu'une seule fois pour une exécution donnée. Dans le cas contraire, on risquerait, en effet, d'obtenir plusieurs fois de suite les m ê m es nom bres. Si l'on souh aite que ce problè m e soitpris en ch arge par la fonction de tirage d'un nom bre elle-m ê m e, ilestnécessaire que cette derniè re soit capable de le faire lors de son prem ier appel(et uniquem ent à ce m om ent-là ). Ce m écanism e passe par l'em ploi d'une variable de classe statique.
  • 138.
    138 Exe rcicesen langage C Program m e #include <stdio.h> #include <stdlib.h> /* pour la fonction rand */ #include <time.h> /* pour la fonction time */ #define N 10 /* les tirages se feront entre 0 et N */ main() { int aleat (int) ; /* prototype fonction de tirage aléatoire */ int ntir, /* nombre de tirages requis */ t[N+1], /* tableau comptage tirages de chaque valeur */ i ; printf ("combien de tirages : ") ; scanf ("%d", &ntir) ; for (i=0 ; i<=N ; i++) t[i] = 0 ; for (i=1 ; i<=ntir ; i++) t[aleat(N)]++ ; printf ("nombre de tirages obtenusn") ; for (i=0 ; i<=N ; i++) { printf ("%4d : ", i) ; printf ("%6dn", t[i]) ; } } /********************************************************/ /* fonction de tirage aléatoire d'un nombre dans [0, n] */ /********************************************************/ int aleat (int n) { int i ; static int prem = 1 ; /* drapeau premier appel */ time_t date ; /* pour l'argument de time */ /* time_t est un type entier défini dans time.h */ /* initialisation générateur au premier appel */ if (prem) { srand (time(&date)) ; prem = 0 ; }
  • 139.
    III. H asarde trécréations 139 /* génération nombre */ i = rand() / (RAND_MAX + 1.) * (n+1) ; return (i) ; } Com m e ntaire s *Le m écanism e du traitem entparticulier à effectuer au prem ier appelest réalisé grâ ce à la variable pre m , déclarée de classe statique. Cette variable estinitialisée à un, lors de la com pilation. Dè s le prem ier appel, elle estm ise à zéro etelle gardera ensuite cette valeur jusqu'à la fin de l'exécution du program m e. Ainsi, la fonction srand n'est effectivem ent appelée qu'une seule fois, lors du prem ier appelde notre fonction ale at. *La fonction tim e fourniten retour le tem ps (exprim é en secondes) écoulé depuis une certaine "origine" dépendant de l'im plém entation. Le type de cette valeur dépend, lui aussi, de l'im plém entation ;toutefois, la norm e prévoitqu'ilexiste, dans tim e .h , un sym bole tim e _t (défini par type de f) précisant le type effectivem ent em ployé. Ici, lorsque nous transm ettons cette valeur à srand, ilestpossible qu'apparaisse une conversion du type tim e _tdans le type unsigne d int; ici, cela n'a guè re d'im portance, dans la m esure où, m ê m e si cette conversion est"dégradante", la valeur obtenue restera im prévisible pour l'utilisateur. D'autre part, la fonction tim e ne se contente pas de fournir une "h eure" en retour ;elle range égalem ent cette m ê m e inform ation à l'adresse qu'on lui fournit(obligatoirem ent) en argum ent;c'est ce qui justifie l'existence de la variable date (qui n'estpas utilisée par ailleurs)etqui doit, ici, absolum entê tre déclarée dans le "bon type", sous peine de risquer d'aboutir à un écrasem entintem pestifde données (dans le cas où on auraitdéclaré date d'un type "plus petit" que le type effectif). III-3 Alé a d'é toile s ________________________________________________________________________________________
  • 140.
    140 Exe rcicesen langage C Enoncé Affich er au h asard un certain nom bre d'étoiles (caractè re "*") à l'intérieur d'un rectangle. Le nom bre d'étoiles souh aitées, ainsi que le nom bre de lignes etde colonnes du rectangle serontfournis en données. Le program m e vérifiera que la zone estassez grande pour recevoir le nom bre d'étoiles requis. On évitera que plusieurs étoiles ne soientsuperposées. Exe m ple combien de lignes : 10 combien de colonnes : 45 combien de points : 200 ** * **** ** *** * ** *** * *** ** * * * ** * ** * * ****** * ** ** * * ** * * * ***** *** ** * *** * * * *** * * * * * ** * * ** * * ** ** ** **** ** ** ** ** * * * * * * ** *** * * * ** * * * * ** *** ** ** * ** * * * * ** * * * * * ***** ** ** * * * * ***** ** *** * ** * ***** **** * * *** * ** **** * ***** ________________________________________________________________________________________ ANALYSE Nous utiliserons un tableau de caractè res à deux dim ensions, dans lequelch aque élém entreprésentera une case de notre rectangle. Nous conviendrons que le prem ier indice représente le rang de la ligne et que le second indice représente le rang de la colonne. Com m e l'utilisateur doit pouvoir ch oisir les dim ensions du rectangle concerné, nous prévoirons de donner à notre tableau une taille suffisante pour couvrir tous les cas possibles (nous avons ch oisi, ici, 25 lignes de 80 caractè res);cela signifie que, la plupartdu tem ps, le program m e n'utilisera qu'une partie de ce tableau. Au départ, nous initialiserons tous les élém ents de la "partie utile" de ce tableau avec le caractè re espace. Nous ch oisirons ensuite au h asard les élém ents dans lesquels nous devrons placer un caractè re "*". Pour ce faire, ilnous suffira de tirer au h asard un num éro de ligne et un num éro de colonne jusqu'à ce que l'em placem ent correspondant soit disponible (caractè re espace). L'algorith m e de tirage au h asard d'un nom bre entier appartenant à un intervalle donné a été exposé dans l'analyse de l'exercice III-1.
  • 141.
    III. H asarde trécréations 141 Ilne nous restera plus qu'à affich er, par exem ple avec la fonction putch ar, les différents élém ents de notre tableau, en prévoyantun "ch angem entde ligne" aux m om ents opportuns. Program m e #include <stdio.h> #include <stdlib.h> #include <string.h> /* pour memset */ #include <time.h> #define NY 25 /* nombre total de lignes de l'écran */ #define NX 80 /* nombre total de colonnes de l'écran */ main() { int aleat (int) ; /* prototype fonction tirage aléatoire */ int ny, /* nombre de lignes du rect. considéré */ nx, /* nombre de col. du rect. considéré */ ix, /* colonne courante */ iy, /* ligne courante */ nb_points, /* nombre de points à tirer */ i, j ; char ecran [NX] [NY] ; /* image de l'écran */ const char blanc = ' ', /* caractère de remplissage */ point = '*' ; /* représentation d'un point */ /* entrée des dimensions du rectangle considéré ... ... et du nombre de points souhaités */ do { printf ("combien de lignes : ") ; scanf ("%d", &ny) ; } while (ny<=0 || ny>=NY) ; do { printf ("combien de colonnes : ") ; scanf ("%d", &nx) ; } while (nx<=0 || nx>=NX) ; do { printf ("combien de points : ") ; scanf ("%d", &nb_points) ; } while (nb_points > nx*ny || nb_points < 1 ) ;
  • 142.
    142 Exe rcicesen langage C /* remplissage aléatoire du tableau image d'écran */ memset (ecran, blanc, NX*NY) ; for (i=1 ; i<=nb_points ; i++) { do { ix = aleat (nx-1) ; iy = aleat (ny-1) ; } while ( ecran [ix] [iy] != blanc) ; ecran [ix] [iy] = point ; } /* affichage du tableau image d'écran */ for (j=0 ; j<ny ; j++) { for (i=0 ; i<nx ; i++) putchar ( ecran [i] [j] ) ; printf ("n") ; } } /*******************************************************/ /* fonction de tirage aléatoire d'un nombre dans [0,n] */ /*******************************************************/ int aleat (int n) { int i ; static int prem = 1 ; /* drapeau premier appel */ time_t date ; /* pour l'argument de time */ /* initialisation générateur au premier appel */ if (prem) { srand (time(&date) ) ; prem = 0 ; } /* génération nombre aléatoire */ i = rand() / (RAND_MAX + 1.) * (n+1) ; return (i) ; } Com m e ntaire s
  • 143.
    III. H asarde trécréations 143 *L'initialisation de la partie utile du tableau avec le caractè re espace auraitpu se program m er ainsi : for (i=0 ; i<nx ; i++) for (j=0 ; j<ny ; j++) ecran [i][j] = ' ' ; Ici, nous avons préféré faire appelà la fonction m e m set, d'exécution plus rapide. Toutefois, celle-ci rem plit d'un caractè re donné une suite d'octets consécutifs ;ceci exclutdonc de lim iter l'initialisation à la partie utile du tableau. Ilne fautpas oublier, en effet, que celle-ci n'estpas form ée de nx*ny octets consécutifs (quoique, en toute rigueur, en tenant com pte de la m aniè re dont sont rangés en m ém oire les différents élém ents d'un tableau, ilsoit possible de lim iter l'initialisation à nx*NY élém ents consécutifs). *Nous avons repris la fonction ale at de l'exercice précédent. Celle-ci tire une valeur entiè re au h asard entre 0 et une lim ite qu'on lui fourniten argum ent;de plus, lors de son prem ier appel, elle effectue une initialisation du générateur de nom bres aléatoires. III-4 Estim ation de pi ________________________________________________________________________________________ Enoncé Calculer une valeur approch ée de pi, par la m éth ode suivante : - on tire un certain nom bre de points au h asard dans un carré donné. - on déterm ine le rapportentre le nom bre de ces points appartenantau cercle inscritdans le carré etle nom bre total de points tirés. Ce rapportfournitune estim ation de la valeur de pi/4. Le nom bre totalde points à tirer sera fourni en donnée. Exe m ple combien de points ? 10000 estimation de pi avec 10000 points : 3.164800e+000
  • 144.
    144 Exe rcicesen langage C ________________________________________________________________________________________ ANALYSE Nous ch oisirons un carré de côté unité. Nous conviendrons de prendre son coin bas gauch e com m e origine d'un repè re cartésien. Nous tirerons alors au h asard le nom bre de points voulus, à l'intérieur de ce carré. Plus précisém ent, pour ch aque point, nous déterm inerons au h asard ses deux coordonnées, en tirantdeux nom bres réels appartenantà l'intervalle [0,1]. A cet effet, nous ferons appelà la m éth ode exposée dans l'analyse de l'exercice III-1. Pour ch aque point, nous calculerons sa distance au centre du carré (de coordonnées : 0.5, 0.5)etnous considérerons qu'il appartientau cercle inscritsi cette distance estinférieure à 0.5 (notez que, par souci de sim plicité, nous travaillerons en faitavec le carré de cette distance). Program m e #include <stdio.h> #include <stdlib.h> main() { float caleat(void) ; /* prototype fonction de tirage valeur aléatoire */ float x, y, /* coordonnées d'un point courant */ d2, /* distance (au carré) d'un point courant au centre */ pi ; /* valeur approchée de pi */ int np, /* nombre de points à tirer */ nc, /* nombre de points à l'intérieur du cercle */ i ; printf ("combien de points ? ") ; scanf ("%d", &np) ; for (nc=0, i=1 ; i<=np ; i++) { x = caleat() ; y = caleat() ; d2 = (x-0.5) * (x-0.5) + (y-0.5) * (y-0.5) ; if (d2 <= 0.25) nc++ ; } pi = (4.0 * nc) / np ;
  • 145.
    III. H asarde trécréations 145 printf ("estimation de pi avec %d points : %e", np, pi) ; } float caleat (void) /* fonction fournissant une valeur aléatoire */ /* appartenant à l'intervalle [0-1] */ { return ( (float) rand() / (RAND_MAX + 1.0) ) ; } D ISCUSSIO N Notre fonction de tirage aléatoire d'un entier fournit toujours la m ê m e suite de valeurs. Ce qui signifie que, pour un nom bre donné de points, nous obtiendrons toujours la m ê m e estim ation de pi. Vous pouvez éviter ce ph énom è ne en utilisantla fonction réalisée dans l'exercice III-2. III-5 Je u du de vin ________________________________________________________________________________________ Enoncé Ecrire un program m e qui ch oisit un nom bre entier au h asard entre 0 et 1000 et qui dem ande à l'utilisateur de le "deviner". A ch aque proposition faite par le joueur, le program m e répondra en situantle nom bre proposé par rapportà celui à deviner (plus grand, plus petitou gagné). Lorsque le joueur aura deviné le nom bre ch oisi, ou lorsqu'un nom bre m axim alde coups (10) aura été dépassé, le program m e affich era la récapitulation des différentes propositions. Exe m ple Devinez le nombre que j'ai choisi (entre 1 et 1000) votre proposition : 500 ----------- trop grand
  • 146.
    146 Exe rcicesen langage C votre proposition : 250 ----------- trop grand votre proposition : 125 ----------- trop grand votre proposition : 64 ----------- trop grand votre proposition : 32 ----------- trop grand votre proposition : 16 ----------- trop grand votre proposition : 8 ----------- trop petit votre proposition : 12 ----------- trop grand votre proposition : 10 ++++ vous avez gagné en 9 coups ---- Récapitulation des coups joués ---- 500 trop grand 250 trop grand 125 trop grand 64 trop grand 32 trop grand 16 trop grand 8 trop petit 12 trop grand 10 exact ________________________________________________________________________________________ ANALYSE Le program m e com m encera par tirer un nom bre entier au h asard, suivant la dém arch e exposée dans l'analyse de l'exercice III-1. Ildevra ensuite répéter l'action : faire joue r le joue ur jusqu'à ce que le joueur aitgagné ou qu'ilaitdépassé le nom bre m axim alde coups perm is. L'action en question consiste sim plem entà :
  • 147.
    III. H asarde trécréations 147 - Dem ander au joueur de proposer un nom bre. - Conserver ce nom bre dans un tableau (pour pouvoir établir la récapitulation finale). Notez que, com pte tenu de ce qu'un nom bre de coups m axim alestim posé, ce dernier fournira le nom bre m axim ald'élém ents de notre tableau. - Com parer le nom bre fourni avec la valeur ch oisie par le program m e etaffich er le m essage correspondant. Program m e #include <stdio.h> #include <stdlib.h> #define NCOUPS 15 /* nombre maximal de coups autorisés */ #define NMAX 1000 /* valeur maximale du nombre à deviner */ main() { int aleat(int) ; /* prototype fonction de tirage d'un nombre au hasard */ int nc, /* compteur du nombre de coups joués */ ndevin, /* nombre à deviner */ n, /* nombre courant proposé par le joueur */ prop[NMAX], /* tableau récapitulatif des nombres proposés */ i ; /* initialisations et tirage du nombre secret */ nc = 0 ; printf ("Devinez le nombre que j'ai choisi (entre 1 et %d)n", NMAX) ; ndevin = aleat(NMAX) ; /* déroulement du jeu */ do { printf ("votre proposition : ") ; scanf ("%d",&n) ; prop [nc++] = n ; if (n < ndevin) printf ("----------- trop petitn") ; else if (n > ndevin) printf ("----------- trop grandn") ; } while (n != ndevin && nc < NCOUPS) ; /* affichage résultats */
  • 148.
    148 Exe rcicesen langage C if (n == ndevin) printf ("nn++++ vous avez gagné en %d coupsn", nc) ; else { printf ("nn---- vous n'avez pas trouvén") ; printf ("le nombre choisi était %dn", ndevin) ; } /* affichage récapitulation */ printf ("n ---- Récapitulation des coups joués ----nn") ; for (i=0 ; i<nc ; i++) { printf ("%4d ", prop[i]) ; if (prop[i] > ndevin) printf ("trop grand n") ; else if (prop[i] < ndevin) printf ("trop petitn") ; else printf ("exactn") ; } } /*******************************************************/ /* fonction de tirage aléatoire d'un nombre dans [0,n] */ /*******************************************************/ int aleat(int n) { int i = rand() / (RAND_MAX + 1.) * (n+1) ; return i ; } D ISCUSSIO N Notre fonction de tirage aléatoire d'un nom bre entier fournittoujours la m ê m e valeur, ce qui gâ ch e quelque peu l'intérê t du jeu. Dans la pratique, ilserait nécessaire de rem placer la fonction ale at de ce program m e par celle proposée dans l'exercice III-2, laquelle perm etd'obtenir un nom bre différentd'une exécution à une autre. III-6 M aste rm ind ________________________________________________________________________________________
  • 149.
    III. H asarde trécréations 149 Enoncé Réaliser un program m e qui ch oisit au h asard une com binaison de 5 ch iffres (com pris entre 1 et 8) et qui dem ande à l'utilisateur de la deviner. A ch aque proposition, le program m e précisera : - le nom bre de ch iffres exacts proposés à la bonne place, - le nom bre de ch iffres exacts m ais proposés à la m auvaise place. Les différentes propositions du joueur seront fournies sous la form e de 5 ch iffres consécutifs (sans aucun séparateur), validés par re turn. Le program m e devra traiter convenablem entle cas des réponses incorrectes : lettre à la place d'un ch iffre, réponse trop courte ou trop longue, ch iffre incorrect(nulou supérieur à 8). On prévoira un nom bre lim ite d'essais, au-delà duquelle program m e s'interrom pra en indiquant quelle était la com binaison à deviner. Exe m ple proposition ? : 12345 2 P 0 C proposition ? : 23456 0 P 1 C proposition ? : 34567 0 P 1 C proposition ? : 45678 0 P 0 C proposition ? : 56789 ** incorrect ** proposition ? : 1133é ** incorrect ** proposition ? : 11332 3 P 1 C proposition ? : 11333 4 P 0 C proposition ? : 11313 5 P 0 C vous avez trouvé en 7 coups ________________________________________________________________________________________
  • 150.
    150 Exe rcicesen langage C ANALYSE Ilparaîtassez natureld'utiliser un tableau à 5 élém ents pour y ranger la com binaison tirée au h asard. Notez que nous pourrions égalem ent tirer au h asard un nom bre de 5 ch iffres, m ais ilfaudrait, de toute façon, en extraire ch acun des ch iffres ;de plus, la m éth ode seraitdifficilem entgénéralisable à un nom bre quelconque de positions. La principale difficulté réside dans l'analyse de la proposition du joueur. Dans la com paraison des deux tableaux (com binaison tirée par le program m e et com binaison proposée par le joueur), ilfaudra tenir com pte des rem arques suivantes : - Un ch iffre com pté "à sa place" ne doitpas pouvoir ê tre égalem entcom pté com m e "exact, m ais m alplacé". - Lorsqu'un tirage com porte plusieurs ch iffres identiques, ilne faut pas qu'un m ê m e ch iffre de la proposition du joueur puisse ê tre com pté plusieurs fois com m e exact. - Lorsqu'une proposition com porte plusieurs ch iffres identiques, il ne faut pas les considérer tous com m e correspondantà un m ê m e ch iffre du tirage. Nous vous proposons la m éth ode suivante : 1 - Nous rech erch ons toutd'abord les ch iffres exacts placés en bonne position. A ch aque fois qu'une coïncidence est relevée, nous supprim ons le ch iffre, à la fois dans la proposition du joueur et dans le tirage (en le rem plaçant, par exem ple, par la valeur 0). 2 - Nous reprenons ensuite, un à un, ch acun des ch iffres du tirage qui n'ontpas été supprim és (c'est-à -dire qui sont différents de 0). Nous les com parons à ch acun des ch iffres de la proposition. Là encore, si une coïncidence est relevée, nous supprim ons les ch iffres correspondants, à la fois dans la proposition etdans le tirage. Notez bien qu'il faut absolum ent éviter de considérer les ch iffres déjà supprim és du tirage : ils risqueraient d'ê tre trouvés égaux à d'autres ch iffres supprim és de la proposition. Cette m éth ode qui détruit le tirage nous oblige nécessairem ent à en faire une copie avant d'entam er l'analyse de la proposition. Nous avons ch oisi de réaliser trois fonctions : - tirage : tirage au h asard de la com binaison (tableau de 5 entiers)à deviner. - e ntre e : entrée de la proposition du joueur. Ilparaît logique que cette fonction fournisse cette proposition dans un tableau d'entiers. Toutefois, afin de traiter convenablem entles cas de réponses incorrectes, la proposition du joueur sera toutd'abord lue dans une ch aîne à l'aide de la fonction cge ts (son m écanism e estdécritdans l'exercice II-4). - analyse : analyse de la proposition du joueur, suivantl'algorith m e décritprécédem m ent. Program m e
  • 151.
    III. H asarde trécréations 151 #include <stdio.h> #include <stdlib.h> #include <string.h> #define NPOS 5 /* nombre de positions */ #define NCHIF 8 /* nombre de chiffres (ici, de 1 a 8) */ #define NCMAX 12 /* nombre maximal de coups */ main() { void tirage (int []) ; /*****************************/ int entree (int []) ; /* prototypes fonctions */ void analyse(int [], int[], int[], int []) ; /*****************************/ int tir[NPOS], /* combinaison tirée par le programme */ prop[NPOS], /* proposition du joueur */ ncoup, /* compteur de coups joués */ bpos, /* nombre de chiffres bien placés */ bchif ; /* nombre de chiffres exacts mais mal placés */ /* initialisations */ tirage (tir) ; ncoup = 0 ; /* déroulement du jeu */ do { while (printf ("proposition ? : "), entree(&prop) ) printf ("n** incorrect **n") ; analyse (prop, tir, &bpos, &bchif) ; printf ("n %22d P %d Cn", bpos, bchif) ; ncoup++ ; } while (bpos < NPOS && ncoup < NCMAX) ; /* affichage résultats */ if (bpos == NPOS) printf ("vous avez trouvé en %d coups", ncoup) ; else { int i ; printf ("vous n'avez pas trouvé en %d coupsn", NCMAX) ; printf ("la bonne combinaison était : ") ; for (i=0 ; i<NPOS ; i++) printf ("%d",tir[i]) ; printf ("n") ; } }
  • 152.
    152 Exe rcicesen langage C /*************************************************/ /* fonction de tirage de la combinaison secrète */ /*************************************************/ void tirage (int tir []) { int i ; for (i=0 ; i<NPOS ; i++) tir[i] = rand() / (RAND_MAX + 1.) * NCHIF + 1 ; } /*************************************************/ /* fonction de lecture de la proposition du joueur */ /*****************************************************/ int entree (int prop []) { char ch[NPOS+3] ; /* chaîne où sera lue la proposition du joueur */ int i ; /* lecture proposition joueur dans chaîne ch */ ch[0] = NPOS+1 ; /* préparation longueur maxi chaîne lue */ cgets (ch) ; /* contrôles */ if (strlen (&ch[2]) != NPOS) return(-1) ; for (i=2 ; i<NPOS+2 ; i++) if (ch[i] < '1' || ch[i] > '1'+NCHIF-1) return(-1) ; /* extraction des chiffres choisis */ for (i=0 ; i<NPOS ; i++) prop[i] = ch[2+i] -'0' ; return (0) ; } /**************************************************/ /* fonction d'analyse de la proposition du joueur */ /**************************************************/ void analyse (int prop [], int tir [], int bpos [] , int bchif []) { int tirbis[NPOS], /* double de la combinaison secrète */ i, j ; /* recopie de la combinaison secrète */ for (i=0 ; i<NPOS ; i++) tirbis[i] = tir[i] ;
  • 153.
    III. H asarde trécréations 153 /* comptage bonnes positions */ *bpos = 0 ; for (i=0 ; i<NPOS ; i++) if (prop[i] == tirbis[i]) { (*bpos)++ ; tirbis[i] = prop[i] = 0 ; } /* comptage bons chiffres mal placés */ *bchif = 0 ; for (i=0 ; i<NPOS ; i++) for (j=0 ; j<NPOS ; j++) if (prop[i] !=0 && prop[i] == tirbis[j]) { (*bchif)++ ; prop[i] = tirbis[j] = 0 ; } } Com m e ntaire s *Le nom bre de positions (NPOS) et le nom bre de ch iffres (NCH IF) ont été définis par #de fine , ce qui en facilite l'éventuelle m odification. *La fonction tirage faitappelà l'algorith m e de tirage au h asard d'un entier, telque nous l'avons exposé dans l'exercice III-1. Toutefois, ici, le nom bre tiré doitappartenir à l'intervalle [1,NCH IF] etnon à l'intervalle [0,NCH IF]. C'estce qui explique que le nom bre réeltiré dans l'intervalle [0,1[ soitm ultiplié par NCH IF etque l'on ajoute 1 au résultat. *La fonction e ntre e lit, com m e prévu, la proposition du joueur sous form e d'une ch aîne. Elle en effectue les contrôles requis en restituantla valeur 0 lorsque la réponse estvalide etla réponse -1 dans le cas contraire. Notez que la décision de dem ander, en cas d'erreur, une nouvelle proposition au joueur estprise dans le program m e principalet non dans la fonction elle-m ê m e. *Les argum ents de la fonction analyse sonttransm is par leur adresse, afin que leur valeur puisse ê tre m odifiée. C'estce qui justifie leur déclaration sous form e de pointeurs sur des entiers. N'oubliez pas que les nom s de tableaux correspondentà leur adresse ;c'estce qui justifie que dans l'appelde analyse, on trouve effectivem entles sym boles prop ettir, alors que, par ailleurs, on y trouve & bpos et& bch if. *Dans la boucle suivante (du program m e principal):
  • 154.
    154 Exe rcicesen langage C while (printf ("proposition ? : "), entree(&prop) ) printf ("n** incorrect **n") ; l'expression figurantdans w h ile utilise un "opérateur séquentiel", ce qui perm etainsi de sim plifier quelque peu l'écriture. A titre indicatif, voici deux constructions équivalentes, l'une parfaitem ent structurée, l'autre basée sur l'utilisation de bre ak (les valeurs des sym boles VRAI etFAUX étantrespectivem ent1 et0): ok = FAUX ; while (!ok) { printf ("proposition ? : ") ; if (entree(&prop)) ok = VRAI ; else printf ("n** incorrect **n") ; } do { printf ("proposition ? : ") ; if (entree(&prop)) break ; else printf ("n** incorrect **n) ; while(1) ; D ISCUSSIO N *Ici, la saisie de la proposition du joueur estparfaitem entsatisfaisante, m ê m e pour un program m e "réel". En particulier, elle autorise les corrections, m ê m e aprè s que l'utilisateur a frappé le dernier ch iffre. *Par contre, telqu'ilestproposé ici, ce program m e ch oisittoujours la m ê m e com binaison, ce qui enlè ve quelque intérê t à la pratique réguliè re du jeu (m ais qui peut faciliter la m ise au pointdu program m e). Pour rém édier à cette lacune, il suffit d'introduire, dans la fonction tirage , une initialisation du générateur de nom bres aléatoires, lors de son prem ier appel, com m e nous l'avons faitdans l'exercice III-2. *Le program m e supporte, sans aucune m odification, des valeurs quelconques de NPOS et des valeurs de NCH IF inférieures à 10. Ilestfacile d'aller au-delà , en m odifiantsim plem entla fonction e ntre e .
  • 155.
    IV: TRIS, FUSIONS ET RECH ERCH E EN TABLE Nous vous proposons ici des exercices de program m ation d'algorith m es classiques ayant trait aux tris et fusions de tableaux, ainsi qu'à la rech erch e en table. IV-1 Tri par e xtraction sim ple ________________________________________________________________________________________ Enoncé Réaliser un program m e de tri par valeurs décroissantes d'un tableau d'entiers, en utilisantl'algorith m e dit"par extraction sim ple" qui se définitde la m aniè re suivante : - On rech erch e le plus grand des n élém ents du tableau. - On éch ange cetélém entavec le prem ier élém entdu tableau. - Le plus petitélém entse trouve alors en prem iè re position. On peutalors appliquer les deux opérations précédentes aux n-1 élém ents restants, puis aux n-2, ... etcela jusqu'à ce qu'ilne reste plus qu'un seulélém ent(le dernier - qui estalors le plus petit). Le program m e affich era tous les "résultats interm édiaires", c'est-à -dire les valeurs du tableau, aprè s ch aque éch ange de deux élém ents. Exe m ple combien de valeurs à trier : 8 donnez vos valeurs à trier
  • 156.
    156 Exe rcicesen langage C 3 9 2 7 11 6 2 8 ---- valeurs à trier ---- 3 9 2 7 11 6 2 8 11 9 2 7 3 6 2 8 11 9 2 7 3 6 2 8 11 9 8 7 3 6 2 2 11 9 8 7 3 6 2 2 11 9 8 7 6 3 2 2 11 9 8 7 6 3 2 2 11 9 8 7 6 3 2 2 ---- valeurs triées ---- 11 9 8 7 6 3 2 2 ________________________________________________________________________________________ ANALYSE L'algorith m e proposé par l'énoncé peutse form aliser com m e suit, en tenantcom pte des conventions d'indices propres au langage C : Répéter, pour i variantde 0 à n-2 : - rech erch er k m telque t(k m )soitle plus grand des t(k ), pour k allantde i à n-1, - éch anger les valeurs de t(k m )etde t(i). Program m e #include <stdio.h> #define NMAX 100 /* nombre maximal de valeurs à trier */ main() { int t [NMAX], /* tableau contenant les valeurs à trier */ nval, /* nombre de valeurs à trier */ kmax, /* position du maximum temporaire */ tempo, /* valeur temporaire pour échange valeurs */
  • 157.
    IV. Tris, fusionse tre ch e rch e e n table 157 i, j, k ; /* lecture des valeurs à trier */ printf ("combien de valeurs à trier : ") ; scanf ("%d", &nval) ; if (nval > NMAX) nval = NMAX ; printf ("donnez vos valeurs à triern") ; for (k=0 ; k<nval ; k++) scanf ("%d", &t[k]) ; printf ("n ---- valeurs à trier ----n") ; for (k=0 ; k<nval ; k++) printf ("%5d", t[k]) ; printf ("nn") ; /* tri des valeurs */ for (i=0 ; i<nval-1 ; i++) /* recherche maxi partiel pour chaque i */ { kmax = i ; /* init recherche maxi partiel */ for (j=i+1 ; j<nval ; j++) /* recherche maxi partiel */ if (t[j] > t[kmax]) kmax = j ; tempo = t[kmax] ; /* mise en place maxi partiel */ t[kmax] = t[i] ; t[i] = tempo ; for (k=0 ; k<nval ; k++) /* affichage intermédiaire */ printf ("%5d", t[k]) ; printf ("n") ; } /* affichage valeurs triées */ printf ("n ---- valeurs triées ----n") ; for (k=0 ; k<nval ; k++) printf ("%5d", t[k]) ; printf ("n") ; } Com m e ntaire s Ce program m e fonctionne pour toutes les valeurs de NMAX, en particulier : - pour NMAX inférieur ou égalà 0, ilne faitrien, - pour NMAX = 1, illitune valeur qu'ilaffich e telle quelle.
  • 158.
    158 Exe rcicesen langage C IV-2 Tri par pe rm utation sim ple ________________________________________________________________________________________ Enoncé Ecrire une fonction réalisant le tri par valeurs croissantes d'un tableau d'entiers, en utilisant l'algorith m e de tri par perm utation sim ple (ditde "la bulle"), qui se définitainsi (n représentantle nom bre d'élém ents du tableau): On parcourtl'ensem ble du tableau, depuis sa fin jusqu'à son début, en com parantdeux élém ents consécutifs, en les inversants'ils sontm alclassés. On se retrouve ainsi avec le plus petitélém entplacé en tê te du tableau. On renouvelle une telle opération (dite "passe")avec les n-1 élém ents restants, puis avec les n-2 élém ents restants, et ainsi de suite... jusqu'à ce que : - soitl'avant-dernier élém entaitété classé (le dernier étantalors obligatoirem entà sa place), - soitqu'aucune perm utation n'aiteu lieu pendantla derniè re passe (ce qui prouve alors que l'ensem ble du tableau estconvenablem entordonné). On prévoira en argum ents : - l'adresse du tableau à trier, - son nom bre d'élém ents, - un indicateur précisant si l'on souh aite que la fonction affich e les valeurs du tableau aprè s ch aque perm utation (0 pour non, 1 pour oui). Exe m ple combien de valeurs à trier : 6 donnez vos valeurs à trier 2 8 4 7 0 8 ---- valeurs à trier ---- 2 8 4 7 0 8 2 8 4 0 7 8 2 8 0 4 7 8
  • 159.
    IV. Tris, fusionse tre ch e rch e e n table 159 2 0 8 4 7 8 0 2 8 4 7 8 0 2 4 8 7 8 0 2 4 7 8 8 ---- valeurs triées ---- 0 2 4 7 8 8 ________________________________________________________________________________________ ANALYSE L'algorith m e nous estindiqué par l'énoncé. Nous utiliserons cependantune répétition de type tantque (instruction w h ile ) qui perm etde prendre convenablem enten com pte le cas où l'on appelle la fonction de tri en lui fournissanten argum ent un nom bre de valeurs inférieur ou égalà 1. Dans la m ise en oeuvre de cet algorith m e, nous ferons appelà un entier i spécifiant le rang à partir duquelle tableau n'est pas encore trié. Initialem ent, ilfaudra prévoir qu'aucun élém ent n'est encore à sa place, ce qui conduira à l'initialisation artificielle de i à -1 (puisque en C, le prem ier élém entd'un tableau porte le num éro 0). D'autre part, un indicateur logique nom m é pe rm utnous servira à préciser si au m oins une perm utation a eu lieu au cours de la derniè re passe. Si nous notons nvalle nom bre de valeurs de notre tableau, l'algorith m e de tri peutalors s'énoncer com m e suit: Tant que i ne désigne pas le dernier élém ent du tableau (c'est-à -dire i < nval-1) et que pe rm ut est VRAI, nous effectuons une passe. Cette derniè re consiste en une succession de com paraisons des élém ents de rang j et j+1, j décrivanttous les élém ents depuis l'avant-dernier jusqu'à celui de rang i+1 (autrem entdit, jdécroissantde nval-2 à i+1). A ch aque perm utation, nous donnons à pe rm ut la valeur VRAI ;nous aurons, bien sûr, pris soin d'initialiser pe rm utà FAUX au débutde ch aque passe. Notez que l'utilisation d'une répétition de type tantque (dans laquelle la condition de poursuite faitintervenir l'indicateur pe rm ut)nous oblige à initialiser artificiellem entpe rm utà VRAI, en toutdébutde travail. Program m e #include <stdio.h> #define VRAI 1 /* pour "simuler" des ... */ #define FAUX 0 /* ... valeurs logiques */ #define NMAX 100 /* nombre maximal de valeurs à trier */ main() {
  • 160.
    160 Exe rcicesen langage C void bulle(int [], int, int ) ; /* prototype fonction de tri */ int t [NMAX], /* tableau contenant les valeurs à trier */ nval, /* nombre de valeurs à trier */ k ; /* lecture des valeurs à trier */ printf ("combien de valeurs à trier : ") ; scanf ("%d", &nval) ; if (nval > NMAX) nval = NMAX ; printf ("donnez vos valeurs à triern") ; for (k=0 ; k<nval ; k++) scanf ("%d", &t[k]) ; printf ("n ---- valeurs à trier ----n") ; for (k=0 ; k<nval ; k++) printf ("%5d", t[k]) ; printf ("nn") ; /* tri des valeurs */ bulle (t, nval, 1) ; /* affichage valeurs triées */ printf ("n ---- valeurs triées ----n") ; for (k=0 ; k<nval ; k++) printf ("%5d", t[k]) ; printf ("n") ; } /**************************************************/ /* fonction de tri par la méthode de la bulle */ /**************************************************/ void bulle (int t[], int nval, int affich) /* t : tableau à trier */ /* nval : nombre de valeurs à trier */ /* affich : indicateur affichages intermédiaires */ { int i, /* rang à partir duquel le tableau n'est pas trié */ j, /* indice courant */ tempo, /* pour l'échange de 2 valeurs */ k ; int permut ; /* indicateur logique précisant si au moins une */ /* permutation a eu lieu lors de la précédente passe */ i = -1 ; permut = VRAI ; while (i < nval-1 && permut)
  • 161.
    IV. Tris, fusionse tre ch e rch e e n table 161 { permut = FAUX ; for (j=nval-2 ; j>i ; j--) { if ( t[j] > t[j+1] ) { permut = VRAI ; tempo = t[j] ; t[j] = t[j+1] ; t[j+1] = tempo ; if (affich) { for (k=0 ; k<nval ; k++) printf ("%5d", t[k]) ; printf ("n") ; } } } i++ ; } } Com m e ntaire s Dans la fonction bullle , la déclaration : int * t ; estéquivalente à : int t[] ; D ISCUSSIO N Les deux algorith m es proposés dans l'exercice précédent et dans celui-ci correspondent à ce que l'on appelle des "m éth odes directes". D'une m aniè re générale, ce sont des algorith m es sim ples à program m er, m ais qui nécessitent un nom bre de com paraisons de l'ordre de n 2 (notez qu'ilexiste une troisiè m e m éth ode directe dite "tri par insertion"). En fait, ilexiste des m éth odes dites "évoluées" qui conduisent à un nom bre de com paraisons de l'ordre de n *log n. Celles-ci débouch entsur des program m es plus com plexes etles opérations qu'elles fontintervenir sontelles-m ê m es plus gourm andes en tem ps que celles des m éth odes directes. Aussi, les m éth odes évoluées ne prennentvéritablem entd'intérê t que pour des valeurs élevées de n.
  • 162.
    162 Exe rcicesen langage C A titre indicatif, nous vous fournissons ici l'algorith m e relatif à la m éth ode évoluée la plus perform ante, nom m ée "Tri rapide" (Quick sort), inventée par C. A. R. H oare. Cet algorith m e, délicat à program m er, est basé sur l'opération de "segm entation" d'un tableau ;celle-ci consiste à partager un tableau en deux parties, nom m ées segm ents, telles que tout élém ent de l'une soit inférieur ou égalà tout élém ent de l'autre. Une telle segm entation peut ê tre réalisée par l'algorith m e suivant: - Prendre un élém entau h asard (on peutprendre l'élém entdu m ilieu). Soitm sa valeur. - Rech erch er, depuis le débutdu tableau, le prem ier élém entt(i)telque t(i)> m . - Rech erch er, depuis la fin du tableau, le prem ier élém entt(j)telque t(j)<m . - Perm uter t(i)ett(j). - Poursuivre ce "parcours" du tableau jusqu'à ce que i etjse rencontrent. Le tri proprem entdits'effectue en appliquantà nouveau l'opération de segm entation à ch aque segm entobtenu, puis aux segm ents obtenus par segm entation de ces segm ents,... etainsi de suite jusqu'à ce que ch aque segm entne contienne plus qu'un seulélém ent. Notez qu'une telle m éth ode se prê te particuliè rem entbien à une program m ation récursive. IV-3 Tri d'un table au de ch aîne s ________________________________________________________________________________________ Enoncé Ecrire une fonction utilisantla m éth ode de tri par extraction sim ple (décrite dans l'exercice IV-1) pour trier un tableau de ch aînes, par ordre alph abétique (sans distinction entre m ajuscules etm inuscules). Cette fonction recevra, en argum ent: - l'adresse d'un tableau de pointeurs sur les ch aînes concernées, - le nom bre de ch aînes à trier. Le tri proprem entditportera, non sur les valeurs des ch aînes elles-m ê m es, m ais uniquem entsur le tableau de pointeurs. On testera cette fonction à l'aide d'un program m e principalcréantun sim ple tableau de ch aînes (ayantdonc ch acune une longueur m axim ale donnée).
  • 163.
    IV. Tris, fusionse tre ch e rch e e n table 163 Exe m ple combien de chaînes à trier ? 7 donnez vos 7 chaînes (validez chacune par 'return') C Turbo C Basic Pascal Turbo Pascal Fortran ADA voici vos chaînes triées ADA Basic C Fortran Pascal Turbo C Turbo Pascal ________________________________________________________________________________________ ANALYSE La m éth ode de tri a été décrite dans l'exercice IV-1. Ilest cependant nécessaire de procéder à plusieurs sortes d'adaptations : - ilfauten faire une fonction, - la relation d'ordre qui sertau tri ne porte plus sur des entiers, m ais sur des ch aînes de caractè res ;cela im plique de recourir à la fonction stricm p (etnon strcm p, puisque l'on souh aite ne pas distinguer les m ajuscules des m inuscules), - les élém ents à perm uter serontdes pointeurs etnon plus des entiers.
  • 164.
    164 Exe rcicesen langage C Program m e #include <stdio.h> #include <string.h> #define NCHMAX 100 /* nombre maximal de chaînes à traiter */ #define LGMAX 25 /* longueur maximale d'une chaîne (sans 0) */ main() { void trichaines (char * *, int ) ; /* prototype fonction de tri */ char chaines [NCHMAX] [LGMAX+1] ; /* tableau des chaînes */ char * adr [NCHMAX] ; /* tableau pointeurs sur les chaînes */ int nch, /* nombre de chaîne à trier */ i ; /* lecture des chaînes et préparation du tableau de pointeurs */ printf ("combien de chaînes à trier ? ") ; scanf ("%d", &nch) ; if (nch > NCHMAX) nch = NCHMAX ; getchar() ; /* pour forcer la lecture de fin de ligne */ printf ("donnez vos %d chaînes (validez chacune par 'return')n", nch) ; for (i=0 ; i<nch ; i++) { fgets (chaines[i], LGMAX+1, stdin) ; /* lit au maximum LGMAX caractères */ adr[i] = chaines[i] ; } /* tri des pointeurs sur les chaînes */ trichaines (adr, nch) ; /* affichage des chaînes après tri */ /* attention aux chaînes de longueur maximum !! */ printf ("nnvoici vos chaînes triéesn") ; for (i=0 ; i<nch ; i++) printf ("%s", adr[i]) ; } void trichaines (char * * adr, int nch) /* adr : adresse tableau de pointeurs sur chaînes à trier */ /* nch : nombre de chaînes */ { char * tempo ; /* pointeur temporaire pour l'échange de 2 pointeurs */ int kmax,
  • 165.
    IV. Tris, fusionse tre ch e rch e e n table 165 i, j ; for (i=0 ; i<nch-1 ; i++) { kmax = i ; for (j=i+1 ; j<nch ; j++) if ( stricmp (adr[kmax], adr[j]) > 0 ) kmax = j ; tempo = adr[kmax] ; adr[kmax] = adr[i] ; adr[i] = tempo ; } } Com m e ntaire s *Ici, les ch aînes à trier ontété placées (par le program m e principal) dans un tableau de caractè res (nom m é ch aines) à deux dim ensions. Notez bien qu'ilne seraitpas possible d'en inverser l'ordre des dim ensions ;ilesten effetnécessaire que tous les caractè res d'une m ê m e ch aîne soientconsécutifs. *Bien que cela n'ait pas été explicitem ent dem andé par l'énoncé, nous avons prévu un contrôle sur la longueur des ch aînes fournies au clavier ;pour ce faire, nous avons fait appelà la fonction fge ts, en l'appliquant au fich ier stdin. L'instruction : fgets (chaines[i], LGMAX+1, stdin) ; litau m axim um LGM AX caractè res sur stdin etles range à l'adresse ch aine [i], en com plétantle toutpar un zéro de fin de ch aîne. Ainsi, on évite les risques de débordem entm ém oire que présente ge ts. Toutefois un léger inonvénientapparaît. En effet, tantque le nom bre de caractè res m axim al(LGM AX) n'estpas atteint, le caractè re n qui a servi à délim iter la ch aîne lue est rangé en m ém oire, au m ê m e titre que les autres. En revanch e, lorsque le nom bre m axim alde caractè res a été atteint, alors précisém entque ce caractè re n n'a pas été rencontré, on ne trouve plus ce caractè re en m ém oire (le caractè re nulde fin de ch aîne, quantà lui, estbien toujours présent). Cetinconvénientestsurtoutsensible lorsque l'on affich e à nouveau les ch aînes par printfaprè s leur tri : les ch aînes de longueur m axim ale ne serontpas suivies d'un ch angem entde ligne. Notez bien qu'en em ployantputs on obtiendrait, en revanch e, 1 caractè re de ch angem ent de ligne pour les ch aînes de longueur m axim ale (transm is par la fonction puts m ê m e)et2 caractè res de ch angem entde ligne pour les autres ch aînes (celui figurantdans la ch aîne etcelui transm is par puts). Dans un "program m e opérationnel", ilfaudraitgérer convenablem entcette situation, ce que nous n'avons pas faitici.
  • 166.
    166 Exe rcicesen langage C *Rappelons que, aprè s la lecture par scanf du nom bre de ch aînes à traiter, le pointeur reste (com m e à l'accoutum ée) positionné sur le dernier caractè re non encore utilisé ;dans le m eilleur des cas, ils'agit de n (m ais ilpeut y avoir d'autres caractè res avant si l'utilisateur a été distrait). Dans ces conditions, la lecture ultérieure d'une ch aîne par ge ts conduira à lire... une ch aîne vide. Pour éviter ce problè m e, nous avons placé une instruction ge tch ar qui absorbe ce caractè re n. En toute rigueur, si l'on souh aitait traiter correctem ent le cas où l'utilisateur a fourni trop d'inform ation pour le scanf précédent, ilserait nécessaire d'opérer une lecture d'une ch aîne par ge ts (ilfaudraitprévoir un em placem entà ceteffet!). *Dans la fonction trich aines, le prem ier argum entadr a été déclaré par : char * * adr Ils'agitd'un pointe ur sur le table au de pointe urs sur les différentes ch aînes. Nous aurions pu égalem entle déclarer par : char * adr[] Notez d'ailleurs que nous avons utilisé le "form alism e" tableau au sein de la fonction elle-m ê m e. Ainsi : adr[i] = adr[j] auraitpu se form uler : * (adr+i) = * (adr+j) *Nous vous rappelons que la fonction stricm p com pare les deux ch aînes dont on lui fournit les adresses et elle fournit une valeur entiè re définie com m e étant: - positive si la prem iè re ch aîne arrive aprè s la seconde, au sens de l'ordre défini par le code des caractè res (sans tenir com pte de la différence entre m ajuscules etm inuscules pour les 26 lettres de l'alph abet), - nulle si les deux ch aînes sontégales, - négative si la prem iè re ch aîne arrive avantla seconde. D ISCUSSIO N D'une m aniè re générale, iln'est pas nécessaire que les ch aînes à trier soient, com m e ici, im plantées en m ém oire de m aniè re consécutive. De m ê m e, la fonction trich aines proposée pourraittoutaussi bien opérer sur des ch aînes dontles em placem ents auraient été alloués "dynam iquem ent" (le ch apitre Vvous propose d'ailleurs un exercice dans ce sens).
  • 167.
    IV. Tris, fusionse tre ch e rch e e n table 167 IV-4 Fusion de de ux table aux ordonné s La fusion consiste à rassem bler en un seultableau ordonné les élém ents de deux tableaux, eux-m ê m es ordonnés. ________________________________________________________________________________________ Enoncé Réaliser une fonction qui fusionne deux tableaux d'entiers ordonnés par valeurs croissantes. On prévoira en argum ents : - les adresses des trois tableaux concernés, - le nom bre de valeurs de ch acun des deux tableaux à fusionner. Pour tester cette fonction, on écrira un program m e principalqui litau clavier deux ensem bles de valeurs que l'on triera au préalable à l'aide de la fonction bulle réalisée dans l'exercice IV-2. Exe m ple combien de valeurs pour le premier tableau ? 5 donnez vos valeurs 3 9 2 8 11 combien de valeurs pour le second tableau ? 7 donnez vos valeurs 12 4 6 3 1 9 6 premier tableau à fusionner 2 3 8 9 11 second tableau à fusionner 1 3 4 6 6 9 12 résultat de la fusion des deux tableaux 1 2 3 3 4 6 6 8 9 9 11 12 ________________________________________________________________________________________
  • 168.
    168 Exe rcicesen langage C ANALYSE La dém arch e, assez sim ple, s'inspire de celle que l'on adopteraitpour résoudre "à la m ain" un telproblè m e. Ilsuffit, en effet, d'avancer en parallè le dans ch acun des deux tableaux à fusionner (t1 et t2), en prélevant, à ch aque fois, le plus petitdes deux élém ents eten l'introduisantdans le tableau résultantt. Plus précisém ent, nous som m es am enés à utiliser trois indices : - i1 : prem ier élém entde t1 non encore pris en com pte, - i2 : prem ier élém entde t2, non encore pris en com pte, - i : em placem entdu proch ain élém entà introduire dans t. Nous initialisons ces trois indices à zéro (com pte tenu des conventions du C). Nous répétons alors le traitem entsuivant: Ch oisir le plus petit des élém ents t1(i1) et t2(i2) et le placer en t(i). Incrém enter de 1 la valeur de l'indice correspondantà l'élém entextrait(i1 ou i2), ainsi que celle de i. Nous poursuivons ainsi jusqu'à ce que l'un des deux tableaux soit épuisé. Ilne reste plus alors qu'à recopier la fin de l'autre tableau. Program m e #include <stdio.h> #define NMAX1 100 /* nombre maximal de valeurs du premier tableau */ #define NMAX2 100 /* nombre maximal de valeurs du second tableau */ main() { void fusion(int [], int [], int [], int, int ) ; /* proto fonction de fusion */ void bulle(int [], int) ; /* proto fonction servant à assurer l'ordre des tableaux */ int t1 [NMAX1], /* premier tableau à fusionner */ t2 [NMAX2], /* second tablleau à fusionner */ t [NMAX1+NMAX2] ; /* tableau résultant de la fusion */ int nval1, /* nombre de valeurs à prélever dans t1 */ nval2, /* nombre de valeurs à prélever dans t2 */ k ; /* lecture des valeurs des deux ensembles à fusionner */ printf ("combien de valeurs pour le premier tableau ? ") ; scanf ("%d", &nval1) ;
  • 169.
    IV. Tris, fusionse tre ch e rch e e n table 169 if (nval1 > NMAX1) nval1 = NMAX1 ; printf ("donnez vos valeursn") ; for (k=0 ; k<nval1 ; k++) scanf ("%d", &t1[k]) ; printf ("combien de valeurs pour le second tableau ? ") ; scanf ("%d", &nval2) ; if (nval2 > NMAX2) nval2 = NMAX2 ; printf ("donnez vos valeursn") ; for (k=0 ; k<nval2 ; k++) scanf ("%d", &t2[k]) ; /* tri préalable et affichage des valeurs à fusionner */ bulle (t1, nval1) ; bulle (t2, nval2) ; printf ("npremier tableau à fusionnern") ; for (k=0 ; k<nval1 ; k++) printf ("%5d", t1[k]) ; printf ("nsecond tableau à fusionnern") ; for (k=0 ; k<nval2 ; k++) printf ("%5d", t2[k]) ; /* fusion et affichage résultats */ fusion (t, t1, t2, nval1, nval2) ; printf ("nn résultat de la fusion des deux tableauxn") ; for (k=0 ; k<nval1+nval2 ; k++) printf ("%5d", t[k]) ; } /********************************************************/ /* fonction de fusion de deux tableaux */ /********************************************************/ void fusion (int t[], int t1[], int t2[], int nval1, int nval2) /* t1 et t2 : tableaux à fusionner */ /* t :tableau résultant */ /* nval1 : nombre de valeurs du premier tableau t1 */ /* nval2 : nombre de valeurs du second tableau t2 */ { int i1, i2, /* indices courants dans les tableaux à fusionner */ i, /* indice courant dans le tableau résultant */ k ;
  • 170.
    170 Exe rcicesen langage C i = 0 ; i1 = 0 ; i2 = 0 ; while (i1 < nval1 && i2 < nval2) { if ( t1[i1] < t2[i2] ) t[i++] = t1[i1++] ; else t[i++] = t2[i2++] ; } if (i1 == nval1) for (k=i2 ; k<nval2 ; k++) t[i++] = t2[k] ; else for (k=i1 ; k<nval1 ; k++) t[i++] = t1[k] ; } /*******************************************************/ /* fonction de tri d'un tableau (méthode de la bulle) */ /*******************************************************/ void bulle (int t[], int nval) { int i, j, tempo, k, permut ; i = -1 ; permut = 1 ; while (i < nval-1 && permut) { permut = 0 ; for (j=nval-2 ; j>i ; j--) if ( t[j] > t[j+1]) { permut = 1 ; tempo = t[j] ; t[j] = t[j+1] ; t[j+1] = tempo ; } i++ ; } } Com m e ntaire s *Pour effectuer le tri préalable des deux tableaux fournis en donnée, nous avons repris la fonction bulle réalisée dans l'exercice IV-2. Nous en avons toutefois supprim é les instructions perm ettant d'affich er, sur dem ande, les im pressions interm édiaires.
  • 171.
    IV. Tris, fusionse tre ch e rch e e n table 171 IV-5 Re ch e rch e dich otom iq ue L'exercice II-4 de facturation par code faisait intervenir un algorith m e séquentielde rech erch e en table. Nous vous proposons ici de réaliser un algorith m e plus perform antde rech erch e par "dich otom ie". ________________________________________________________________________________________ Enoncé Ecrire un program m e qui rech erch e, à partir d'un code d'article (num érique), l'inform ation qui lui estassociée, à savoir un libellé (ch aîne)etun prix unitaire (réel). Com m e dans l'exercice II-4, le program m e utilisera un tableau de structures, déclaré à un niveau global, pour conserver les inform ations requises. Cette fois, par contre, ces derniè res serontrangées par ordre croissantdu num éro de code. La localisation d'un num éro de code donné se fera par une rech erch e dich otom ique. Celle-ci consiste à profiter de l'ordre du tableau pour accélérer la rech erch e en procédantcom m e suit: - On considè re l'élém ent figurantau "m ilieu" du tableau. Si le code ch erch é lui est égal, la rech erch e est term inée. S'illui estinférieur, on en conclutque le code rech erch é ne peutse trouver que dans la prem iè re m oitié du tableau ; dans le cas contraire, on en conclutqu'ilse trouve dans la seconde m oitié. - On recom m ence alors l'opération sur la "m oitié" concernée, puis sur la m oitié de cette m oitié, et ainsi de suite... jusqu'à ce que l'une des conditions suivantes soitsatisfaite : *on a trouvé l'élém entch erch é, *on estsûr qu'ilne figure pas dans le tableau. Exe m ple s code article recherché : 24 le code 24 n'existe pas ________________ code article recherché : 19 article de code 19 libellé : Balance de ménage prix : 278.00 ________________________________________________________________________________________
  • 172.
    172 Exe rcicesen langage C ANALYSE L'algorith m e proposé par l'énoncé suggè re d'utiliser trois variables perm ettantde spécifier, à un instantdonné, la partie du tableau dans laquelle s'effectue la rech erch e : gauch e : débutde la partie restantà explorer, droite : fin de la partie restantà explorer, m ilie u : position ch oisie pour le "m ilieu" de cette partie restantà explorer. Notez déjà que cette notion de m ilieu estquelque peu am biguë. Nous conviendrons qu'elle correspond à la partie entiè re de la m oyenne des indices gauch e etdroite . L'algorith m e de rech erch e par dich otom ie peutalors s'énoncer ainsi (t désignant le tableau, n le nom bre de codes et x l'élém entch erch é): - Initialiser gauch e etdroite de façon qu'ils désignentl'ensem ble du tableau. - Répéter le traitem entsuivant: *Déterm iner le m ilieu de la partie à explorer : m ilieu = (gauch e + droite)/2 *Com parer l'élém entch erch é x avec t(m ilie u): + S'ils sontégaux, l'élém entch erch é estlocalisé en position m ilie u, + Si x est supérieur à t(m ilie u), l'élém ent ch erch é ne peut se situer que dans la partie droite ;on réalise l'affectation : debut=m ilieu + 1 + dans le cas contraire, l'élém entch erch é ne peutse situer que dans la partie gauch e ;on réalise l'affectation : fin = m ilieu - 1 Ilnous reste à spécifier la condition d'arrê t (ou de poursuite) de cette répétition. On peut déjà noter que, à ch aque parcours de la boucle, soitla valeur de gauch e augm ente, soitcelle de droite dim inue. Ainsi, on estsûr qu'au boutd'un nom bre fini de tours on aboutira à l'une des situations suivantes : - l'élém enta été localisé. - la valeur de gauch e estsupérieure à celle de droite . Elles nous fournissentdonc toutnaturellem entla condition de fin de notre boucle.
  • 173.
    IV. Tris, fusionse tre ch e rch e e n table 173 Notez que, dans un prem ier tem ps, la valeur de gauch e devient égale à celle de droite ;m ais, dans ce cas, nous ne savons pas encore si le seulélém ent restant à exam iner est ou non égalà x ;aussi est-ilnécessaire de faire un tour supplém entaire pour s'en assurer. Program m e #include <stdio.h> /* ------ structure contenant les informations relatives aux */ /* différents articles -------------- */ #define NBART 6 /* nombre total d'articles */ typedef struct { int code ; /* code article */ char * lib ; /* libellé */ float pu ; /* prix unitaire */ } t_article ; t_article article [NBART] = { 11, "Gaufrier", 268.0, 14, "Cafetière 12 T", 235.0, 16, "Grille-pain", 199.50, 19, "Balance de ménage", 278.0, 25, "Centrifugeuse", 370.0, 26, "Four raclette 6P", 295.25 } ; /* ----------------------------------------------------------------------*/ #define VRAI 1 /* pour "simuler" des ..... */ #define FAUX 0 /* ..... valeurs logiques */ main() { int coderec, /* code article recherché */ codecour, /* code courant */ gauche, /* limite gauche de la recherche */ droite, /* limite droite de la recherche */ milieu, /* nouvelle limite (droite ou gauche */ trouve ; /* indicateur code trouvé/non trouvé */ printf ("code article recherché : ") ; scanf ("%d", &coderec) ; gauche = 0 ; droite = NBART-1 ; trouve = FAUX ;
  • 174.
    174 Exe rcicesen langage C while (gauche <= droite && !trouve) { milieu = (gauche+droite) / 2 ; codecour = article[milieu].code ; if ( codecour == coderec ) trouve = VRAI ; else if ( codecour < coderec) gauche = milieu + 1 ; else droite = milieu - 1 ; } if (trouve) printf ("article de code %dnlibellé : %snprix : %10.2f", coderec, article[milieu].lib, article[milieu].pu) ; else printf ("le code %d n'existe pas", coderec) ; } Com m e ntaire s *Notez bien la condition régissantla boucle while : gauche <= droite && !trouve - D'une part, com m e nous l'avons dit dans notre analyse, nous poursuivons notre exploration, m ê m e quand les valeurs de gauch e etdroite sontégales, de m aniè re à savoir si le seulélém entrestantà exam iner convientou non. - D'autre part, nous y faisons intervenir un indicateur logique (trouve ). Nous aurions pu nous en passer, à condition de placer un bre ak dans la boucle. Toutefois, dans ce cas, ilaurait fallu prévoir, en fin de boucle, un test supplém entaire perm ettantde savoir si la rech erch e avaitété fructueuse ou non. D ISCUSSIO N Ilfautprendre garde, dans le déroulem entde l'algorith m e, à ne pas se contenter de prendre com m e nouvelle borne de la partie de tableau à explorer la valeur de m ilie u, en écrivant: debut=m ilieu ou : fin = m ilieu En effet, dans ce cas, on ne peut plus prouver que la boucle s'ach è ve en un nom bre fini de tours. Certaines situations conduisentd'ailleurs à une boucle infinie.
  • 175.
    V: GESTIO NDYNAMIQUE Les données d'un program m e se répartissent en trois catégories : statiques, autom atiques et dynam iques. Les données statiques sont définies dè s la com pilation ;la gestion des données autom atiques reste transparente au program m eur et seules les données dynam iques sontvéritablem entcréées (dans le tas)sur son initiative. D'une m aniè re générale, l'utilisation de données dynam iques fournitdes solutions à des problè m es tels que : - gestion de données dontl'am pleur n'estpas connue lors de la réalisation du program m e, - m ise en oeuvre de structures dites dynam iques, telles que les listes ch aînées ou les arbres binaires. Ce ch apitre vous en propose quelques exem ples. V-1 Crible dynam iq ue ________________________________________________________________________________________ Enoncé Réaliser un program m e qui déterm ine les prem iers nom bres prem iers par la m éth ode du crible d'Eratosth è ne, exposée dans l'exercice I-2. Cette fois, par contre, le nom bre d'entiers à considérer ne sera pas fixé par le program m e, m ais fourni en donnée. Le program m e allouera dynam iquem ent l'em placem ent m ém oire nécessaire au déroulem ent de l'algorith m e. En cas de m ém oire insuffisante, ildem andera à l'utilisateur de form uler une dem ande m oins im portante. On s'astreindra ici à utiliser la fonction m alloc. Exe m ple combien d'entiers voulez-vous examiner : 200
  • 176.
    176 Exe rcicesen langage C entre 1 et 200 les nombres premiers sont : 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 ________________________________________________________________________________________ ANALYSE L'algorith m e lui-m ê m e a déjà été exposé dans l'exercice I-2. La nouveauté réside ici dans l'allocation dynam ique de l'espace im parti au tableau d'entiers. Pour ce faire, la dém arch e la plus classique consiste à faire appelà la fonction m alloc, com m e nous le préconise l'énoncé. Program m e #include <stdio.h> #include <stdlib.h> #define VRAI 1 /* pour simuler des ...*/ #define FAUX 0 /* ... valeurs "logiques" */ main() { unsigned n, /* nombre d'entiers à considérer */ * raye, /* pointeur sur tableau servant de crible */ prem, /* dernier nombre premier considéré */ i ; int na ; /* compteur de nombres premiers affichés */ /* lecture du nombre d'entiers à considérer et allocation dynamique du tableau correspondant */ do { printf ("combien d'entiers voulez-vous examiner : ") ; scanf ("%u", &n) ; raye = (unsigned *) malloc ( (n+1)*sizeof(unsigned) ) ; if (raye == NULL) printf ("** mémoire insuffisante ") ; } while (raye == NULL) ;
  • 177.
    V.Gestion dynam ique177 /* initialisations du crible */ for (i=1 ; i<=n ; i++) /* mise à "zéro" du crible */ raye[i] = FAUX ; raye[1] = VRAI ; /* on raye le nombre 1 */ /* passage au crible */ prem = 1 ; while (prem*prem <= n) { while (raye[++prem] && prem<n ) {} /* recherche premier nb prem non rayé */ for (i=2*prem ; i<=n ; i+=prem) /* on raye tous ses multiples */ raye[i] = VRAI ; } /* affichage résultats */ printf ("entre 1 et %u les nombres premiers sont :n", n) ; na = 0 ; for (i=1 ; i<=n ; i++) if ( !raye[i] ) { printf ("%7u", i) ; if (++na%10 == 0) printf ("n") ; /* 10 nombres par ligne */ } } Com m e ntaire s *L'allocation de l'espace m ém oire nécessaire au tableau d'entiers estréalisée par l'instruction : raye = (unsigned *) malloc ( (n+1)*sizeof(unsigned) ) ; dans laquelle raye estun pointeur sur des entiers non signés. Or, le prototype de m alloc estprécisém ent: void * malloc (size_t) ; Le résultatfourni par m alloc estun "pointeur générique" qui peutê tre converti im plicitem enten un pointeur de n'im porte queltype. Aussi, l'opérateur de "cast" (unsigne d *) n'estpas indispensable ici. Notre instruction d'allocation m ém oire auraitpu s'écrire : raye = malloc ( (n+1) * sizeof(unsigned) ) ;
  • 178.
    178 Exe rcicesen langage C En ce qui concerne l'argum entde m alloc, celui-ci esta priori d'un type size _tdéfini (par type de f) dans stdlib.h . Le type exactcorrespondantdépend de l'im plém entation (m ais ilest toujours non signé - en général, ils'agit de unsigne d int). Notez que le résultatfourni par size ofestdu m ê m e type size _t. Rappelons que m alloc fourniten résultatun pointeur sur le débutde la zone concernée lorsque l'allocation a réussi etun pointeur nuldans le cas contraire (notez que le sym bole NULLestdéfini dans stdlib.h ). *En ce qui concerne l'algorith m e de passage au crible, vous rem arquez que nous avons em ployé exactem entles m ê m es instructions que dans le program m e de l'exercice I-2. Pourtant, dans ce dernier, le sym bole raye désignait un tableau d'entiers, tandis qu'ici ildésigne un pointeur sur des entiers. Cela estpossible parce qu'en langage C, un nom de tableau estun pointeur (constant). D ISCUSSIO N *Le ch oix du type unsigne d pour n estquelque peu arbitraire ;ilestguidé par le faitque m alloc adm etgénéralem entun argum entde ce type. En supposantque telestle cas, on constate qu'alors l'expression : (n+1) * sizeof (unsigned) conduità des valeurs erronées dè s que la valeur de n*size of(int)dépasse la capacité du type int(n'oubliez pas qu'iln'y a pas de détection de dépassem ent de capacité pour les opérations portant sur des entiers). Le résultat peut alors ê tre catastroph ique car le nom bre d'octets dem andés à m alloc se trouve ê tre inférieur à celui réellem entutilisé. Le problè m e se com plique encore un peu si l'on tientcom pte de ce que, dans certaines im plém entations, le type size _t peu correspondre à autre ch ose que unsigne d int. En toute rigueur, ilfaudrait donc s'assurer que le nom bre de valeurs dem andées par l'utilisateur est effectivem ent inférieur à une certaine lim ite à fixer en fonction de l'im plém entation concernée. V-2 Cré ation dynam iq ue de ch aîne s Lorsqu'un program m e doittraiter un grand nom bre de ch aînes de longueur variable etque ce nom bre n'estpas connu a priori, ilpeut s'avérer intéressant de faire allouer dynam iquem ent (par le program m e) l'espace m ém oire nécessaire au stock age des ch aînes. C'est ce que vous propose cet exercice qui peut ê tre considéré com m e préalable à un traitem ent ultérieur de ces ch aînes (par exem ple un tri com m e vous le proposera l'exercice V-3).
  • 179.
    V.Gestion dynam ique179 ________________________________________________________________________________________ Enoncé Ecrire un program m e qui lit un nom bre quelconque de ch aînes au clavier et qui les range en m ém oire dans des em placem ents alloués dynam iquem ent au fur et à m esure des besoins. Les adresses de ch acune des ch aînes seront conservées dans un tableau de pointeurs. Ce dernier sera réservé dans le program m e (en classe autom atique) etsa taille (fixe)im posera donc une valeur m axim ale au nom bre de ch aînes qu'ilsera ainsi possible de traiter. L'utilisateur signalera qu'ila fourni sa derniè re ch aîne en la faisantsuivre d'une ch aîne "vide". Le program m e affich era ensuite les ch aînes lues, à titre de sim ple contrôle. Rem arque : on utilisera la fonction m alloc eton supposera que les lignes lues au clavier ne peuventjam ais dépasser 127 caractè res. Exe m ple ----- chaîne numéro 1 (return pour finir) C ----- chaîne numéro 2 (return pour finir) Turbo C ----- chaîne numéro 3 (return pour finir) Basic ----- chaîne numéro 4 (return pour finir) Pascal ----- chaîne numéro 5 (return pour finir) Turbo Pascal ----- chaîne numéro 6 (return pour finir) fin création liste des chaînes créées ------- chaîne numéro 1 C ------- chaîne numéro 2 Turbo C ------- chaîne numéro 3
  • 180.
    180 Exe rcicesen langage C Basic ------- chaîne numéro 4 Pascal ------- chaîne numéro 5 Turbo Pascal ________________________________________________________________________________________ ANALYSE L'énoncé nous im pose donc de définir, au sein du program m e, un tableau de pointeurs destiné à contenir les adresses des ch aînes à créer. Ch aque ch aîne sera d'abord lue dans une zone interm édiaire (non dynam ique). On lui allouera ensuite, dynam iquem ent, à l'aide de la fonction m alloc, un em placem entdontla taille correspond exactem entà sa longueur ;l'adresse ainsi obtenue sera m ém orisée dans le tableau de pointeurs. Le traitem entsera interrom pu : - soitquand le tableau de pointeurs estplein, - soitquand l'utilisateur fournitune ch aîne vide. De plus, à ch aque allocation réalisée par m alloc, on s'assurera que l'espace m ém oire nécessaire a pu ê tre obtenu. Dans le cas contraire, on prévoira d'interrom pre le program m e. Program m e #include <stdio.h> #include <stdlib.h> /* pour la fonction exit */ #include <string.h> #define NCHMAX 1000 /* nombre maximal de chaînes */ #define LGLIGNE 127 /* longueur maximale d'une ligne d'écran */ main() { char ligne [LGLIGNE+1], /* chaîne servant à lire une ligne écran */ * adr [NCHMAX], /* tableau de pointeurs sur les chaînes */ * ptr ; /* pointeur courant sur une chaîne */ int nch, /* compteur du nombre de chaînes */ i ; /* mise à zéro du tableau de pointeurs */
  • 181.
    V.Gestion dynam ique181 for (i=0 ; i<NCHMAX ; i++) adr[i] = NULL ; /* boucle de création dynamique des chaînes */ nch=0 ; while (nch < NCHMAX) /* tant que nb max chaînes non atteint */ { printf ("----- chaîne numéro %d (return pour finir)n", nch+1) ; gets (ligne) ; if ( strlen(ligne) ) { if ( (ptr = malloc (strlen(ligne)+1)) != NULL) strcpy (adr[nch++]=ptr, ligne) ; else { printf ("nn*** erreur allocation dynamique") ; exit(-1) ; /* arrêt si erreur alloc dynam */ } } else break ; /* sortie boucle si réponse vide */ } printf ("nfin créationn") ; /* liste des chaînes ainsi créées */ printf ("nnliste des chaînes créesnn") ; i = 0 ; for (i=0 ; i<nch ; i++) printf ("------- chaîne numéro %dn%sn", i+1, adr[i]) ; } Com m e ntaire s *Ici, com pte tenu de ce que nous précisait l'énoncé, nous avons ch oisi de lire nos ch aînes dans un tableau de 128 caractè res, à l'aide de la fonction ge ts. *Nous avons rem is à "zéro" le tableau de pointeurs sur nos ch aînes. Ils'agitlà d'une opération superflue m ais qui peut s'avérer utile pendant la ph ase de m ise au point du program m e. Notez l'usage du sym bole NULL;prédéfini dans le fich ier stdlib.h , ilcorrespond à la constante pointeur nulle. *La création des ch aînes estréalisée par une boucle tant que (instruction w h ile ), dans laquelle nous avons prévu deux autres sorties :
  • 182.
    182 Exe rcicesen langage C - une sortie par bre ak , dans le cas où l'utilisateur a fourni une ch aîne vide, - un arrê texceptionneldu program m e par e xit, dans le cas où l'allocation dynam ique a éch oué. Cette fonction (dont le prototype figure dans stdlib.h ) requiert un argum ent;sa valeur est transm ise au systè m e et elle pourrait éventuellem entê tre récupérée par d'autres program m es. Notez que, en l'absence de l'instruction #include relative à stdlib.h , le com pilateur accepte un appelde e xit sans argum ent (ilest incapable de détecter l'erreur - laquelle n'a d'ailleurs aucune incidence sur l'exécution du program m e lui-m ê m e). Naturellem ent, beaucoup d'autres form ulations seraientpossibles. D ISCUSSIO N *Le faitde réserver le tableau dans le program m e (en classe autom atique)im pose une lim ite au nom bre de ch aînes qu'il est ainsi possible de traiter ;cette lim ite est indépendante de la m ém oire réellem ent disponible. On peut am éliorer quelque peu la situation en faisantégalem entallouer dynam iquem entl'espace nécessaire à ce tableau de pointeurs. Il fauttoutefois en connaître la taille (ou du m oins une valeur m axim ale)lors de l'exécution du program m e. Cela peutfaire l'objetd'une donnée fournie par l'utilisateur com m e dans l'exercice suivant. V-3 Tri dynam iq ue de ch aîne s ________________________________________________________________________________________ Enoncé Ecrire un program m e perm ettantde trier par ordre alph abétique des ch aînes fournies en donnée. Com m e dans l'exercice précédent, on allouera dynam iquem ent des em placem ents m ém oire aux ch aînes, au fur et à m esure de leur lecture, et leurs adresses seront conservées dans un tableau de pointeurs. Par contre, ici, ce dernier verra, lui aussi, son em placem entalloué dynam iquem enten débutde program m e ;pour ce faire, on dem andera à l'utilisateur de fournir une valeur m axim ale du nom bre de ch aînes qu'ilsera am ené à fournir. On utilisera l'algorith m e de "tri par extraction sim ple" exposé dans l'exercice V-1 eton fera appelà la fonction m alloc. Exe m ple
  • 183.
    V.Gestion dynam ique183 nombre maximal de chaînes ? 100 ------- chaîne numéro 1 (return pour finir) C ------- chaîne numéro 2 (return pour finir) Turbo C ------- chaîne numéro 3 (return pour finir) Basic ------- chaîne numéro 4 (return pour finir) Pascal ------- chaîne numéro 5 (return pour finir) Turbo Pascal ------- chaîne numéro 6 (return pour finir) Fortran ------- chaîne numéro 7 (return pour finir) ADA ------- chaîne numéro 8 (return pour finir) fin création liste triée des chaînes crées ADA Basic C Fortran Pascal Turbo C Turbo Pascal ________________________________________________________________________________________ ANALYSE Ilnous suffiten faitd'adapter le program m e de l'exercice précédent, en lui adjoignant: - la réservation dynam ique du tableau de pointeurs, - le tri du tableau de ch aînes ainsi créé, par réorganisation des pointeurs. Nous utiliserons pour cela l'algorith m e de tri par extraction sim ple Celui-ci a été exposé dans l'énoncé de l'exercice V-1 etson adaptation au tri de ch aînes a été expliquée dans l'analyse de l'exercice V-2.
  • 184.
    184 Exe rcicesen langage C Program m e #include <stdio.h> #include <stdlib.h> #include <string.h> #define LGLIGNE 127 /* longueur maximale d'une ligne d'écran */ main() { char ligne [LGLIGNE+1], /* chaîne servant à lire une ligne écran */ * * adr, /* adresse tableau pointeurs sur les chaînes */ * ptr, /* pointeur courant sur une chaîne */ * tempo ; /* pointeur temporaire pour éch. 2 pointeurs */ unsigned nchmax, /* nombre maximal de chaînes */ nch, /* compteur du nombre de chaînes */ i, j, kmax ; /* création et mise à zéro du tableau de pointeurs */ printf ("nombre maximum de chaînes ? ") ; scanf ("%d", &nchmax) ; getchar() ; /* pour sauter la validation */ if ( (adr = malloc (nchmax*sizeof(char*)) ) == NULL) { printf ("nn*** erreur allocation dynamique") ; exit(-1) ; /* arrêt si erreur alloc dynam */ } for (i=0 ; i<nchmax ; i++) adr[i] = NULL ; /* boucle de création dynamique des chaînes */ nch = 0 ; while (nch < nchmax) /* tant que nb max de chaînes non atteint */ { printf ("------- chaîne numéro %d (return pour finir)n", nch+1) ; gets (ligne) ; if ( strlen(ligne) ) { if ( ( ptr = malloc (strlen(ligne)+1)) != NULL) strcpy (adr[nch++]=ptr, ligne) ; else { printf ("nn*** erreur allocation dynamique") ; exit(-1) ; /* arrêt si erreur alloc dynam */ } } else break ; /* sortie boucle si réponse vide */ }
  • 185.
    V.Gestion dynam ique185 printf ("nfin créationn") ; /* tri des chaînes par réarrangement des pointeurs */ for (i=0 ; i<nch-1 ; i++) { kmax = i ; for (j=i+1 ; j<nch ; j++) if ( stricmp (adr[kmax], adr[j]) > 0 ) kmax = j ; tempo = adr[kmax] ; adr[kmax] = adr[i] ; adr[i] = tempo ; } /* liste triées des chaînes ainsi créées */ printf ("nnliste triée des chaînes créesnn") ; for (i=0 ; i<nch ; i++) puts ( adr[i] ) ; } Com m e ntaire s *Dans le program m e de l'exercice V-2, le sym bole adr désignaitun table au de pointe urs. Ici, ce m ê m e sym bole désigne un pointe ur sur un table au de pointe urs. Or, m algré cette différence apparente, vous constatez que nous em ployons toujours la notation : adr[i] avec la m ê m e signification dans les deux cas. En fait, dans le précédentprogram m e, adr étaitune constante pointeur dontla valeur étaitcelle de l'adresse de débutdu tableau de pointeurs. Dans le présent program m e, adr est une variable pointeur dont la valeur est égalem ent celle de débutdu tableau de pointeurs. Ainsi, dans les deux cas : adr[i] estéquivalentà : *(adr + i) Notez cependantque l'équivalence entre les deux program m es n'estpas totale. En effet, dans le prem ier cas, adr n'est pas une lvalue (m ot anglais dont une traduction approch ée pourrait ê tre : valeur à gauch e);par exem ple, l'expression adr++ seraitincorrecte. Dans le second cas, par contre, adr estbien une lvalue .
  • 186.
    186 Exe rcicesen langage C *Nous n'avons pris aucune précaution particuliè re en ce qui concerne les lectures au clavier qui sont réalisées ici par ge ts et scanf. Indépendam m ent des anom alies h abituelles encourues en cas de données incorrectes (ch aîne trop longue pour ge ts, donnée non num érique pour scanf), un problè m e supplém entaire apparaît, lié au faitqu'aprè s une lecture par scanf, le pointeur reste positionné sur le dernier caractè re non encore utilisé, à savoir ici le n (du m oins si l'utilisateur a validé norm alem ent, sans fournir d'inform ations supplém entaires). Si la lecture suivante est, à son tour, effectuée par scanf, aucun problè m e particulier ne se pose, le caractè re n étantsim plem entignoré. Iln'en va plus de m ê m e lorsque la lecture suivante esteffectuée par ge ts ;dans ce cas, en effet, ce caractè re estinterprété com m e un caractè re de "fin" et ge ts fournit... une ch aîne vide. C'estpour éviter ce ph énom è ne que nous avons dû introduire une instruction ge tch ar pour absorber le n. D ISCUSSIO N Pour pouvoir allouer convenablem entl'em placem entdu tableau de pointeurs, notre program m e a besoin que l'utilisateur lui fournisse une valeur m axim ale du nom bre de ch aînes. Si nous souh aitions qu'ilen soitautrem ent, ilseraitnécessaire de pouvoir allouer provisoirem entun em placem entà ce tableau, quitte à l'étendre ensuite au fur età m esure des besoins à l'aide de la fonction re alloc. Une telle extension pourraitê tre réalisée, soit à ch aque nouvelle ch aîne entrée, soit par blocs de taille fixe (par exem ple toutes les 100 ch aînes). V-4 Cré ation d'une liste ch aîné e On appelle liste ch aînée ou liste liée une suite ordonnée d'élém ents dans laquelle ch aque élém ent, sauf le dernier, com porte un pointeur sur l'élém entsuivant. ________________________________________________________________________________________ Enoncé Ecrire un program m e qui crée une liste ch aînée d'élém ents com portantch acun : - un nom (ch aîne)d'au m axim um 10 caractè res, - un â ge. Les inform ations correspondantes serontlues au clavier etl'utilisateur frappera un nom "vide" aprè s les données relatives au dernier élém ent.
  • 187.
    V.Gestion dynam ique187 Le program m e affich era ensuite les inform ations contenues dans la liste ainsi créée, dans l'ordre inverse de celui dans lequelelles aurontété fournies. On prévoira deux fonctions : l'une pour la création, l'autre pour la liste. Elles posséderont com m e unique argum ent l'adresse de débutde la liste (pointeur sur le prem ier élém ent). Exe m ple om : Laurence age : 19 nom : Yvette age : 35 nom : Catherine age : 20 nom : Sebastien age : 21 nom : NOM AGE Sebastien 21 Catherine 20 Yvette 35 Laurence 19 ________________________________________________________________________________________ ANALYSE Ch aque élém entde notre liste sera représenté par une structure. Nous voyons que celle-ci doitcontenir un pointeur sur un élém ent de m ê m e type. Cela fait intervenir une certaine "récursivité" dans la déclaration correspondante, ce qui est accepté en C. En ce qui concerne l'algorith m e de création de la liste, deux possibilités s'offrentà nous : - Ajouter ch aque nouvelélém entà la fin de la liste. Le parcours ultérieur de la liste se fera alors dans le m ê m e ordre que celui dans lequelles données correspondantes ontété introduites. - Ajouter ch aque nouvelélém enten débutde liste. Le parcours ultérieur de la liste se fera alors dans l'ordre inverse de celui dans lequelles données correspondantes ontété introduites.
  • 188.
    188 Exe rcicesen langage C Com pte tenu de ce que l'énoncé nous dem ande d'affich er la liste à l'envers, aprè s sa création, ilparaîtplus apportun de ch oisir la seconde m éth ode. Com m e dem andé, la création de la liste sera réalisée par une fonction. Le program m e principalse contentera de réserver un pointeur (nom m é de but) destiné à désigner le prem ier élém ent de la liste. Sa valeur effective sera fournie par la fonction de création. L'algorith m e de création, quantà lui, consistera à répéter le traitem entd'insertion d'un nouvelélém enten débutde liste, à savoir : - créer dynam iquem entun em placem entpour un nouvelélém entety ranger les inform ations fournies au clavier, - affecter au pointeur contenu dans ce nouvelélém entl'ancienne valeur de de but, - affecter à de butl'adresse de ce nouvelélém ent. Nous conviendrons, de plus, que le dernier élém ent de la liste possè de un pointeur nul, ce qui nous facilitera l'initialisation de l'algorith m e ;en effet, celle-ci se ram è ne alors à l'affectation à de butd'une valeur nulle. Program m e #include <stdio.h> #include <stdlib.h> #include <string.h> #define LGNOM 20 /* longueur maximale d'un nom */ typedef struct element /* définition du type élément */ { char nom [LGNOM+1] ; /* nom */ int age ; /* age */ struct element * suivant ; /* pointeur element suivant */ } t_element ; main() { void creation (t_element * *) ; /* fonction de création de la liste */ void liste (t_element *) ; /* fonction de liste de la liste */ t_element * debut ; /* pointeur sur le début de la liste */ creation (&debut) ; liste (debut) ; }
  • 189.
    V.Gestion dynam ique189 /****************************************************/ /* fonction de création d'une liste chaînée */ /****************************************************/ void creation (t_element * * adeb) { char nomlu [LGNOM+1] ; /* pour lire un nom au clavier */ t_element * courant ; /* pour l'échange de valeurs de pointeurs */ * adeb = NULL ; /* liste vide au départ */ while (1) /* boucle de création apparemment infinie ... */ { /* ... mais, en fait, interrompue sur "nom vide" */ printf ("nom : ") ; gets (nomlu) ; if (strlen(nomlu)) { courant = (t_element *) malloc (sizeof(t_element)) ; strcpy (courant->nom, nomlu) ; printf ("age : ") ; scanf ("%d", &courant->age) ; getchar() ; /* pour sauter le n */ courant->suivant = * adeb ; * adeb = courant ; } else break ; /* sortie boucle si nom vide */ } } /******************************************************/ /* fonction de liste d'une liste chaînée */ /******************************************************/ void liste (t_element * debut) { printf ("nn NOM AGEnn") ; while (debut) { printf ("%15s %3dn", debut->nom, debut->age) ; debut = debut->suivant ; } }
  • 190.
    19 0 Exercices en langage C Com m e ntaire s *Nous avons ici ch oisi de déclarer notre structure à un niveau globaletde faire appelà type de f. Cette déclaration à un niveau globalévite de devoir décrire la m ê m e structure en différents endroits, ce qui serait, non seulem ent laborieux m ais, de surcroît, source d'erreurs. Par contre, le recours à type de fn'apporte qu'une sim plification des déclarations des élém ents de ce type (dans le cas contraire, ilsuffiraitde rem placer t_e le m e ntpar structe le m e nt). Notez bien, par contre, qu'iln'estpas possible de rem placer, au sein de la définition de notre structure, l'écriture : struct element * suivant par : t_element * suivant *La fonction de création reçoiten argum entl'adresse du pointeur de but, car elle doitpouvoir lui attribuer une valeur. La fonction de liste, quant à elle, se contente de la valeur de ce m ê m e pointeur. Cette différence se répercute naturellem entsur la m aniè re d'utiliser cetargum entdans ch acune des deux fonctions. Notez d'ailleurs que nous avons pu nous perm ettre, dans la fonction de liste, de m odifier la valeur ainsi reçue (le pointeur de buty décritsuccessivem entles différents élém ents de la liste). *Là encore, les lectures au clavier ontété réalisées par scanfetge ts, donc sans protections particuliè res. Com m e nous l'avons déjà signalé dans le précédentexercice, l'utilisation conjointe de ces deux fonctions pose un problè m e lié au fait que, aprè s une lecture par scanf, le pointeur reste positionné sur le dernier caractè re non encore utilisé, à savoir (généralem ent)n. C'estce qui justifie l'introduction d'une instruction ge tch ar pour absorber ce caractè re intem pestif.
  • 191.
    VI: RECURSIVITE La récursivitéestune notion délicate m ais qui a l'avantage de conduire souventà des program m es sim ples. Les trois prem iers exercices de ce ch apitre sontplutôtdes "exercices d'école" destinés à vous faire explorer différentes situations en vous forçantà écrire une fonction récursive, là où, en pratique, on ne seraitpas am ené à le faire. VI-1 le cture ré cursive (1) ________________________________________________________________________________________ Enoncé Ecrire une fonction récursive de lecture d'une valeur entiè re au clavier. La fonction devra s'appeler elle-m ê m e dans le cas où l'inform ation fournie estincorrecte (non num érique). On prévoira une fonction à un argum ent(l'adresse de la variable pour laquelle on veutlire une valeur)etsans valeur de retour. On pourra faire appelà fge ts etsscanfpour détecter convenablem entles réponses incorrectes. Re m arq ue Nous vous conseillons de com parer cetexercice au suivantdans lequelle m ê m e problè m e estrésolu par l'em ploi d'une fonction récursive sans argum entetavec valeur de retour. Exe m ple
  • 192.
    19 2 Exercices en langage C donnez un nombre entier : un ** réponse incorrecte - redonnez-la : 'à ** réponse incorrecte - redonnez-la : 40 -- merci pour 40 ________________________________________________________________________________________ ANALYSE Au sein de la fonction (que nous nom m erons le cture ), nous lirons la valeur attendue à l'aide de fge ts (..., stdin), associé à sscanf, com m e nous l'avons déjà faitdans certains des exercices précédents. Nous considérerons la réponse de l'utilisateur com m e correcte lorsque le code de retour de sscanf sera égalà 1. Si tel n'estpas le cas, nous ferons à nouveau appelà la m ê m e fonction le cture . Program m e #include <stdio.h> #define LG_LIG 20 /* longueur maxi information lue au clavier */ main() { void lecture (int *) ; /* prototype fonction (récursive) de lecture */ int n ; /* entier à lire */ printf ("donnez un nombre entier : ") ; lecture (&n) ; printf ("-- merci pour %d", n) ; } void lecture (int *p) { int compte ; /* compteur du nb de valeurs OK */ char ligne[LG_LIG+1] ; /* pour lire une ligne au clavier par fgets */ /* +1 pour tenir compte du 0 de fin */ fgets (ligne, LG_LIG, stdin) ; compte = sscanf (ligne, "%d", p) ; if (!compte) { printf ("** réponse incorrecte - redonnez la : ") ; lecture (p) ;
  • 193.
    VI. Récursivité 193 } } Com m e ntaire s *Notez bien qu'au sein de la fonction le cture , au niveau de l'appelde sscanf, nous voyons apparaître p et non & p, puisque ici p estdéjà un pointeur sur la variable donton veutlire la valeur. *Si nous avions utilisé sim plem entge ts (com m e dans l'exercice VI.5 de la prem iè re partie) au lieu de fge ts (..., stdin), nous aurions pu égalem entnous protéger de m auvaises réponses de l'utilisateur, m ais nous aurions dû définir une taille m axim ale pour la ch aîne lue au clavier ;nous aurions couru le risque de "débordem ent m ém oire", dans le cas où l'utilisateur auraitfourni une réponse trop longue. D ISCUSSIO N Ch aque nouvelappelde le cture entraîne l'allocation autom atique, sur la pile, d'em placem ents pour : - l'argum entp, - les objets locaux : com pte etligne . Or, en fait, ne sont nécessaires que les valeurs correspondant au dernier appelde le cture (celui où la lecture s'est convenablem ent déroulée) ;dans ces conditions, l'em pilem ent des différents em placem ents alloués au tableau ligne est superflu. Si l'on souh aite faire quelques économ ies d'espace m ém oire à ce niveau, on peut s'arranger pour que cet em placem entne soitréservé qu'une seule fois : - soit dans le program m e appelant (ici le program m e principal);dans ce cas, ilfaudra en transm ettre l'adresse en argum ent, ce qui entraîne l'em pilem entd'une variable supplém entaire. - soiten classe globale ;dans ce cas, on peutégalem enttraiter de la sorte com pte etp (c'est-à -dire , en fait, n), ce qui supprim e du m ê m e coup tous les argum ents etles objets locaux de le cture . Notez qu'ilrestera quand m ê m e, à ch aque appel, une allocation autom atique d'espace pour l'adresse de re tour. - soiten classe statique (static)au sein de la fonction. Là encore, nous pouvons traiter de la m ê m e m aniè re la variable com pte , la variable p, quantà elle, restantsoum ise aux em pilem ents.
  • 194.
    19 4 Exercices en langage C VI-2 Le cture ré cursive (2) ________________________________________________________________________________________ Enoncé Ecrire une fonction récursive de lecture d'une valeur entiè re au clavier. La fonction devra s'appeler elle-m ê m e dans le cas où l'inform ation fournie estincorrecte (non num érique). On prévoira cette fois une fonction dans laquelle la valeur de retour estla valeur lue (iln'y aura donc pas d'argum ents). Là encore, on pourra faire appelà fge ts (..., stdin)etsscanfpour détecter convenablem entles réponses incorrectes. Re m arq ue Cetexercice estsurtoutdestiné à ê tre com paré au précédentdans lequelle m ê m e problè m e estrésolu par l'em ploi d'une fonction avec argum entetsans valeur de retour. Exe m ple donnez un nombre entier : un ** réponse incorrecte - redonnez la : 'à ** réponse incorrecte - redonnez la : 40 -- merci pour 40 ________________________________________________________________________________________ ANALYSE Com m e précédem m ent, au sein de notre fonction (nom m ée le cture ), nous lirons la valeur attendue à l'aide de fge ts associé à sscanf. Nous considérerons la réponse de l'utilisateur com m e correcte lorsque le code de retour de sscanfsera égalà 1. Si cela n'estpas le cas, nous ferons de nouveau appelà la m ê m e fonction le cture .
  • 195.
    VI. Récursivité 195 Program m e #include <stdio.h> #define LG_LIG 20 /* longueur maxi information lue au clavier */ main() { int lecture (void) ; /* fonction (récursive) de lecture */ int n ; /* entier à lire */ printf ("donnez un nombre entier : ") ; n = lecture() ; printf ("-- merci pour %d", n) ; } int lecture (void) { int compte, /* compteur du nb de valeurs OK */ p ; /* entier à lire */ char ligne[LG_LIG+1] ; /* pour lire une ligne au clavier par fgets */ fgets (ligne, LG_LIG, stdin) ; compte = sscanf (ligne, "%d", &p) ; if (!compte) { printf ("** réponse incorrecte - redonnez-la : ") ; p = lecture() ; } return(p) ; } Com m e ntaire s *Cette fois, on notera que p désigne une variable locale de type int, dontl'em placem entest alloué autom atiquem ent à ch aque appelde la fonction le cture , de la m ê m e m aniè re que pour les autres objets locaux com pte etligne . Par ailleurs, si aucun em placem entn'estalloué ici pour un quelconque argum ent, ilfauten prévoir un pour la valeur de retour. On rem arque d'ailleurs qu'ici cette valeur se trouve "propagée" de proch e en proch e, lors du "dépilem ent" des appels. *Prenez garde à ne pas écrire :
  • 196.
    19 6 Exercices en langage C if (!compte) { printf ("** réponse incorrecte - redonnez-la : ") ; p = lecture() ; } else return (p) ; car la fonction ne renverrait une valeur que lorsque la lecture se serait déroulée convenablem ent. Notez d'ailleurs que dans ce cas, bon nom bre de com pilateurs vous préviendraitpar un m essage d'avertissem ent("w arning"). Par contre, ilseraittoutà faitcorrect(etéquivalent)d'écrire : if (!compte) { printf ("** réponse incorrecte - redonnez la : ") ; return (lecture()) ; } else return (p) ; D ISCUSSIO N Les rem arques faites dans le précédent exercice à propos des em pilem ents de ligne (et éventuellem ent com pte ) s'appliquentencore ici. VI-3 Le cture ré cursive (3) ________________________________________________________________________________________ Enoncé Ecrire une fonction récursive de lecture d'un entier au clavier. La fonction devra s'appeler elle-m ê m e dans le cas où l'inform ation fournie estincorrecte. Cette fois, la fonction possédera 3 argum ents : - le m essage qu'elle doit im prim er avant de lire une valeur (le m essage "donnez un nom bre entier :" ne sera donc plus affich é par le program m e principal), - l'adresse de la variable dans laquelle on doitlire une valeur, - le nom bre m axim ald'essais autorisés.
  • 197.
    VI. Récursivité 197 Elle fournira un code de retour égalà 0 si la lecture a fini par aboutir età -1 lorsque la lecture n'a pas pu aboutir dans le nom bre d'essais im partis. Com m e dans les deux précédents exercices, on fera appelà fge ts associée à sscanf. Exe m ple s donnez un nombre entier : huit ** réponse incorrecte - redonnez-la : 8 -- merci pour 8 ____________________ donnez un nombre entier : un ** réponse incorrecte - redonnez-la : deux ** réponse incorrecte - redonnez-la : trois ** réponse incorrecte - redonnez-la : quatre ** réponse incorrecte - redonnez-la : cinq -- nombre d'essais dépassé ________________________________________________________________________________________ ANALYSE Le m essage à im prim er sera transm is sous form e de l'adresse d'une ch aîne. La fonction affich era ce m essage dè s son appel. Son contenu devra donc ê tre : donne z un nom bre e ntie r : dans l'appelinitialde la fonction (réalisé dans le program m e principal), et: **réponse incorre cte - re donne z -a : dans l'appelde la fonction par elle-m ê m e en cas de réponse incorrecte. En ce qui concerne le nom bre m axim ald'appels, on le transm ettra par valeur et on s'arrangera pour faire décroître sa valeur de 1 à ch aque appel. La récursivité des appels cessera lorsque l'une des deux conditions suivantes sera satisfaite : - valeur lue correcte - on fournira alors 0 com m e valeur de retour,
  • 198.
    19 8 Exercices en langage C - nom bre m axim ald'appels dépassé - on fournira alors -1 com m e valeur de retour. Program m e #include <stdio.h> #define LG_LIG 20 /* longueur maxi information lue au clavier */ main() { int lecture (char *, int *, int) ; /* proto fonction (récursive) de lecture */ int n ; /* entier à lire */ const nessais = 5 ; /* nombre d'essais autorisés */ if ( lecture ("donnez un nombre entier : ", &n, nessais) != -1) printf ("-- merci pour %d", n) ; else printf ("-- nombre d'essais dépassés") ; } int lecture (char * mes, int * p, int nmax) /* mes : adresse message à afficher avant lecture */ /* p : adresse de la valeur à lire */ /* nmax : nombre d'essais autorisés */ { int compte ; /* compteur du nb de valeurs OK */ char ligne [LG_LIG] ; /* pour lire une ligne au clavier par fgets */ printf ("%s", mes) ; fgets (ligne, LG_LIG, stdin) ; compte = sscanf (ligne, "%d", p) ; if (!compte) if (--nmax) return (lecture ("** réponse incorrecte - redonnez la : ", p, nmax) ) ; else return (-1) ; else return (0) ; } Com m e ntaire s *Nous avons ch oisi ici de faire affich er le m essage : nom bre d'essais dépassé
  • 199.
    VI. Récursivité 199 dans le program m e principal. Ils'agitlà d'un ch oix arbitraire puisque nous aurions toutaussi bien pu le faire affich er par la fonction elle-m ê m e. VI-4 Puissance e ntiè re ________________________________________________________________________________________ Enoncé Ecrire une fonction récursive perm ettantde calculer la valeur de x k pour x réelquelconque etk entier relatifquelconque. On exploitera les propriétés suivantes : x 0 = 1, x k = x pour k = 1, x -k = 1 /x k pour k positif, x k = (x k -1 )x pour k positifim pair, x k = (x k /2 )x pour k positifpair. On testera cette fonction à l'aide d'un program m e principalperm ettantà l'utilisateur de fournir en données les valeurs de x etde k . Exe m ple s donnez une valeur réelle : 4 donnez une puissance entière : -2 4.000000e+000 à la puissance -2 = 6.250000e-002 _______________________ donnez une valeur réelle : 5.2
  • 200.
    200 Exe rcicesen langage C donnez une puissance entière : 3 5.200000e+000 à la puissance 3 = 1.406080e+002 ________________________________________________________________________________________ ANALYSE L'énoncé fournitles "définitions récursives" à em ployer. Program m e #include <stdio.h> main() { double puissance(double, int) ; /* proto fonction d'élévation à la puissance */ double x ; /* valeur dont on cherche la puissance neme */ int n ; /* puissance à laquelle on veut élever x */ printf ("donnez une valeur réelle : ") ; scanf ("%le", &x) ; printf ("donnez une puissance entière : ") ; scanf ("%d", &n) ; printf ("%le à la puissance %d = %le", x, n, puissance (x, n) ) ; } double puissance (double x, int n) { double z ; if (n < 0) return (puissance (1.0/x, -n) ) ; /* puissance négative */ else if (n == 0) return (1) ; /* x puissance 0 égale 1 */ else if (n == 1) return (x) ; /* x puissance 1 égale x */ else if (n%2 == 0) { z = puissance (x, n/2) ; /* puissance paire */ return (z*z) ; } else return (x * puissance (x, n-1) ) ; /* puissance impaire */
  • 201.
    VI. Récursivité 201 } Comm e ntaire s Ilestpréférable d'écrire : z = puissance (x, n/2) ; return (z*z) ; plutôtque : return (puissance (x,n/2) * puissance (x,n/2) ) ; qui produiraitdeux fois plus d'appels de la fonction puissance . VI-5 Fonction d'Ack e rm ann ________________________________________________________________________________________ Enoncé Ecrire une fonction récursive calculant la valeur de la fonction d'Ack erm ann, définie pour m et n, entiers positifs ou nuls, par : A(m ,n)=A(m -1, A(m ,n-1)) pour m > 0 etn> 0, A(0,n)=n+ 1 pour n> 0, A(m ,0)=A(m -1,1) pour m > 0. Cette fonction possédera en argum entles valeurs de m etde n etfournira en résultatla valeur de A correspondante. On visualisera l'em pilem ent des appels et leur dépilem ent en affich ant un m essage accom pagné de la valeur des deux argum ents lors de ch aque entrée dans la fonction ainsi que juste avant sa sortie (dans ce dernier cas, on affich era égalem entla valeur que la fonction s'apprê te à retourner). On testera cette fonction à l'aide d'un program m e principalauquelon fournira en données les valeurs de m etde n.
  • 202.
    202 Exe rcicesen langage C Exe m ple valeurs de m et n ? : 1 1 ** entrée Acker (1, 1) ** entrée Acker (1, 0) ** entrée Acker (0, 1) -- sortie Acker (0, 1) = 2 -- sortie Acker (1, 0) = 2 ** entrée Acker (0, 2) -- sortie Acker (0, 2) = 3 -- sortie Acker (1, 1) = 3 Acker (1, 1) = 3 ________________________________________________________________________________________ Program m e #include <stdio.h> main() { int m, n, a ; int acker (int, int) ; /* prototype fonction de calcul fonction d'Ackermann */ printf ("valeurs de m et n ? : ") ; scanf ("%d %d", &m, &n) ; a = acker (m, n) ; printf ("nnAcker (%d, %d) = %d", m, n, a) ; } /***********************************************************/ /* fonction récursive de calcul de la fonction d'Ackermann */ /***********************************************************/ int acker (int m, int n) { int a ; /* valeur de la fonction */ printf ("** entrée Acker (%d, %d)n", m, n) ;
  • 203.
    VI. Récursivité 203 if(m<0 || n<0) a = -1 ; /* cas arguments incorrects */ else if (m == 0) a = n+1 ; else if (n == 0) a = acker (m-1, 1) ; else a = acker (m-1, acker(m, n-1) ) ; printf ("-- sortie Acker (%d, %d) = %dn", m, n, a) ; return (a) ; } VI-6 Tours de H anoi ________________________________________________________________________________________ Enoncé Réaliser une fonction récursive proposantune solution au problè m e ditdes tours de H anoi, lequels'énonce ainsi : On dispose de trois piquets, num érotés 1, 2 et 3 et de n disques de tailles différentes. Au départ, ces disques sont em pilés par taille décroissante sur le piquetnum éro 1. Le butdu jeu estde déplacer ces n disques du piquetnum éro 1 sur le piquetnum éro 3, en respectantles contraintes suivantes : - on ne déplace qu'un seuldisque à la fois (d'un piquetà un autre), - un disque ne doitjam ais ê tre placé au-dessus d'un disque plus petitque lui. On testera cette fonction avec un program m e principalperm ettant de ch oisir, en donnée, le nom bre totalde disques à déplacer (n). Si vous n'ê tes pas fam iliarisé avec ce type de problè m e, nous vous conseillons de tenter tout d'abord de le résoudre m anuellem entavantde ch erch er à program m er la fonction dem andée. Exe m ple combien de disques ? 4 déplacer un disque de 1 en 2
  • 204.
    204 Exe rcicesen langage C déplacer un disque de 1 en 3 déplacer un disque de 2 en 3 déplacer un disque de 1 en 2 déplacer un disque de 3 en 1 déplacer un disque de 3 en 2 déplacer un disque de 1 en 2 déplacer un disque de 1 en 3 déplacer un disque de 2 en 3 déplacer un disque de 2 en 1 déplacer un disque de 3 en 1 déplacer un disque de 2 en 3 déplacer un disque de 1 en 2 déplacer un disque de 1 en 3 déplacer un disque de 2 en 3 ________________________________________________________________________________________ ANALYSE Pour n=1, la solution estévidente ;ilsuffitde déplacer l'unique disque du piquetnum éro 1 au piquetnum éro 3. Dè s que n est supérieur à 1, on rem arque qu'ilest nécessaire d'utiliser le piquet num éro 2 pour des stock ages interm édiaires. On peut considérer que le problè m e consiste à déplace r n disques du piquet num é ro 1 ve rs le piquet num é ro 3, e n utilisant le piquet num é ro 2 com m e piquet inte rm é diaire . On peut m ontrer que cette opération se décom pose en trois opérations plus sim ples : - déplacer les n-1 disques supérieurs du piquet num éro 1 vers le piquet num éro 2 ;pendant cette ph ase, on peut utiliser le piquetnum éro 3 com m e piquetinterm édiaire, - déplacer les n-1 disques du piquetnum éro 2 vers le piquetnum éro 3 ;là encore, on peututiliser le piquetnum éro 1 com m e piquet interm édiaire (le disque initialem ent présent sur ce piquet étant plus grand que tous les disques à déplacer). Cela nous conduità la réalisation d'une fonction récursive possédantcom m e argum ents : - le nom bre de disques à déplacer, - le num éro du piquet"de départ", - le num éro du piquet"d'arrivée", - le num éro du piquet"interm édiaire".
  • 205.
    VI. Récursivité 205 Programm e #include <stdio.h> main() { void hanoi (int, int, int, int) ; int nd ; /* nombre total de disques */ printf ("combien de disques ? ") ; scanf ("%d", &nd) ; hanoi (nd, 1, 3, 2) ; } /***********************************************/ /* fonction résolvant le pb des tours de hanoi */ /***********************************************/ void hanoi (int n, int depart, int but, int inter) /* n : nombre de disques à déplacer */ /* depart : tour d'où l'on part */ /* but : tour où l'on arrive */ /* inter : tour intermédiaire */ { if (n>0) { hanoi (n-1, depart, inter, but) ; printf ("déplacer un disque de %d en %dn", depart, but) ; hanoi (n-1, inter, but, depart) ; } }
  • 207.
    VII: TRAITEM ENTD E FICH IERS Les exercices de ce ch apitre vous fournissentdes exem ples classiques de traitem entde fich iers correspondantà différents aspects : - traitem entséquentiel, - accè s direct, - fich iers de type texte. VII-1 Cré ation s é q ue ntie lle de fich ie r ________________________________________________________________________________________ Enoncé Ecrire un program m e de création séquentielle d'un fich ier com portant, pour un certain nom bre de personnes, les inform ations suivantes, fournies au clavier : - nom (au m axim um 20 caractè res), - â ge, - nom bre d'enfants, - â ge de ch acun des différents enfants ;on ne dem andera (etdonc on n'enregistrera)que l'â ge des 15 prem iers enfants (m ais le nom bre figurantdans le ch am p précédentpourra ê tre supérieur à 15). L'utilisateur fournira un nom "vide" pour signaler qu'iln'a plus de personnes à enregistrer.
  • 208.
    208 Exe rcicesen langage C On ne prévoira aucun contrôle particulier au niveau de la saisie des données Exe m ple donnez le nom du fichier à créer : person ----- pour terminer la saisie, donnez un nom 'vide' --- nom : dubois age : 32 nombre enfants : 1 age enfant no 1 : 7 nom : dunoyer age : 29 nombre enfants : 0 nom : dutronc age : 45 nombre enfants : 3 age enfant no 1 : 21 age enfant no 2 : 18 age enfant no 3 : 17 nom : -------- FIN CREATION FICHIER ---------- ________________________________________________________________________________________ ANALYSE La structure de ch aque enregistrem ent du fich ier découle de l'énoncé. Cependant, en ce qui concerne la m aniè re de représenter le nom des personnes, nous devons décider de la présence ou de l'absence du caractè re de fin de ch aîne (0). Ici, nous avons ch oisi, par facilité, d'introduire ce caractè re, ce qui im plique que la zone correspondante soitde longueur 21. Pour créer notre fich ier, nous utiliserons les fonctions de niveau 2, c'est-à -dire ici fope n etfw rite . Rappelons que celles- ci travaillentavec un pointeur sur une structure de type FILE (prédéfini dans stdio.h ). La valeur de ce pointeur nous est fournie par fope n ;cette fonction restitue un pointeur nulen cas d'erreur d'ouverture. La création du fich ier consiste sim plem entà répéter les actions :
  • 209.
    VII. Traite me ntde fich ie rs 209 - lecture d'inform ations au clavier, - écriture de ces inform ations dans le fich ier. Cette répétition doitê tre interrom pue à la rencontre d'un nom vide. Program m e #include <stdio.h> #include <string.h> #include <stdlib.h> #define LGNOM 20 /* longueur maxi d'un nom */ #define NBENFMAX 15 /* nombre maxi d'enfants */ #define LNOMFICH 20 /* longueur maxi nom de fichier */ main() { char nomfich [LNOMFICH+1] ; /* nom du fichier à créer */ FILE * sortie ; /* descripteur fichier (niveau 2) */ struct { char nom [LGNOM+1] ; int age ; /* description d'un enregistrement */ int nbenf ; /* du fichier */ int agenf [NBENFMAX] ; } bloc ; int i ; /* ouverture fichier à créer */ /* attention : mode d'ouverture w au lieu de wb dans certains cas */ printf ("donnez le nom du fichier à créer : ") ; gets (nomfich) ; if ( (sortie = fopen (nomfich, "w")) == NULL ) { printf ("***** erreur ouverture - abandon programme") ; exit(-1) ; } /* création du fichier à partir d'informations */ /* fournies au clavier */ printf ("----- pour terminer la saisie, donnez un nom 'vide' ---n") ; do { printf ("nom : ") ; /* saisie nom */ gets (bloc.nom) ; if ( strlen(bloc.nom) == 0) break ; /* sortie boucle si nom vide */ printf ("age : ") ; scanf ("%d", &bloc.age) ; /* saisie age */
  • 210.
    210 Exe rcicesen langage C printf ("nombre enfants : ") ; scanf ("%d", &bloc.nbenf) ; /* saisie nb enfants */ for (i=0 ; i < bloc.nbenf && i < NBENFMAX ; i++) { printf ("age enfant no %d : ", i+1) ; /* saisie age des */ scanf ("%d", &bloc.agenf[i]) ; /* différents enfants */ } getchar() ; /* pour éliminer n */ printf ("n") ; fwrite (&bloc, sizeof(bloc), 1, sortie) ; /* écriture fichier */ } while (1) ; /* fin création */ fclose(sortie) ; printf ("n -------- FIN CREATION FICHIER ----------") ; } Com m e ntaire s *Notez le "m ode d'ouverture" w b : w : ouverture en écriture ;si le fich ier n'existe pas, ilestcréé. S'ilexiste, son ancien contenu estperdu. b : m ode dit"binaire" ou "non translaté". En fait, l'indication b ne se justifie que dans les im plém entations qui distinguentles fich iers de texte des autres. Une telle distinction estm otivée par le faitque le caractè re de fin de ligne (n) possè de, sur certains systè m es, une représentation particuliè re obtenue par la succession de deux caractè res. La présence de b évite le risque que le fich ier concerné soit considéré com m e un fich ier de type texte, ce qui am è nerait une interprétation non souh aitée des couples de caractè res représentantune fin de ligne. *Ici, nous avons faitappelà la fonction e xit(son prototype figure dans stdlib.h ) pour interrom pre le program m e en cas d'erreur d'ouverture du fich ier. Ils'agitlà d'un ch oix arbitraire. Nous aurions pu dem ander à l'utilisateur de proposer un autre nom de fich ier. *En ce qui concerne la boucle de création du fich ier, nous avons ch oisi de la program m er sous form e d'une boucle infinie : do ....... .......
  • 211.
    VII. Traite me ntde fich ie rs 211 while (1) ; que nous interrom pons au m om ent opportun par bre ak . Nous aurions pu égalem ent ch oisir d'introduire les prem iè res instructions de la boucle dans l'expression conditionnantune instruction w h ile , de cette m aniè re : while (printf("nom : "), gets(bloc.nom), strlen(bloc.mot) ) *Com m e prévu par l'énoncé, aucun contrôle particulier n'est effectué sur les données qui sont donc lues par scanf et ge ts. Là encore se pose le problè m e d'ignorer le n qui subsiste aprè s une lecture par scanf, ce qui im pose d'introduire artificiellem entune instruction ge tch ar (pour plus de détails sur ce problè m e, voyez les com m entaires de l'exercice V-3). *Rappelons que la fonction d'écriture dans le fich ier (fw rite )possè de 4 argum ents : - L'adresse de débutd'un ensem ble de blocs à écrire (notez bien la notation & bloc et non sim plem ent bloc, dans la m esure où le nom d'une structure désigne sa valeur etnon son adresse, com m e cela estle cas pour un tableau). - La taille d'un bloc. Notez qu'ici nous avons utilisé la fonction size of, ce qui assure la portabilité du program m e. - Le nom bre de blocs de cette taille à écrire (ici, 1). - L'adresse de la structure décrivantle fich ier (elle a été fournie par fope n). D ISCUSSIO N *Ce program m e n'exam ine pas le code de retour de fw rite , lequelprécise le nom bre de blocs réellem entécrits dans le fich ier (ce nom bre étantinférieur au nom bre souh aité en cas d'erreur d'écriture). Ilfauttoutefois noter, à ce propos, que, généralem ent, un certain nom bre d'erreurs sont "récupérées" par le systè m e qui affich e alors lui-m ê m e son propre m essage. *Com m e le prévoyait l'énoncé, ce program m e n'est pas protégé d'éventuelles erreurs dans les réponses fournies par l'utilisateur. A titre indicatif, voici quelques situations que l'on peutrencontrer : - Si l'utilisateur fournitun nom de fich ier de plus de 20 caractè res, ily aura écrasem entd'inform ations en m ém oire. Ici, ilserait toutefois assez facile de rem édier à ce problè m e en attribuant au sym bole LNO M FICH une valeur supérieure au nom bre de caractè res que l'on peut frapper au clavier dans l'im plém entation concernée. On pourrait égalem entlire un nom bre de caractè res lim ités en utilisant, au lieu de ge ts (nom fich ), l'instruction : fgets (nomfich, LNOMFICH, stdin) ; Notez toutefois que, dans ce cas, les caractè res supplém entaires frappés éventuellem entpar l'utilisateur sur la m ê m e "ligne" seraientpris en com pte par une proch aine instruction de lecture sur l'entrée standard.
  • 212.
    212 Exe rcicesen langage C Dans certaines im plém entations (notam m ent Turbo/Borland C et C/Quick C M icrosoft), ilest possible de régler com plè tem ent le problè m e en utilisant l'instruction cge ts qui a le m érite de lim iter, non seulem ent le nom bre de caractè res pris en com pte, m ais égalem entceux effectivem entfrappés au clavier. - Si l'utilisateur fournit plus de caractè res que n'en attend scanf, ceux-ci seront utilisés (avec plus ou m oins de bonh eur) par une lecture suivante. Là encore, le problè m e ne peut ê tre convenablem ent réglé que d'une façon dépendant de l'im plém entation, par exem ple avec la fonction cge ts (associée, cette fois, à sscanf) citée précédem m ent. - Si l'utilisateur fournit des caractè res non num ériques là où scanf attend des ch iffres, le résultat de la lecture sera arbitraire ;le program m e ne s'en apercevra pas puisqu'ilne teste pas le code de retour de scanf(qui fournitle nom bre de valeurs effectivem entlues). De plus, là encore, les caractè res non traités serontrepris par une lecture ultérieure. Le prem ier point peut, là encore, ê tre résolu par l'em ploi de sscanf, associé à fge ts (..., stdin). Là encore, dans certaines im plém entations, cge ts (associée à sscanf)perm etde régler totalem entle problè m e. VII-2 Liste s é q ue ntie lle d'un fich ie r ________________________________________________________________________________________ Enoncé Réaliser un program m e perm ettant d'affich er successivem ent ch acun des enregistrem ents d'un fich ier analogue à ceux créés par le program m e précédent. Le program m e présentera un seulenregistrem entà la fois, accom pagné d'un num éro précisant son rang dans le fich ier (on attribuera le num éro 1 au prem ier enregistrem ent);ilattendra que l'utilisateur frappe la touch e re turn avantde passer à l'enregistrem entsuivant. L'affich age des inform ations sera réalisé par une fonction à laquelle on transm ettra en argum ent l'enregistrem ent à affich er etson num éro. Le m odè le m ê m e de la structure correspondante sera, quantà lui, défini à un niveau global. Le program m e devra s'assurer de l'existence du fich ier à lister. Exe m ple donnez le nom du fichier à lister : person enregistrement numéro : 1 NOM : dubois AGE : 32 NOMBRE D'ENFANTS : 1
  • 213.
    VII. Traite me ntde fich ie rs 213 AGE ENFANT 1 : 7 enregistrement numéro : 2 NOM : dunoyer AGE : 29 NOMBRE D'ENFANTS : 0 enregistrement numéro : 3 NOM : dutronc AGE : 45 NOMBRE D'ENFANTS : 3 AGE ENFANT 1 : 21 AGE ENFANT 2 : 18 AGE ENFANT 3 : 17 -------- FIN LISTE FICHIER ---------- ________________________________________________________________________________________ Program m e #include <stdio.h> #include <string.h> #define LGNOM 20 /* longueur maxi d'un nom */ #define NBENFMAX 15 /* nombre maxi d'enfants */ #define LNOMFICH 20 /* longueur maxi nom de fichier */ struct enreg { char nom [LGNOM+1] ; int age ; int nbenf ; int agenf [NBENFMAX] ; } ;
  • 214.
    214 Exe rcicesen langage C main() { void affiche (struct enreg *, int) ; /* fonction d'affichage */ char nomfich [LNOMFICH+1] ; /* nom du fichier à lister */ FILE * entree ; /* descripteur fichier (niveau 2) */ struct enreg bloc ; /* enregistrement fichier */ int num ; /* numéro d'enregistrement */ /* ouverture fichier à lister */ /* attention : mode d'ouverture : r au lieu de rb dans certains cas */ do { printf ("donnez le nom du fichier à lister : ") ; gets (nomfich) ; if ( (entree = fopen (nomfich, "rb")) == 0 ) printf ("fichier non trouvén") ; } while (!entree) ; /* liste du fichier */ num = 1 ; while (fread(&bloc, sizeof(bloc), 1, entree), ! feof(entree) ) { affiche (&bloc, num++) ; getchar() ; /* attente frappe "return" */ } /* fin liste */ fclose(entree) ; printf ("nn -------- FIN LISTE FICHIER ----------") ; } /*************************************************/ /* fonction d'affichage d'un enregistrement */ /*************************************************/ void affiche (struct enreg * bloc, int num) { int i ; printf ("nnenregistrement numéro : %dnn", num) ; printf ("NOM : %sn", bloc->nom) ; printf ("AGE : %dn", bloc->age) ; printf ("NOMBRE D'ENFANTS : %dn", bloc->nbenf) ; for (i=0 ; i < bloc->nbenf && i < NBENFMAX ; i++)
  • 215.
    VII. Traite me ntde fich ie rs 215 printf ("AGE ENFANT %2d : %2dn", i+1, bloc->agenf[i]) ; } Com m e ntaire s *Notez le m ode d'ouverture rb : r : ouverture en lecture. Si le fich ier n'existe pas, fope n fournitun pointeur nul. b : ouverture en m ode "binaire" ou "non translaté" (pour plus d'inform ations sur la différence entre les m odes translaté etnon translaté, voyez les com m entaires de l'exercice VII-1). *Rappelons que la fonction de lecture fre ad possè de 4 argum ents, com parables à ceux de fw rite : - l'adresse de débutd'un ensem ble de blocs à lire, - la taille d'un bloc (en octets), - le nom bre de blocs de cette taille à lire, - l'adresse de la structure décrivantle fich ier (elle a été fournie par fope n). *La fonction fe ofprend la valeur vrai (1) lorsque la fin de fich ier a été effectivem ent rencontrée. Autrem ent dit, ilne suffitpas, pour détecter la fin d'un fich ier, d'avoir sim plem ent lu son dernier octet;ilest, de plus, nécessaire d'avoir tenté de lire au-delà . C'estce qui justifie que cette condition soitexam inée aprè s fre ad etnon avant. *Voyez la façon dontnous avons program m é la boucle de lecture des différents enregistrem ents du fich ier. Cela nous évite une sortie en cours de boucle par bre ak , com m e dans : do { fread (&bloc, sizeof(bloc), 1, entree) ; if (feof(entree)) break ; affiche (&bloc, num++) ; getchar() ; } while (1) ; ou un testsupplém entaire dans la boucle com m e dans :
  • 216.
    216 Exe rcicesen langage C do { fread (&bloc, sizeof(bloc), 1, entree) ; if (!feof(entree)) { affiche (&bloc, num++) ; getchar ; } } while (!feof(entree)) ; D ISCUSSIO N *Ce program m e n'exam ine pas le code de retour de fre ad (celui-ci précise le nom bre de blocs réellem entlus). *Notre program m e n'estpas protégé contre la fourniture par l'utilisateur d'un nom de fich ier de plus de 20 caractè res. Voyez la discussion de l'exercice précédent. *Le passage à l'enregistrem entsuivantestdéclench é par la frappe de re turn. M ais si l'utilisateur frappe un ou plusieurs caractè res (validés par return), ilverra défiler plusieurs enregistrem ents de suite. La solution à ce problè m e dépend, ici encore, de l'im plém entation. Par exem ple, dans un environnem ent DOS, avec Turbo/Borland C/C+ + ou Quick C/C M icrosoft, ilsuffira de "vider le tam pon du systè m e" par : while (kbhit()) getch ; avantch aque attente. VII-3 Corre ction de fich ie r ________________________________________________________________________________________ Enoncé Réaliser un program m e perm ettantd'effectuer des corrections sur un fich ier analogue à ceux créés par le program m e de l'exercice VII-1.
  • 217.
    VII. Traite me ntde fich ie rs 217 L'utilisateur désignera un enregistrem ent par son num éro d'ordre dans le fich ier. Le program m e s'assurera de son existence et l'affich era d'abord telquelavant de dem ander les m odifications à lui apporter. Ces derniè res seront effectuées ch am p par ch am p. Pour ch aque ch am p, le program m e en affich era à nouveau la valeur, puis ildem andera à l'utilisateur d'entrer une éventuelle valeur de rem placem ent. Si aucune m odification n'est souh aitée, ilsuffira à ce dernier de répondre directem entpar la frappe de re turn. On prévoira deux fonctions : - une pour l'affich age d'un enregistrem ent(on pourra reprendre la fonction affich e de l'exercice précédent), - une pour la m odification d'un enregistrem ent. Exe m ple donnez le nom du fichier à modifier : person numéro enregistrement à modifier (0 pour fin) : 14 numéro enregistrement à modifier (0 pour fin) : 2 enregistrement numéro : 2 NOM : dunoyer AGE : 29 NOMBRE D'ENFANTS : 0 entrez vos nouvelles infos (return si pas de modifs) NOM : Dunoyer AGE : NOMBRE D'ENFANTS : 1 AGE ENFANT 1 : 15 numéro enregistrement à modifier (0 pour fin) : 0 -------- FIN MODIFICATIONS FICHIER ---------- ________________________________________________________________________________________
  • 218.
    218 Exe rcicesen langage C ANALYSE A partir du m om entoù l'on souh aite retrouver un enregistrem entpar son rang dans le fich ier, ilparaîtlogique de réaliser un "accè s direct". Rappelons qu'en langage C celui-ci s'obtienten agissantsur la valeur d'un pointeur dans le fich ier à l'aide de la fonction fseek. La lecture et l'écriture, quant à elles, restent toujours réalisées par les fonctions fre ad et fw rite . L'énoncé ne nous im pose pas de contrôle sur l'inform ation lue au clavier. Néanm oins, nous devons ê tre en m esure d'accepter etde reconnaître com m e telle une "réponse vide". Dans ces conditions, nous ne pouvons pas em ployer scanf qui risqueraitde conduire à un bouclage sur le caractè re n. Une solution à un telproblè m e consiste à lire tout d'abord la réponse de l'utilisateur sous form e d'une ch aîne, ce qui perm etde déceler convenablem entles réponses vides. Si l'on souh aite une solution dépendante de l'im plém entation, cela peutse faire soitavec ge ts, soit(si l'on souh aite lim iter le nom bre de caractè res pris en com pte)avec fge ts (..., stdin).Ici, nous utiliserons la prem iè re possibilité, en faisant appel à une zone de 128 caractè res (dans bon nom bre d'im plém entations, on ne peutpas frapper au clavier de "lignes" plus longues!). Lorsqu'une inform ation num érique estattendue, ilnous suffitalors de "décoder" le contenu de cette ch aîne. Cela peutse faire, soitavec la fonction sscanfassortie (ici)d'un form at%d, soitavec la fonction standard atoi. Par souci de diversité, nous avons ch oisi ici la seconde. Program m e #include <stdio.h> #include <string.h> #define VRAI 1 /* pour simuler ..... */ #define FAUX 0 /* ..... des booléens */ #define LGNOM 20 /* longueur maxi d'un nom */ #define NBENFMAX 15 /* nombre maxi d'enfants */ #define LNOMFICH 20 /* longueur maxi nom de fichier */ struct enreg { char nom [LGNOM+1] ; int age ; int nbenf ; int agenf [NBENFMAX] ; } ; main() {
  • 219.
    VII. Traite me ntde fich ie rs 219 void affiche (struct enreg *, int) ; /* fonction d'affichage */ void modifie (struct enreg *) ; /* fonction de modif d'un enreg */ char nomfich [LNOMFICH+1] ; /* nom du fichier à lister */ FILE * fichier ; /* descripteur fichier (niveau 2) */ struct enreg bloc ; /* enregistrement fichier */ int num, /* numéro d'enregistrement */ horsfich ; /* indicateur "logique" */ long nb_enreg, /* nbre d'enregistrements du fichier */ pos ; /* position courante (octets) dans fich */ /* ouverture (en mise à jour) fichier à modifier et calcul de sa taille */ /* attention, mode d'ouverture r+ au lieu de r+b dans certains cas */ do { printf ("donnez le nom du fichier à modifier : ") ; gets (nomfich) ; if ( (fichier = fopen (nomfich, "r+b")) == 0 ) printf ("fichier non trouvén") ; } while (! fichier) ; fseek (fichier, 0, 2) ; nb_enreg = ftell (fichier) / sizeof(bloc) ; /* boucle de corrections d'enregistrements */ /* jusqu'à demande d'arrêt */ do { do { printf ("nnuméro enregistrement à modifier (0 pour fin) : "); scanf ("%d", &num) ; getchar() ; /* pour sauter le dernier n" */ horsfich = num < 0 || num > nb_enreg ; } while (horsfich) ; if (num == 0 ) break ; /* sortie boucle si demande arrêt */ pos = (num-1) * sizeof(bloc) ; /* calcul position courante */ fseek (fichier, pos, 0) ; /* positionnement fichier */ fread (&bloc, sizeof(bloc), 1, fichier) ; /* lecture enreg */ affiche (&bloc, num) ; /* affichage enreg */ modifie (&bloc) ; /* modif enreg */ fseek (fichier, pos, 0) ; /* repositionnement fichier */ fwrite (&bloc, sizeof(bloc), 1, fichier) ; /* réécriture enreg */
  • 220.
    220 Exe rcicesen langage C } while (1) ; /* fin modifications */ fclose(fichier) ; printf ("nn -------- FIN MODIFICATIONS FICHIER ----------n") ; } /*************************************************/ /* fonction d'affichage d'un enregistrement */ /*************************************************/ void affiche (struct enreg * bloc, int num) { int i ; printf ("nnenregistrement numéro : %dnn", num) ; printf ("NOM : %sn", bloc->nom) ; printf ("AGE : %dn", bloc->age) ; printf ("NOMBRE D'ENFANTS : %dn", bloc->nbenf) ; for (i=0 ; i < bloc->nbenf && i < NBENFMAX ; i++) printf ("AGE ENFANT %2d : %2dn", i+1, bloc->agenf[i]) ; } /***************************************************/ /* fonction de modification d'un enregistrement */ /***************************************************/ void modifie (struct enreg * bloc) { char ligne[127] ; /* chaîne de lecture d'une ligne d'écran */ int i ; printf ("nnentrez vos nouvelles infos (return si pas de modifs)n") ; printf ("NOM : ") ; gets (ligne) ; if (strlen(ligne)) strcpy (bloc->nom, ligne) ; printf ("AGE : ") ; gets (ligne) ; if (strlen(ligne)) bloc->age = atoi(ligne) ; printf ("NOMBRE D'ENFANTS : ") ;
  • 221.
    VII. Traite me ntde fich ie rs 221 gets (ligne) ; if (strlen(ligne)) bloc->nbenf = atoi(ligne) ; for (i=0 ; i < bloc->nbenf && i < NBENFMAX ; i++) { printf ("AGE ENFANT %2d : ", i+1) ; gets (ligne) ; if (strlen(ligne)) bloc->agenf[i] = atoi(ligne) ; } } Com m e ntaire s *Nous avons ouvertle fich ier dans le m ode r+ b, lequelautorise la m ise à jour (lecture etécriture en un em placem ent quelconque du fich ier). Notez qu'ilfaut éviter d'écrire ici rb+ , ce qui ne provoquerait généralem ent pas d'erreur d'ouverture, m ais qui em pê ch erait toute écriture dans le fich ier (ici, notre program m e ne s'apercevrait pas de cette anom alie puisqu'ilne teste pas le code de retour de fw rite ). En ce qui concerne l'indication b, rappelons que celle-ci n'est indispensable que dans les im plém entations qui distinguent les fich iers de type texte des autres. Revoyez éventuellem entles com m entaires de l'exercice VII.1. *Aprè s l'ouverture du fich ier, nous en déterm inons la taille (dans la variable nb_e nre g) à l'aide des fonctions fseek et fte ll. Plus précisém ent: fseek (fichier, 0, 2) nous place à 0 octetde la fin (code 2)du fich ier et: ftell (fichier) nous donne la position courante du pointeur associé au fich ier (qui pointe ici sur la fin). Ilnous est alors facile de la transform er en un nom bre d'enregistrem ents, en la divisantpar la taille d'un enregistrem ent. *N'oubliez pas qu'aprè s avoir lu un enregistrem ent, ilestnécessaire, avantde le réécrire, de positionner à nouveau le pointeur dans le fich ier. D ISCUSSIO N
  • 222.
    222 Exe rcicesen langage C *Com m e dans les précédents program m es, nous n'avons pas introduitde protections particuliè res vis-à -vis des réponses fournies par l'utilisateur (voyez les discussions des précédents program m es). Toutefois, ici, la m aniè re m ê m e dontnous avons program m é la saisie des corrections, iln'existe pas, à ce niveau, de risque de "plangage" consécutif à une m auvaise réponse puisque nous n'avons pas faitappelà scanf. VII-4 Com ptage de le ttre s e tm ots d'un fich ie r te xte ________________________________________________________________________________________ Enoncé Ecrire un program m e qui, à partir d'un fich ier texte, déterm ine : - le nom bre de caractè res qu'ilcontient, - le nom bre de ch acune des lettres de l'alph abet(on ne considérera que les m inuscules), - le nom bre de m ots, - le nom bre de lignes. Les fins de lignes ne devront pas ê tre com ptabilisées dans les caractè res. On adm ettra que deux m ots sont toujours séparés par un ou plusieurs des caractè res suivants : - fin de ligne - espace - ponctuation : : . , ;?! - parenth è ses : ( ) - guillem ets : " - apostroph e : ' On adm ettra égalem ent, pour sim plifier, qu'aucun m ot ne peut ê tre com m encé sur une ligne et se poursuivre sur la suivante. Ilest conseillé de réaliser une fonction perm ettant de décider si un caractè re donné, transm is en argum ent, est un des séparateurs m entionnés ci-dessus. Elle restituera la valeur 1 lorsque le caractè re estun séparateur etla valeur 0 dans le cas contraire.
  • 223.
    VII. Traite me ntde fich ie rs 223 Exe m ple donnez le nom du fichier à examiner : b:letfic.c votre fichier contient 87 lignes, 371 mots et 3186 caractères dont : 69 fois la lettre a 6 fois la lettre b 74 fois la lettre c 36 fois la lettre d 163 fois la lettre e ........ 110 fois la lettre t 63 fois la lettre u 7 fois la lettre v 3 fois la lettre w 6 fois la lettre x 0 fois la lettre y 1 fois la lettre z et 1979 autres caractères ________________________________________________________________________________________ ANALYSE Com m e nous avons déjà eu l'occasion de le voir dans les exercices I-5 etI-6, ce type de problè m e peutê tre résolu d'au m oins deux m aniè res : - en effectuantune répétition du traitem entd'un caractè re, - en effectuantune répétition du traitem entd'une ligne, lui-m ê m e constitué de la répétition du traitem ent de ch acun des caractè res qu'elle contient. Toutefois, ici, nous avons à faire à un fich ier dans lequella longueur m axim ale d'une ligne n'estpas connue a priori, ce qui rend la seconde m éth ode difficile à m ettre en oeuvre. Nous ch oisirons donc la prem iè re ;ch aque caractè re du fich ier sera donc lu par fge tc. Rappelons que certaines im plém entations distinguent les fich iers de type texte des autres. Dans ce cas, une telle distinction n'estpas liée au contenu m ê m e du fich ier (en fait, on peuttoujours considérer qu'un fich ier, quelque soitson contenu, estform é d'une suite d'octets, donc, finalem ent, d'une suite de caractè res). Elle a sim plem entpour objectif de
  • 224.
    224 Exe rcicesen langage C faire en sorte que, pour le program m e, les "fins de ligne" apparaissenttoujours m atérialisées par un caractè re unique, à savoir n (alors que, précisém ent, certaines im plém entations, DOS notam m ent, représentent une fin de ligne par un "coupe" de caractè res). Lorsqu'une telle distinction estnécessaire, ilestprévu d'introduire l'indication t, au niveau du m ode d'ouverture du fich ier (de m ê m e qu'on y introduisaitl'indication b pour signaler qu'ilne s'agissaitpas d'un fich ier de type texte). Bien entendu, ici, nous avons toutintérê tà profiter de cette possibilité, de m aniè re à nous faciliter la détection des fins de ligne et, surtout, à obtenir un program m e portable (à l'exception, éventuellem ent, de l'indication t). Les com ptages à effectuer au niveau des caractè res (nom bre de caractè res, nom bre de ch acune des m inuscules) peuvent ê tre réalisés de façon naturelle, à condition toutefois de ne pas com ptabiliser n com m e un caractè re (au contraire, à sa rencontre, ilfaudra incrém enter le com pteur de lignes). En ce qui concerne les com ptages de m ots, nous procéderons com m e dans le prem ier program m e de l'exercice I-6 en em ployant: - une fonction perm ettantde tester si un caractè re estun séparateur, - un indicateur logique : m ot_e n_cours. Program m e #include <stdio.h> #define LNOMFICH 20 /* longueur maximale d'un nom de fichier */ #define VRAI 1 /* pour "simuler" des ..... */ #define FAUX 0 /* ..... valeurs logiques */ main() { int sep (char) ; /* fonction test "caractère séparateur?" */ char nomfich [LNOMFICH+1] ; /* nom du fichier à examiner */ FILE * entree ; /* descripteur du fichier à examiner */ char c ; /* caractère courant */ int compte [26], /* pour compter les différentes lettres */ numl, /* rang lettre courante dans l'alphabet */ ntot, /* compteur nombre total de caractères */ nautres, /* compteur carac autres que minuscules */ nmots, /* compteur du nombre de mots */ nlignes, /* compteur du nombre de lignes */ mot_en_cours, /* indicateur logique : mot trouvé */ i ;
  • 225.
    VII. Traite me ntde fich ie rs 225 /* entrée du nom de fichier à examiner et ouverture */ /* attention, mode r au lieu de rt, dans certains cas */ do { printf ("donnez le nom du fichier à examiner : ") ; gets (nomfich) ; if ( (entree = fopen (nomfich, "rt")) == NULL) printf ("***** fichier non trouvén") ; } while (entree == NULL) ; /* initialisations */ for (i=0 ; i<26 ; i++) compte[i] = 0 ; ntot = 0 ; nautres = 0 ; nmots = 0 ; nlignes = 0 ; mot_en_cours = FAUX ; /* boucle d'examen de chacun des caractères du fichier */ while ( c = fgetc (entree), ! feof (entree) ) { if (c == 'n') nlignes++ ; /* comptages au niveau caractères */ else { ntot++ ; numl = c -'a' ; if (numl >= 0 && numl < 26) compte[numl]++ ; else nautres++ ; } if (sep(c)) /* comptages au niveau mots */ { if (mot_en_cours) { nmots++ ; mot_en_cours = FAUX ; } } else mot_en_cours = VRAI ; } /* affichage résultats */ printf ("nvotre fichier contient %d lignes, %d motsn", nlignes, nmots) ;
  • 226.
    226 Exe rcicesen langage C printf ("et %d caractères dont :n", ntot) ; for (i=0 ; i<26 ; i++) printf ("%d fois la lettre %cn", compte[i], 'a'+i) ; printf ("net %d autres caractèresn", nautres) ; } /*********************************************************/ /* fonction de test "caractère séparateur" */ /*********************************************************/ int sep (char c) { char sep[12] = {'n', /* fin de ligne */ ' ', /* espace */ ',', ';', ':', '.', '?', '!', /* ponctuation */ '(', ')', /* parenthèses */ '"', ''' } ; /* guillemets, apostr*/ int nsep=12, /* nbre séparateurs */ i ; i = 0 ; while ( c!=sep[i] && i<nsep ) i++ ; if (i == nsep) return (0) ; else return (1) ; } Com m e ntaire s Le fich ier a été ouverten m ode rt: r : ouverture en lecture. Si le fich ier n'existe pas, fope n fournitun pointeur nul. t: ouverture en m ode translaté (voyez à ce propos, le prem ier com m entaire de l'exercice VII-1). Notez que le ch oix du m ode translaté n'est jam ais absolum ent indispensable. Toutefois, com m e nous l'avons dit dans l'analyse, ilnous facilite la détection de fin de ligne et, de plus, ilrend le program m e transportable (par exem ple sous UNIX, où une fin de ligne estreprésentée par n).
  • 227.
    VII. Traite me ntde fich ie rs 227 D ISCUSSIO N Nous avons supposé (im plicitem ent)que notre program m e traitaitun véritable fich ier texte, autrem entditque ce dernier se term inaitpar une fin de ligne. Si cela n'étaitpas le cas : - la derniè re ligne ne seraitpas com ptabilisée, - le dernier m otne seraitpas com ptabilisé, à m oins d'ê tre suivi d'au m oins un séparateur.
  • 229.
    VIII: ANALYSE NUM ERIQUE Cech apitre vous propose quelques applications du langage C à l'analyse num érique. Nous avons ch erch é à y introduire les tech niques de program m ation qui interviennentfréquem m entdans ce dom aine. Citons, par exem ple : - la représentation etles m anipulations de m atrices, - la représentation de nom bres com plexes, - la réalisation de m odules susceptibles de travailler avec une fonction quelconque ou avec des tableaux de dim ensions quelconques. VIII-1 Produitde m atrice s ré e lle s ________________________________________________________________________________________ Enoncé Ecrire une fonction calculantle produitde deux m atrices réelles. On supposera que le prem ier indice de ch aque tableau représentantune m atrice correspond à une ligne. On prévoira en argum ents : - les adresses des deux m atrices à m ultiplier etcelle de la m atrice produit, - le nom bre de lignes etle nom bre de colonnes de la prem iè re m atrice, - le nom bre de colonnes de la seconde m atrice (son nom bre de lignes étant obligatoirem ent égalau nom bre de colonnes de la prem iè re). Un program m e principalperm ettra de tester cette fonction.
  • 230.
    230 Exe rcicesen langage C Exe m ple MATRICE A 0 1 2 3 1 2 3 4 2 3 4 5 3 4 5 6 4 5 6 7 MATRICE B 0 1 2 1 2 3 2 3 4 3 4 5 PRODUIT A x B 14 20 26 20 30 40 26 40 54 32 50 68 38 60 82 ANALYSE Rappelons que si A estune m atrice n, p (n lignes etp colonnes)etsi B estune m atrice p, q, la m atrice produit: C = A x B estune m atrice n, q définie par : c ij = a ik b k j Program m e #define N 5 #define P 4 #define Q 3
  • 231.
    VIII. Analyse numé rique 231 main() { void prod_mat(double *, double *, double *, int, int, int) ; double a[N][P], b[P][Q], c[N][Q] ; int i, j ; /* initialisation matrice a */ for (i=0 ; i<N ; i++) for (j=0 ; j<P ; j++) a[i][j] = i+j ; /* initialisation matrice b */ for (i=0 ; i<P ; i++) for (j=0 ; j<Q ; j++) b[i][j] = i+ j ; /* calcul produit a x b */ /* les "cast" (int *) sont facultatifs */ prod_mat ( (double *) a, (double *) b, (double *) c, N, P, Q) ; /* affichage matrice a */ printf (" MATRICE An") ; for (i=0 ; i<N ; i++) { for (j=0 ; j<P ; j++) printf ("%4.0f", a[i][j]) ; printf ("n") ; } printf ("n") ; /* affichage matrice b */ printf (" MATRICE Bn") ; for (i=0 ; i<P ; i++) { for (j=0 ; j<Q ; j++) printf ("%4.0f", b[i][j]) ; printf ("n") ; } printf ("n") ; /* affichage produit */ printf (" PRODUIT A x Bn") ; for (i=0 ; i<N ; i++) { for (j=0 ; j<Q ; j++) printf ("%4.0f", c[i][j]) ; printf ("n") ; }
  • 232.
    232 Exe rcicesen langage C } void prod_mat ( double * a, double * b, double * c, int n, int p, int q) { int i, j, k ; double s ; double *aik, *bkj, *cij ; cij = c ; for (i=0 ; i<n ; i++) for (j=0 ; j<q ; j++) { aik = a + i*p ; bkj = b + j ; s = 0 ; for (k=0 ; k<p ; k++) { s += *aik * *bkj ; aik++ ; bkj += q ; } * (cij++) = s ; } } Com m e ntaire s *Dans la fonction prod_m at, nous n'avons pas pu utiliser le "form alism e" des tableaux pour les m atrices a, b et c car celles-ci possè dentdeux dim ensions non connues lors de la com pilation du program m e. Rappelons qu'un telproblè m e ne se pose pas lorsqu'ils'agitde tableaux à une seule dim ension (car une notation telle que t[i] a toujours un sens, quelle que soitla taille de t)ou lorsqu'ils'agitd'un tableau à plusieurs dim ensions dontseule la prem iè re estinconnue (com pte tenu de la m aniè re dontles élém ents d'un tableau sontrangés en m ém oire). Dans ces conditions, nous som m es obligé de faire appelau form alism e des pointeurs pour repérer un élém entquelconque de nos m atrices. Pour ce faire, nous transm ettons à la fonction prodm atl'adresse de débutdes trois m atrices concernées. Notez qu'en toute rigueur (du m oins d'aprè s la norm e ANSI), dans le program m e m ain, un sym bole telque a estdu type (double [P]) *(c'est-à -dire qu'ilreprésente un pointeur sur des blocs de P élém ents de type double ), et non pas sim plem entdu type double *. Ildoitdonc ê tre converti dans le type double *, cette conversion ne m odifiantpas, en fait, l'adresse correspondante (revoyez éventuellem ent les com m entaires de l'exercice V.5 de la prem iè re partie de cet ouvrage). Cette conversion quelque peu fictive peutsoitê tre m ise en place autom atiquem entpar le com pilateur, au vu du
  • 233.
    VIII. Analyse numé rique 233 prototype, soitê tre explicitée à l'aide d'un opérateur de "cast" ;cette derniè re façon de faire a souventle m érite d'éviter des m essages d'avertissem entintem pestifs ("w arnings"). *Notez que, dans la définition de la fonction prodm at, nous avons dû tenir com pte de la m aniè re dontle langage C range en m ém oire les élém ents d'un tableau à deux dim ensions (suivantce qu'on nom m e abusivem entles "lignes" du tableau, c'est-à -dire suivantl'ordre obtenu en faisantvarier en prem ier le dernier indice). Plus précisém ent: - Le sym bole aik représente un pointeur courant sur les élém ents a ik . Pour ch aque valeur de i, aik est initialisé à l'adresse du prem ier élém entde la ligne i de la m atrice a (a+i*p)etilestincrém enté d'une colonne , en m ê m e tem ps que l'indice k (d'où la présence de aik ++ dans la boucle en k ). - De m ê m e, bk j représente un pointeur courant sur les élém ents b k j . Pour ch aque valeur de j, bk j est initialisé à l'adresse du prem ier élém entde la colonne jde la m atrice b (b+j) etilestincrém enté d'une ligne en m ê m e tem ps que l'indice k (d'où la présence de bkj=bkj+q dans la boucle en k ). - Enfin, cij représente un pointeur courant sur les élém ents c ij . Ilest initialisé à l'adresse du prem ier élém ent de la m atrice c. Ilprogresse de 1 à ch aque tour de la boucle la plus interne en j(notez qu'iln'en auraitpas été ainsi si nous avions inversé les deux boucles en i etj). D ISCUSSIO N *On a souventtendance à dire qu'une fonction com m e prod_m attravaille sur des m atrices de dim ensions variables. En fait, le term e estquelque peu am bigu. Ainsi, dans notre exem ple, les m atrices donton transm etl'adresse en argum entà prod_m atontune taille bien déterm inée dans le program m e principal. Iln'en reste pas m oins que : - d'une part, la m ê m e fonction peuttravailler sur des m atrices de tailles différentes, - d'autre part, rien n'em pê ch eraitqu'au sein du program m e principal, les m atrices a, b etc voientleur taille définie uniquem entlors de l'exécution etleurs em placem ents alloués dynam iquem ent. *Au sein d'une fonction com m e prod_m at, ilestpossible d'em ployer le form alism e des tableaux à la place de celui des pointeurs en faisantappelà un artifice. Celui-ci consiste à créer, pour ch aque m atrice, un tableau de pointeurs contenant l'adresse de débutde ch aque ligne. Ainsi, par exem ple, pour la m atrice a, on pourrait réserver un tableau nom m é ada par : double * * ada ; Ilseraitrem pli de la m aniè re suivante : for (i=1 ; i<n ; i++) ada[i] = a + i*p ;
  • 234.
    234 Exe rcicesen langage C Dans ces conditions, en effet, la notation ada [i] [j] correspondrait(com pte tenu de l'associativité de gauch e à droite de l'opérateur [])à : (ada [i]) [j] c'est-à -dire à : * (ada [i] + j) Autrem entdit, cette notation ada [i][j]désigneraitsim plem entla valeur de l'élém entsitué à l'intersection de la ligne i et de la colonne jde la m atrice a. On notera que pour que cet artifice soit utilisable au sein d'une fonction com m e prod_m at, censée travailler sur des m atrices de taille quelconque, ilestnécessaire que les em placem ents des tableaux de pointeurs tels que ada soientalloués dynam iquem ent. VIII-2 Arith m é tiq ue com ple xe ________________________________________________________________________________________ Enoncé Ecrire deux fonctions calculantla som m e etle produitde deux nom bres com plexes. Ces derniers serontreprésentés par une structure com portantdeux élém ents de type double , correspondantà la partie réelle età la partie im aginaire. Ch acune de ces fonctions com portera trois argum ents : - l'adresse des deux nom bres com plexes (structures)concernés, - l'adresse du résultat(structure). Un program m e principalperm ettra de tester ces deux fonctions avec les valeurs com plexes : 0,5 + i 1 + i
  • 235.
    VIII. Analyse numé rique 235 Exe m ple 0.500000 + 1.000000 i et 1.000000 + 1.000000 i ont pour somme 1.500000 + 2.000000 i et pour produit -0.500000 + 1.500000 i ________________________________________________________________________________________ ANALYSE Soitdeux nom bres com plexes : x = a + ib y = c + id On saitque : x + y = (a+ c)+ i (b+ d) etque : x y = (ac - bd)+ i (ad + bc) Program m e typedef struct { double reel ; double imag ; } complexe ; main() { void somme (complexe *, complexe *, complexe *) ; void produit (complexe *, complexe *, complexe *) ; complexe z1, z2, s, p ; z1.reel = 0.5 ; z1.imag = 1.0 ; z2.reel = 1.0 ; z2.imag = 1.0 ; somme (&z1, &z2, &s) ; produit (&z1 ,&z2, &p) ; printf ("%lf + %lf i et %lf + %lf i n",
  • 236.
    236 Exe rcicesen langage C z1.reel, z1.imag, z2.reel, z2.imag) ; printf ("ont pour somme %lf + %lf i n", s.reel, s.imag) ; printf ("et pour produit %lf + %lf i n", p.reel, p.imag) ; } void somme (complexe * x, complexe * y, complexe * som) { som->reel = x->reel + y->reel ; som->imag = x->imag + y->imag ; } void produit (complexe * x, complexe * y, complexe * prod) { prod->reel = x->reel * y->reel - x->imag * y->imag ; prod->imag = x->reel * y->imag + x->imag * y->reel ; } Com m e ntaire s *Nous avons défini, à un niveau global, un m odè le de structure nom m é com ple xe . *Notez bien que, dans le program m e principal, l'accè s à une structure se faitpar l'opérateur "." (com m e dans z1.re e l) car z1 désigne ici la valeur d'une structure ;par contre, dans les fonctions, ilse faitpar l'opérateur -> (com m e dans x- >re e l) car x désigne alors l'adresse d'une structure. On peut toutefois éviter l'em ploi de cet opérateur, en rem arquant que x-> re e lestéquivalentà (*x).re e l. *En toute rigueur, d'aprè s la norm e ANSI, ilestpossible de transm ettre, en argum ent d'une fonction, la valeur d'une structure. Aussi, aurions-nous pu prévoir que som m e et produit reçoivent les valeurs des com plexes sur lesquels porte l'opération. En revanch e, le résultat devrait toujours ê tre transm is par valeur puisque déterm iné par la fonction elle- m ê m e. Par exem ple, la définition de som m e auraitpu ê tre : void somme (complexe x, complexe y, complexe * som) { prod->reel = x.reel + y.reel ; prod->imag = x.imag + y.imag ; }
  • 237.
    VIII. Analyse numé rique 237 D ISCUSSIO N Dans la pratique, les fonctions som m e etproduitseraientcom pilées séparém entdes fonctions les utilisant. Pour ce faire, ilestnécessaire qu'elles disposentde la description de la structure com ple xe . On voitqu'on risque alors d'ê tre am ené à décrire une m ê m e structure à différentes reprises. Certes, ici la ch ose n'est pas bien grave, dans la m esure où cette définition estsim ple. D'une m aniè re générale, toutefois, on a toutintérê tà régler ce type de problè m e en plaçantune fois pour toutes une telle définition dans un fich ier (d'extension h , par exem ple) qu'on incorpore par #include dans tous les program m es en ayantbesoin. VIII-3 Produitde m atrice s com ple xe s ________________________________________________________________________________________ Enoncé Ecrire une fonction calculantle produit de deux m atrices com plexes. Ch aque m atrice sera définie com m e un tableau à deux dim ensions dans lequelch aque élém ent sera une structure représentant un nom bre com plexe ;cette structure sera constituée de deux élém ents de type double correspondant à la partie réelle et à la partie im aginaire du nom bre. On supposera que le prem ier indice du tableau représentantune m atrice correspond à une ligne. On prévoira en argum ents : - les adresses des deux m atrices à m ultiplier, - l'adresse de la m atrice produit, - le nom bre de lignes etde colonnes de la prem iè re m atrice, - le nom bre de colonnes de la deuxiè m e m atrice (son nom bre de lignes étant obligatoirem ent égalau nom bre de colonnes de la prem iè re). On réalisera un program m e principalperm ettantde tester cette fonction. On pourra éventuellem entfaire appelaux fonctions som m e etproduitréalisées dans l'exercice VIII-2 pour calculer la som m e etle produitde deux nom bres com plexes. Exe m ple MATRICE A 0+ 0i 1+ 2i 2+ 4i 3+ 6i
  • 238.
    238 Exe rcicesen langage C 1+ 1i 2+ 3i 3+ 5i 4+ 7i 2+ 2i 3+ 4i 4+ 6i 5+ 8i 3+ 3i 4+ 5i 5+ 7i 6+ 9i 4+ 4i 5+ 6i 6+ 8i 7+ 10i MATRICE B 0+ 0i 1+ 2i 2+ 4i 1+ 1i 2+ 3i 3+ 5i 2+ 2i 3+ 4i 4+ 6i 3+ 3i 4+ 5i 5+ 7i PRODUIT A x B -14+ 42i -32+ 66i -50+ 90i -14+ 54i -36+ 90i -58+ 126i -14+ 66i -40+ 114i -66+ 162i -14+ 78i -44+ 138i -74+ 198i -14+ 90i -48+ 162i -82+ 234i ________________________________________________________________________________________ ANALYSE Les form ules de définition du produitde m atrices com plexes restentcelles proposées dans l'analyse de l'exercice VIII-1 pour les m atrices réelles ;ilsuffitd'y rem placer les opérations + etx portantsur des réels par les opérations som m e et produitde deux com plexes (les rè gles de ces deux opérations ontété exposées dans l'analyse de l'exercice VIII-2). Program m e #define N 5 #define P 4 #define Q 3 typedef struct { double reel ; double imag ; } complexe ;
  • 239.
    VIII. Analyse numé rique 239 main() { void prod_mat (complexe *, complexe *, complexe *, int, int, int) ; complexe a[N][P], b[P][Q], c[N][Q] ; int i, j ; /* initialisation matrice a */ for (i=0 ; i<N ; i++) for (j=0 ; j<P ; j++) { a[i][j].reel = i+j ; a[i][j].imag = i+2*j ; } /* initialisation matrice b */ for (i=0 ; i<P ; i++) for (j=0 ; j<Q ; j++) { b[i][j].reel = i+j ; b[i][j].imag = i+2*j ; } /* calcul produit a x b */ /* les "cast" (complexe *) sont facultatifs */ prod_mat ((complexe *) &a, (complexe *) &b, (complexe *) &c, N, P, Q) ; /* affichage matrice a */ printf (" MATRICE An") ; for (i=0 ; i<N ; i++) { for (j=0 ; j<P ; j++) printf ("%4.0lf+%4.0lfi ", a[i][j].reel, a[i][j].imag) ; printf ("n") ; } printf ("n") ; /* affichage matrice b */ printf (" MATRICE Bn") ; for (i=0 ; i<P ; i++) { for (j=0 ; j<Q ; j++) printf ("%4.0lf+%4.0lfi ", b[i][j].reel, b[i][j].imag) ; printf ("n") ; } printf ("n") ;
  • 240.
    240 Exe rcicesen langage C /* affichage produit */ printf (" PRODUIT A x Bn") ; for (i=0 ; i<N ; i++) { for (j=0 ; j<Q ; j++) printf ("%4.0lf+%4.0lfi ", c[i][j].reel, c[i][j].imag) ; printf ("n") ; } } /*********************************************************/ /* fonction de calcul de produit de 2 matrices complexes */ /*********************************************************/ void prod_mat ( complexe * a, complexe * b, complexe * c, int n, int p, int q) { void produit() ; int i, j, k ; complexe s, pr ; complexe *aik, *bkj, *cij ; cij = c ; for (i=0 ; i<n ; i++) for (j=0 ; j<q ; j++) { aik = a + i*p ; bkj = b + j ; s.reel = 0 ; s.imag = 0 ; for (k=0 ; k<p ; k++) { produit (aik, bkj, &pr) ; s.reel += pr.reel ; s.imag += pr.imag ; aik++ ; bkj += q ; } * (cij++) = s ; } } void produit (x, y, prod) complexe *x, *y, *prod ; { prod->reel = x->reel * y->reel - x->imag * y->imag ; prod->imag = x->reel * y->imag + x->imag * y->reel ; }
  • 241.
    VIII. Analyse numé rique 241 Com m e ntaire s La fonction prod_m at peut ê tre considérée com m e une adaptation de la fonction prod_m at de l'exercice VIII-1. Cette fois, les sym boles aik , bk j et cij désignent, non plus des pointeurs sur des réels, m ais des pointeurs sur une structure représentantun nom bre com plexe. La souplesse du langage C en m atiè re d'opérations arith m étiques sur les pointeurs fait que les instructions d'incrém entation de ces quantités restent les m ê m es (l'unité étant ici la structure com ple xe - soit 2 élém ents de type double , au lieu d'une valeur de type double ). D ISCUSSIO N Les rem arques faites dans l'exercice VIII-2, à propos de la description de la structure com ple xe restent naturellem ent valables ici. VIII-4 Re ch e rch e de zé ro d'une fonction par dich otom ie ________________________________________________________________________________________ Enoncé Ecrire une fonction déterm inant, par dich otom ie, le zéro d'une fonction quelconque (réelle d'une variable réelle et continue). On supposera connu un intervalle [a,b]sur lequella fonction ch ange de signe, c'est-à -dire telque f(a).f(b)soit négatif. On prévoira en argum ents : - les valeurs des bornes a etb (de type double )de l'intervalle de départ, - l'adresse d'une fonction perm ettant de calculer la valeur de f pour une valeur quelconque de la variable. On supposera que l'en-tê te de cette fonction estde la form e : double fct (x) double x ;
  • 242.
    242 Exe rcicesen langage C - l'adresse d'une variable de type double destinée à recueillir la valeur approch ée du zéro de f, - la valeur de la précision (absolue)souh aitée (de type double ). Le code de retour de la fonction sera de -1 lorsque l'intervalle fourni en argum entne convientpas, c'est-à -dire : - soitlorsque la condition a<b n'estpas satisfaite, - soitlorsque la condition f(a).f(b)<0 n'estpas satisfaite. Dans le cas contraire, le code de retour sera égalà 0. Un program m e principalperm ettra de tester cette fonction. Exe m ple zéro de la fonction sin entre -1 et 1 à 1e-2 près = 0.000000e+000 zéro de la fonction sin entre -1 et 2 à 1e-2 près = 1.953125e-003 zéro de la fonction sin entre -1 et 2 à 1e-12 près = -2.273737e-013 ________________________________________________________________________________________ ANALYSE La dém arch e consiste donc, aprè s avoir vérifié que l'intervalle reçu en argum entétaitconvenable, à répéter le traitem ent suivant: - prendre le m ilieu m de [a,b]: m = (a+ b)/2 - calculer f(m ), - si f(m )=0, le zéro esten m , - si f(a).f(m )<0, ilexiste un zéro sur [a,m ];on rem place donc l'intervalle [a,b]par [a,m ]en faisant: b = m - si f(a).f(m )> 0, ilexiste un zéro sur [b,m ];on rem place donc l'intervalle [a,b]par [b,m ], en faisant: a = m Le traitem ent est interrom pu soit lorsque l'intervalle [a,b] aura été suffisam m ent réduit, c'est-à -dire lorsque |b-a| est inférieur à la précision souh aitée, soitlorsque le zéro a été localisé exactem ent(f(m )=0).
  • 243.
    VIII. Analyse numé rique 243 Program m e #include <stdio.h> #include <math.h> /* pour la fonction sin */ main() { /* fonction de recherche d'un zéro par dichotomie */ int dichoto ( double (*(double)(), double, double, double *, double) ; double z, /* zéro recherché */ a, b, /* bornes de l'intervalle de recherche */ eps ; /* précision souhaitée */ dichoto (sin, -1.0, 1.0, &z, 1.0e-2) ; printf ("zéro de la fonction sin entre -1 et 1 à 1e-2 près = %len",z); dichoto (sin, -1.0, 2.0, &z, 1.0e-2) ; printf ("zéro de la fonction sin entre -1 et 2 à 1e-2 près = %len",z); dichoto (sin, -1.0, 2.0, &z, 1.0e-12) ; printf ("zéro de la fonction sin entre -1 et 2 à 1e-12 près = %len",z); } /*************************************************************/ /* fonction de recherhce dichotomique du zéro d'une fonction */ /*************************************************************/ int dichoto ( double (* f)(double), double a, double b, double * zero, double eps) /* f : fonction dont on cherche le zéro */ /* a, b : bornes de l'intervalle de recherche */ /* zero : zéro estimé */ /* eps : précision souhaitée) */ { double m, /* milieu de l'intervalle courant */ fm, /* valeur de f(m) */ fa, fb ; /* valeurs de f(a) et de f(b) */ fa = (*f)(a) ; fb = (*f)(b) ; if (fa*fb >= 0 || a >= b) return (-1) ; /* intervalle incorrect */ while (b-a > eps) { m = (b+a) / 2.0 ;
  • 244.
    244 Exe rcicesen langage C fm = (*f)(m) ; if (fm == 0) break ; /* zéro atteint */ if (fa*fm < 0) { b = m ; fb = fm ; } else { a = m ; fa = fm ; } } * zero = m ; return (0) ; } Com m e ntaire s *Notez, dans la fonction dich oto : - la déclaration de l'argum entcorrespondantà l'adresse de la fonction donton ch erch e le zéro : double (*f)(double) Celle-ci s'interprè te com m e suit: (*f)estune fonction recevantun argum entde type double etfournissantun résultatde type double , *festdonc une fonction recevantun argum entde type double etfournissantun résultatde type double , f est donc un pointeur sur une fonction recevant un argum ent de type double et fournissant un résultat de type double . - l'utilisation du sym bole f;ainsi (*f)(a) représente la valeur de la fonction (*f) (fonction d'adresse f), à laquelle on fournitl'argum enta. Les m ê m es réflexions s'appliquentau prototype servantà déclarer dich oto. *La fonction dich oto recevanten argum entles valeurs des argum ents a etb (etnon des adresses), nous pouvons nous perm ettre de les m odifier au sein de la fonction, sans que cela ait d'incidence sur les valeurs effectives des bornes définies dans le program m e principal. *Voyez com m ent, dans le program m e principal, un sym bole com m e sin est interprété par le com pilateur com m e l'adresse d'une fonction prédéfinie ;ilest toutefois nécessaire d'avoir incorporé son prototype (situé dans m ath .h );en
  • 245.
    VIII. Analyse numé rique 245 l'absence de l'instruction #include correspondante, le com pilateur détecterait un erreur puisque alors le sym bole sin ne seraitpas défini. D ISCUSSIO N En th éorie, la m éth ode de dich otom ie conduittoujours à une solution, avec une précision aussi grande qu'on le désire, à partir du m om ent où la fonction ch ange effectivem ent de signe sur l'intervalle de départ. En pratique, toutefois, les ch oses ne sontpas toujours aussi idylliques, com pte tenu de la lim itation de la précision des calculs. Toutd'abord, si on im pose une précision trop faible par rapportà la précision de l'ordinateur, on peutaboutir à ce que : m = (a+b)/2 soitégalà l'une des deux bornes a ou b. Ilestalors facile de m ontrer que l'algorith m e peutboucler indéfinim ent. D'autre part, les valeurs de f(a) etde f(b) sontnécessairem entévaluées de m aniè re approch ée. Dans le cas de form ules quelque peu com plexes, on peuttrè s bien aboutir à une situation dans laquelle f(a).f(b)estpositif. La prem iè re situation est assez facile à éviter : ilsuffit de ch oisir une précision relative (attention, ici, notre fonction travaille avec une précision absolue) inférieure à celle de l'ordinateur. Iln'en va pas de m ê m e pour la seconde dans la m esure où iln'estpas toujours possible de m aîtriser la précision des calculs des valeurs de f.