Vous êtes sur la page 1sur 67

I- Syntaxe du langage c

Un programme C est compos:


de directives du pr processeur, commenant par #, termines par un retour la ligne (pas de ;). de dclarations globales (termines par un ;). d'une suite de fonctions, crites les unes aprs les autres (sans imbrication).

Les fonctions sont crites sous la forme : entte { corps } L'entte est de la forme : type_rsultat nom (arguments) . Le type_rsultat n'tait obligatoire (avant la norme ANSI) que s'il tait diffrent de int (entier). Il doit dsormais tre void (rien) si la fonction ne renvoie rien (dans un autre langage on l'aurait alors appel sous-programme, procdure, ou sous-routine). Les arguments, s'ils existent, sont passs par valeur. Si la fonction ne ncessite aucun argument, il faut indiquer (void) d'aprs la norme ANSI, ou du moins (). Le corps est compos de dclarations de variables locales, et d'instructions.

Variables / identificateurs / adresse / pointeurs

On appelle variable une mmoire de l'ordinateur (ou plusieurs), laquelle on a donn un nom, ainsi qu'un type, pour que le compilateur puisse lui rserver la quantit de mmoire ncessaire. Dans cette variable, on pourra y stocker une valeur, et la modifier au cours du programme. Exemple : int a; a est une variable entire, le compilateur va lui rserver en mmoire la place ncessaire un entier (2 octets en Turbo C, 4 sous gcc). Le nom de cette variable est choisi par le programmeur. On prfre utiliser le terme identificateur plutt que nom, car il permet d'identifier tout objet que l'on voudra utiliser (pas seulement les variables). Les identificateurs doivent suivre quelques rgles de base : il peut tre form de lettres (A Z), de chiffres et du caractre _ (soulign). Le premier caractre doit tre une lettre (ou _ mais il vaut mieux le rserver au compilateur). les minuscules sont diffrentes des majuscules. Le blanc est donc interdit dans un identificateur. Les lettres accentues sont galement interdites. La plupart des compilateurs acceptent n'importe quelle longueur d'identificateurs (tout en restant sur la mme ligne) mais seuls les 32 premiers caractres sont significatifs. On appelle "blanc" : soit un espace, soit un retour la ligne, soit une tabulation, soit un commentaire, soit plusieurs de ceux-ci. Les commentaires sont une portion de texte commenant par /* et finissant par le premier */ rencontr. Les commentaires ne peuvent donc pas tre imbriqus. Mais un commentaire peut comporter n'importe quel autre texte, y compris sur plusieurs lignes. La plupart des compilateurs acceptent les commentaires commenant par // et se finissant la fin de la ligne, qui eux peuvent tre inclus dans un commentaire /*...*/ (c'est en fait une extension du C++). L'endroit o le compilateur a choisi de mettre la variable est appel adresse de la variable (c'est en gnral un nombre, chaque mmoire d'un ordinateur tant numrote de 0 ? ). Cette adresse ne nous intresse que rarement de manire explicite, mais souvent de manire indirecte. Par exemple, dans un tableau, compos d'lments conscutifs en mmoire, en connaissant son adresse (son dbut), on retrouve facilement l'adresse des diffrentes composantes par une simple addition. On appelle pointeur une variable dans laquelle on place (mmorise) une adresse de variable (o elle est) plutt qu'une valeur (ce qu'elle vaut). Les types de variables scalaires simples que l'on utilise le plus couramment sont le char (un caractre), l'int (entier) et le float (rel). Le char est en fait un cas particulier des int, chaque caractre tant reprsent par son numro de code ASCII.

Expressions / oprateurs
Une expression est un calcul qui donne une valeur rsultat (exemple : 8+5). Une expression comporte des variables, des appels de fonction et des constantes combins entre eux par des oprateurs (ex : MaVariable*sin(VarAngle*PI/180) ). Une expression de base peut donc tre un appel une fonction (exemple sin(3.1416). Une fonction est un bout de programme (que vous avez crit ou faisant partie d'une bibliothque) auquel on "donne" des valeurs (arguments), entre parenthses et spars par des virgules. La fonction fait un calcul sur ces arguments pour "retourner" un rsultat. Ce rsultat pourra servir, si ncessaire, dans une autre expression, voir comme argument d'une fonction exemple atan(tan(x)). Les arguments donns l'appel de la fonction (dits paramtres rels ou effectifs) sont recopis dans le mme ordre dans des copies (paramtres formels), qui elles ne pourront que modifier les copies (et pas les paramtres rels). Dans le cas de fonctions devant modifier une variable, il faut fournir en argument l'adresse (par l'oprateur &, voir plus bas), comme par exemple pour scanf. Pour former une expression, les oprateurs possibles sont assez nombreux, nous allons les dtailler suivant les types de variables qu'ils grent.

I- Arithmtiques
Ces oprateurs s'appliquent des valeurs entires ou relles.

1- unaires
Ce sont les oprateurs un seul argument : - et +. Le rsultat est du mme type que l'argument.

2- deuxaires
Le terme "deuxaire" n'est pas standard, je l'utilise parce que binaire est pour moi associ la base 2. Ces oprateurs ncessitent deux arguments, placs de part et d'autre de l'oprateur. Ce sont + (addition), (soustraction), * (produit), / (division), % (reste de la division). % ncessite obligatoirement deux arguments entiers, les autres utilisent soit des entiers, soit des rels. Les oprandes doivent tre du mme type, le rsultat sera toujours du type des oprandes. Lorsque les deux oprandes sont de type diffrent (mais numrique videment), le compilateur prvoit une conversion implicite (vous ne l'avez pas demande mais il la fait nanmoins) suivant l'ordre : { char -> int -> long -> float -> double } et { signed > unsigned }. On remarque qu'il considre les char comme des entiers, les oprations sont en fait faites sur les numros de code (ASCII). Les calculs arithmtiques sont faits uniquement soit en long soit en double, pour viter des dpassements de capacit. exemples :
int a=1,b=2,c=32000; float x=1,y=2; a=(c*2)/1000; /* que des int, le rsultat est 64, mme si l'on est pass par un rsultat intermdiaire (64000) qui dpassait la capacit des entiers (mais pas celle des long) */ b=7/b; /* signe = donc en premier calcul de l'argument droite : 7 (entier) / 2 (entier) donne 3 (entier, reste 1, que l'on obtient par 5%2). donc b=3 */ x=7/b; /* 7 et b entiers => passage en rel inutile, calcul de 7/3 donne 2 (entier, reste 1) puis oprateur = (transformation du 2 en 2.0 puis transfert dans X qui vaut

donc 2.0) */ x=7/y; /* un int et un float autour de / : transformation implicite de 7 en rel (7.0), division des deux rel (3.5), puis transfert dans x */ x=((float)(a+1))/b; /* calcul (entier) de a+1, puis transformation explicite en float, et donc implicite de b en float, division 65.0/3.0 -> 21.666... */

II- Relationnels
1- comparaisons
Ces oprateurs sont deuxaires : = = (galit), != (diffrent), <, >, <=, >=. Des deux cts du signe opratoire, il faut deux oprandes de mme type (sinon, transformation implicite) mais numrique (les caractres sont classs suivant leur numro de code ASCII). Le rsultat de l'opration est 0 si faux, 1 si vrai (le rsultat est de type int). Exemple : (5<7)+3*((1+1)= =2) donne 4. Attention, le compilateur ne vous prvient pas si vous avez mis = au lieu de = = (= est aussi un oprateur), mais le rsultat sera diffrent de celui prvu.

2- logique boolenne
Le rsultat est toujours 0 (faux) ou 1 (vrai), les oprandes devant tre de type entier (si char conversion implicite), 0 symbolisant faux, toute autre valeur tant considre vraie. Oprateur unaire : ! (non). !arg vaut 1 si arg vaut 0, et 0 sinon. Oprateurs deuxaires : && (ET, vaut 1 si les 2 oprandes sont non nuls, 0 sinon) et || (OU, vaut 0 si les deux oprandes sont nuls, 1 sinon). Le deuxime oprande n'est valu que si le premier n'a pas suffi pour conclure au rsultat (ex (a= =0)&&(x++<0) incrmente x si a est nul, le laisse intact sinon).

3- binaires
Ces oprateurs ne fonctionnent qu'avec des entiers. Ils effectuent des oprations binaires bit bit. On peut utiliser ~ (complment, unaire), & (et), | (ou inclusif), ^ (ou exclusif), >> (dcalage droite, le 2me oprande est le nombre de dcalages), << (dcalage gauche). Contrairement aux oprateurs relationnels, les rsultats ne se limitent pas 0 et 1. exemples : 7&12 donne 4 (car 0111&1100 donne 0100); ~0 donne -1 (tous les bits 1, y compris celui de signe); 8<<2 donne 32.

III- Affectation
1- affectation simple =
En C, l'affectation (signe =) est une opration comme une autre. Elle ncessite deux oprantes, un droite, appel Rvalue, qui doit tre une expression donnant un rsultat d'un type donn, et un gauche (Lvalue) qui doit dsigner l'endroit en mmoire o l'on veut stocker la Rvalue. Les deux oprandes doivent tre de mme type, dans le cas d'oprandes numriques si ce n'est pas le cas le compilateur effectuera une conversion implicite (la Lvalue doit tre de type "plus fort" que la Rvalue). L'opration d'affectation rend une valeur, celle qui a t transfre, et peut donc servir de Rvalue.

Exemples : a=5 (met la valeur 5 dans la variable a. Si a est float, il y a conversion implicite en float); b=(a*5)/2 (calcule d'abord la Rvalue, puis met le rsultat dans b); a=5+(b=2) (Le compilateur lit l'expression de gauche droite. la premire affectation ncessite le calcul d'une Rvalue : 5+(b=2). Celle ci comporte une addition, dont il value le premier oprande (5) puis le second (b=2). Il met donc 2 dans b, le rsultat de l'opration est 2, qui sera donc ajout 5 pour tre mis dans a. A vaut donc 7 et b, 2. Le rsultat de l'expression est 7 (si l'on veut s'en servir). Remarque : il ne faut pas confondre = et = =. Le compilateur ne peut pas remarquer une erreur (contrairement au Pascal ou Fortran) car les deux sont possibles. Exemple : if (a=0) est toujours faux car quelle que soit la valeur initiale de a, on l'crase par la valeur 0, le rsultat de l'opration vaut 0 et est donc interprt par IF comme faux.

2- incrmentation / dcrmentation
++a : ajoute 1 la variable a. Le rsultat de l'expression est la valeur finale de a (c'est dire aprs incrmentation). On l'appelle incrmentation prfixe. a++ : ajoute 1 la variable a. Le rsultat de l'expression est la valeur initiale de a (c'est dire avant incrmentation). C'est l'incrmentation postfixe. de mme, la dcrmentation --a et a-- soustrait 1 a. exemple : j=++i est quivalent j=(i=i+1). Je vous dconseille les imbrications du genre i=i++ + ++i difficilement comprhensibles.

3- affectation largie
+= , -= , *= , /= , %= , <<= , >>= , &= , ^= , |= a+=5 est quivalent a=(a+5). Il faut encore ici une Rvalue droite et une Lvalue gauche.

IV- Oprateurs d'adresses


Ces oprateurs sont utilises avec des pointeurs. On utilise

&variable : donne l'adresse d'une variable *pointeur : rfre la variable pointe (oprateur d'indirection) . : champ d'une structure -> : champ point

exemple : supposons dclarer : int i1=1,i2=2; int *p1,*p2; i1 et i2 sont deux mmoires contenant un entier, alors que p1 et p2 sont des pointeurs, puisqu'ils contiennent une adresse d'entier. p1=&i1; met dans p1 l'adresse de i1. p2=p1; met la mme adresse (celle de i1) dans p2. printf("%d\n",*p1); affiche ce qui est dsign (point) par p1 donc i1 donc 1. p2=&i2;*p2=*p1; l'adresse pointe par p2 mettre ce qui est point par p1, donc copier la valeur de i1 dans i2. printf("%d\n",i2); affiche donc 1.

V- Autres
1- conditionnel ? :
C'est un (le seul) oprateur ternaire. L'expression a?b:c vaut la valeur de b si a est vrai (entier, diffrent de 0), et c si a est faux. Exemple : max=a>b?a:b

2- squentiel ,
Cet oprateur permet de regrouper deux sous expressions en une seule. On effectue le premier oprande puis le second, la valeur finale de l'expression tant celle du second oprande. On l'utilise pour valuer deux (ou plus) expressions l o la syntaxe du C ne nous permettait que d'en mettre une, exemple : for(i=j=0;i>10;i++,j++). Dans le cas d'une utilisation de cet oprateur dans une liste, utilisez les parenthses pour distinguer les signes , : exemple (inutile) : printf("%d %d",(i++,j++),k) i est modifi mais sa valeur n'est pas affiche.

VI- Ordre de priorit et associativit


oprateurs associativit description () [] -> . -> ! ~ ++ -- - + & * (cast) <unaires (* pointeurs) */% -> multiplicatifs +-> addition >> << -> dcalages < <= > >= -> relations d'ordre = = != -> galit & -> binaire ^ -> binaire | -> binaire && -> logique || -> logique ?: -> conditionnel (ternaire) = += -= *= etc. <affectation , <squentiel Dans ce tableau, les oprateurs sont classs par priorit dcroissante (mme priorit pour les oprateurs d'une mme ligne). Les oprateurs les plus prioritaires seront valus en premier. L'associativit dfinit l'ordre d'valuation des oprandes. La plupart se font de gauche droite ( 4/2/2 donne (4/2)/2 donc 1 (et pas 4/(2/2))). Les seules exceptions sont :

les oprateurs unaires, crits gauche de l'oprateur. L'oprande est valu puis l'opration est effectue, le rsultat est celui de l'opration; sauf dans le cas de l'incrmentation / dcrmentation postfixe, o le rsultat de l'expression est la valeur de l'argument avant l'opration.

L'affectation : on calcule l'oprande de droite, puis on l'affecte celui de gauche. Le rsultat est la valeur transfre. La virgule : la valeur droite est calcule avant celle gauche (en particulier lors d'un appel de fonction). Exemple d'embrouille : fonction(tableau[i],++i); Les oprateurs logiques et conditionnel valuent toujours leur premier argument. Le second par contre n'est valu que si c'est ncessaire. Donc a && i++ ne change pas toujours i, contrairement a & i++ (mais qui crirait de telles horreurs ?)

Instructions
Une instruction peut tre : - soit une expression (pouvant comprendre une affectation, un appel de fonction...), termin par un ; qui en fait signifie "on peut oublier le rsultat de l'expression et passer la suite", - soit une structure de contrle (boucle, branchement...), - soit un bloc d'instructions : ensemble de dclarations et instructions dlimits par des accolades {}. Un bloc sera utilis chaque fois que l'on dsire mettre plusieurs instructions l o on ne peut en mettre qu'une. Seule la premire forme est termine par un ;. Un cas particulier est l'instruction vide, qui se compose uniquement d'un ; (utilis l o une instruction est ncessaire d'aprs la syntaxe).

Structures de contrle
Normalement, les instructions s'effectuent squentiellement, c'est dire l'une aprs l'autre. Pour accder une autre instruction que la suivante, on a trois solutions : le branchement inconditionnel, le branchement conditionnel et la boucle.

I- Boucles
Une boucle permet de rpter plusieurs fois un bloc d'instructions.

1- While (tant que)


structure : while (expression) instruction Tant que l'expression est vraie (!=0), on effectue l'instruction, qui peut tre simple (termine par ;), bloc (entre {}) ou vide (; seul). L'expression est au moins value une fois. Tant qu'elle est vraie, on effectue l'instruction, ds qu'elle est fausse, on passe l'instruction suivante (si elle est fausse ds le dbut, l'instruction n'est jamais effectue). exemple :
#include <stdio.h> int main(void) { float nombre,racine=0; puts("entrez un nombre rel entre 0 et 10"); scanf("%f",&nombre); while (racine*racine<nombre) racine+=0.01;

printf("la racine de %f vaut %4.2f 1%% prs\n", nombre, racine); }

Exercice (while_puiss) : faire un programme qui affiche toutes les puissances de 2, jusqu' une valeur maximale donne par l'utilisateur. On calculera la puissance par multiplications successives par 2. Solution.
#include <stdio.h> void main(void) { int puissance=1,max; puts("nombre maximal dsir (ne pas dpasser 16000) ?"); scanf("%d",&max); while (puissance<max) printf("%d\n",puissance*=2); }

Exercice (while_err) : que fait ce programme ?


#include <stdio.h> #include <math.h> #define debut 100 #define pas 0.01 int main(void) { float nombre=debut; int compte=0,tous_les; puts("afficher les rsultats intermdiaires tous les ? (333 par exemple) ?"); scanf("%d",&tous_les); while (fabs(nombre-(debut+(compte*pas)))<pas) { nombre+=pas; if (!(++compte%tous_les)) printf("valeur obtenue %12.8f, au lieu de %6.2f en %d calculs\n", nombre,(float)(debut+(compte*pas)), compte); } printf("erreur de 100%% en %d calculs\n",compte); }

Solution :Ce programme dmontre les erreurs de calcul toujours effectues sur des nombres rels. On additione successivement 0.01 (qui n'a pas de reprsentation finie en binaire) un rel initialement nul. On compte le nombre de calculs jusqu' obtenir une erreur de 100%. Dans ce cas il faut 16246 calculs. On peut essayer d'autres pas et d'autres dbuts.

2- Do While (faire tant que)


structure : do instruction while (expression); (attention au ; final) comme while, mais l'instruction est au moins faite une fois, avant la premire valuation de l'expression. exemple :
#include <stdio.h> int main(void)

{ int a; do { puts("veuillez entrer le nombre 482"); scanf("%d",&a); } while (a!=482); puts("c'est gentil de m'avoir obi"); }

Les boucles sont la solution employer pour effectuer plusieurs fois la mme instruction (ou bloc d'instructions). L'exemple ci-dessus montre l'utilisation d'un do-while pour une action qu'on a prvu d'effectuer une seule fois normalement, mais que peut-tre on effectuera plusieurs fois. Donc en C on ne dit jamais "faire X, si erreur recommencer X", mais "faire X tant que erreur". Exercice (do_while) : crivez un programme de jeu demandant de deviner un nombre entre 0 et 10 choisi par l'ordinateur. On ne donnera pas d'indications avant la dcouverte de la solution, o l'on indiquera le nombre d'essais. La solution sera choisie par l'ordinateur par la fonction rand() qui rend un entier alatoire (dclare dans stdlib.h). Solution.
#include <stdio.h> #include <stdlib.h> /* pour rand() */ #include <time.h> /* pour trouver l'heure pour srand */ void main(void) { int solution,reponse,nb_essais=0; srand(time(NULL)); /* initialiser le gnrateur partir du compteur de temps, pour qu'il soit plus alatoire */ solution=rand()%11; /* reste sera toujours entre 0 et 10 */ do { nb_essais++; puts("proposez votre nombre entre 0 et 10"); scanf("%d",&reponse); } while (reponse!=solution); printf("trouv en %d essais\n",nb_essais); }

3- For (pour)
structure : for ( expr_initiale;expr_condition;expr_incrmentation) instruction Cette boucle est surtout utilise lorsque l'on connat l'avance le nombre d'itrations effectuer. L'expr_initiale est effectue une fois, en premier. Puis on teste la condition. On effectue l'instruction puis l'incrmentation tant que la condition est vraie. L'instruction et l'incrmentation peuvent ne jamais tre effectues. La boucle est quivalente :
expr_initiale; while (expr_condition) { instruction expr_incrmentation; }

Une ou plusieurs des trois expressions peuvent tre omises, l'instruction peut tre vide. for(;;); est donc une boucle infinie. exemple :
{ char c; for(c='Z';c>='A';c--)putchar(c); }

Exercice (for) : faire un programme qui calcule la moyenne de N notes. N et les notes seront saisies par scanf. Le calcul de la moyenne s'effectue en initialisant une variable 0, puis en y ajoutant progressivement les notes saisies puis division par N. Solution.
#include <stdio.h> void main(void) { int i,N; float note,somme=0,moyenne; puts("nombre de notes ? "); scanf("%d",&N); for(i=0;i<N;i++) { printf("entrez votre %dime note",i+1); scanf("%f",&note); somme+=note; } moyenne=somme/N; printf("moyenne calcule :%5.2f\n",moyenne); }

II- Branchements conditionnels


On a souvent besoin de n'effectuer certaines instructions que dans certains cas. On dispose pour cela du IF et du SWITCH.

1- If - Else (Si - Sinon)


structure : if (expression) instruction1 ou : if (expression) instruction1 else instruction2 Si l'expression est vraie (!=0) on effectue l'instruction1, puis on passe la suite. Sinon, on effectue l'instruction 2 puis on passe la suite (dans le cas sans else on passe directement la suite). Exercice (jeu) : modifier le jeu de l'exercice (do_while) en prcisant au joueur chaque essai si sa proposition est trop grande ou trop petite. Solution.
#include <stdio.h> #include <stdlib.h> /* pour rand() */ #include <time.h> /* pour trouver l'heure pour srand */ void main(void) { int solution,reponse,nb_essais=0; { time_t t;srand((unsigned) time(&t)); } /* initialiser le gnrateur*/ solution=rand()%11; /* reste sera toujours entre 0 et 10 */ do

10

{ nb_essais++; puts("proposez votre nombre entre 0 et 10"); scanf("%d",&reponse); if (reponse>solution) puts("trop grand"); else if (reponse!=solution) puts("trop petit"); } while (reponse!=solution); printf("trouv en %d essais\n",nb_essais); if (nb_essais==1) puts("vous avez eu un peu de chance"); else if (nb_essais<4) puts("bravo"); else if (nb_essais>6) puts("ce score me semble bien minable"); }

L'instruction d'un if peut tre un autre if (imbriqu) exemple : if(c1) i1; else if (c2) i2; else if (c3) i3; else i4; i5; si c1 alors i1 puis i5, sinon mais si c2 alors i2 puis i5, ... Si ni c1 ni c2 ni c3 alors i4 puis i5. Le else tant facultatif, il peut y avoir une ambigut s'il y a moins de else que de if. En fait, un else se rapporte toujours au if non termin (c'est dire qui on n'a pas encore attribu de else) le plus proche. On peut aussi terminer un if sans else en l'entourant de {}. exemple : if(c1) if(c2) i1; else i2; : si c1 et c2 alors i1, si c1 et pas c2 alors i2, si pas c1 alors (quel que soit c2) rien. if (c1) {if (c2) i1;} else i2; : si c1 etx>=0) if (y>=0 c2 alors i1, si c1 et pas c2 alors rien, si pas c1 alors i2. Un programmeur expriment utilisera toujours un maximum de "else" : if(x>=0 && y>=0)....; if(x<=0 && y>=0)....; if(x>=0 && y<=0)....; if(x<0 && y<0)....; mauvais : toujours 8 tests quelques risques d'erreur (si nul) if(x>=0) if (y>=0)....; else ....; /*x>=0 et y<0*/ else if (y>=0)....; /*x<0 de toute faon*/ else ....; /*x<0 et y<0*/ bon : toujours 2 tests et impossibilit de faire deux cas ou aucun

ou encore : if(rep=='O').....; else if(rep=='+'&& delta>0).....; else if(rep=="A").....; else if(z==0).....; else printf("cas non prvu\n");

11

Quoi qu'il arrive, on ne fera qu'un cas. Dans le second inutile de rajouter rep!='O', on n'arrive au Nime cas que si tous les prcdents sont faux. Tous les cas sont donc automatiquement exclusifs. Pour acclrer le programme il suffit de mettre en premier le cas le plus courant, on ne fera en majorit qu'un seul test.

2- Switch - Case (brancher - dans le cas)


Cette structure de contrle correspond un "goto calcul". structure : switch (expression_entire) { case cste1:instructions case cste2:instructions ........ case csteN:instructions default :instructions } L'expression ne peut tre qu'entire (char, int, long). L'expression est value, puis on passe directement au "case" correspondant la valeur trouve. Le cas default est facultatif, mais si il est prvu il doit tre le dernier cas. exemple : fonction vrifiant si son argument c est une voyelle. int voyelle(char c) { switch(c) { case 'a': case 'e': case 'i': case 'o': case 'u': case 'y':return(1); /* 1=vrai */ default :return(0); } } Remarque : l'instruction break permet de passer directement la fin d'un switch (au } ). Dans le cas de switch imbriqus on ne peut sortir que du switch intrieur. Exemple : switch (a) { case 1:inst1;inst2;....;break; case 2:....;break; default:..... } /*endroit o l'on arrive aprs un break*/ Exercice (calcul) : faire un programme simulant une calculatrice 4 oprations. Solution.
#include <stdio.h>

12

void main(void) { float val1,val2,res; char op; int fin=0; do { puts("calcul effectuer (par ex 5*2), ou 1=1 pour finir ? "); scanf("%f%c%f",&val1,&op,&val2); switch (op) { case '*':res=val1*val2;break; case '/':res=val1/val2;break; case '+':res=val1+val2;break; case '-':res=val1-val2;break; case '=':fin++; /* pas besoin de break, je suis dj au } */ } if (!fin) printf("%f%c%f=%f\n",val1,op,val2,res); } while (!fin); }

III- Branchements inconditionnels


Quand on arrive sur une telle instruction, on se branche obligatoirement sur une autre partie du programme. Ces instructions sont viter si possible, car ils rendent le programme plus complexe maintenir, le fait d'tre dans une ligne de programme ne suffisant plus pour connatre immdiatement quelle instruction on a fait auparavant, et donc ne permet plus d'assurer que ce qui est au dessus est correctement termin. Il ne faut les utiliser que dans certains cas simples.

1- Break (interrompre)
Il provoque la sortie immdiate de la boucle ou switch en cours. Il est limit un seul niveau d'imbrication. exemples : do {if(i==0)break;....}while (i!=0); /* un while aurait t mieux */ for (i=0;i<10;i++){....;if (erreur) break;} /* remplacer par for(i=0;(i<10)&&(!erreur);i++){...} */

2- Continue (continuer)
Cette instruction provoque le passage la prochaine itration d'une boucle. Dans le cas d'un while ou do while, saut vers l'valuation du test de sortie de boucle. Dans le cas d'un for on passe l'expression d'incrmentation puis seulement au test de bouclage. En cas de boucles imbriques, permet uniquement de continuer la boucle la plus interne. exemple : for (i=0;i<10;i++) {if (i==j) continue; ...... } peut tre remplac par : for (i=0;i<10;i++) if (i!=j) { ...... }

13

3- Goto (aller )
La pratique des informaticiens a montr que l'utilisation des goto donne souvent des programmes non maintenables (impossibles corriger ou modifier). Les problmes qu'ils posent ont amen les programmeurs expriments ne s'en servir qu'exceptionnellement. structure : goto label; Label est un identificateur (non dclar, mais non utilis pour autre chose), suivi de deux points (:), et indiquant la destination du saut. Un goto permet de sortir d'un bloc depuis n'importe quel endroit. Mais on ne peut entrer dans un bloc que par son { (qui crera proprement les variables locales du bloc). {..... {..... goto truc; ..... } ..... truc: ..... }

4- Return (retourner)
Permet de sortir de la fonction actuelle (y compris main), en se branchant son dernier }. Return permet galement (et surtout) de rendre la valeur rsultat de la fonction. structure : return; ou return(expression); exemple : int max(int a, int b) {if (a>b) return(a); else return(b);}

5- Exit (sortir)
Ceci n'est pas un mot clef du C mais une fonction disponible dans la plupart des compilateurs (dfinie par ANSI, dans STDLIB.H). Elle permet de quitter directement le programme (mme depuis une fonction). On peut lui donner comme argument le code de sortie (celui que l'on aurait donn return dans main). Cette fonction libre la mmoire utilise par le programme (variables + alloc) et ferme (sur beaucoup de compilateurs) les fichiers ouverts. structure : exit(); ou exit(valeur);

14

Dclaration et stockage des variables


Une variable doit tre dfinie par le programmateur dans une dclaration, o l'on indique le nom que l'on dsire lui donner, son type (int, float,...) pour que le compilateur sache combien de mmoire il doit lui rserver et les oprateurs qui peuvent lui tre associs, mais aussi comment elle doit tre gre (visibilit, dure de vie,...).

I- Dclarations locales
Dans tout bloc d'instructions, avant la premire instruction, on peut dclarer des variables. Elles seront alors "locales au bloc" :elles n'existent qu' l'intrieur du bloc. Ces variables sont mises en mmoire dans une zone de type "pile" : quand, l'excution, on arrive sur le dbut du bloc ({), on rserve la mmoire ncessaire aux variables locales au sommet de la pile (ce qui en augmente la hauteur), et on les retire en quittant le bloc. L'intrt consiste n'utiliser, un instant donn, que la quantit ncessaire de mmoire, qui peut donc resservir par aprs pour d'autres variables. Le dsavantage (rarement gnant et pouvant tre contrecarr par la classe STATIC) est qu'en quittant le bloc (par } ou un branchement), et y entrant nouveau plus tard (par son {), les variables locales ne sont plus ncessairement recres au mme endroit, et n'auront plus le mme contenu. De plus la libration/rservation de la pile aura fait perdre un peu de temps. Par contre, lorsque l'on quitte temporairement un bloc (par appel une fonction), les variables locales restent rserves. La sortie d'un bloc par un branchement gre la libration des variables locales, mais seule l'entre dans un bloc par son { gre leur cration. Exemple : #include <stdio.h> int doubl(int b) {int c; c=2*b; b=0; return(c); } int main(void) { int a=5; printf("%d %d\n",doubl(a),a); } A l'entre du bloc main, cration de a, qui l'on donne la valeur 5. Puis appel de doubl : cration de b au sommet de la pile, on lui donne la valeur de a. Puis entre dans le bloc, cration sur la pile de c, on lui donne la valeur b*2=10, on annule b (mais pas a), on rend 10 la fonction appelante, et on libre le sommet de la pile (c et b n'existent plus) mais a reste (avec son ancienne valeur) jusqu' la sortie de main. On affichera donc : 10 5. Une variable locale est cre l'entre du bloc, et libre la sortie. Cette priode est appele sa dure de vie. Mais pendant sa dure de vie, une variable peut tre visible ou non. Elle est visible : dans le texte source du bloc d'instruction partir de sa dclaration jusqu'au }, mais tant qu'une autre variable locale de mme nom ne la cache pas. Par contre elle n'est pas visible dans une fonction appele par le bloc (puisque son code source est hors du bloc). autre exemple : int main(void); {int a=1; {int b=2; {int a=3; fonction(a);

[1] [2] [3] [4]

15

[5] [6] } [7] } [8] int fonction (int b) [a] {int c=0; [b] c=b+8; [c] } [d] analysons progressivement l'volution de la pile au cours du temps (en gras : variable visible) : [1] a=1 [2] a=1 | b=2 [3] a=1 | b=2 | a=3 : seul le a le plus haut est visible (a=3), l'autre vit encore (valeur 1 garde) mais n'est plus visible. [4a] a=1 | b=2 | a=3 | b=3 : entre dans la fonction, recopie de l'argument rel (a) dans l'argument formel (b). Mais a n'est plus visible. [4b] a=1 | b=2 | a=3 | b=3 | c=0 [4c] a=1 | b=2 | a=3 | b=3 | c=11 : quand le compilateur cherche la valeur de b, il prend la plus haute de la pile donc 3 (c'est la seule visible), met le rsultat dans le c le plus haut. L'autre b n'est pas modifi. [4d] a=1 | b=2 | a=3 : suppression des variables locales b et c du sommet de la pile [5] a=1 | b=2 : sortie de bloc donc libration de la pile [6a] a=1 | b=2 | b=1 : l'argument rel (a) n'est plus le mme qu'en [4a] [6b] a=1 | b=2 | b=1 | c=0 [6c] a=1 | b=2 | b=1 | c=9 [6d] a=1 | b=2 : suppression b et c [7] a=1 [8] la pile est vide, on quitte le programme Notez que la rservation et l'initialisation prennent un peu de temps chaque entre du bloc. Mais ne prsumez jamais retrouver une valeur sur la pile, mme si votre programme n'a pas utilis la pile entre temps (surtout sur systme multitche ou avec mmoire virtuelle). Une dclaration a toujours la structure suivante : [classe] type liste_variables [initialisateur]; (entre [] facultatif) Le type peut tre simple (char, int, float,...) ou compos (tableaux, structures..., voir plus loin). La liste_variables est la liste des noms des variables dsires, spares par des virgules s'il y en a plusieurs. Chaque nom de la liste peut tre prcds d'une *, ceci spcifiant un pointeur. L'initialisateur est un signe =, suivi de la valeur donner la variable lors de sa cration ( chaque entre du bloc par exemple). La valeur peut tre une constante, mais aussi une expression avec des constantes voire des variables (visibles, dj initialises). La classe peut tre :

} fonction(a);

auto (ou omise, c'est la classe par dfaut pour les variables locales) : la variable est cre l'entre du bloc (dans la pile) et libre automatiquement sa sortie (comme expliqu plus haut). register : la variable est cre, possde la mme dure de vie et visibilit qu'une classe auto, mais sera place dans un registre du (micro)processeur. Elle sera donc d'un accs trs rapide. Si tous les registres sont dj utiliss, la variable sera de classe auto. Mais le compilateur peut avoir besoin

16

des registres pour ses besoins internes ou pour les fonctions des bibliothques, s'il n'en reste plus le gain peut se transformer en perte. De plus les compilateurs optimiss choisissent de mettre en registre des variables auto, et souvent de manire plus pertinente que vous. Mais le compilateur ne sait pas l'avance quelles fonctions seront appeles le plus souvent, dans ce cas une optimisation manuelle peut tre utile, par exemples dans le cas des lments finis o une mme instruction peut tre rpte des milliards de fois. static : la variable ne sera pas dans la pile mais dans la mme zone que le code machine du programme. Sa dure de vie sera donc celle du programme. Elle ne sera initialise qu'une fois, au dbut du programme, et restera toujours rserve. En retournant dans un bloc, elle possdera donc encore la valeur qu'elle avait la prcdente sortie. Sa visibilit reste la mme (limite au bloc). Une variable statique permet en gnral un gain en temps d'excution contre une perte en place mmoire.

II- Dclarations globales


Une dclaration faite l'extrieur d'un bloc d'instructions (en gnral en dbut du fichier) est dite globale. La variable est stocke en mmoire statique, sa dure de vie est celle du programme. Elle est visible de sa dclaration jusqu' la fin du fichier. Elle sera initialise une fois, l'entre du programme (initialise 0 si pas d'autre prcision). Le format d'une dclaration globale est identique une dclaration locale, seules les classes varient. Par dfaut, la variable est publique, c'est dire qu'elle pourra mme tre visible dans des fichiers compils sparment (et relis au link). La classe static, par contre, rend la visibilit de la variable limite au fichier actuel. La classe extern permet de dclarer une variable d'un autre fichier (et donc ne pas lui rserver de mmoire ici, mais la rendre visible). Elle ne doit pas tre initialise ici. Une variable commune plusieurs fichiers devra donc tre dclare sans classe dans un fichier (et y tre initialise), extern dans les autres. Toute fonction, pour pouvoir tre utilise, doit auparavant tre soit dclare, soit dfinie. On dtaillera sa dfinition au paragraphe suivant. Une dclaration de fonction est gnralement globale, et alors connue des autres fonctions. Une dclaration de fonction est appele "prototype". Le prototype est de la forme : [classe] type_retourn nom_fonction(liste_arguments); elle est donc identique l'entte de la fonction mais : - est termine par un ; comme toute dclaration - les noms des arguments n'ont pas besoin d'tre les mmes, il peuvent mme tre omis (les types des arguments doivent tre identiques). Sans prcision de classe, la fonction est publique. Sinon, la classe peut tre extern (c'est ce que l'on trouve dans les fichiers .H) ou static (visibilit limite au fichier). Le prototype peut tre utilis pour utiliser une fonction du mme fichier, mais avant de l'avoir dfinie (par exemple si l'on veut main en dbut du fichier). En gnral, lorsque l'on cre une bibliothque (groupe de fonctions et variables regroupes dans un fichier compil sparment), on prpare un fichier regroupant toutes les dclarations extern, not .H, qui pourra tre inclus dans tout fichier utilisant la bibliothque. exemples de dclarations globales : int i,j; /* publiques, initialises 0 */ static int k=1; /* prive, initialise 1 */ extern int z; /* dclare (et initialise) dans un autre fichier */ float produit(float,float); /* prototype d'une fonction dfinie plus

17

loin dans ce fichier */ extern void change(int *a, int *b); /* prototype d'une fonction dfinie dans un autre fichier */ Avant la norme ANSI, le prototype n'existait pas. Une fonction non dfinie auparavant tait considre comme rendant un int (il fallait utiliser un cast si ce n'tait pas le cas).

III- Dclaration de type


La norme ANSI permet de dfinir de nouveaux types de variables par typedef. structure : typedef type_de_base nouveau_nom; Ceci permet de donner un nom un type donn, mais ne cre aucune variable. Une dclaration typedef est normalement globale et publique. exemple : typedef long int entierlong; /* dfinition d'un nouveau type */ entierlong i; /* cration d'une variable i de type entierlong */ typedef entierlong *pointeur; /* nouveau type : pointeur = pointeur d'entierlong */ pointeur p; /* cration de p (qui contiendra une adresse), peut tre initialis par =&i */ Remarques : Le premier typedef pouvait tre remplac par un #define mais pas le second. L'utilisation du typedef permet de clarifier les programmes, puisqu'on peut dfinir un nom de type pour dclarer des variables dont la fonction sera clairement dtaille : typedef float prix; typedef float quantite; prix a,b,c; quantite x,y; Une autre utilisation importante est pour dfinir un type complexe, o l'on dfinira petit petit les composants (exemple tableau de pointeurs de structures, on dfinit d'abord la structure, puis le pointeur de structure, puis le tableau de tout cela).

18

Fonctions
I- Dfinitions gnrales
Une fonction est dfinie par son entte, suivie d'un bloc d'instructions entte : type_retourn nom_fonction(liste_arguments) (pas de ;) Avant la norme ANSI, le type_retourn pouvait tre omis si int. Dsormais il est obligatoire, si la fonction ne retourne rien on indique : void. La liste_arguments doit tre type (ANSI), alors qu'auparavant les types taient prciss entre l'entte et le bloc : ANSI: float truc(int a, float b) {bloc} K&R: float truc(a,b) int a;float b; {bloc} Si la fonction n'utilise pas d'arguments il faut la dclarer (ANSI) nom(void) ou (K&R) nom(). L'appel se fera dans les deux cas par nom() (parenthses obligatoires). Les arguments (formels) sont des variables locales la fonction. Les valeurs fournies l'appel de la fonction (arguments rels) y sont recopis l'entre dans la fonction. Les instructions de la fonction s'excutent du dbut du bloc ({) jusqu' return(valeur) ou la sortie du bloc (}). La valeur retourne par la fonction est indique en argument de return. exemples : 1) fonction ne recevant aucun argument, et ne retournant rien. Ce n'est pas parce qu'elle ne retourne rien qu'elle ne fait rien, ici elle affiche un message l'cran. Elle ne reoit aucun argument, ici parce qu'elle n'en a pas besoin, quelquefois c'est parce qu'elle n'utilise que des variables globales (chose que je ne conseille pas).
void message(void) { int i; for(i=0;i<10;i++)printf("bonjour monde\n"); }

On appelle cette fonction par message(); Une fonction possde ses propres variables locales (ici i), mais n'a pas accs aux variables locales des autres fonctions, pas mme celles de main (mais aux variables globales, que je dconseille fortement au dbutant) 2) fonction recevant un (ou plusieurs) argument(s), et ne retournant rien. Une telle fonction a besoin d'informations pour effectuer sa tche, ce qui est le cas le plus courant.
void insiste(int combien_de_fois,char * compliment) { int i; printf("Oh que tu es "); for(i=0;i<combien_de_fois;i++)printf("%s ",compliment); printf("!!!!! \n); }

19

On appelle cette fonction par exemple par insiste(10,"belle"); ou int j=3; char i[]="fatiguant"; insiste(j*2,i); Tout ce qui importe l'appel est qu'on donne en premier argument une valeur entire et en second une chane de caractres. Leurs noms n'ont aucun rapport avec ceux utiliss dans la fonction. 3) fonction recevant un (ou plusieurs, ou aucun) argument(s), et retournant une valeur. Une telle fonction a besoin (ou non) d'informations pour effectuer sa tche, mais quand la tche est termine (en gnral c'tait un calcul), elle rend (retourne) le rsultat (une et une seule valeur). Je vous rapelle que les variables locales de la fonction ne sont pas visibles l'extrieur de la fonction, sans cela les calculs auraient t inutiles.
float produit(float a;float b) { float z; z=a*b; return(z); }

On appelle cette fonction dans une expression ncessitant un float : x=produit(2,4.5); ou


y=2+produit(sin(x)+produit(1.12,X),produit(2,2)) %f\n",z,produit(z,2));

ou printf("le

double de %f est

4) que fait-on pour retourner plusieurs valeurs ? C'est impossible directement. On peut retourner un pointeur qui permet de retourner l'adresse d'un ensemble de valeurs (tableau ou structure). On peut aussi transmettre des "rcipients" pour les valeurs rsultantes, grce au passage d'arguments par adresse. On pourait aussi utiliser des variables globales, mais ce qui prouve que vous tes un programmeur expriment est que vous savez que c'est une trs mauvaise solution.

II- Rcursivit, gestion de la pile


Une fonction peut s'appeler elle-mme :
int factorielle(int i) { if (i>1) return(i*factorielle(i-1)); else return(1); }

analysons la pile en appelant factorielle(3) : i=1 i=2 i=2 i=2 i=3 i=3 i=3 i=3 i=3 (a) (b) (c) (d) (e)

(a) appel de factorielle(3), cration de i, qui on affecte la valeur 3. comme i>1 on calcule i*factorielle(i-1) : i=3,i-1=2 on appelle factorielle(2) (b) cration i, affect de la valeur 2, i>1 donc on appelle factorielle(1) (c) cration de i, i=1 donc on quitte la fonction, on libre le pile de son sommet, on retourne o la fonction factorielle(1) a t appele en rendant 1. (d) on peut maintenant calculer i*factorielle(1), i (sommet de la pile) vaut 2, factorielle(1) vaut 1, on peut rendre 2, puis on "dpile" i (e) on peut calculer i*factorielle(2), i vaut 3 (sommet de la pile), factorielle(2) vaut 2, 3*2=6, on retourne 6, la pile est vide et retrouve sont tat initial.

20

Attention, la rcursivit est gourmande en temps et mmoire, il ne faut l'utiliser que si l'on ne sait pas facilement faire autrement :
int factorielle(int i) { int result; for(result=1;i>1;i--) result*=i; return(result); }

III- Arguments passs par adresse


Imaginons la fonction :
void change(int i;int j) {int k;k=i;i=j;j=k;}

Lors d'un appel cette fonction par change(x,y), les variables locales i,j,k sont cres sur la pile, i vaut la valeur de x, j celle de y. Les contenus de i et j sont changs puis la pile est libre, sans modifier x et y. Pour rsoudre ce problme, il faut passer par des pointeurs. On utilisera les oprateurs unaires : & (adresse de) et * (contenu de). Dfinissons donc la fonction ainsi :
void change(int *i;int *j) {int k;k=*i;*i=*j;*j=k;}

On appelle la fonction par change(&x,&y); les deux arguments formels de la fonction (i et j) sont des pointeurs sur des int, c'est dire qu' l'appel, on cre sur la pile des variables i et j pouvant contenir une adresse, dans i on recopie l'argument rel qui et l'adresse de x, et l'adresse de y dans j. en entrant dans le bloc, on cre une variable locale k pouvant contenir un entier. Puis on met dans k non pas la valeur de i mais le contenu point par i (donc ce qui se trouve l'adresse marque dans i, qui est l'adresse de x), donc le contenu de x. On place la valeur pointe par j (donc y) l'adresse pointe par j (donc x). Puis on place la valeur de k l'adresse pointe par j (y). On a donc chang x et y. On a tout intrt essayer cet exemple en se fixant des adresses et des valeurs, et voir l'volution des contenus des variables. En conclusion, pour effectuer un passage d'argument par adresse, il suffit d'ajouter l'oprateur & devant l'argument rel ( l'appel de la fonction), et l'oprateur * devant chaque apparition de l'argument formel, aussi bien dans l'entte que le bloc de la fonction.

IV- La fonction main


Si on le dsire, la fonction main peut rendre un entier (non sign) au systme d'exploitation (sous MSDOS, on rcupre cette valeur par ERRORLEVEL). Une valeur 0 signale en gnral une fin normale du programme, sinon elle reprsente un numro d'erreur. L'arrive sur le } final retourne la valeur 0, dans le cas o on n'a pas indiqu de return(code). De mme, le systme d'exploitation peut transmettre des arguments au programme. La dclaration complte de l'entte de la fonction main est :
int main(int argc,char *argv[], char *env[])

Le dernier argument est optionnel.

21

On peut aussi utiliser char **argv, mais cela peut paratre moins clair. argc indique le nombre de mots de la ligne de commande du systme d'exploitation, argv est un tableau de pointeurs sur chaque mot de la ligne de commande, env pointe sur les variables de l'environnement (sous MSDOS obtenues par SET, sous UNIX par env). Si votre programme s'appelle COPIER, et que sous votre systme vous ayez entr la commande COPIER TRUC MACHIN alors argc vaut 3, argv[0] pointe sur "COPIER", argv[1] sur "TRUC" et argv[2] sur "MACHIN". argv[3] vaut le pointeur NULL. env est un tableau de pointeurs sur les variables d'environnement, on n'en connat pas le nombre mais le dernier vaut le pointeur NULL.

V- Fonction retournant un pointeur et pointeur de fonction


type*fonc(arguments) est une fonction qui renvoie un pointeur. exemple :
int *max(int tableau[], int taille) { int i,*grand; for(grand=tableau,i=1;i<taille;i++) if(tableau[i]>*grand) grand=tableau+i; return(grand); }

Cette fonction rend l'adresse du plus grand entier du tableau. type (*fonc)(arguments) est un pointeur sur une fonction exemple :
int max(int,int); int min(int,int); int main(void); { int (*calcul)(int,int); /* calcul est un pointeur donc une variable qui peut tre locale */ char c; puts("utiliser max (A) ou min (I) ?"); do c=getchar(); while ((c!='A')&&(c!='I')); calcul=(c=='A')?&max:&min; printf("%d\n",(*calcul)(10,20)); } int max(int a,int b) {return(a>b?a:b);} int min(int a,int b) {return(a<b?a:b);}

Cette fonctionnalit du C est assez peu utilise, mais est ncessaire dans les langages orients objets.

22

Les types de donnes du C


Nous allons dfinir les diffrents types de variables existantes en C. On verra les types scalaires (entiers,...) et les types agrgs (combinaisons de scalaires, tableaux par exemple).

I- Variables scalaires
On appelle variable scalaire une variable ne contenant qu'une seule valeur, sur laquelle on pourra faire un calcul arithmtique. On possde trois types de base (char, int, float) que l'on peut modifier par 3 spcificateurs (short, long, unsigned).

1- char : caractre (8 bits)


Une constante caractre est dsigne entre apostrophes (simples quotes). 'a' correspond un octet (alors que "a" deux octets : 'a' et '\0'. On peut dfinir certains caractres spciaux, par le prfixe \ (antislash) :

\n nouvelle ligne \t tabulation \b backspace \r retour chariot (mme ligne) \f form feed (nouvelle page) \' apostrophe \\ antislash \" double quote \0 nul \nombre en octal sur 3 chiffres (ou moins si non suivi d'un chiffre). \xnombre : en hexa

Les char sont considrs dans les calculs comme des int (on considre leur code ASCII). Par dfaut en C un char est sign donc peut varier de -128 +127. Pour utiliser les caractres spciaux du PC (non standard), il vaut mieux utiliser des unsigned char (de 0 255). Mais le comme C fait les calculs modulo 256, a marche presque pareil.

2- int : entier
Si l'on dsire une taille prcise, utiliser short int (16 bits) ou long int (32 bits). Sans prcision, int donnera les programmes les plus rapides pour une machine donne (int = short sur PC, mais long sur les stations 32 bits). Par dfaut, les int sont signs, mais on peut prciser unsigned int. Dsormais certains compilateurs considrent short comme 16 bits, int comme 32 bits et long comme 64 bits.

3- float : rel
Un flottant est un nombre stock en deux parties, une mantisse et un exposant. La taille de la mantisse dfinit le nombre de chiffres significatifs, alors que la taille de l'exposant dfinit le plus grand nombre acceptable par la machine. Les oprations sur les rels sont plus lents que sur les entiers. Pour une addition par exemple, il faut d'abord dcaler la mantisse pour galiser les exposants puis faire l'addition. Les rels sont toujours signs. On peut par contre utiliser le spcificateur long pour des rels avec une

23

prcision accrue. On peut galement utiliser le nom double au lieu de long float. Certains compilateurs acceptent mme des long double (quadruple prcision).

4- Tailles et plages
taille (en bits) plage de valeurs type char 8 -128 +127 unsigned char 8 0 255 short (short int) 16 -32768 32767 unsigned short 16 0 65535 long (long int) 32 -2.147.483.648 2.147.483.647 unsigned long 32 0 4.294.967.295 float 32 -3.4e38 3.4e38 (7 chiffres significatifs) double (long float) 64 -1.7e308 1.7e308 (15 chiffres significatifs) long double (non standard) 80 ou 128 a dpend

5- Conversions de type / cast


Dans les calculs, les char sont automatiquement transforms en int. Quand un oprateur possde des arguments de type diffrent, une transformation de type est effectue automatiquement, suivant l'ordre : char -> int -> long -> float -> double signed -> unsigned Attention la transformation n'est effectue que le plus tard possible, si ncessaire. 5/2+3.5 donnera donc 5.5. De plus les oprations arithmtiques sont toujours effectues sur des long ou double, pour une prcision maximale quels que soient les rsultats intermdiaires (voir exemples au chapitre expressions arithmtiques). On peut forcer une transformation en utilisant le cast, qui est un oprateur unaire. La syntaxe est : (type_rsultat) valeur__transformer exemple : {float x;int a=5; x=(float)a;} Un cast transformant un rel en entier prendra la partie entire. Cette transformation doit tre explicite, elle est impossible implicitement. Pour obtenir l'entier le plus proche , utiliser (int)(rel_positif+0.5). Il faut bien noter que le cast n'est une opration de transformation que pour les types scalaires, pour tous les autres types, le cast ne permet que de faire croire au compilateur que la variable est d'un autre type que ce qu'il attendait, pour qu'il n'mette pas de message d'erreur ( utiliser avec grande prudence).

6- Enumrations
On peut dfinir un nouveau type (d'entiers) sous la forme : [classe] enum nomdutype {liste_valeurs} [liste_variables]; ([] facultatifs)
exemple : enum jour {lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche};

24

On a cr un nouveau type. toute variable de type jour pourra valoir soit lundi, soit mardi, etc... On peut directement mettre la liste des variables crer (entre le "}" et le ";"), ou indiquer :
enum nomdutype liste_variables;

exemple : enum jour aujourd_hui=mardi;) En fait le type jour est un type int, avec lundi=0, mardi=1,... On peut donc faire toutes les oprations entires (aujourd_hui++ par exemple). Il n'y a aucun test de validit (dimanche+1 donne 7). Ceci permet de rendre les programmes plus clairs. On obtiendrait un rsultat quivalent avec #define. Attention, printf affichera un entier, mais on peut faire:
char *nom[7]={"lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"," dimanche"}; puis printf("%s",nom[aujourd_hui]);

On peut aussi prvoir une codification non continue :


enum truc {a=4,b,c,d=2,e,f} : d=2,e=3,f=a=4,b=5,c=6

En utilisant typedef, on n'a pas besoin de rpter enum dans la dclaration de variables :
typedef enum {coeur,carreau,pique,trfle}couleurs; couleurs i,j,k;

25

Tableaux
I- Tableaux unidimensionnels
Un tableau est un regroupement, dans une mme variable, de plusieurs variables simples, toutes de mme type. dclaration : [classe] type nom [nombre_d'lments]; exemple : int tab[10]; Ceci rserve en mmoire un espace contigu pouvant contenir 10 entiers. Le premier est tab[0], jusqu' tab[9]. Attention, en utilisant tab[10] ou plus, aucune erreur ne sera signale et vous utiliserez une partie de mmoire qui a certainement t rserve pour autre chose. Il est possible de dfinir un tableau de n'importe quel type de composantes (scalaires, pointeurs, structures et mme tableaux). Il est galement possible de dfinir un type tableau par typedef :
typedef float vecteur[3]; vecteur x,y,z;

On peut aussi initialiser un tableau. Dans ce cas la dimension n'est pas ncessaire. Mais si elle est donne, et est suprieure au nombre de valeurs donnes, les suivantes seront initialises 0 :
vecteur vect0={0,0,0}; int chiffres[]={0,1,2,3,4,5,6,7,8,9}; int tableau[20]={1,2,3}; /* les 17 autres 0 */

On peut galement dclarer un tableau sans en donner sa dimension. Dans ce cas l le compilateur ne lui rserve pas de place, elle aura du tre rserve autre part (par exemple tableau externe ou argument formel d'une fonction). Exercice (moyenne) : Ecrire le programme qui lit une liste de Nb nombres, calcule et affiche la moyenne puis l'cart entre chaque note et cette moyenne. Solution.
#include <stdio.h> #define max 100 typedef float tableau[max]; tableau tab; int Nb; void lecture(void) { int i; puts("entrez le nombre de notes traiter :"); do scanf("%d",&Nb); while ((Nb<=0)||(Nb>max)); for(i=0;i<Nb;i++) { printf("valeur n %d ? ",i+1); scanf("%f",&tab[i]); } } float moyenne(void) { int i; float somme=0; for (i=0;i<Nb;i++) somme+=tab[i];

26

return(somme/Nb); } void affichage(float moy) { int i; printf("la moyenne des %d notes est %f\n",Nb,moy); for(i=0;i<Nb;i++) printf("%dime note : cart : %f\n",i+1,tab[i]-moy); } void main(void) { lecture(); affichage(moyenne()); }

II- Tableaux et pointeurs / arithmtique des pointeurs


En dclarant, par exemple, int TAB[10]; l'identificateur TAB correspond en fait l'adresse du dbut du tableau. Les deux critures TAB et &TAB[0] sont quivalentes (ainsi que TAB[0] et *TAB). On dfinit l'opration d'incrmentation pour les pointeurs par TAB+1=adresse de l'lment suivant du tableau. L'arithmtique des pointeurs en C a cette particularit que l'opration dpend du type de variable pointe, ajouter 1 consistant ajouter l'adresse la taille de l'objet point. On dfinit donc l'addition (pointeur+entier): TAB+i=&TAB[i], la soustraction (pointeur - entier), mais galement la soustraction (pointeur - pointeur) qui donne un nombre d'lments. Les oprations de comparaisons entre pointeurs sont donc galement possibles. Dclarons : int TAB[10],i,*ptr; Ceci rserve en mmoire - la place pour 10 entiers, l'adresse du dbut de cette zone est TAB, - la place pour l'entier i, - la place pour un pointeur d'entier (le type point est important pour dfinir l'addition). Analysons les instructions suivantes :
ptr=TAB; /*met l'adresse du dbut du tableau dans ptr*/ for(i=0;i<10;i++) { printf("entrez la %dime valeur :\n",i+1); /* +1 pour commencer 1*/ scanf("%d",ptr+i); /* ou &TAB[i] puisque scanf veut une adresse*/ } puts("affichage du tableau"); for(ptr=TAB;ptr<TAB+10 /* ou &TAB[10] */;ptr++) printf("%d ",*ptr); puts(" "); /* attention actuellement on pointe derrire le tableau ! */ ptr-=10; /* ou plutt ptr=TAB qui lui n'a pas chang */ printf("%d",*ptr+1); /* affiche (TAB[0])+1 */ printf("%d",*(ptr+1)); /* affiche TAB[1] */ printf("%d",*ptr++); /* affiche TAB[0] puis pointe sur TAB[1] */ printf("%d",(*ptr)++); /* affiche TAB[1] puis ajoute 1 TAB[1]*/

TAB est une "constante pointeur", alors que ptr est une variable (donc TAB++ est impossible). La dclaration d'un tableau rserve la place qui lui est ncessaire, celle d'un pointeur uniquement la place d'une adresse.

27

Pour passer un tableau en argument d'une fonction, on ne peut que le passer par adresse (recopier le tableau prendrait de la place et du temps). exemple utilisant ces deux critures quivalentes :
#include <stdio.h> void annule_tableau(int *t,int max) { for(;max>0;max--)*(t++)=0; } void affiche_tableau(int t[], int max) { int i; for(i=0;i<max;i++) printf("%d : %d\n",i,t[i]); } int main(void) { int tableau[10]; annule_tableau(tableau,10); affiche_tableau(tableau,10); }

Exercice (rotation) : Ecrire un programme qui lit une liste de Nb nombres, la dcale d'un cran vers le haut (le premier doit se retrouver en dernier), l'affiche puis la dcale vers le bas. On pourra dcomposer le programme en fonctions. Solution.
#include <stdio.h> typedef int composante; void lecture(composante *t,int *nb) { int i; puts("nombre de valeurs entrer ? "); scanf("%d",nb); for(i=1;i<=*nb;i++) { printf("%dime valeur : ",i); scanf("%d",t++); } } void affiche(composante *t, int max) { int i; for(i=0;i<max;i++) printf("%d ",*t++); puts(" "); } void decale_bas(composante *deb, int max) { composante c,*t; t=deb+(max-1); c=*t; while (t>deb) {*t=*(t-1);t--;} *t=c; } void decale_haut(composante *t, int nb) { composante c;int i; c=*t; for (i=1;i<nb;i++) {*t=*(t+1);t++;} *t=c; } void main(void)

28

{ composante tableau[100]; int nombre; lecture(tableau,&nombre); puts("tableau initial :"); affiche(tableau,nombre); decale_haut(tableau,nombre); puts("dcalage vers le haut :"); affiche(tableau,nombre); decale_bas(tableau,nombre); puts("dcalage vers le bas :"); affiche(tableau,nombre); }

Exercice (classer) : Classer automatiquement un tableau de Nb entiers puis l'afficher dans l'ordre croissant puis dcroissant. On pourra utiliser des fonctions de l'exercice prcdent. On pourra crer un (ou plusieurs) tableau temporaire (donc local). Si vous vous en sentez la force, prvoyez le cas de valeurs gales. Solution.
#include <stdio.h> #define dim 100 typedef int composante; void lecture(composante *t,int *nb) { int i; puts("nombre de valeurs entrer ? "); scanf("%d",nb); for(i=1;i<=*nb;i++) { printf("%dime valeur : ",i); scanf("%d",t++); } } void affiche(composante *t, int max) { int i; for(i=0;i<max;i++) printf("%d ",*t++); puts(" "); } int indice_min(composante t[],int indice_dep, int nb_indices) /* cherche l'indice de la valeur du tableau : * -soit gale t[indice_dep], mais d'indice > indice_dep; * -soit la plus petite mais >t[indice_deb] */ { int i,indice_resultat=-1; for(i=indice_dep+1;i<nb_indices;i++) if (t[i]==t[indice_dep]) return(i); /* si on est encore l c'est qu'il n'y en pas d'gal */ for(i=0;i<nb_indices;i++) if ((t[i]>t[indice_dep]) && ((indice_resultat<0) || (t[i]<t[indice_resultat]))) indice_resultat=i; return(indice_resultat); } void copier(composante *source, composante *dest, int nb) /* copie le tableau source dans le tableau dest */ { int i; for(i=0;i<nb;i++) *(dest++)=*(source++);

29

} void classe(composante tab[], int nb) { composante tempo[dim]; /* un malloc(sizeof(composante)*nb) aurait t mieux mais on n'en a pas encore parl en cours */ int i,ind_faits,indice; /* 1er : recherche du plus petit, le 1er si ex aequo */ indice=0; for(i=1;i<nb;i++) if(tab[i]<tab[indice]) indice=i; tempo[ind_faits=0]=tab[indice]; /* les suivants : recherche le + petit mais > au prcdent */ for(ind_faits=1;ind_faits<nb;ind_faits++) { indice=indice_min(tab,indice,nb); tempo[ind_faits]=tab[indice]; } copier(tempo,tab,nb); } void main(void) { composante tableau[dim]; int nombre; lecture(tableau,&nombre); puts("tableau initial :"); affiche(tableau,nombre); classe(tableau,nombre); puts("tableau class :"); affiche(tableau,nombre); }

III- Chanes de caractres


En C, comme dans les autres langages, certaines fonctionnalits ont t ajoutes aux tableaux dans le cas des tableaux de caractres. En C, on reprsente les chanes par un tableau de caractres, dont le dernier est un caractre de code nul (\0). Une constante caractres est identifie par ses dlimiteurs, les guillemets " (double quote). exemples :
puts("salut"); char mess[]="bonjour"; /* vite de mettre ={'b','o',..,'r','\0'} */ puts (mess);

mess est un tableau de 8 caractres (\0 compris). On peut au cours du programme modifier le contenu de mess, condition de ne pas dpasser 8 caractres (mais on peut en mettre moins, le \0 indiquant la fin de la chane). Mais on peut galement initialiser un pointeur avec une chane de caractres :
char *strptr="bonjour";

Le compilateur cre la chane en mmoire de code (constante) et une variable strptr contenant l'adresse de la chane. Le programme pourra donc changer le contenu de strptr (et donc pointer sur une autre chane), mais pas changer le contenu de la chane initialement cre. Exercice (chanes) : crire un programme qui dtermine le nombre et la position d'une sous-chane dans une chane (exemple ON dans FONCTION : en position 1 et 6). Solution.
#include <stdio.h>

30

#define taille 255 int debut_egal(char *ch,char *sch) /* rpond si sch est exactement le dbut de ch */ { while (*sch && *ch) { if (*sch!=*ch) return(0); else {ch++;sch++;} } return(!(*sch)); } void recherche(char *ch,char *sch) { int pos=0; while (*ch) { if (debut_egal(ch,sch)) printf("trouv en position %d\n",pos); ch++;pos++; } } void main(void) { char ch[taille],sch[taille]; puts("chane tester ? "); gets(ch); puts("sous-chane trouver ?"); gets(sch); recherche(ch,sch); }

IV- Bibliothques de fonctions pour tableaux et chanes


Toutes les fonctions standard d'entre / sortie de chanes considrent la chane termine par un \0, c'est pourquoi en entre elles rajoutent automatiquement le \0 (gets, scanf), en sortie elles affichent jusqu'au \0 (puts, printf). La bibliothque de chanes (inclure string.h) possde des fonctions utiles la manipulation de chanes :
int strlen(chane) donne la longueur de la chane (\0 non compris)

char *strcpy(char *destination,char *source) recopie la source dans la destination, rend un pointeur sur la destination (dest=source quand lui ne recopie pas la chane mais uniquement son adresse !). Faites attention de ne pas dpasser la dimension de la destination, sinon utilisez le suivant. char *strncpy(char *destination,char *source,int longmax) idem strcpy mais s'arrte au \0 ou longmax (qui doit comprendre le \0) char *strcat(char *destination,char *source) recopie la source la suite de la destination, rend un pointeur sur la destination char *strncat(char *destination,char *source,int longmax) idem, mais plus sr int strcmp(char *str1,char*str2) rend 0 si str1= =str2, <0 si str1<str2, >0 si str1>str2 (class alphabtiquement, donc test du premier caractre, si diffrents test du second, etc...) Les majuscules sont diffrentes des minuscules (infrieures). Idem pour strncmp, qui lui vrifie la dimension.

Des fonctions similaires, mais pour tous tableaux (sans s'arrter au \0) sont dclares dans mem.h. La longueur est donner en octets (on peut utiliser sizeof) :

int memcmp(void *s1,void *s2,int longueur);

31

void *memcpy(void *dest,void *src,int longueur);

On possde galement des fonctions de conversions entre scalaires et chanes, dclares dans stdlib.h

int atoi(char *s) traduit la chane en entier (s'arrte au premier caractre impossible, 0 si erreur ds le premier caractre) de mme atol et atof

J'utilise galement sscanf et sprintf pour les conversions, qui offrent un maximum de possibilits. Dans ctype.h, on trouve des fonctions utiles (limites au caractres) :

int isdigit(int c) rend un entier non nul si c'est un chiffre ('0' '9'), 0 sinon. de mme : isalpha (A Z et a z, mais pas les accents), isalnum (isalpha||isdigit), isascii (0 127), iscntrl (0 31), islower (minuscule), isupper, isspace (blanc, tab, return...), isxdigit (0 9,A F,a f)... int toupper(int c) rend A Z si c est a z, rend c sinon (attention, les accents ne sont pas grs). Egalement tolower

V- Allocation dynamique de mmoire


La taille dclare d'un tableau est dfinie la compilation (alors que quand elle n'est pas constante il serait utile de la dfinir lors de l'excution du programme). Dans le cas d'une taille inconnue l'avance, il faut surdimensionner le tableau (et donc rserver des mmoires dont on ne servira que rarement, aux dpends d'autres variables ou tableaux). C'est pour cela qu'on les appelle des tableaux statiques. En C, le lien entre les tableaux et pointeurs permet la notion de tableaux dynamiques : on peut dfinir la dimension d'un tableau lors de l'excution. Il faut d'abord rserver une zone de mmoire contigu, de la taille dsire (mais pas plus). Il faut avoir dclar une variable pointeur qui contiendra l'adresse du dbut du tableau. A l'excution, lorsque l'on connat la taille dsir, on peut rserver une zone mmoire (dans la zone appele "tas" ou "heap") par les fonctions : - void *malloc(int taille) : rserve une zone mmoire contigu de taille octets, et retourne un pointeur sur le dbut du bloc rserv. Retourne le pointeur NULL en cas d'erreur (en gnral car pas assez de mmoire). - void *calloc(int nb, int taille) : quivalent malloc(nb*taille). exemple :
float *tab; int nb; puts("taille dsire ?"); scanf("%d",&nb); tab=(float*)calloc(nb,sizeof(float));

malloc et calloc ncessitent un cast pour que le compilateur ne signale pas d'erreur. - void free(void *pointeur) libre la place rserve auparavant par malloc ou calloc. Pointeur est l'adresse retourne lors de l'allocation. En quittant proprement le programme, la mmoire alloue est automatiquement restitue mme si on omet d'appeler free.

32

- void *realloc(void *pointeur,int taille) essaie, si possible, de rajuster la taille d'un bloc de mmoire dj allou (augmentation ou diminution de taille). Si ncessaire, le bloc est dplac et son contenu recopi. En retour, l'adresse du bloc modifi (pas ncessairement la mme qu'avant) ou le pointeur NULL en cas d'erreur. Ces fonctions sont dfinies dans stdlib.h ou alloc.h (suivant votre compilateur). Une fois la zone de mmoire rserve, et son adresse de dbut connue (supposons l'avoir stocke dans le pointeur nomm "tab" comme dcrit dans l'exemple ci-dessus), on peut y accder soit par "l'criture pointeurs" soit par "l'criture tableaux", puisqu'elles sont quivalentes :

premier lment : *tab ou tab[0] iime lment : *(tab+i) ou tab[i] adresse du iime lment (pour scanf par exemple) : tab+i ou &(tab[i])

On peut remarquer que ces tableaux ne sont pas totalement dynamiques : la dimension doit tre connue (et l'allocation faite) avant la premire utilisation du tableau. Elle est ensuite difficilement modifiable (du moins pour l'augmenter). On ne peut par exemple pas dire "entrez vos mots, terminez par une ligne vide", mais "combien de mots ?" puis "entrez vos mots". Une erreur frquente consiste "perdre" l'adresse du dbut de la zone alloue (par tab=quelque chose, ou parce que tab tait une variable locale qui ne vit plus) et donc il devient alors impossible d'accder au dbut de la zone, ni de la librer.

VI- Tableaux multidimensionnels


On peut dclarer par exemple int tab[2][3] : matrice de 2 lignes de 3 lments. Un tableau peut tre initialis : int t[2][3]={{1,2,3},{4,5,6}} mais cette criture est quivalente {1,2,3,4,5,6}, car il place dans l'ordre t[0][0], t[0][1], t[0][2], t[1][0], t[1][1], t[1][2], c'est dire ligne aprs ligne. Dans un tableau multidimensionnel initialis, la dimension la plus gauche peut tre omise (ici int t[][3]={...} tait la rigueur possible). t correspond l'adresse &t[0][0], mais t[1] est aussi un tableau (une ligne), donc dsigne l'adresse &t[1][0]. En fait, une matrice est un tableau de lignes. On peut expliciter cela par typedef (c'est d'aprs moi la meilleure solution pour dclarer des tableaux multidimensionnels) :
typedef int ligne[3]; typedef ligne matrice[2]; matrice t; /* 3 lignes au lieu d'une mais tellement plus clair */

En utilisant pointeurs et allocation dynamique, pour grer un tableau de NBLIG lignes de NBCOL lments, on peut :

soit crer une matrice complte : allocation par t=malloc(NBLIG* NBCOL* sizeof(lment)), accs l'lment l,c par *(t+l*NBCOL+c). soit crer un tableau de NBLIG pointeurs de lignes, puis chaque ligne sparment. Ceci permet une optimisation si les lignes n'ont pas toutes la mme longueur (traitement de textes par exemple) mais aussi de manipuler facilement les lignes (exemple : changer deux lignes sans recopier leur contenu). Cette mthode est plus rapide que la prcdente, car les adresses de chaque dbut de ligne sont immdiatement connues, sans calcul. soit utiliser des pointeurs de pointeurs (mme principe que le cas prcdent, mais remplacement du tableau de pointeurs (dimension prvue l'avance) par une allocation dynamique.

33

Exercice (matrices) : faire le calcul de multiplication d'une matrice (M lignes, L colonnes) par une matrice (L,N) : rsultat (M,N). Solution.
#include <stdio.h> #define DIM 10 typedef float ligne[DIM]; typedef ligne matrice[DIM]; typedef float *pointeur; void lecture(matrice t,int *lig,int *col) /* lit l'cran une matrice */ { int l,c; float f; puts("nombre de lignes de la matrice ?"); scanf("%d",lig); puts("nombre de colonnes de la matrice ?"); scanf("%d",col); for (l=0;l<*lig;l++) for(c=0;c<*col;c++) { printf("lment [%d,%d] ? ",l,c); scanf("%f",&f); t[l][c]=f; } } void zero(float *t) /* met toute la matrice 0 */ { int i; for(i=0;i<DIM*DIM;i++) *t++=0; } void unit(matrice t) /* remplit la matrice unit (1 diagonale, 0 autre) */ { int i; zero(t); for(i=0;i<DIM;i++) t[i][i]=1; } void init(matrice t) /* initialisation de matrice numlig.numcol */ { int i,j; for(i=0;i<DIM;i++) for(j=0;j<DIM;j++) t[i][j]=i+j/10.0; } void affiche(matrice t,int l,int c) /* puts("affichage du tableau ligne par ligne :"); */ { int i,j; for(i=0;i<l;i++) { printf("ligne %d : ",i); for(j=0;j<c;j++) printf("%3.1f ",t[i][j]); printf("\n"); } } void produit(matrice a,matrice b,matrice c,int m,int l,int n) /* calcul du produit */ { int im,il,in; zero(c); for(im=0;im<m;im++) for(in=0;in<n;in++)

34

for(il=0;il<l;il++) c[im][in]+=a[im][il]*b[il][in]; } void main(void) { int m,l,n,i; matrice a,b,c; lecture(a,&m,&l); lecture(b,&i,&n); if(i!=l) puts("calcul impossible : dimensions incompatibles"); affiche(a,m,l); puts("--- FOIS ---"); affiche(b,l,n); puts("--- FAIT ---"); produit(a,b,c,m,l,n); affiche(c,m,n); }

Exercice (dterminant) : crire un programme qui calcule le dterminant d'une matrice carre (N,N), sachant qu'il vaut la somme (sur chaque ligne) de l'lment de la ligne en 1re colonne par le dterminant de la sous-matrice obtenue en enlevant la ligne et la 1re colonne (en changeant le signe chaque fois). Le dterminant d'une matrice (1,1) est sont seul lment. On utilisera bien videment la rcursivit. Il existe (heureusement) d'autres mthodes plus rapides. Solution.
#include <stdio.h> #include <stdlib.h> #define DIM 10 typedef float ligne[DIM]; typedef ligne matrice[DIM]; typedef float *pointeur; long nb_appels; void lecture(matrice t,int *lig) /* lit une matrice (au clavier) */ { int l,c; float f; puts("dimension de la matrice ?"); scanf("%d",lig); for (l=0;l<*lig;l++) for(c=0;c<*lig;c++) { printf("lment [%d,%d] ? ",l,c); scanf("%f",&f); t[l][c]=f; } } void zero(matrice t,int dim) /* met toute la matrice 0 */ { int i,j; for(i=0;i<dim;i++)for(j=0;j<dim;j++) t[i][j]=0; } void unit(matrice t, int dim) /* remplit la matrice unit (1 diagonale, 0 autre) */ { int i; zero(t,dim);

35

for(i=0;i<dim;i++) t[i][i]=1; } void affiche(matrice t,int l) /* puts("affichage du tableau ligne par ligne :"); */ { int i,j; for(i=0;i<l;i++) { printf("ligne %d : ",i); for(j=0;j<l;j++) printf("%3.1f ",t[i][j]); printf("\n"); } } void copiesauflc(matrice source,matrice dest,int dim,int ligavirer) { int l,c,ld=0; for (l=0;l<dim;l++) if (l!=ligavirer) { for (c=1;c<dim;c++) dest[ld][c-1]=source[l][c]; ld++; } } float determinant(matrice m,int dim) { matrice sous_m; int l,signe=1; float det=0; nb_appels++; if (dim==1) return(m[0][0]); for(l=0;l<dim;l++) { copiesauflc(m,sous_m,dim,l); det+=signe*m[l][0]*determinant(sous_m,dim-1); signe=-signe; } return(det); } void produit(matrice a,matrice b,matrice c,int dim) /* calcul du produit */ { int im,il,in; zero(c,dim); for(im=0;im<dim;im++) for(in=0;in<dim;in++) for(il=0;il<dim;il++) c[im][in]+=a[im][il]*b[il][in]; } void main(int argc,char *argv[]) { int taille; matrice mat; /* lecture(mat,&taille); */ taille=atoi(argv[1]); /* test avec matrice unit, */ unit(mat,taille); /* au moins je connais le rsultat*/ affiche(mat,taille); printf("dterminant : %20.17f ",determinant(mat,taille)); printf(" en %ld appels\n",nb_appels); }

36

Structures et unions
Dans un tableau, tous les constituants doivent obligatoirement tre du mme type. Ce n'est pas le cas des structures, qui sont des variables composes de plusieurs variables (ou CHAMPS) de types diffrents. Chaque champIl es n'est plus dsign par un numro comme dans un tableau, mais par un identificateur.

I- Dclaration
dclaration : struct nom_type {dclaration des champs} liste_variables ; exemple :
struct identite { char nom[30]; char prenom[20]; short int age; }jean,jacques,groupe[20];

Toute variable de type identit (jean, jacques, groupe[i]) comporte trois champs : nom, prenom et age. sizeof(jacques) retournera 52. Les champs peuvent tre de n'importe quel type valable (scalaires, tableaux, pointeurs...), y compris une structure ( condition d'tre dclar plus haut). nom_type et liste_variables sont optionnels mais au moins l'un des deux doit tre prsent. Les noms de champs ont une porte limite la structure (c'est dire qu'un autre objet peut avoir le mme nom, s'il n'est pas cit dans cette structure). Nom_type (ici identite) est le nom d'un nouveau type, il peut tre utilis plus loin pour dclarer d'autres variables, voire d'autres types:
struct identite lui; struct prisonnier {long numero; struct identite id;} moi;

Il est galement possible d'utiliser typedef, et (pas sur tous les compilateurs) d'initialiser une structure :
typedef struct {int jour;int mois;int anne;}date; date aujourdhui={24,12,1992}; /*vite de rpter struct*/

II- Utilisation
On accde une composante par NOM_VARIABLE . NOM_CHAMP , par l'oprateur unaire "."
gets(jean.nom); printf("initiales : %c %c\n",lui.nom[0],lui.prenom[0]); printf("nom %s \n",groupe[10].nom); scanf("%d",&moi.id.age);

Une composante d'enregistrement s'utilise comme une variable du mme type (avec les mmes possibilits mais aussi les mmes limitations). Depuis la norme ANSI, on peut utiliser l'affectation pour des structures (recopie de tous les champs), ainsi que le passage des structures en arguments de fonction passs par valeur. Sur les compilateurs non ANSI, il faut utiliser des pointeurs. On utilise des pointeurs de structures comme des pointeurs sur n'importe quel autre type. L'oprateur -> permet une simplification d'criture (il signifie champ point) :
date *ptr; ptr=(struct date *)malloc(sizeof(date)); *ptr.jour=14; ptr->mois=7;

Les structures sont tres importantes en C, et encore plus dans les langages plus rcents, les objets en dcoulent.

37

Les champs d'une structure peuvent tre relativement complexes (tableaux, autres structures), et on peut crer des tableaux de structures. Exemple : dclarez le types et variables suivants : une date est compose de 3 entiers j, m et a. Une opration est une rfrence (entier long), une quantit et un prix unitaire. Une facture est une date, un numro de client, un nombre d'oprations et au maximum 20 oprrations. Un client est un numro, un nom et une adresse. Un produit est une rfrence, une dsignation, un prix unitaire, une quantit en stock. Une compta est un tableau de 1000 factures. Un stock est un tableau (dynamique) de produits. Et l'on peut continuer ainsi longtemps.

III- Champs de bits


En ne dfinissant que des champs entiers (signs ou non), on peut dfinir la taille (en bits) de chaque champ. Il suffit pour cela de prciser, derrire chaque champ, sa taille aprs un ":".
struct tat{unsigned ReadOnly:1;int Crc:6;}

Les champs sont crs partir des bits de poids faible. Le nom du champ est optionnel (dans le cas de champs rservs, non utiliss par le programme). Les champs n'ont alors pas d'adresse (impossible d'utiliser & sur un champ). On utilise ces structures comme les autres.

IV- Unions
dclaration : union nom_type {dclaration des champs} liste_variables ; Les diffrents champs commenceront tous la mme adresse (permet d'utiliser des variables pouvant avoir des types diffrents au cours du temps, mais un seul un instant donn). Les champs peuvent tre de tout type, y compris structures. On les utilise comme les structures, avec les oprateurs "." et "->". Exercice (tel) A l'aide d'un tableau de personnes (nom, prnom, numro dans la rue, rue, code postal, ville, numro de tlphone), faire un programme de recherche automatique de toutes les informations sur les personnes rpondant une valeur d'une rubrique donne (tous les PATRICK , tous ceux d'Obernai, travaillant l'IPST, etc...). On suppose que le tableau est dj initialis. Solution.
#include <stdio.h> #include <string.h> #include <stdlib.h> #define DIM 100 enum champs {nom,prenom,num,rue,cp,ville,tel}; char *nomchamp[7]={"Nom", "Prnom", "Numro", "Rue", "Code Postal", "Ville", "Tel"}; typedef struct { char nom[15]; char prenom[20]; int num; char rue[60]; long codepostal; char ville[20]; char tel[15]; } fiche; fiche *rech_nom(fiche *,char *); fiche *rech_prenom(fiche *,char *);

38

fiche fiche fiche fiche fiche fiche

*rech_num(fiche *,char *); *rech_rue(fiche *,char *); *rech_cp(fiche *,char *); *rech_ville(fiche *,char *); *rech_tel(fiche *,char *); *rech_nom(fiche *,char *);

typedef fiche *ptrfiche; typedef ptrfiche (*ptrfonction)(ptrfiche,char*); ptrfonction tabfonction[7]={rech_nom, rech_prenom, rech_num, rech_rue, rech_cp, rech_ville, rech_tel}; void affiche(fiche *f) { if(f->nom[0]) printf("%s %s\n%d, %s\n%ld %s\nTel : %s\n", f->nom, f->prenom, f->num, f->rue, f->codepostal, f->ville, f->tel); else printf("fiche inconnue\n"); } int idem(char *s1,char *s2) /* compare deux chaines, dit si elles sont gales (1), ou non (0). On pourrait supposer galit quand la chaine s2 est incluse dans s1 */ { return(strcmp(s1,s2)?0:1); } fiche *rech_nom(fiche *pf,char *n) {while ((pf->nom[0])&&(!idem(pf->nom,n)))pf++; return(pf);} fiche *rech_prenom(fiche *pf,char *n) {while ((pf->nom[0])&&(!idem(pf->prenom,n)))pf++; return(pf);} fiche *rech_num(fiche *pf,char *n) {while ((pf->nom[0])&&(pf->num!=atoi(n)))pf++; return(pf);} fiche *rech_rue(fiche *pf,char *n) {while ((pf->nom[0])&&(!idem(pf->rue,n)))pf++; return(pf);} fiche *rech_cp(fiche *pf,char *n) {while ((pf->nom[0])&&(pf->codepostal!=atoi(n)))pf++; return(pf);} fiche *rech_ville(fiche *pf,char *n) {while ((pf->nom[0])&&(!idem(pf->ville,n)))pf++; return(pf);} fiche *rech_tel(fiche *pf,char *n) {while ((pf->nom[0])&&(!idem(pf->tel,n)))pf++; return(pf);} int choix(void) { char lig[40]; enum champs i,rep; for (i=nom;i<=tel;i++) printf("%d:%s ",i,nomchamp[i]); printf("\nou -1 pour quitter. Type de recherche dsire ? "); gets(lig); sscanf(lig,"%d",&rep); return(rep); } void lecture(fiche *tab) { char lig[40];

39

do { printf("nom (rien pour finir) ?"); gets(tab->nom); if(tab->nom[0]) { printf(" prnom ? "); gets(tab->prenom); printf(" N ? "); gets(lig); sscanf(lig,"%d",&(tab->num)); printf(" rue ? "); gets(tab->rue); printf(" code postal ? "); gets(lig); sscanf(lig,"%ld",&(tab->codepostal)); printf(" ville ? "); gets(tab->ville); printf("n de tlphone ? "); gets(tab->tel); } } while ((tab++)->nom[0]); } void main(void) { enum champs c; char clef[40]; fiche tab[DIM]; lecture(tab); do { if (((c=choix())<0)||(c>6)) break; printf("quel(le) %s recherche-t'on ? ",nomchamp[c]); gets(clef); affiche(tabfonction[c](tab,clef)); } while (c>=0); }

V- Structures chanes
Le principal problme des donnes stockes sous forme de tableaux est que celles-ci doivent tre ordonnes : le "suivant" doit toujours tre stock physiquement derrire. Imaginons grer une association. Un tableau correspond une gestion dans un cahier : un adhrent par page. Supposons dsirer stocker les adhrents par ordre alphabtique. Si un nouvel adhrent se prsente, il va falloir trouver o l'insrer, gommer toutes les pages suivantes pour les rcrire une page plus loin, puis insrer le nouvel adhrent. Une solution un peu plus simple serait de numroter les pages, entrer les adhrents dans n'importe quel ordre et disposer d'un index : un feuille o sont indiqus les noms, dans l'ordre, associs leur "adresse" : le numro de page. Toute insertion ne ncessitera de dcalages que dans l'index. Cette mthode permet l'utilisation de plusieurs index (par exemple un second par date de naissance). La troisime solution est la liste chane : les pages sont numrotes, sur chaque page est indique la page de l'adhrent suivant, sur le revers de couverture on indique l'adresse du premier. L'utilisation d'une telle liste ncessite un vritable "jeu de piste", mais l'insertion d'un nouvel adhrent se fera avec le minimum d'oprations. Appliquons cela , de manire informatique, une liste d'entiers, avec pour chaque valeur l'adresse (numro de mmoire) du suivant :

40

Si l'on veut insrer une valeur dans la liste, les modifications apporter sont minimes :

En C on dfinira un type structure regroupant une valeur entire et un pointeur :


struct page {int val; struct page *suivant; };

Un pointeur (souvent global) nous indiquera toujours le dbut de la liste:


struct page *premier;

Au fur et mesure des besoins on se cre une nouvelle page :


nouveau=(struct page *)malloc(sizeof(struct page));

En n'oubliant pas de prciser le lien avec le prcdent :


precedent->suivant=nouveau;

le dernier lment ne doit pas pointer sur n'importe quoi, on choisit gnralement soit le pointeur NULL, soit le premier (la liste est dite boucle). exemple :
#include <stdio.h> #include <conio.h> #include <ctype.h> #include <alloc.h> /*ou stdlib.h*/ struct page {int val; struct page *suivant; }; struct page *premier;

int encore(void) /* demande si on en veut encore*/ { printf("encore (O/N) ? "); return(toupper(getche())= ='O'); } void lecture(void) { struct page *precedent,*nouveau; premier=(struct page *)malloc(sizeof(struct page)); puts("entrez votre premier entier"); scanf("%d",&premier->val); precedent=premier; while (encore()) { nouveau=(struct page *)malloc(sizeof(struct page)); precedent->suivant=nouveau; precedent=nouveau; puts("\nentrez votre entier"); scanf("%d",&nouveau->val); } precedent->suivant=NULL; }

41

void affiche(struct page *debut) { printf("\nliste : "); while(debut!=NULL) { printf("%d ",debut->val); debut=debut->suivant; } printf("\n"); } int main(void) { lecture(); affiche(premier); }

Exercice (liste et insertion ) : modifier la fonction lecture ci-dessus pour que la liste soit stocke dans l'ordre inverse de son introduction (chaque nouvel lment est plac devant la liste dj existante). solution de cet exercice (et du suivant). L#include <stdio.h>
#include <conio.h> #include <ctype.h> #include <alloc.h> struct page {int val; struct page *suivant; }; struct page *premier; int encore(void) /* demande si on en veut encore*/ { printf("encore (O/N) ? "); return(toupper(getche())=='O'); } void lecture(void) { struct page *precedent,*nouveau; premier=(struct page *)malloc(sizeof(struct page)); puts("entrez votre premier entier"); scanf("%d",&premier->val); precedent=premier; while (encore()) { nouveau=(struct page *)malloc(sizeof(struct page)); precedent->suivant=nouveau; precedent=nouveau; puts("\nentrez votre entier"); scanf("%d",&nouveau->val); } precedent->suivant=NULL; } void affiche(struct page *debut) { printf("\nliste : "); while(debut!=NULL) { printf("%d ",debut->val); debut=debut->suivant; } printf("\n"); }

42

void suppression(void) { struct page *actu,*prec; actu=premier; while (actu!=NULL) { printf("\nvaleur : %d - supprimer celui-ci (O/N) ? ",actu->val); if (toupper(getche())=='O') { if(actu==premier)premier=actu->suivant; else prec->suivant=actu->suivant; free(actu); break; } else { prec=actu; actu=prec->suivant; } } } void ajouter(void) { struct page *nouveau,*prec; printf("\najouter en premier (O/N) ? "); if (toupper(getche())=='O') { nouveau=(struct page *)malloc(sizeof(struct page)); nouveau->suivant=premier; premier=nouveau; printf("\nnouvelle valeur ? "); scanf("%d",&(nouveau->val)); } else { prec=premier; while(prec!=NULL) { printf("\nvaleur : %d - insrer aprs celui_ci (O/N) ? ", prec->val); if (toupper(getche())=='O') { nouveau=(struct page *)malloc(sizeof(struct page)); nouveau->suivant=prec->suivant; prec->suivant=nouveau; printf("\nnouvelle valeur ? "); scanf("%d",&(nouveau->val)); break; } else prec=prec->suivant; } } } void main(void) { lecture(); affiche(premier); do { suppression(); affiche(premier); } while(encore());

43

do { ajouter(); affiche(premier); } while(encore()); }

es modifications sont aises, une fois que l'on a repr l'endroit de la modification. Exemple : suppression d'un lment :
void suppression(void) { struct page *actu,*prec; actu=premier; while (actu!=NULL) { printf("\nvaleur : %d - supprimer celui_ci (O/N) ? ", actu->val); if (toupper(getche())= ='O') { if(actu= =premier)premier=actu->suivant; else prec->suivant=actu->suivant; free(actu); break; } else { prec=actu; actu=prec->suivant; } } }

Exercice (insertion) : ajouter au programme prcdent une procdure d'insertion d'une valeur dans la liste. La solution de l'exercice prcdent contient galement cette insertion Ce type de donnes (structure pointant sur un mme type) est utilis dans d'autres cas. Par exemple, pour reprsenter un arbre, il suffit pour chaque lment de connatre l'adresse de chaque fils :

Remarque : si le nombre de fils n'est pas constant, on a intrt stocker uniquement le fils an, ainsi que le frre suivant(voir partie algorithmique et structures de donnes).

44

II- Les fichiers de donnes


Les donnes stockes en mmoire sont perdues ds la sortie du programme. Les fichiers sur support magntique (bande, disquette, disque) sont par contre conservables, mais au prix d'un temps d'accs aux donnes trs suprieur. On peut distinguer les fichiers squentiels (on accde au contenu dans l'ordre du stockage) ou accs direct (on peut directement accder n'importe quel endroit du fichier). Les fichiers sont soit binaires (un float sera stock comme il est cod en mmoire , d'o gain de place mais incompatibilit entre logiciels), soit format ASCII (un float binaire sera transform en dcimal puis on crira le caractre correspondant chaque chiffre). Les fichiers tant dpendants du matriel, ils ne sont pas prvus dans la syntaxe du C mais par l'intermdiaire de fonctions spcifiques.

Fichiers bruts
C'est la mthode la plus efficace et rapide pour stocker et rcuprer des donnes sur fichier (mais aussi la moins pratique). On accde au fichier par lecture ou criture de blocs (groupe d'octets de taille dfinie par le programmeur). C'est au programmeur de prparer et grer ses blocs. On choisira en gnral une taille de bloc constante pour tout le fichier, et correspondant la taille d'un enregistrement physique (secteur, cluster...). On traite les fichiers par l'intermdiaire de fonctions, prototypes dans stdio.h (ouverture et fermeture) et dans unistd.h (les autres), disponibles sur la plupart des compilateurs, pour Turbo C il faut inclure io.h. La premire opration effectuer est d'ouvrir le fichier. Ceci consiste dfinir le nom du fichier (comment il s'appelle sous le systme) et comment on veut l'utiliser. On appelle pour cela la fonction :
int open(char *nomfic, int mode);

nomfic pointe sur le nom du fichier (pouvant contenir un chemin d'accs). Mode permet de dfinir comment on utilisera le fichier. On utilise pour cela des constantes dfinies dans fcntl.h : O_RDONLY lecture seule, O_WRONLY criture seule, O_RDWR lecture et criture. On peut combiner cet accs avec d'autres spcifications, par une opration OU (|) : O_APPEND positionnement en fin de fichier (permet d'augmenter le fichier), O_CREAT cre le fichier s'il n'existe pas, au lieu de donner une erreur, sans effet s'il existe (rajouter en 3me argument S_IREAD | S_IWRITE | S_IEXEC dclars dans stat.h pour tre compatible UNIX et crer un fichier lecture/ criture/ excution autorise, seul S_IWRITE utile sur PC), O_TRUNC vide le fichier s'il existait, O_EXCL renvoie une erreur si fichier existant (utilis avec O_CREAT). Deux modes spcifiques au PC sont disponibles : O_TEXT change tous les \n en paire CR/LF et inversement, O_BINARY n'effectue aucune transformation. La fonction rend un entier positif dont on se servira par la suite pour accder au fichier (HANDLE), ou -1 en cas d'erreur. Dans ce cas, le type d'erreur est donn dans la variable errno, dtaille dans errno.h. On peut ensuite, suivant le mode d'ouverture, soit lire soit crire un bloc (l'opration est alors directement effectue sur disque) :
int write(int handle, void *bloc, unsigned taille);

45

On dsigne le fichier destination par son handle (celui rendu par open), l'adresse du bloc crire et la taille (en octets) de ce bloc. Le nombre d'octets crits est retourn, -1 si erreur.
int read(int handle, void *bloc, unsigned taille);

lit dans le fichier dsign par son handle, et le met dans le bloc dont on donne l'adresse et la taille. La fonction retourne le nombre d'octets lus (<=taille, <si fin du fichier en cours de lecture, 0 si on tait dj sur la fin du fichier, -1 si erreur).
int eof(int handle)

dit si on se trouve (1) ou non (0) sur la fin du fichier. Lorsque l'on ne se sert plus du fichier, il faut le fermer (obligatoire pour que le fichier soit utilisable par le systme d'exploitation, entre autre mise jour de sa taille :
int close(int handle)

fermeture, rend 0 si ok, -1 si erreur. Le fichier peut tre utilis squentiellement (le "pointeur de fichier" est toujours plac derrire le bloc que l'on vient de traiter, pour pouvoir traiter le suivant). Pour dplacer le pointeur de fichier en n'importe que autre endroit, on appelle la fonction :
long lseek(int handle, long combien, int code);

dplace le pointeur de fichier de combien octets, partir de : dbut du fichier si code=0, position actuelle si 1, fin du fichier si 2. La fonction retourne la position atteinte (en nb d'octets), -1L si erreur.
long filelength(int handle);

rend la taille d'un fichier (sans dplacer le pointeur de fichier). Exemple : copie de fichier (les noms de fichiers sont donns en argument du programme)
#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys\stat.h> #define taillebloc 1024 int main(int argc,char *argv[]) { int source, destination; char buffer[taillebloc]; int nb_lus,nb_ecrits; if (argc!=3) {puts("erreur arguments");return(1);} if((source=open(argv[1],O_RDONLY|O_BINARY))<0) {puts("erreur ouverture");return(2);} if((destination=open(argv[2], O_WRONLY| O_CREAT| O_TRUNC| O_BINARY, S_IREAD| S_IWRITE| S_IEXEC))<0) {puts("erreur ouverture");return(2);} do { nb_lus=read(source,(char *)buffer,taillebloc); if (nb_lus>0) nb_ecrits= write(destination,(char*)buffer, nb_lus); }

46

while ((nb_lus==taillebloc)&&(nb_ecrits>0)); close(source); close(destination); return(0); }

Fichiers buffriss
Les oprations d'entre / sortie sur ces fichiers se font par l'intermdiaire d'un "buffer" (bloc en mmoire) gr automatiquement. Ceci signifie qu'une instruction d'criture n'impliquera pas une criture physique sur le disque mais dans le buffer, avec criture sur disque uniquement quand le buffer est plein. Les fichiers sont identifis non par un entier mais par un pointeur sur une structure FILE (dfinie par un typedef dans stdio.h). Les fonctions disponibles (prototypes dans stdio.h) sont : FILE *fopen(char *nomfic, char *mode) : ouvre le fichier, suivant le mode : r (lecture seule), w (criture, si le fichier existe il est d'abord vid), a (append : criture la suite du contenu actuel, cration si inexistant), r+ (lecture et criture, le fichier doit exister), w+ (lecture et criture mais effacement au dpart du fichier si existant), a+ (lecture et criture, positionnement en fin de fichier si existant, cration sinon). Sur PC, on peut rajouter t ou b au mode pour des fichiers texte (gestion des CR/LF, option par dfaut) ou binaires, ou le dfinir par dfaut en donnant la variable _fmode la valeur O_TEXT ou O_BINARY. fopen rend un identificateur (ID) qui nous servira pour accder au fichier. En cas d'erreur, le pointeur NULL est retourn, le type d'erreur est donn dans une variable errno, dtaille dans errno.h. La fonction void perror(char *s mess) affichera le message correspondant l'erreur, en gnral on lui donne le nom du fichier. int fread(void *bloc, int taille, int nb, FILE *id) : lit nb lments dont on donne la taille unitaire en octets, dans le fichier dsign par id, le rsultat tant stock l'adresse bloc. La fonction rend le nombre d'lments lus (<nb si fin de fichier), 0 si erreur. int fwrite(void *bloc, int taille, int nb, FILE *id) : criture du bloc sur fichier, si le nombre rendu est diffrent de nb, il y a eu erreur (tester ferror ou errno). int fclose(FILE *id) : ferme le fichier, en y recopiant le reste du buffer si ncessaire. Cette fonction est obligatoire pour tre sur d'avoir l'intgralit des donnes effectivement transfres sur le disque. Retourne 0 si tout s'est bien pass. int fflush(FILE *id) : transfre effectivement le reste du buffer sur disque, sans fermer le fichier ( appeler par exemple avant une instruction qui risque de crer un "plantage"). int fseek(FILE *id, long combien, int mode) : dplace le pointeur de fichier de combien octets, partir de : dbut du fichier (mode=0), position actuelle (mode=1) ou fin du fichier (mode=2). Retourne 0 si tout c'est bien pass. Cette fonction n'est utilisable que si l'on connat la taille des donnes dans le fichier (impossible d'aller directement une ligne donne d'un texte si on ne connat pas la longueur de chaque ligne). int feof(FILE *id) dit si on est en fin de fichier ou non (0). Les fichiers buffriss permettent aussi des sorties formates :

47

au niveau caractre : char fgetc(FILE *id), char fputc(char c, FILE *id), et mme char ungetc(char c, FILE *id) qui permet de "reculer" d'un caractre. Cette fonction correspond donc {fseek(id,1,1);c=fgetc(id)}. au niveau chane de caractres : char *fgets(char *s, int max, FILE *id) : lit une chane en s'arrtant au \n ou max-1 caractres lus, rsultat dans la zone pointe par s, et retour du pointeur s ou NULL si erreur. char fputs(char *s, FILE *id) : crit la chane dans le fichier sans ajouter de \n, rend le dernier caractre crit ou EOF si erreur. int fprintf(FILE *id, char *format, listearguments) : rend le nb d'octets crits, ou EOF si erreur. Les \n sont transforms en CR/LF si fichier en mode texte (spcifique PC). int fscanf(FILE *id, char *format, listeadresses) : rend le nombre de variables lues et stockes, 0 si erreur). En gnral, on utilise les fichiers buffriss : - Soit en accs direct, en lecture et criture, avec tous les lments de mme type et mme taille (souvent une structure, en format binaire), ceci permettant d'accder directement un lment donn (le 48me, le prcdent, l'avant dernier...). - Soit en accs squentiel, avec des lments de type diffrent, tous formats sous forme ASCII, en lecture seule ou criture seule (il est peu probable que le remplacement d'un lment se fasse avec le mme nombre d'octets et ncessiterait un dcalage dans le fichier), ces fichiers seront comprhensibles par n'importe quel autre programme (diteur de texte, imprimante...). Un tel fichier s'utilise comme l'cran et le clavier, par des fonctions similaires. Exercice (agenda) : modifier l'agenda de l'exercice tel en permettant de sauver les donnes sur disque. On utilisera un fichier binaire accs direct. On pourra ensuite apporter les amliorations suivantes : recherche rapide par dichotomie sans lire tout le fichier (en le supposant class par ordre alphabtique), cration de fichiers index classs alphabtiquement sur les noms, dpartement et ville pour accs rapide par dichotomie, les autres se faisant par recherche squentielle, avec possibilit d'ajout, suppression, dition du fichier.
#include #include #include #include #include <stdio.h> <string.h> <stdlib.h> <ctype.h> <conio.h>

/* dfinitions des types et variables associes */ enum champs {nom,prenom,num,rue,cp,ville,tel}; char *nomchamp[7]={"Nom","Prnom","Numro","Rue", "Code Postal","Ville","Tel"}; typedef struct { char nom[15]; char prenom[20]; int num; char rue[60]; long codepostal; char ville[20];

48

char tel[15]; } fiche; #define taille sizeof(fiche) typedef fiche *ptrfiche; /* dfinitions des fonctions de recherche, regroupes dans un tableau */ fiche *rech_nom(fiche *,char *); fiche *rech_prenom(fiche *,char *); fiche *rech_num(fiche *,char *); fiche *rech_rue(fiche *,char *); fiche *rech_cp(fiche *,char *); fiche *rech_ville(fiche *,char *); fiche *rech_tel(fiche *,char *); fiche *rech_nom(fiche *,char *); typedef ptrfiche (*ptrfonction)(ptrfiche,char*); ptrfonction tabfonction[7]= {rech_nom, rech_prenom, rech_num, rech_rue, rech_cp, rech_ville, rech_tel}; /* variables globales */ FILE *fic; /* fichier de donnes */ char *nomfic="agenda.dat"; int nb; /* nb de fiches dans le fichier */ void init(void) /* ouvre le fichier, dtermine le nb de fiches de fic */ { if ((fic=fopen(nomfic,"a+b"))==NULL) { puts("ouverture impossible du fichier de donnes"); exit(1); } fseek(fic,0,2); nb=(int)ftell(fic)/taille; printf("%d fiches prsentes dans l'agenda\n",nb); } void ajouter(void) { char lig[40]; fiche f; printf("nom ? "); gets(f.nom); printf(" prnom ? "); gets(f.prenom); printf(" Numero ? "); gets(lig); sscanf(lig,"%d",&(f.num)); printf(" rue ? "); gets(f.rue); printf(" code postal ? "); gets(lig); sscanf(lig,"%ld",&(f.codepostal)); printf(" ville ? "); gets(f.ville); printf("n de tlphone ? "); gets(f.tel); fseek(fic,0L,2); if(fwrite(&f,taille,1,fic)!=1) { puts("impossible d'ajouter cette fiche au fichier "); exit(0); }

49

nb++; } void affiche(fiche *f) { if((f!=NULL)&&(f->nom[0])) printf("%s %s\n%d, %s\n%ld %s\nTel : %s\n",f->nom, f-> prenom, f->num, f->rue,f->codepostal,f->ville,f->tel); else printf("fiche inconnue\n"); } int idem(char *s1,char *s2) /* compare deux chaines, dit si elles sont gales (1), ou non (0). On considre gales majuscules et minuscules. Une des chaines peut se terminer par *, on supposera identique si tout ce qui prcde l'* tait identique */ { for(;;) { if (((!*s1)&&(!*s2))||(*s1=='*')||(*s2=='*')) return(1); if ((toupper(*s1)!=toupper(*s2))||(!*s1)||(!*s2)) return(0); s1++;s2++; } } fiche *rech_nom(fiche *pf,char *n) { int nblu; fseek(fic,0L,0); do nblu=fread(pf,taille,1,fic); while ((nblu==1)&&(!idem(pf->nom,n))); if (nblu==1) return(pf); else return(NULL); } /* les autres recherches sont continuer */ int choix(void) { char lig[40]; enum champs i,rep; for (i=nom;i<=tel;i++) printf("%d:%s ",i,nomchamp[i]); printf("\nou -1 pour quitter. Type de recherche dsire ? "); gets(lig); sscanf(lig,"%d",&rep); return(rep); } void recherche(void) { enum champs c; char clef[40]; fiche f; do { if (((c=choix())<0)||(c>6)) break; printf("quel(le) %s recherche-t'on ? ",nomchamp[c]); gets(clef); affiche(tabfonction[c](&f,clef)); } while (c>=0); }

50

void main(void) { char rep; init(); do { puts("Ajouter une fiche, Recherche d'une fiche, Quitter le prog ? "); switch (rep=toupper(getch())) { case 'A':ajouter();break; case 'R':recherche();break; case 'Q':fclose(fic);puts("Au revoir");break; default :puts("option non prvue"); } } while (rep!='Q'); }

Exercice (fic_format) : modifier le programme de produit de matrices en lui permettant de donner sur la ligne de commande trois noms de fichiers : les deux premiers contiendront la description des matrices multiplier, le dernier sera cr et contiendra le rsultat. Les fichiers seront formats, contiendront en premire ligne le nombre de lignes puis le nombre de colonnes, puis chaque ligne du fichier contiendra une ligne de la matrice.

51

III- Directives du pr compilateur


Attention, les directives se terminent par le retour la ligne et pas un ";". #include <nomfic> permet d'insrer cet endroit un fichier, qui serra cherch dans le rpertoire correspondant la bibliothque du C (sur PC dans \TC\INCLUDE) #include "nomfic" permet d'insrer cet endroit un fichier, comme s'il tait crit ici, en le cherchant dans le rpertoire actuel (le votre) #define nom valeur : remplace chaque occurrence du nom par la valeur. On ne peut prendre en compte qu'une ligne (le retour la ligne terminant la dfinition). En C on a l'habitude de noter les constantes numriques en majuscules. exemple :
#define PI 3.1415926 #define begin { #define end }

#define macro(parametres) dfinition : permet la dfinition d'une macro, les paramtres seront substitus lors de la rcriture. Il ne faut pas de blanc entre le nom de la macro et la "(" pour diffrencier d'un define simple.
#define double(a) a*2

remplacera double (i+5) par i+5*2. Il faut donc utiliser des parenthses, mme si elles semblent inutiles :
#define double(a) (a)*2

autre exemple:
#define max(x,y) x>y?x:y

replacera max(a,max(b,c)) par a>b>c?b:c?a:b>c?b:c


#define max(x,y) (((x)>(y)?(x):(y)

replacera max(a,max(b,c)) par (((a)>( (((b)>(c))?(b):(c)) )) ? (a) : (((( b)>(c)) ? (b) : (c))) ce qui donne des parenthses superflues mais le rsultat escompt. Une macro ressemble une fonction, mais sera d'excution plus rapide : le texte est directement insr l'endroit voulu, mais pas de gestion de pile. #undef nom : annule le prcdent #define nom ... #if (expression) : les lignes qui suivent ne seront lues (et compiles que si l'expression est vraie #endif : fin de porte du #if prcdent

52

#else : possible mais optionnel, faire attention en cas de #if imbriqus #ifdef nom : comme #if mais vrai s'il y a eu auparavant un #define nom #ifndef nom : vrai si variable non dfinie exemples:
#ifndef entier #define entier int /* si une autre bibliothque incluse plus haut l'a dj dfini, on ne le redfinit plus */ #endif #ifdef biblio_graphique initialise_ecran(); efface_ecran(); trace(dessin); #else puts("si on avait eu un cran graphique j'aurai fait un dessin"); #endif /* biblio_graphique peut tre dfini dans un "header" (fichier.h) inclus ou non plus haut */

53

IV- Utiliser un compilateur (gcc ou Turbo C)


gcc (GNU C Compiler)
gcc est un compilateur extrmement efficace. De plus, il est du domaine public, donc gratuit. Par contre, son efficacit se paye : il fonctionne en mode "texte". Il existe divers environnements graphiques de programmation de haut niveau, par exemple kdevelop, mais je vous dtaille ici son utilisation de base. Processus de compilation : ouvrez une fentre shell (on dit aussi console ou terminal ou fentre de commandes) Sous Linux/KDE, c'est l' icne stylisant un cran et un coquillage, sous Windows "Dmarrer-> excuter-> cmd". Appelez un diteur de texte (par les menus, ou dans le shell Linux kwrite nomfichier.c & ou dans la fentre DOS write ou notepad) et tapez le texte de votre programme source. N'oubliez pas de sauver ! Choisissez un nom de fichier si possible compatible avec tous les systmes, sans espaces ni accents, je vous conseille les minuscules. L'extention doit tre ".c". Dans le terminal, compilez (gcc nomfichier.c -o nomprogramme). S'il n'y a pas d'erreur, appelez le programme par son nom, dans le terminal (ne lancez pas les programmes par un clic en mode graphique si votre programme ne commence pas par ouvrir une fentre).

Turbo C de BORLAND
Turbo C a t d'aprs moi le compilateur le plus intressant sur PC sous MS-DOS : diteur intgr, li aux messages d'erreur, diteur multifentres, aide en ligne, dbogueur puissant. Nous nous en servons encore sur nos machines anciennes. C'est un programme commercial (donc payant). Mais son diteur en distribuait diverses versions gratuites sur son site web www.borland.com. Placez vous dans votre rpertoire (pas celui de Turbo C pour ne pas mlanger avec vos fichier) et appelez TC. Les commandes sont accessibles par menus, il vaut mieux possder une souris. Sinon, on accde une commande d'un menu par ALT et lettre en surbrillance, on change d'option par TAB, on valide par ESPACE. Au menu principal, on peut : FILE : grer les fichiers : en crer un nouveau (new), reprendre un fichier dj existant (open, puis choisir le fichier dans la liste), sauver le fichier actuel (save ou save as), quitter TC (quit ou ALT X). EDIT : couper - coller, et surtout remettre une ligne de texte dans son tat initial (restore line), surtout aprs l'avoir efface par erreur par CTRL Y. SEARCH : recherche ou remplacement d'un texte dans tout le fichier (CTRL L pour rechercher le suivant). RUN : excuter le programme. On peut excuter le programme pas pas : Aller jusqu' la ligne dans laquelle est le curseur, avancer d'une ligne (trace into ou step over). Ceci permet de voir dans quel ordre s'excutent les instructions. COMPILE : compiler pour voir les erreurs, il faut faire un EXE pour pouvoir utiliser le programme hors de TC.

54

DEBUG : en mode pas pas on peut voir ou modifier l'tat d'une variable (evaluate, inspect), voir l'tat de la pile (imbrication des fonctions et arguments rels : call stack) voir dans une fentre tout moment l'tat d'une variable (add watch). PROJECT : un projet contient la liste de fichiers ncessaires au fonctionnement d'un programme (fichiers spars). OPTION : on peut tout choisir : rpertoires, niveau de messages d'erreur de compilation, mode 43 lignes (environnement preferences) et mme couleurs. WINDOW : choix des fentres ouvertes, dplacement, changement de taille... Entre autres la fentre OUTPUT correspond l'cran tel que le gre votre programme, utile pour voir la fois vos rsultats et votre programme. HELP : aide En vous plaant sur une commande d'un menu, l'appui sur F1 vous dtaille ce que permet cette commande. Dans une fentre de texte , l'appui sur CTRL F1 vous donne une aide sur le mot sous le curseur, uniquement si c'est un mot clef du C (mais connat toutes les fonctions, sait dans quel header elle est dclare). par exemple, tapez printf puis CTRL F1, vous aurez une indication sur tous les formats possibles ainsi que des exemples. En choisissant l'option menus longs, les touches de raccourci sont indiques ct des fonctions des menus. Dernier conseil, sauvez votre source avant de l'excuter, au cas ou votre programme planterait la machine (automatisation possible sous options)

55

TP programmation info n1
Organisation des fichiers sous Linux

Noms de fichiers sous Unix : majuscules diffrentes des minuscules (prfrer les minuscules), on peut utiliser galement des chiffres, - (sauf en premier caractre), _ (soulign), . (point) et quelques autres. En tous cas jamais d'espace, et viter les accents.

Commandes en mode texte sous Windows et Unix


DOS et Telnet vers Windows DIR CD rpertoire DEL fichier DEL *.* MD rpertoire RD rpertoire
UNIX

afficher la liste des fichiers (dtaille) changer de rpertoire de travail effacer un fichier effacer tous les fichiers crer un rpertoire supprimer un rpertoire (vide) monter un "device" local copier un fichier dplacer (ou renommer) un fichier ou rpertoire copier un rpertoire et ses sous-rpertoires grer les accs aux fichiers aide sur une commande

COPY source dest MOVE src dest XCOPY /S src dest ATTRIB commande /?

ls -l cd rpertoire rm fichier rm * mkdir rpertoire rmdir rpertoire mount device pt_de_montage cp source dest mv src dest cp -R src dest chmod , chown , chgrp man commande

Processus de compilation : ouvrez une fentre shell (terminal) (icne stylisant un cran et un coquillage). Appeler l'diteur (kwrite nomfichier.c &) et taper le texte. Ne pas oublier de sauver ! Dans le terminal, compiler (gcc nomfichier.c -o nomprogramme). S'il n'y a pas d'erreur, appeler le

56

programme par son nom, dans le terminal (ne pas lancer les programmes par un clic en mode graphique si votre programme ne commence pas par ouvrir une fentre). Accs une machine distante (attention, il faut que le systme distant accepte les accs extrieurs) : ssh -X iup2@ipst207xx puis toute commande "texte" ou graphique ("konqueror &" par exemple). Il existe la mme chose pour accder une machine Windows (telnet), ou depuis Windows (putty.exe) : voir IUP1 Accs aux fichiers d'une machine : dans le gestionnaire de fichiers de votre systme (explorateur de fichiers Windows, Konqueror,...) taper l'adresse ftp://iup2@ipst207xx ou (mieux) sftp si les deux systmes le permettent (transmission code, plus sr). On peut aussi monter (localement) un rpertoire distant (qui doit tre partag)

mount //machine/nom_partage pt_de_montage -t smbfs (local linux, distant Windows) net use X: \\machine\nom_partage (local Windows, distant Windows) mount //machine/nom_partage pt_de_montage -t nfs (local linux, distant Unix) utiliser PCNFS (local Windows, distant Unix)

Saisissez ce programme, compilez le et lancez le


/* premier programme, TP IUP2 2003 */ /*directives du prcompilateur *****************************************/ //inclusion d'entte pour utiliser les fonctions standard #include <stdio.h> //dfinition de deux constantes #define lng_txt 40 #define taux 6.55957 /* dclarations globales (types et prototypes) *************************/ typedef char texte[lng_txt]; //un texte est un tableau de 40 caractres maxi void dire(texte); //cette fonction affiche le texte qu'on lui donne float convertir(float); //convertit un rel en un autre /* fonction principale : doit s'appeler main ***************************/ int main(void) { float en_francs,en_euros; //dclaration locale main dire("bonjour"); //il faut toujours tre poli printf("entrez une somme en francs : "); scanf("%f",&en_francs); //lire la somme en_euros=convertir(en_francs); //appel d'une fonction printf("%0.2ff font %0.2f euros\n",en_francs,en_euros); dire("au revoir, bientt."); } /* fonction qui effectue la conversion *********************************/ //entte : on lui donne un rel et elle en rend un autre float convertir(float somme_en_francs) { float temporaire; //dclaration locale temporaire=somme_en_francs/taux; //calcul return temporaire; //c'est ainsi qu'une fonction rend son (unique) rsultat } /* fonction qui affiche un texte ***************************************/ //je n'en ai besoin que dans cette fonction, on peut l'inclure ici

57

#include <string.h> /* dfinit la fonction 'strlen' */ void dire(texte msg) { int i,nb; //variables locales nb=strlen(msg)+4; //calcul de la longueur du cadre for(i=0;i<nb;i++)printf("*"); //affichage de 'nb' toiles printf("\n* %s *\n",msg); //passage la ligne, *, espace, le texte, for(i=0;i<nb;i++)printf("*"); printf("\n"); //passage la ligne (pour le prochain prompt) }

Ecrivez votre premier programme


a) il demande le solde initial de votre compte, la somme que vous y ajoutez, puis il calcule le solde. b) en plus il demande le nombre de fois que vous ajoutez cette me somme. Vous devez obligatoirement utiliser une boucle et faire des additions successives. c) testez de grands nombres de boucles, pour voir quand il lui faut quelques secondes, et remarquez l'erreur (par exemple par ajouts de 0.01) Proposition de solution :
#include <stdio.h> int main(void) { float debut,somme,solde; int i,nb; printf("solde initial ?"); scanf("%f",&debut); printf("mouvement ?"); scanf("%f",&somme); printf("nombre de mouvements ?"); scanf("%d",&nb); solde=debut; for(i=0;i<nb;i++) solde=solde+somme; printf("solde finial : %f \n",solde); }

TP programmation info n2
Solution du dernier exercice du 1er TP :
#include <stdio.h> int main(void) { float debut,somme,solde; int i,nb; printf("solde initial ?"); scanf("%f",&debut); printf("mouvement ?"); scanf("%f",&somme); printf("nombre de mouvements ?"); scanf("%d",&nb); solde=debut; for(i=0;i<nb;i++) solde=solde+somme; printf("solde finial : %f \n",solde); }

58

Rsum sur les formes de base des structures de contrle :


A) boucles (rpter une instruction, ou un bloc de plusieurs instructions entre accolades) Si l'on connat le nombre de boucles (ici variable entire nb) :
for (i=0;i<nb;i++) instruction

Sinon, si quoi qu'il arrive il faut faire au moins une fois l'instruction (tant que la condition est vraie) :
do instruction while (condition);

sinon (toujours tant que la condition est vraie, si elle est fausse au dbut l'instruction n'est mme pas faite une seule fois) :
while (condition) do instruction

B) tests Si la condition est vraie, on fait l'instruction puis on passe la suite, sinon on passe directement la suite :
if (condition) instruction

Si la condition est vraie, on fait l'instruction1, sinon l'instruction2. Puis on passe la suite :
if (condition) instruction1 else instruction 2

Suivant la condition, une seule instruction est effectue :


if (condition_1) instruction_1 else if (condition_2) instruction_2 //ici condition_1 est ncessairement fausse ... etc ... //ici condition_1 et condition_2 sont fausses else instruction_n //si aucune condition prcdente n'est vrifie

C) les conditions utilisent les comparaisons (<, <=, >, >=, != (diffrent), == (gal)) et des oprateurs boolens (&& (et), | | (ou), ! (non)). Exemple : if ((x>=0)&&(x<10)) signifie : si x dans [0,10[ Premier exercice (tests) : Un problme physique se modlise par l'quation ax2+bx+c=0. Trouvez toutes les solutions (relles), quelles que soient les valeurs a,b,c proposes par l'utilisateur. Si, pour la compilation, il faut inclure la librairie mathmatique : gcc prog.c -lm -o prog Second exercice (boucles) : recherche d'une racine positive de ax3+bx2+cx+d=0. Attention, ce n'est pas la bonne mthode, ce problme est uniquement pos pour vous faire travailler les boucles. On cherche d'abord encadrer la premire racine positive (f(x) change de signe) par pas de 1 (si on arrive 10000 on abandonne). Puis dans un second temps on restreint l'encadrement de moiti jusqu' ce qu'on ait une prcision de 10-3 (dichotomie). Dernier exercice (boucles et tests) : Le but est d'approfondir les structures de contrle. Pour cela , l'ordinateur va nous faire jouer au plus ou moins : il va choisir un nombre, nous allons tenter de le dcouvrir. Pour faire choisir un nombre alatoire par l'ordinateur, il faut entrer ce programme :

59

Evidement, trouver la solution en un seul essai est assez peu probable. Il faudrait poser plusieurs fois la question (jusqu' ce qu'on propose la bonne valeur). Pour nous aider, le programme devra nous dire, chaque proposition, si notre nombre est trop grand ou trop petit. Prvenez moi quand a fonctionne ! S'il reste du temps, amliorez ce jeu. Nous comparerons et classerons vos programmes la fin de la sance ! Ajoutez, dans l'ordre : A) la fin du jeu, indiquez le nombre d'essais tents. B) En fonction de ce nombre d'essais, donnez une apprciation diffrente (au moins 3 : bravo / pas mal / pas trop tt). C) Vrifiez si les propositions sont cohrentes avec les indications donnes prcdemment (lui signaler qu'il est idiot de proposer 18 alors qu' la proposition du nombre 25 on lui avait dit qu'il tait trop petit). D) Au cours du jeu, donnez de temps en temps des encouragements (pas toujours dans le mme ordre, au hasard mais aussi en fonction de la prcision de la proposition). E) Toute autre amlioration qui vous semble possible, ou amusante.

Proposition de solution :
1) rsolution de ax2 + bx + c dans TOUS les cas
#include <stdio.h> #include <math.h> /* pour sqrt, la racine carre */ int main (void) { float a,b,c; float delta,r1,r2; printf("rsolution de ax2 + bx + c \n"); printf("entrez la valeur de a : "); scanf("%f",&a); printf("entrez la valeur de b : "); scanf("%f",&b); printf("entrez la valeur de c : "); scanf("%f",&c); if (a==0) /* sinon diviser par 2*a risque de poser problme */ { if (b!=0) printf("une racine simple x=%f\n",-c/b); else if (c==0) printf("il y a une infinit de solutions Ox=0\n"); else printf("aucune solution l'quation %f=0\n",c); } else

60

{ delta=b*b-4*a*c; /* b*b : c'est comme cela qu'on calcule un carr */ if (delta==0) printf("une racine double : %f\n",-b/(2*a)); else if (delta<0) printf("aucune racine relle (mais complexes)\n"); else { r1=(-b-sqrt(delta))/(2*a); r2=(-b+sqrt(delta))/(2*a); printf("il y a 2 racines relles : x1=%f et x2=%f\n",r1,r2); } } }

2) recherche d'une racine positive de ax3+bx2+cx+d=0


#include<stdio.h> #include<math.h> #include<stdlib.h> #define f(x) (a*(x)*(x)*(x)+b*(x)*(x)+c*(x)+d) int main(void) { float a, b, c, d,x,f0,gauche,droite,y; printf("ax3+bx+cx +c=0"); printf("\nEntrez la valeur de a ?"); scanf("%f",&a); printf("\nEntrez la valeur de b ?"); scanf("%f",&b); printf("\nEntrez la valeur de c ?"); scanf("%f",&c); printf("\nEntrez la valeur de d ?"); scanf("%f",&d); f0=f(0); x=1; while(f(x)*f0>0 && x<10000) x+=1; if(x==10000) {printf ("pas de solution\n"); exit(0);} printf("la solution est comprise entre %f et %f\n",x-1,x); gauche=x-1; droite=x; do { x= (gauche+droite)/2; if ((f0*f(x))<0) droite=x; else gauche=x; } while ((droite-gauche)>0.001) ; printf("la solution est comprise entre %f et %f\n",gauche,droite); }

3) jeu du plus ou moins


#include <stdio.h> #include <stdlib.h> #include <time.h> int main (void) { int nbatrouver,proposition,compteur=0; int min=1,max=100;

61

printf("Vous devez deviner un nombre entre %d et %d \n",min,max); srand(time(NULL)); nbatrouver=(rand()%(max+1))+min; do { compteur++; printf("entrez votre proposition :"); scanf("%d",&proposition); if(proposition<nbatrouver) if(proposition>min) { printf("trop petit\n"); min=proposition; } else printf("donne incohrente\n"); else if(proposition!=nbatrouver) if(proposition<max) { printf("trop grand\n"); max=proposition; } else printf("donne incohrente\n"); } while (proposition!=nbatrouver); printf("gagn en %d essais \n",compteur); if (compteur<2)printf("tricheur !\n"); else if (compteur<5)printf ("bravo\n"); else if (compteur<10)printf("pas mal\n"); else printf("plutt nul\n"); }

TP programmation informatique n 3
1) Etude de la rcursivit. Soit la fonction
int f(int n) { int p; if(n<2) return 1; else { p=n*f(n-1); return p } }

testez son rsultat pour quelques valeurs simples. Essayez de comprendre comment elle fonctionne. Vous pouvez insrer des printf pour mieux comprendre son droulement. 2) Ecrire un programme demandant deux nombres flottants (par scanf), puis qui calcule leur somme et l'affiche. (le programme doit tre trs simple) 3) Amliorer le programme pour qu'il sache traiter les 4 oprations . On demandera le premier nombre, puis le signe opratoire que l'on stockera dans une variable de type char, puis on demandera le second nombre.

Pour saisir un caractre, on peut utiliser scanf(''%c '' &variable) ; ou alors variable=getchar(); ou encore mieux : do s=getchar(); while (s<=' ');

62

Que se passe-t-il si vous tapez directement une expression (par ex. 1+2) ds la 1re question (dans les 3 cas) ? Pour slectionner l'opration effectuer, utilisez des tests if-else if mais prvoyez aussi (grce aux else ) le cas o l'utilisateur a demand une opration non prvue. Vous pouvez galement, si vous y tenez, utiliser une instruction switch.

4) Ajout d'une boucle : on veut maintenant faire une suite d'oprations : par exemple on veut entrer 5 * 3 (le programme affiche 15) puis + 2 (le programme affiche 17), etc jusqu' ce qu'on donne le signe opratoire = qui termine le programme. 5) fonction factorielle : on dsire galement pouvoir calculer des factorielles. Exemples : !5=, ou 3+!5= . Vous dcomposerez bien videment votre programme en plusieurs fonctions. 6) dernire proposition : vous pouvez tenter de rajouter des parenthses, mais c'est plus compliqu. Si vous vous limitez un seul niveau de parenthses (pas d'imbrications) il suffit de prvoir une variable supplmentaire pour stocker le rsultat intermdiaire.

proposition de solution
pour une version pdf voyez ici.

1) F calcule (rcursivement) une factorielle.


f(1) ou tout nb <1 donnent 1. f(2) donne 2*f(1)=2. f(3) donne 3*f(2)=6. f(4) donne 4*f(3)=4*6=24.

2) version que tous doivent savoir faire (question 4)


#include int main(void) { float n2,res; char signe; printf("premier nombre ?"); scanf("%f",&res); do { printf("signe opratoire ?"); do signe=getchar(); while(signe<=' ')); if(signe!='=') { printf("prochain nombre ?"); scanf("%f",&n2); if(signe=='+')res=res+n2; else if(signe=='-')res=res-n2; else if(signe=='*')res=res*n2; else if(signe=='/')res=res/n2; else printf("signe non conforme\n"); printf("rsultat intermdiaire : %f\n",res); } } while(signe!='='); printf("le rsultat vaut %f\n",res); }

3) version avec parenthses et ! (question 6)


#include // on est oblig de prototyper au moins la fonction calculer

63

char lire_un_signe(void); float lire_un_nb(void); float calculer(void); int fact(int); int fact(int n) { int p; if(n<2) return 1; else { p=n*fact(n-1); return p; } } char lire_un_signe(void) { //lit un signe, qui devrait tre +, -, *, /, (, ) ou = char s; do s=getchar(); while (s<=' '); //on limine les espace et CR return(s); } float lire_un_nb(void) { float n; int nb; char c; nb=scanf("%f",&n); if(nb==0) //on n'a pas pu lire, ne peut tre que (, ! ou une erreur { c=lire_un_signe(); if(c=='!') n=fact(lire_un_nb()); else if(c!='(')printf("erreur dans vos parenthses !!!\n"); else n=calculer(); //on calcule jusqu' la prochaine ) } return(n); } float calculer(void) { float res,n2; char signe; res=lire_un_nb(); do { signe=lire_un_signe(); if(signe=='+'||signe=='*'||signe=='-'||signe=='/') { n2=lire_un_nb(); if(signe=='+')res+=n2; else if(signe=='-')res-=n2; else if(signe=='*')res*=n2; else if(signe=='/')res/=n2; } } while(signe!='='&&signe!=')'); return(res); } int main(void) {

64

float res; printf("entrez votre quation, termine par = (sur printf("vous pouvez utiliser des parenthses (bien signs\n"); printf("!5 ou !(2+3) donnent factorielle(5), alors factorielle(2)+3\n"); printf("exemple : (2*(-1))+(-1*-3)*(1/(4*0.5))+1) res=calculer(); printf("le rsultat vaut %f\n",res); }

une ou plusieurs lignes)\n"); appaires) et des nombres que !2+3 donnde (doit donner 1.5)\n?");

TP programmation informatique n4
Nous allons tudier l'utilisation de base des tableaux (statiques unidimensionnels), ainsi que les fonctions. Nous supposons vouloir traiter diverses valeurs relles (des tempratures) mesures lors d'un essai thermique, regroupes dans un tableau. Soient les dclarations :
#define DIM 100 typedef float tableau[DIM]; int main (void) { tableau t; int nb; }

1. Dans un premier temps, compltez le programme pour qu'il :


demande le nombre de tempratures demande les valeurs des tempratures puis affiche le tableau

2. Dcomposez ce programme en fonctions : une fonction qui effectue la saisie, une pour l'affichage, et videment le programme principal. Les donnes seront obligatoirement transmises en arguments des fonctions. Je vous conseille de commencer par l'affichage, car il ne ncessite pas de modification des arguments. 3. Calculez la temprature moyenne ( l'aide d'une fonction qui retourne un flottant) mais imprimez la dans le programme principal 4. Crez une boucle dans le programme principal qui permette de rpter ce menu : que voulez vous faire ? 1 -> saisir le tableau 2 -> l'afficher 3 -> moyenne 0 -> quitter le programme 5. 6. 7. 8. Rajoutez la possibilit d'ajouter une temprature ( la suite des autres) via une fonction. Rajoutez la possibilit de supprimer une valeur(demander sa position) Modifiez l'ajout d'une temprature en permettant de l'insrer un endroit prcis Rajoutez la possibilit d'excuter une rotation, dans les deux sens : le premier passe en dernier et tous les autres se dcalent; le dernier passe en premier et tous les autres se dcalent. (2 fonctions) 9. Si vous vous vous sentez en forme, tentez de trier les tempratures (optionnel)

65

Propostion de solution
#include <stdio.h> #define DIM 100 typedef float tableau[DIM]; void saisie (tableau t,int *nb,int dim) { int i; do { printf("combien de valeurs (entre 1 et %d) ? ",dim); scanf("%d",&*nb); } while(*nb<0 || *nb>dim); for(i=0;i<*nb;i++) { printf("entrez valeur %d :",i); scanf("%f",t+i); } } void affichage (tableau t, int nb) { int i; for(i=0;i<nb;i++) printf("valeur %d : %f\n",i,t[i]); } float moyenne (tableau t, int nb) { float somme; int i; somme=0; for(i=0;i<nb;i++)somme+=t[i]; return(somme/nb); } void ajouter (tableau t, int *nb,int dim) { if(*nb>=dim)printf("plus de place !\n"); else { printf("Entrez la valeur rajouter : "); scanf("%f",&t[*nb]); (*nb)++; } } void supprimer (tableau t, int *nb) { int i,j; if(*nb<=0)printf("dja vide !\n"); else { printf("Quelle valeur voulez vous supprimer ?"); scanf("%d",&j); if (j<*nb && j>=0) { (*nb)--; for(i=j;i<*nb;i++)

66

t[i]=t[i+1]; } } } void rotbas (tableau t, int nb) { int i,rot; rot=t[0]; for(i=0;i<nb;i++) t[i]=t[i+1]; t[nb-1]=rot; } void rothaut (tableau t, int nb) { int i,rot; rot=t[nb-1]; for(i=nb;i>0;i--) /*il faut commencer par la fin ! */ t[i]=t[i-1]; t[0]=rot; } void trier (tableau t, int nb) { int i,j, min,rot; for(i=0;i<nb-1;i++) /* on les classe tous sauf le dernier */ { min=i; for(j=i+1;j<nb;j++) /* recherchons le plus petit */ if(t[j]<t[min]) min=j; rot=t[i]; /* on les change */ t[i]=t[min]; t[min]=rot; } } int main (void) { tableau tab; int nb,i,choix; nb=0; do { printf("que voulez vous faire?\n1 : saisir\n2 : afficher\n"); printf("3 : calcul moyenne\n4 : ajout valeur\n5 : supprimer valeur\n"); printf("6 : rotation vers le bas\n7 : rotation vers le haut\n"); printf("8 : trier\n0 : quitter\n-> "); scanf("%d",&choix); switch(choix) { case 1: saisie(tab,&nb,DIM);break; case 2: affichage(tab,nb);break; case 3: printf("La moyenne vaut %f\n",moyenne(tab,nb));break; case 4: ajouter(tab,&nb,DIM);break; case 5: supprimer(tab,&nb);break; case 6: rotbas(tab,nb);break; case 7: rothaut(tab,nb);break; case 8: trier(tab,nb);break; } } while(choix); }

67