Académique Documents
Professionnel Documents
Culture Documents
Introduction
Histoire...
Le langage C est cr en 1972 aux Bell Labs (ATT) par Denis Ritchie pour dvelopper le systme d'exploitation UNIX. A partir de 1978, le livre "The C Programming Langage" de Brian Kernighan et Denis Ritchie sert de rfrence pour le langage C. Il faut attendre 1982 pour que l'ANSI (American National Standard Institute) propose un groupe de travail (X3J11) pour normaliser le langage C et sa bibliothque standard. Le langage C ainsi normalis est nomm couramment C ANSI.
Premier programme
Ce premier programme doit pouvoir tre compris grossirement avec les explications fournies. Il donne une ide de la forme d'un programme.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /* premier.c: moyenne de deux entiers jp dalban - Aut 1992 */ #include <stdio.h> main() { int a,b; float c; printf("entrez deux entiers: "); scanf("%d %d",&a,&b); c = (a+b)*0.5; printf("leur moyenne est: %f \n",c); }
Les lignes de ce programme ont t numrots pour la convenance des explications. Lignes 1-4: Commentaires. Ils sont dlimits par /* et */. Ligne 5: Directive d'inclusion du fichier stdio.h ncessaire pour utiliser les entres/sorties Ligne 6: Ligne vide. Pour arer un peu. Ligne 7: Entte de la fonction main. C'est la fonction principale du programme. Elle n'a pas de paramtres (si il y en avait ils seraient entre les parenthses). Ligne 8: Dbut du corps de la fonction. Ligne 9: Dclaration de deux entiers a et b. Ligne 10: Dclaration d'un nombre flottant c (rel). Ligne 11: Ligne vide. Pour faire joli. Ligne 12: Printf affiche "entrez deux entiers: " Ligne 13: Scanf attend du clavier une rponse au format "%d %d" c'est dire deux dcimaux. Ces dcimaux taps au clavier seront placs le premier l'adresse de a (c'est dire dans la variable a), l'autre l'adresse de b (c'est dire dans la variable b).
Ligne 14: Calcul de c, moyenne de a et b. Ligne 15: Printf affiche "leur moyenne est %f \n". A la place de %f est affich la valeur de c. Le caractre \n indique un passage la ligne suivante. Ligne 16: Fin de la fonction main.
Les nom qui commencent par '_' sont normalement rservs un usage particulier.
Mots-cls du langage
Un mot-cl est un nom prdfini du langage. En C ANSI, les mots-cls ne peuvent tre redfinis. Note: En C++, les mots-cls sont redfinissables (surcharge) Ces mots-cls sont: auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while
Forme du programme
Le langage respecte un certaine syntaxe dans laquelle interviennent diffrents symboles: parenthses, accolades, ponctuation, espaces. Ces symboles sont ncessaires pour assurer la structure du programme. Si cette syntaxe est respecte, la forme peut tre libre. On peut crire un programme entier sur une ligne...
void main(void){int a,b;float c;c=(a+b)*0.5;}
Dans cet exemple (correct), on a enlev tous les espaces et toutes les fin de lignes superflues. Les seuls espaces restants sont indispensables car sinon des identificateurs seraient 'colls'... Evidemment, un programme doit pouvoir tre lu facilement. Il doit tre - ar (lignes vides), - indent (retraits indiquant une structure) - bien comment (mais pas trop non plus) Dans le programme suivant on a ajout des fins de lignes (ret) et des tabulations (tab) pour indenter le texte.
void main(void) ret { ret tab int a,b; ret tab float c; ret tab c=(a+b)*0.5; ret } ret
Commentaires
Les commentaires sont compris entre /* et */. Le compilateur ignore compltement ce qui est entre ces deux limites. Exemple:
/* Ceci est un commentaire */ /* Ceci aussi */
Les tableaux, les structures et les pointeurs sont tudis plus loin.
Pourquoi un type ?
Rien ne sert de connatre la valeur d'un objet si on n'en pas le type... En effet, on peut toujours connatre la valeur d'un objet, ne serait-ce qu'en regardant dans la mmoire. On verra par exemple dans la mmoire l'adresse de l'objet : 01001101 Cette configuration binaire peut tre aussi bien: - le nombre 77 - la lettre M (code ASCII) - ou toute autre chose, tout dpend de la signification de l'objet pour le programmeur. Si l'on connait le type, alors parler de valeur a une signification. Si cet objet est un entier dire que sa valeur est 77 est correct. Si c'est un caractre, c'est le caractre M. Plus prcisment, le type d'un objet dfinit: - quel ensemble de valeurs qu'il peut recevoir, - comment une valeur est code en binaire dans la mmoire, - quelles sont les oprations qui lui sont applicables. La notion de type assure ainsi une certaine qualit de la programmation car le compilateur peut faire des vrifications sur la bonne utilisation de objets (et des paramtres des fonctions).
Les entiers
Le type entier est le type int. Par dfaut, il s'agit d'entier sign.
Varits d'entiers
Le type int peut tre prfix de long ou short. Ce prfixe permet de choisir entre entier 16 bits (short int) et entier 32 bits (long int). Quant au type int, il est au format le plus long que la machine est capable de traiter efficacement : 16 bits sur les machines 16 bits (courant en informatique industrielle) ou 32 bits sur la plupart des machines actuelles. Les entiers sont signs par dfaut. Si l'on souhaite que le bit de signe soit supprim pour permettre la numration d'aller plus loin, on prfixe le type entier par unsigned. unsigned signed unsigned signed 16 16 32 32 0 -32 768 0 -2 147 483 648 65 535 32 767 4 294 967 295 2 147 483 647
Formats en mmoire
La reprsentation mmoire habituelle est celle des nombre signs en complments deux, 16 ou 32 bits (2 ou 4 octets).
- pas de prfixe : dcimal -0 : octal (lire zro et non lettre o) - 0x : hexadcimal Il est possible aussi d'indiquer avec l'aide d'un suffixe le type de la constante. A dfaut de suffixe, la constante est int ou long int si sa valeur est trop grande pour tre de type int. Le suffixe indique le type: - u,U - l,L -> unsigned int -> long int
Notes: Seuls les nombres exprims en dcimal peuvent tre signs. Les constantes short n'existent pas. Exemples: 99 99L -1 0xff 0111 0xffffffffU entier en dcimal, valeur +99 entier long en dcimal, valeur +99, rang sur 32 bits entier en dcimal, valeur -1 entier en hexadcimal, valeur 255 soit 15*16+15*1 entier en octal, valeur 73 soit 1*64+1*8+1*1 entier non sign en hexadcimal, valeur 4 294 967 295 et non -1
Les caractres
Le type caractre est char. Un caractre est considr en C comme un entier. Par exemple lcriture: c = A+1 ; est lgale. Ceci permet de manipuler les caractres comme des nombres et notamment de les comparer.
Chaque caractre possde une valeur entire comprise entre 0 et 127, code en mmoire sur 8 bits. Le langage C ne spcifie pas si le type char est sign ou non. Il est donc imprudent daffecter une variable de type char une valeur dont le bit 7 serait gal 1. Ce problme ne devrait pas se poser si lon se limite aux 128 caractres ASCII (voir table en annexe). Un caractre peut cependant tre dclar unsigned char pour signifier au compilateur que le bit 7 ne doit pas tre considr comme un bit de signe. Ceci permet de manipuler des caractres de valeurs comprises entre 0 et 255. A linverse, un caractre peut tre dclar signed char. Exemple:
char c ; int i ; c = \xFF ; i = c ;
Le bit 7 de c vaut 1. Le caractre peut tre considr par certains compilateurs comme positif et ngatif par dautres... La variable i peut donc valoir -1 ou 255 !!! En dclarant unsigned char c ou signed char c, on lve le doute: dans le premier cas, i vaut 255, dans le second, il vaut -1.
Constantes caractres
Les caractres sont toujours reprsents entre deux guillemets simples. Exemples:
A, B, C ... a, b, c ... 0, 1, 2 ... +, =, ? ... Certains caractres non imprimables trs courants sont reprsents prfixs par un back-slash (\): \f \n \r \t
saut de page (form feed) saut la ligne (new line) retour debut ligne (return) tabulation (tab)
Tous les caractres peuvent tre reprsents en indiquant leur code ASCII en octal ou en hexadcimal de la manire suivante: \ooo caractre de code octal oo \xhh caractre de code hexadcimal hh
Les flottants
Le type float permet de mmoriser des nombres dcimaux.
Varits de flottants
Les variables de type float sont habituellement ranges sur 32 bits sous une forme M.10N. Comme cet espace mmoire ne permet pas de ranger une mantisse (M) et un exposant (N) suffisamment grands, on dispose dun type double prcision double qui occupe normalement un espace mmoire double (64 bits) et un type de prcision tendue long double (80 bits). On trouve dans les fichiers <limits.h> et <float.h> des informations sur ces types pour le systme sur lequel vous travaillez.
Formats en mmoire
Dans cette dernire notation, ma mantisse est un dcimal relatif et lexposant est un entier relatif. Le signe introduisant lexposant est e ou E.
Notion de variable
Une variable est une zone de mmoire affecte d'un type. Le rle d'une variable est de mmoriser une valeur correspondant son type. Toutes variable doit tre dclar avant son utilisation.
int x; int a,b,c; char c; float u,v; /* /* /* /* entier x */ trois entiers: a,b,c */ c est un caractre */ u et v sont des flottants */
Oprateurs
Oprateurs arithmtiques
BINAIRES (deux oprandes) + somme diffrence * produit / quotient % reste UNAIRES (un seul oprande) + identit oppos -dcrmentation ++ incrmentation
Les oprateurs + - et * s'appliquent aussi bien des nombres entiers que flottants. Exemple:
/* moyenne.c : moyenne de deux nombres flottants */ #include <stdio.h> void main(void) { float a,b,m; scanf("%f%f",&a,&b); m = (a+b)/2; printf("moyenne:%f\n",m); }
L'oprateur / appliqu des entiers ralise une division entire. Mais si un des deux oprandes est flottant, il ralise la division flottante.
3 / 2donne 1 (division entire) 3.0 / 2 donne 1.5 (division des flottants car un oprande est flottant)
Incrmentation et dcrmentation
Les oprations d'incrmentation et de dcrmentation dcrmentent ou incrmentent un entier, un flottant.ou un pointeur. Elles sont surtout utiles dans des boucles pour grer des indices ou des compteurs entiers. Applique AVANT le nom de l'objet une telle opration est excute AVANT l'instruction qui la contient. Applique APRES, elle est excute APRES. Exemple:
/* increm.c : incrementation d'un compteur entier #include <stdio.h> void main(void) { int c1=0, c2=0; printf("%d %d\n", c1 , c2); c1++; ++c2; printf("%d %d\n", c1 , c2); printf("%d %d\n", c1++ , ++c2); } */
/* 0 et 0 */ /* 1 et 1 */ /* 1 et 2 */
L'utilisation de ces oprateurs pour les pointeurs est prsent au paragraphe des pointeurs.
ET bit bit OU bit bit OU exclusif bit bit dcalage droite dcalage gauche complment 1 bit bit
ATTENTION: Ne pas confondre ces oprateurs bit bit avec les oprateurs logiques qui sont prsents plus loin.
L'oprateur est appliqu bit bit c'est dire: R = A op B <=> Rk = Ak op Bk pour k variant de 0 n-1 o n vaut 8 pour les caractres, 16 pour les short et 32 pour les long. Exemple:
/* bit.c : oprations #include <stdio.h> void main(void) { int a,b,c; a = 0xFF00; b = 0x1234; c = a & b; printf("%x\n",c); c = ~c; printf("%x\n",c); } bit bit, ici un ET et un NON */
/* /* /* /* /* /*
Ces oprateurs permettent de dcaler un entier droite ou gauche d'un nombre donn de bits: R>>n a pour rsultat R dcal de n bits droite
R<<n a pour rsultat R dcal de n bits gauche Dans les deux cas: - les bits sortant sont perdus - les bits entrant sont des 0 Note: Le dcalage na pas lieu dans le premier oprande. Celui-ci nest donc pas affect par lopration. Exemple:
/* shift.c : oprations de dcalage */ #include <stdio.h> void main(void) { int a,b; a = 0x1234; /* a= 0001 0010 0011 0100 */ b = a>>4; /* b= 0000 0001 0010 0011 , a inchang */ printf("%x\n",b); /* 0123 en hexa */ b = a<<4; /* b= 0010 0011 0100 0000 , a inchang */ printf("%x\n",b); /* 2340 en hexa */ }
Oprateurs relationnels
Les oprateurs relationnels permettent de raliser des comparaisons. < > <= >= == != infrieur suprieur infrieur ou gal suprieur ou gal gal diffrent
Le rsultat d'une comparaison est un entier qui vaut: - 0 quand le rsultat est FAUX - 1 quand le rsultat est VRAI Exemple:
/* compare.c : oprateurs relationnels */ #include <stdio.h> void main(void) { int a,b,c; a = 1; b = 2; c = (a>b) printf("%d\n",c); /* rponse 0, donc (a>b) est FAUX */ b = (a<b); printf("%d\n",c); /* rponse 1, donc (a<b) est VRAI */ }
Mais videmment, c'est dans les test des instruction if, for, while que ces oprateurs ont leur pleine utilit. Exemple:
/* test.c : oprateurs relationnels */ #include <stdio.h> void main(void) { int a,b; a = 1; b = 2; if (a>b) printf("a suprieur b\n"); else printf("a infrieur ou gal b\n"); }
Oprateurs logiques
Ces oprateurs sont faits pour combiner plusieurs comparaisons en tests complexes. && || ! ET logique OU logique (ou inclusif) NON logique
Ils fonctionnent avec la logique habituelle: VRAI VRAI FAUX FAUX Exemple:
/* tests2.c : oprateurs logiques */ #include <stdio.h> void main(void) { int a,b,c; a = 1; b = 2; c = 3; if ( (a<=b) && (b<=c) ) printf("b appartient [a,c]\n"); /* notre cas */ else printf("b n'appartient pas [a,c]\n"); if ( !( (b==a) || (b==c) ) ) printf("b n'est ni a ni c\n"); } /* notre cas */
== == == ==
|| || || ||
== == == ==
ATTENTION
1) Ces oprateurs considrent que FAUX=0 et que tout ce qui nest pas faux est VRAI. 2) Les oprateurs && et || nvaluent pas toujours leurs deux oprandes: - dans le calcul de c1&&c2, si c1 est FAUX, c2 nest pas valu car quelque soit sa valeur le rsultat est FAUX, - dans le calcul de c1||c2, si c1 est VRAI, c2 nest pas valu car quelque soit sa valeur le rsultat est VRAI.
On confond souvent lusage des oprateurs bit bit et des oprateurs logiques. 1) Les oprateurs bit bit sont faits pour manipuler les bits dun entier. Par exemple, pour tester un bit lu sur un port, on peut appliquer un masque avec un & afin dcraser les bits inutiles:
if ( port & 0x4 == 0 ) printf(bit 2 du port ZERO \n); else printf(bit 2 du port UN \n);
2) Les oprateurs logiques permettent de combiner des conditions. Les oprandes dun oprateur logique sont normalement une comparaison ou un rsultat de comparaison. Par exemple, crire
if ( port && 0x4 ) ....
veut dire que lon considre port comme une condition: VRAI si un des bits vaut 1 ou FAUX si tous les bits sont 0. Ce qui est dj un peu trange, mais ventuellement acceptable... Quant 0x4, il doit tre considr comme toujours VRAI. et en ce sens il est quivalent 0x1 et la condition se rsume
if ( port ) ....
10
Moralit: Il faut savoir ce que lon veut faire: manipulation de bits ou combinaison de conditions.
Affectation
L'affectation est un oprateur qui permet d'affecter (attribuer) une valeur un objet. = Affectation
A gauche du signe gal, on indique l'objet qui reoit. A droite, on indique la valeur. Exemple : "x=100" signifie bien que la variable x reoit la valeur 100. Mais l'affectation est un oprateur comme les autres et cela va nous permettre certaines critures tranges et parfois commodes. Comme tout oprateur l'affectation possde un rsultat qui est simplement la valeur affecte. Exemple1 : "c=a+(b=3)" est une expression dont l'valuation est ralise ainsi: - b est affect de la valeur 3 et l'expression devient "c=a+3", - a+3 est calcul, - c est affect du rsultat. Exemple2 : "a=b=c=0" est une expression quivalente "a=(b=(c=0))" du fait de l'associativit de droite gauche de l'affectation. Elle est donc value ainsi: - c=0 et l'expression devient a=(b=0), - b=0 et l'expression devient a=0, - a=0.
Oprateur sizeof
L'oprateur sizeof permet de connatre la taille d'un objet ou d'un type. sizeof (T) taille de T en octets o T est le nom d'un objet ou d'un type
Cet oprateur s'utilise comme une fonction dont le rsultat est un entier. Exemple:
/* sizeof.c : oprateur sizeof() */ #include <stdio.h> void main(void) { char s[] = "hello"; int L; /* taille d'une variable */ L = sizeof(s); printf("%d\n",L); caractres) */ /* taille d'un type */ printf("%d\n",sizeof(int)); printf("%d\n",sizeof(char)); }
/* 6 (voir chanes de
/* 4 (entiers 32bits) */ /* 1 */
11
Pointeurs
Adresse d'une variable et notion de pointeur
L'oprateur & permet d'obtenir l'adresse d'un objet. Exemple:
{ int x; printf ("%d \n",x); printf ("%x \n, &x); }
Dans cet exemple on affiche en dcimal la VALEUR de x, puis en hexadcimal, son ADRESSE. Note: C'est une habitude courante d'afficher les adresses en hexadcimal car les cartes mmoire sont plus facilement lisibles si les adresses sont en hexa. Une adresse peut tre mmorise dans une variable. Une telle variable est appele pointeur. Quand un pointeur a reu ladresse dun objet, on dit quil pointe vers cet objet. Appliqu un pointeur l'oprateur * dsigne l'objet point. Exemple:
{ int x,y; ... y = *&x; } /* identique y=x */
L'expression &x a pour valeur l'adresse de x. Elle pointe donc vers x. L'expression *&x est quivalente *(&x) dsigne l'objet point par &x c'est dire x.
Voir plus loin pour les dclaration de pointeurs vers d'autres types. Exemple de pointeur vers un entier:
{ int x,y; int *px; ... px = &x; y = *px; }
/*
identique y=x
*/
Ici, la variable px est un pointeur vers un entier. La dclaration "int *px" indique en effet que la valeur pointe par px ("*px") est un entier. Ce pointeur est ensuite affect de l'adresse de x. Il pointe donc effectivement vers x. Ensuite le pointeur est utilis pour accder la valeur de x et l'affecter y.
12
/* dclaration de I */
Dans cet exemple, la fonction traiter prsente deux inconvnients: - I est pass par valeur, d'o recopie de la valeur de I dans x ==> efficacit mdiocre, - traiter travaille sur une copie de I et non sur I ==> le traitement effectu ne porte pas sur I ! La fonction traiter2 ne possde aucun de ces deux problmes.
Expressions et instructions
Notion d'expression
Une expression est la combinaison de valeurs d'objets au moyen d'oprateurs et de fonctions. Les oprateurs ont t prsents prcdemment.
13
Quand une expression comporte plusieurs oprateurs, l'ordre d'excution des oprateurs est fix soit par l'utilisation de parenthses soit par dfaut par les rgles de priorit et d'associativit. Pour les oprateurs courants, ces rgles sont celles utilises en mathmatiques. Quand on n'est pas sr, le mieux est d'utiliser des parenthses ou bien de consulter la table des priorits (voir plus loin). Exemple: Dans l'expression "a*2+b*3", a est multipli par deux, b par 3, puis les deux rsultats sont additionns. La plupart des oprateurs ont pour seule action de produire un rsultat partir des oprandes (en particulier, ils ne modifient pas leurs oprandes). On dit que ces oprateurs n'ont pas d'effet de bord. Les seuls oprateurs qui prsentent des effets de bord sont l'affectation ainsi que les oprateurs d'incrmentation et de dcrmentation. Dans ces deux cas, en effet, un oprande est modifi. Exemple: Dans l'expression "a+(b=3)" l'opration "(b=3)" produit le rsultat 3 et en effet de bord affecte 3 b. Dans l'expression "x = i++" l'opration "i++" produit comme rsultat la valeur de i et en effet de bord (aprs instruction) incrmente i.
Notion d'instruction
Une instruction est une entit excutable. Cela peut tre une instruction simple ou une instruction complexe. Une instruction simple est une expression valuer: - les affectations - les appels de fonction ou bien une des instructions goto, break et continue. Une instruction complexe est une des instructions if, for, while, switch. On dit complexe car elles sont composes de plusieurs parties: condition, instructions,.... Ces instructions sont tudies plus loin. Les instructions simples sont toujours termines par un point-virgule.
14
On notera que tous les oprateurs ont une associativit de gauche droite sauf les oprateurs unaires et l'affectation. Exemple1: Dans l'expression "a*x+b" les oprateurs * et + sont de priorits diffrentes. Ils sont appliqus dans l'ordre * puis +. Exemple 2: Dans l'expression "a-b+c" les oprateurs - et + sont de priorits gales. Ils sont appliqus dans l'ordre (de gauche droite) indiqu par leur associativit: - puis +. Exemple 3: Dans l'expression "*&v+1" les oprateurs * et & sont de priorits gales et plus fortes que celle de +. Les oprateurs sont appliqus dans l'ordre: & (&v est l'adresse de v), puis * (qui dsigne la valeur pointe par &v, c'est dire v), puis +. Cette expression est donc identique "v+1". Exemple 4: "a=b=c=0" est une expression quivalente "a=(b=(c=0))" (voir le paragraphe sur l'affectation). Cette expression affecte 0 a,b et c.
Conversions de type
Conversions implicites dans les expressions
Dans une expression, l'homognt des types est thoriquement souhaitable (on nous a souvent dit de n'ajouter des pommes qu' des pommes). Cependant, dans la pratique, on est souvent amen vouloir combiner dans des expressions des valeurs de type diffrents mais voisins, par exemple int et float... Que fait donc le compilateur quand nous mixons les types?
Principe
Les deux types qui doivent tre combins par un oprateur sont souvent voisins. Et il y a toujours un type qui est "plus fort" que l'autre. Le compilateur convertit simplement la valeur du type faible en type fort et l'on se retrouve dans le cas o les deux oprandes sont de mmes types. Une telle conversion est dite implicite. Exemple: Dans l'expression "a+b", - si "int a,b" alors a+b est un int (aucune conversion) - si "float a,b" alors a+b est un float (aucune conversion) - si "int a" et "float b", alors a est converti en float et a+b est un float
En fait, la conversion est effectue en deux tapes: 1) la promotion consiste lever les valeurs des types char ou short au type int (on parle souvent de la promotion entire). Cette promotion est systmatique. 2) l'ajustement consiste galiser les types des deux oprandes comme il a t expliqu ci-dessus. La "force" des types est: int < long < float < double < long double
15
Exemple: Dans l'expression "a+b", - si "short a" et "short b" alors a et b sont promus int et a+b est un int - si "short a" et "double b" alors a est promu int puis ajust double, le rsultat est double.
Conseil d'ami: travaillez toujours en sign. Et si le non-sign est indispensable, ne le mixez pas avec du sign! Sinon, voir un ouvrage prcis.
Si par contre la fonction n'a pas de prototype, on a les conversions implicites suivantes: - char et short sont convertis en int, - float est converti en double.
16
La norme ANSI admet comme lgales toutes les conversions. On peut donc tout faire. Reste savoir si ce que l'on fait rpond notre problme. Donc attention, dans la pratique, il faut se limiter ce dont on connait les effets: - entier vers flottant: - flottant vers entier: valeur intacte troncature
Les conversions explicites sont aussi utilisables sur les pointeurs. L'objectif est souvent dans ce cas de faire certains tours de passe-passe par exemple pointer avec un mme pointeur sur des objets de types diffrents. Mais ceci est dj de la programmation avance...
Instructions de contrle
Les instructions de contrle ont pour fonction de pouvoir orienter (contrler) l'excution du programme en modifiant son cours. On a besoin habituellement de deux types de structure de contrle: 1) Les boucles permettent de REPETER une instruction ou un groupe d'instructions. Le langage C offre pour cela les instructions while et for. 2) Les aiguillages permettent d'ORIENTER le traitement vers une ou un groupe d'instructions en fonction d'une condition. On dispose de deux instructions: if et switch. Pour prsenter les diffrentes instructions de contrle, nous allons tudier un mme exercice au long des paragraphes qui suivent.
L'exercice
Il s'agit de compter les caractres 'a', 'b', et 'c' d'une chane entre au clavier. On compte aussi le nombre de caractres diffrents. Le programme pourrait avoir l'allure suivante:
/* exo.c : comptage des a,b et c dans une chane */ #include <stdio.h> void main(void) { char ch[20]; int na,nb,nc,n;
17
/* indice */
Il est clair que le comptage des caractres consiste parcourir la chane (boucle) et selon qu'un caractre est un 'a', un 'b' ou un 'c' incrmenter le compteur correspondant (aiguillage). Nous allons donc examiner les diffrentes possibilits d'criture de ce traitement au cours des paragraphes qui suivent.
goto
L'instruction goto permet d'effectuer un branchement un endroit du programme dsign par une tiquette. goto etiquette L'usage de goto ne conduit pas une programmation de qualit. Le texte du programme est souvent difficile lire et donc corriger. Nous dconseillons donc son usage. L'exercice propos a cependant t trait.
L'exercice
i = 0; debut_boucle: if (ch[i] == '\0') /*
Le test de chaque caractre peut lui aussi tre fait avec goto et if:
if ( ch[i] == 'a' ) goto A; if ( ch[i] == 'b' ) goto B; if ( ch[i] == 'c' ) goto C; n++; goto suite; A: na++; goto suite; B: nb++; goto suite; C: nc++; goto suite; suite:
goto fin_boucle;
18
if ( ch[i] == 'b' ) goto B; if ( ch[i] == 'c' ) goto C; n++; goto suite; A: na++; goto suite; B: nb++; goto suite; C: nc++; goto suite; suite: i++; goto debut_boucle; fin_boucle: printf("%d %d %d %d \n",na,nb,nc,n); }
On constate le style "BASIC" de la programmation. La lecture est difficile cause des nombreux goto qui obligent suivre le fil du programme. Dans ce programme, il y a cependant un effort de structuration. Mais quand le programme est plus long et le programmeur moins soigneux, on obtient ce que certains nomment "un plat de spaghetti".
if
L'instruction if sert orienter le traitement en fonction d'une condition. Le if peut tre utilis avec ou sans else. if ( condition ) action if ( condition ) action1 else action2 Une condition est habituellement une expression. Elle est FAUSSE si la valeur de l'expression est 0, VRAIE sinon. Une action peut tre soit une instruction soit un bloc de plusieurs instructions. Un bloc est dlimit par des accolades: { }.
L'exercice
if if if if ( ( ( ( ch[i] == 'a' ) na++ ; ch[i] == 'b' ) nb++ ; ch[i] == 'c' ) nc++ ; (ch[i]!='a')&&(ch[i]!='b')&&(ch[i]!='c') ) n++;
Il y a imbrication car le deuxime if est l'instruction contenue dans le else du premier et le troisime if dans le else du second. Cette imbrication est visualis par une prsentation classique nomme indentation (retrait d'une tabulation droite). Quand on a un doute sur la structure, ne pas hsiter faites des blocs:
if ( ch[i] == 'a' ) na++ ; else { if ( ch[i] == 'b' ) nb++ ; else {
19
L encore, l'indentation est utilise pour visualiser les imbrications de structures. Evidemment, c'est plus long. Mais il faut penser que le plus important est la lisibilit et la concision et non la compacit. Une trop grande compacit du texte n'apporte rien, mme pas un gain de vitesse d'excution...
switch
L'instruction switch permet d'orienter l'excution dans differentes directions en fonction des valeurs d'une expression. switch ( expression ) { case valeur_1 : liste_instructions case valeur_2 : liste_instructions case valeur_3 : liste_instructions ... case valeur_N : liste_instructions default: liste_instructions } La liste d'instruction est facultative. Elle est normalement termine par l'instruction break pour terminer le swtich quand une valeur est traite. Le cas default est utilis lorsqu'aucune des valeurs ne correspond celle de l'expression.
L'exercice
L'instruction switch est idale pour tester un caractre et effectuer diffrentes actions selon les valeurs possibles:
switch ( ch[i] ) { case 'a': na++ ; break ; case 'b': nb++ ; break ; case 'c': nc++ ; break ; default : n++ ; }
L'oubli de l'instruction break change le comportement de cette structure. Par exemple si on les oublit tous, quand le caractre est un 'a' les trois compteurs sont incrments, quand c'est un 'b' les compteurs nb et nc sont incrments, et quand c'est un 'c', seulement nc est incrment.
while
L'instruction while est une instruction pour raliser une boucle. L'action est excute de manire rptitive tant que la condition est vraie. Cette condition est value AVANT l'excution de l'action. Ceci veut dire que si la condition est fausse ds le dpart, l'action ne sera jamais excute. while ( condition ) action La condition est habituellement une expression. L'action peut tre soit une simple instruction soit un bloc d'instructions dlimit par des accolades { }.
20
L'exercice
i = 0; while ( ch[i] != '\0' ) { /* ICI mettre le test du caractre ch[i] */ /* par exemple le switch vu plus haut */ i++ ; }
L'instruction while nous permet de dfinir la boucle pour parcourir les caractres de la chane.
Cette boucle est excute tant que le caractre '\0' n'est pas dtect. Dans le cas d'une chane vide, ce caractre est dtect immdiatement et la boucle termine.
do .. while
L'instruction do ... while est une instruction voisine de while dans laquelle l'valuation de la condition est ralise APRES l'action. Ceci veut dire que si la condition est fausse ds le dpart, l'action est quand mme excute une fois. Dans certains cas c'est ncessaire, dans d'autres ce comportement est nfaste. do action while ( condition ) La condition est habituellement une expression. L'action peut tre soit une simple instruction soit un bloc d'instructions dlimit par des accolades { }.
L'exercice
i = 0; do { /* ICI mettre le test du caractre ch[i] */ /* par exemple le switch vu plus haut */ i++; } while ( ch[i-1] != '\0' ) ;
Cette instruction nous permet de dfinir la boucle pour parcourir les caractres de la chane.
L'utilisation du do...while pose dans notre cas deux difficults: 1) On remarque qu'il est ncessaire de tester le caractre ch[i-1] car l'incrmentation est dj ralise au moment du test. 2) Dans le cas d'une chane vide, le caractre de fin de chane n'est dtect qu'aprs excution de l'intrieur de la boucle. Comme '\0' n'est ni 'a' ni 'b' ni 'c', le compteur n sera incrment et laissera penser que la chane n'tait pas vide... Donc dans cet exercice le while sera prfr au do...while.
for
L'instruction for est une instruction de type while qui apporte un grande facilit d'criture des boucles. Elle rsulte de la constatation que dans un grand nombre de boucles, on a en plus de l'action et la condition: - une action d'initialisation - une action excuter avant chaque rebouclage
21
for ( action_initiale ; condition ; action_avant_rebouclage ) action L'action initiale est expression. Habituellement c'est une affectation. L'action avant rebouclage est aussi une expression valuer. Cela peut tre une incrmentation. La condition et l'action ont un rle analogue celui du while. L'instruction for est quivalente un while: action_initiale ; while ( condition ) { action action_avant_rebouclage ; }
L'exercice
i = 0; while ( ch[i] != '\0' ) { /* ICI mettre le test du caractre ch[i] */ /* par exemple le switch vu plus haut */ i++ ; }
on distingue: - " i=0 " qui est une action d'initialisation - " i++ " qui est une action avant rebouclage - " ch[i]!='\0' " qui est la condition d'excution L'criture avec un for est donc:
for ( i=0 ; ch[i]!='\0' ; i++ ) { /* ICI mettre le test du caractre ch[i] */ /* par exemple le switch vu plus haut */ }
Ruptures de boucle
Une boucle se termine normalement quand sa condition devient fausse. Dans certains cas, il est cependant ncessaire de rompre la boucle en cours. Les instructions break et continue peuvent tre utilises avec while, do .. while et for.
Break
L'instruction break, utilise par ailleurs dans le switch, provoque une terminaison de la boucle englobante. BOUCLE avec BREAK: while ( ... ) { .... break; .... } EQUIVALENCE: while ( ... ) { .... goto etiqu; .... } etiqu:
22
Continue
L'instruction continue provoque l'abandon de l'action et force un rebouclage. BOUCLE avec CONTINUE: while ( ... ) { .... continue; .... } EQUIVALENCE: while ( ... ) { .... goto etiqu; .... etiqu: }
Programmez structur !
On voit donc que les instructions de contrle du langage C (goto exclu) permettent une criture structure et claire d'un programme, gnralement de bien meilleure qualit que celle obtenue avec l'usage des goto.
L'exercice
/* exo2.c : comptage des a,b et c dans une chane, version 2 */ #include <stdio.h> void main(void) { char ch[20]; int na,nb,nc,n; int i; na=nb=nc=n=0; scanf("%s",ch); for ( i=0 ; ch[i]!='\0' ; i++ ) { switch ( ch[i] ) { case 'a': na++ ; break ; case 'b': nb++ ; break ; case 'c': nc++ ; break ; default : n++ ; } } printf("%d %d %d %d \n",na,nb,nc,n); }
Comparez-donc ce programme exo2.c et le programme exo1.c crit avec des goto et tirez-en votre conclusion.
Fonctions
Une fonction est un ensemble ordonn dinstructions dfini par le programmeur. Tout programme possde une fonction principale nomme main. Toute fonction peut son tour appeler dautres fonctions qui sont dfinies soit dans le programme, soit dans une librairie de fonctions. Chaque fonction possde un nom et ralise habituellement un travail prcis. Pour cela, elle reoit comme donnes les valeurs de ses paramtres et renvoie habituellement un rsultat. Le nom dune fonction est souvent choisi en rapport avec travail ralis. Une fonction a deux aspects: sa dfinition et son utilisation.
23
Dans ce paragraphe sur les fonctions, nous continuons dvelopper l'exercice dfini dans le paragraphe prcdent qui consiste calculer le nombre de caractres a, b et c d'une chane.
Les avantages de la modularit en informatique sont trs importants et mme fondamentaux dans le sens que les normes programmes qui pilotent certaines applications nauraient jamais pu tre envisags sans mthode et en particulier sans une conception modulaire. A titre dexemple, les programmes qui grent la navette Herms reprsentent plusieurs millions de lignes de programme en langage volu et ncessitent plusieurs milliers de dhomme x anne (par exemple 300 personnes pendant 5 ans). Ces avantages sont: 1) Le travail en quipe Chaque quipe travaille sur un module. Evidemment, ceci nest possible quaprs une solide analyse de lapplication qui permet de dfinir les fonctionalits des modules et leur interfaces. 2) La rutilisabilit Si un module a t utilis dans un programme, il pourra ltre dans dautres. Lors de la conception initiale dun module, il est important de le concevoir de manire quil soit le plus rutilisable possible: suffisamment gnral, facilement utilisable, bien document... 3) La testabilit Un module est suffisamment indpendant pour tre test par ses concepteurs avant dtre incorpor dans un grand programme. La certification de ce dernier sera ainsi facilite. 4) La maintenance Quand un grand programme prsente un mauvais fonctionnement, ou bien quand il doit tre rajeuni pour le rendre plus rapide ou plus sr, ou parce que lapplication a change, son organisation modulaire permettra de ne changer que les modules concerns. 5) La rduction de la complexit Les utilisateurs dun module ne connaissent que son interface. La manire dont il est ralis est prive et ne concerne que lquipe qui la ralis. Ainsi, chaque quipe ne voit que la complexit de son module et pas celle des autres.
De manire pratique, un module peut tre un simple fichier qui regoupe un ensemble de fonctions. Pour raliser une application faite de plusieurs modules, on peut utiliser plusieurs techniques. Ces techniques sont dcrites dans le paragraphe sur la compilation spare.
24
Une fonction doit tre considre comme un composant disposant dentres (les paramtres) et dune sortie (le rsultat). - Les entres possdent un nom et un type comme des variables. Elles sont dsignes sous le nom de paramtres formels. Ces paramtres formels reoivent des valeurs appeles paramtres effectifs. - Le rsultat possde un nom (celui de la fonction) et un type. - Le corps de la fonction est exprim en fonction des paramtres formels ce qui lui permet de pouvoir tre excut pour toute combinaison de paramtres effectifs pourvu que les types soient respects. Dans lexemple ci-dessous la fonction delta qui calcule le discriminant du trinme ax2+bx+c est reprsente schmatiquement comme un composant lectronique.
0.5
paramtres effectifs
float a
float b
float c
paramtres formels
-2
La premire ligne dcrit les entres/sorties de la fonction: nom et types des paramtres, nom et type du rsultat. Cette ligne dclarative est nomme prototype de la fonction. Le corps de la fonction, compris entre des accolades { }, indique les actions qu'elle doit excuter. Les valeurs des paramtres sont utilises et le rsulat de la fonction est renvoy par l'instruction return. Utiliser une fonction consiste l'appeler en lui passant des paramtres et en recevant son rsultat. La dfinition d'une fonction est normalement place AVANT son utilisation. Exemple: Soit le trinme x2 + 2kx - 5 o k est une variable. Le test du discriminant peut tre ralis ainsi:
if ( delta (1,2*k,-5) ) >= 0 { .... } else { ....}
On notera que les paramtres effectifs passs lappel peuvent tre des expressions (valeurs de variables ou de constantes ou combinaison).
25
L'exercice
Il est possible dans notre exercice de dfinir une fonction qui calcule le nombre de fois qu'un caractre donn apparait dans une chane de caractres:
int compte( char s[], char c) { int i,n; n=0; for ( i=0 ; s[i]!='\0' ; i++ ) { if ( s[i] == c ) n++ ; } return n ; */ } /* indice et compteur */ /* compteur nul au dbut */ /* boucle */ /* on compte ... */ /* retour de n comme rsultat
Modle: Cette fonction accepte en entre une chane de caractres s et un caractre c. Le rsultat est un entier.
"hello"
'l'
char[] s
char c
int compte
2
Corps: Dans le corps, on utilise la fois les paramtres s et c mais aussi des variables locales i et n. Finalement, la valeur de n est renvoye comme rsultat. Pour utiliser cette fonction, il suffit par exemple d'crire:
na = compte(ch,'a') ; nb = compte(ch,'b') ; na = compte(ch,'c') ;
Dans ces trois appels, on passe la mme chane ch en premier paramtre, mais en second paramtre c'est successivement 'a', puis 'b' et enfin 'c'. Il y a trois excutions de la mme fonction sur des donnes diffrentes. De la mme manire, on peut crire une fonction qui calcule la longeur d'une chane:
int mesure( char s[] ) { int i,n; n=0; for ( i=0 ; s[i]!='\0' ; i++ ) return n ; }
n++ ;
n++ ;
26
return n ; } void main(void) { char ch[20]; int na,nb,nc,n; scanf("%s",ch); na = compte(ch,'a') nb = compte(ch,'b') na = compte(ch,'c') n = mesure(ch) - na } ; ; ; - nb - nc ;
printf("%d %d %d %d \n",na,nb,nc,n);
Evidement, vous constatez que ce programme exo3.c est plus long que exo2.c. Mais d'un autre ct, il est sous une forme qui le prpare la modularit. Attendez donc la suite pour juger...
Paramtres
Comme il a t vu plus haut, les paramtres sont les entres-sorties dune fonction. Les paramtres sont employs en deux occasions: - lors de la dfinition de la fonction: On donne alors leur nom et leurs type. Ils servent pour crire les instructions du corps de fonction. On parle alors de paramtre formel car, ce stade, ils ne sont pas porteurs dune valeur particulire. - lors de lappel de la fonction: Une valeur est affecte ce moment chaque paramtre formel. Cette valeur est nomme paramtre effectif. Les paramtres peuvent tre manipuls dans une fonction comme des variables locales.
Ici x peut tre utilise par f et main car elle est globale.
27
Les variables locales des fonctions f et main sont compltement indpendantes. Par exemple la variable a de la fonction f() et la variable a de la fonction main(), bien qu'ayant le mme nom ne dsignent pas le mme objet.
La visibilit d'une variable globale s'tend de sa dclaration la fin du fichier courant. La visibilit d'une variable locale s'tend de sa dclaration la fin de la fonction. Pour cette raison, sauf pour une raison particulire, on dclare habituellement les variables globales avant toute fonction dans le fichier courant et on dclare les variables locales en dbut du corps d'une fonction.
Si dans une fonction, une variable locale porte le mme nom qu'une variable globale alors le nom de cette variable dsigne dans cette fonction la variable locale. La variable globale est occulte: elle n'est plus accessible.
Les variables globales ont une existence permanente: elles persistent tout au long du programme. Les variables locales ont une existence phmre: elle ne durent que le temps dexcution de la fonction qui les contient.
Notion de prototype
Un prototype dcrit les entres-sorties dune fonction. Par exemple pour la fonction delta ci-dessus le prototype est:
float delta ( float a , float b , float c )
Un prototype possde deux utilisations: a) elle constitue lentte dans la dfinition dune fonction; b) elle constitue une dclaration pour une fonction dfinie dans un autre fichier et compile sparment (cf le paragraphe sur la compilation spare). Dans les deux cas le prototype permet au compilateur de vrifier que la fonction est utilise correctement. Note: La notion de prototype est la diffrence majeure entre le C K&R et le C ANSI. Elle a t introduite pour remdier la laxit bien connue de C. Les noms des paramtres peuvent tre omis dans le cas b) car comme le corps nest pas prsent ils ne servent rien. Ils peuvent cependant rester dans le prototype pour faciliter la lecture du programme.
float delta ( float , float , float )
L'exercice
Supposons que les fonctions compte et mesure ont t compiles sparment, le programme se contente alors de dclarer leurs prototypes avant leur utilisation.
/* exo4.c : comptage des a,b et c dans une chane, version 4 */ #include <stdio.h> int compte( char s[], char c ); /* prototype de compte */ int mesure( char s[]); /* prototype de mesure */ void main(void) {
28
printf("%d %d %d %d \n",na,nb,nc,n);
Le type void
s Fonctions qui ne renvoient pas de rsultat
Les fonctions qui ne renvoient pas de rsultat doivent avoir le type void (vide) et n'utilisent pas l'instruction return. Il y a deux situations o l'on programme des fonctions qui ne renvoient pas de rsultat: 1) le rle de la fonction n'est pas de calculer un rsultat mais d'avoir une action sur l'environnement, par exemple, envoyer des informations, positionner un actionneur... 2) la fonction calcule un rsultat qu'elle dpose directement dans un objet global ou local dont elle connat l'adresse. Ces fonctions ne peuvent pas tre utilise dans une expression (puisqu'elles n'ont pas de valeur): elle sont appelles directement dans une instruction comme un ordre. Exemple: Supposons qu'un module robot dfinit des fonctions de pilotage de robot. Le programme suivant les utilise.
/* void void void void robot.c : pilotage d'un robot */ /* /* /* /* mise en activit du robot */ dsactivation du robot */ marche n mtres */ tourne de a degrs (trigo) */ robot_start(void); robot_stop(void); robot_walk(float n); robot_turn(int a);
void main(void) { /* un parcours carr */ robot_start(); for (i=0;i<4;i++) { robot_walk(1.0); robot_turn(90); } robot_stop(); }
Note: Dans le prototype des fonctions de contrle du robot, on a ajout des noms fictifs (a et n) aux paramtres. Ceci est autoris et permet un commentaire prcis.
Quand une fonction n'a pas de paramtre le type void doit tre indiqu leur place. Il y a deux situations o l'on programme des fonctions sans paramtres: 1) la fonction ralise une action qui n'a pas besoin d'tre prcise par un paramtre (comme robot_int() de l'exemple prcdent). 2) la fonction prend des informations dans des objets globaux. Exemple: La fonction ci-dessous opre sur des objets globaux. Elle ralise la somme de deux entiers, avec la particularit que les donnes et le rsultat sont lus et crits dans des objets globaux.
/* inutile.c : fonction sans intert */ #include <stdio.h>
29
int a,b,c; void add(void) { c=a+b; } void main(void) { a=1; b=2; add(); printf("%d \n",c); }
/* affiche 3 */
Ce type de fonction va contre sens du rle d'une fonction qui doit pouvoir servir dans diverses situation. En effet, cette fonction add ne saura jamais qu'additionner a et b pour mettre le rsultat en c. Elle ne peut pas tre utilise pour incrmenter a par exemple.
Dclaration
La dclaration d'un pointeur sur fonction est un peu barbare mais elle est en fait simplement calque sur le prototype de la fonction sur laquelle on doit pointer: Si l'on doit pointer sur une fonction F de type: un pointeur P vers F doit tre dclar: Exemples de dclarations: FONCTION int f1 (int) { ... }; float f2 ( int, int ) { .. }; POINTEUR vers FONCTION int (*p1) (int); float (*p2) ( int, int ); rsultat F ( paramtres ) rsultat (*P) ( paramtres )
Affectation et appel
Pour appeler une fonction via un pointeur, il faut videmment que le pointeur possde comme valeur l'adresse d'une fonction. Ensuite, il suffit d'appliquer l'oprateur d'excution () la fonction pointe. Exemples d'affectation et d'appel (suite de l'exemple prcdent): AFFECTATION p1 = &f1; p2 = &f2; Exemple:
/* pf.c : pointeurs sur fonctions */ void f(void) { ... }
30
/* pf est un pointeur sur une fonction comme /* pf reoit l'adresse de f */ /* exec de f par appel direct */ /* exec de f par appel indirect via pf
Tableaux
Notion de tableau
Un tableau est une collection d'objets de mme type rangs successivement dans la mmoire. Chacun de ces objets est nomm lment et possde un indice. Le premier lment du tableau possde l'indice 0. Les lments dun tableau sont des objets de type simple ou structur, par exemple: - des tableaux dont les lments sont des structures, - des tableaux dont les lments sont aussi des tableaux. Dans ce dernier cas, on parle souvent de tableaux multi-dimentionnels. Le premier indice est l'indice de ligne, le second celui de colonne.
0 0 1 2 0 1
m
La dclaration peut tre accompage d'une valeur d'initialisation. Exemples:
int v[3] = { -1, 0, +1 }; int m[3][3] = { {1,1,1,1},{2,2,2,2},{3,3,3,3} };
Les lments indiqus sont dans l'ordre o ils sont rangs en mmoire. Pour les tableaux bidimentionnels, le rangement est ligne par ligne. Donc dans le cas de m, la ligne 1 vaut 1, la ligne 2 vaut 2 et la ligne 3 vaut 3. On peut soigner la prsentation en crivant:
int m[3][3] = { {1,1,1,1},
31
{2,2,2,2}, {3,3,3,3} };
Note: Les dclarations initialisantes ne sont possibles pour les tableaux que pour des variables globales (ou pour des locales dclares static). Ceci est aussi vrai pour les structures.
0 0 0 1 2 0 1 2 1 2
2 0 1 2
32
Tableaux et pointeurs
Le nom d'un tableau est un pointeur vers ce tableau. Si t est le nom d'un tableau, t pointe vers ce tableau, c'est dire a pour valeur ladresse de t[0]. Ceci est fait pour rendre systmatique le passage de l'adresse des tableaux dans les appels de fonction. Exemple:
float somme(float t[], int n) { int i; float s; for (i=0;i<n;i++) s = s + t[i]; return s; } void main(void) { float tab[5] = {1.0, 2.0, 3.0, 4.0, 5.0}; float total; .... total = somme(tab,5); ... }
Dans l'appel "somme(tab)" la valeur de tab passe en paramtre est l'adresse du tableau et non le tableau lui-mme. On vite ainsi de copier 40 octets (5x4 octets) en passant une adresse de 4 octets.
Addition de pointeurs
Si un pointeur p pointe sur un objet donn alors p+1 est un pointeur sur un objet de mme type situ la suite du premier. De mme sont dfinis p+2, p+3, etc... Si t est le nom d'un tableau, on peut le considrer aussi comme pointeur et lon a: t == &t[0] t+1 == &t[1] t+2 == &t[2] .... t+i == &t[i] Exemple: Avec le tableau tab de lexemple prcdent. et, en appliquant * aux deux membres *t == t[0] *t == t[1] *t == t[2] *t == t[i]
tab[3] tab[4]
tab+1
tab+2
tab+3
tab+4
tab s
Soustraction de pointeurs
Si p1 et p2 sont deux pointeurs sur un mme type d'objet, alors p2-p1 = i signifie p2 = p1+i au sens dfini ci-dessus. Exemple:
33
Avec le mme tableau tab, on a par exemple: (tab+4) - (tab+1) == 3. Ce 3 est la diffrence dindice entre les lments points.
Chanes de caractres
Notion de chane de caractres
Une chane de caractre est une suite de caractres. Une chane de caractres est range dans un tableau de caractres. Elle est termine par une caractre spcial pour la dlimiter: le caractre not '\0' dont le code est zro.
Dans une dclaration initialisante, la taille du tableau n'est pas ncessaire. Elle est estime automatiquement par le compilateur. Exemple:
char ch1[] = "hello"; char ch2[40] = "hello";
Accs un caractre
Comme dans tout tableau, il est possible d'accder aux lments avec l'oprateur []. Si C est une chane de caractres, C[0] dsigne le premier caractre, C[1] le second, etc ... Exemple:
/* inverse.c : inversion d'une chane de caractres */ #include <stdio.h> void main(void) { char ch[40]; char c; int i,n; scanf("%s", ch); /* On determine la taille de la chaine */ for (n=0; ch[n]!='\0'; n++) { } /* On parcours la chaine en permutant ch[i] et ch[n-i] */ /* tant que i reste infrieur n-i sinon on permute 2 fois! */ for (i=0; i<n-i ; i++) { c = ch[i]; ch[i] = ch[n-i]; ch[n-i] = c; } printf("%s \n", ch); }
34
h e l l o \0
68 65 6C 6C 6F 00 xx xx xx xx
ch
35
Structures
Notion de structure
Une structure regroupe une collection d'objets de types diffrents nomms membres. Les membres d'une structure sont rangs conscutivement dans la mmoire dans l'ordre de leur dclaration. Alors que pour les tableaux, chaque lment est distingu par son indice, dans une structure un membre est distingu par un nom. Chaque membre d'une structure peut tre soit d'un type simple, soit un tableau, soit encore une structure.
Les objets x et y sont deux structures comportant trois membres, un flottant a, un entier b et un caractre c. Comme la dclaration d'une structure est souvent longue, il est possible de crer un modle de structure. Ce modle fait l'objet d'une dclaration et sert ensuite pour celle des variables. Exemple:
struct individu { char nom[40]; char prenom[40]; int age; float taille; } ;
Dans la dclaration ci-dessus on dfinit un modle de structure nomm individu. La prsentation est are pour une meilleure lisibilit. Cette structure possde quatre membres: nom, prenom, age et taille. Ce modle permet ensuite de dclarer des structures:
struct individu x,y,z;
Note: Les dclarations initialisantes ne sont possibles pour les structures que pour des variables globales (ou pour des locales dclares static).
Accs un membre
L'oprateur . permet de dsigner un membre d'une structure. Si la structure est pointe par un pointeur, une notation commode -> permet d'accder aux membres.
L'oprateur .
36
L'oprateur ->
Cette notation est commode pour accder aux champs d'une structure quand celle-ci est pointe par un pointeur. Si p pointe vers une structure dont les champs sont u,v,w, ... le champ v peut tre dsign par: (*p).v ou encore par: p->v Exemple:
void main(void) { struct individu i,*p; p = &i; i.age = 30 ; (*p).age = 30 ; p->age = 30 ; } /* individu et pointeur sur individu */ /* affectation p avec l'adr. de i */ /* /* /* /* 3) via trois manires de faire la mme choses */ 1) directement */ 2) via p, notation pointe */ p, notation -> */
Autres possibilits
Ce paragraphe apporte de nouvelles possibilits pour structurer des donnes: lunion, le champ de bits et lnumration et prsente loprateur typedef pour crer de nouveaux noms de type. Note: Autant l'usage des unions et des champs de bits est marginale (voire dconseill pour les dbutants), autant l'usage des numrations et la cration de nouveaux types par typedef sont encourags car ils permettent une criture plus lisible des programmes.
Champ de bits
Un champ de bit est une forme particulire de structure dans laquelle on prcise le nombre de bits allous chaque membre. L'utilisation la plus intressante est d'conomiser la place dans la mmoire. Exemple:
37
On veut simplement stocker dans une grande matrice 100x100 des triplets d'entiers signs compris entre -10 et +10. Pour cela 5 bits suffisent pour chaque entier. La matrice pourra tre dfinie ainsi:
struct triplet { int x : 5; int y : 5; int z : 5; } struct triplet M[100][100] ;
Une autre utilisation possible est pour permettre un accs aux bits dun port reli un priphrique.
struct port { unsigned unsigned unsigned unsigned unsigned } ; int int int int int status : 1 ; ready : 1 ; data : 4 ; unused1 : 1 ; unused2 : 1 ;
void main(void) { struct port *pa = (struct port *) 0x1000 ; ... if ( pa->status == 1 ) { pa->data = 0xF; pa->ready = 1 ; } ... }
/* port en 0x1000 */
Cette faon de faire prsente deux gros inconvnients: 1) La norme NE DIT PAS dans quel ordre est ralise laffectation des bits: du MSB vers le LSB ou linverse. 2) Quand on opre sur un membre dun champ de bit, on peut penser que lon ne manipule QUE ce membre. En fait, au niveau machine, on ne peut pas faire moins que de lire ou dcrire entirement un octet cest dire le port entier. Or les ports sont souvent sensibles la lecture et/ou lcriture, par exemple, la lecture dun port configur en lecture synchrone (latch) provoque souvent un reset du bit dtat correspondant. Il en rsulte donc parfois des effets non dsirs.
Union
Une union est une variante de structure dans laquelle les membres sont superposs dans la mmoire. Exemple:
union etrange { int i ; float f ; }; union etrange u ;
Les membres u.i et u.f ne peuvent tre utiliss simultanments. Si on affecte u.i cela dtruit u.f et rciproquement Il n'y a pas de grande utilit des unions pour un programmeur modeste. Les utilisations les plus frquentes sont pour: - raliser des variantes dans des structures, - faire des conversions de type tranges, - conomiser la mmoire de manire froce...
38
Enumrations
Quand une variable prend un nombre limit de valeurs symboliques, il est souvent lgant de dfinir un type numr pour la modliser. Une variable dclare d'un type numr ne peut tre affecte que par les valeurs symboliques prvues dans l'numration. Le compilateur y veillera! Note: par dfaut, le compilateur affecte en fait aux valeurs symboliques des valeurs entires conscutives partir de 0. Le programmeur peut ventuellement prciser les valeurs quils souhaite affecter aux symboles. Exemples: Dans ce programme le type couleur est dfini par numration. Puis un tableau de couleur est dfini et initialis. Le traitement consiste trouver le nombre de chacune des trois couleurs dans le tableau. Pour cela, on utilise un tableau de trois compteurs dont les couleurs sont les indices.
/* enum.c : type enumr */ #include <stdio.h> enum couleur { rouge , vert , bleu } ; void main(void) { enum couleur t[6] = { vert, vert, rouge, vert, bleu, rouge } ; int nb[3] = { 0, 0, 0 }; enum couleur c ; int i ; for (i=0; i<6; i++ ) { c = t[i] ; switch (c) { case rouge : nb[rouge]++; break; case vert : nb[vert]++; break; case bleu : nb[bleu]++; break; } } printf("%d %d %d \n",nb[rouge],nb[vert],nb[bleu]); }
Note 1: le compilateur affecte considre que: rouge=0, vert=1, bleu=2. Mais, il tait possible de choisir les valeurs symboliques:
enum couleur { rouge=4 , vert=7 , bleu=13 } ;
Note 2: Le traitement ci-dessus peut tre crit de manire encore plus simple:
for (i=0; i<6; i++ ) { c = t[i] ; nb[c]++ ; }
typedef
Typedef est une commodit du langage pour dfinir des synonymes des noms de types. On cr un synonyme dun nom de type: - quand on prfre un nom vocateur de manire augmenter la lisibilit du programme, - quand le nom du type est long. Pour crer un nom de type avec typedef, il suffit de procder comme pour dclarer une variable du type que lon souhaite et de faire prcder le tout de typedef. Il est courant de mettre une majuscule un nom de type cr par typedef.
39
Exemples:
typedef typedef typedef typedef int Nombre ; struct individu Individu ; float Vecteur[3] ; float Matrice[3][3] ;
Le premier typedef manque dintrt, mais les suivants sont plus intressants. Dans le second exemple, Individu est synonyme de struct individu. Ainsi, les dclarations dobjet sont plus claires.
AU LIEU DE: int a,b ; struct individu x,y,z; float u[3],v[3] ; float m[3][3] ; ON ECRIT: Nombre a,b ; Individu x,y,z ; Vecteur u,v ; Matrice m ;
Lusage de mettre une majuscule aux noms de types crs par typedef prsente deux avantages: - dans le cas des structures, on lve lambigut du nom de la structure et du nom souhait (cf Individu), - idem pour les union et les numrations, - on distingue plus clairement les noms des types.
Objets complexes
Les objets complexes, comme tous les objets, sont toujours dfinis partir dun type qui sert de modle. Autrement dit, construire des objets revient construire des types. Pour dfinir des types de donnes complexes, on respecte souvent DEUX rgles: - utiliser intensivement le typedef, - dfinir des types complexes partir de types plus simples. Exemple 1:
typedef struct individu Individu ; typedef Individu *pIndividu ; typedef pIndividu Repertoire[1] ; Repertoire r ; /* Individu dfini plus haut */ /* pIndividu --> Individu */ /* tableau de 100 pIndividu */
Lobjet r, dclar Repertoire, est un tableau de pointeurs vers 100 Individus. Si r devait tre dclar dun coup sans typedef ni type intermdiaire, on aurait crit:
struct individu *r[100] ;
Cest plus court mais moins lisible, et pas du tout plus efficace... Exemple 2: On a souvent besoin de tableaux de pointeurs sur fonction quand on programme des automates. Supposons que ces fonctions sont des actions et quelles sont sans paramtre et ne retournent pas de rsultat.
typedef void Action(void) ; Action */ typedef Action *pAction ; Action */ /* fonction de type /* pointeur sur
enum etat { initial, marche, arret, panne, nEtats } ; typedef enum etat Etat ; /* Etat du systme */ enum evenement { nuit, jour, poussoir, porte, fenetre, nEvenements } ; typedef enum evenement Evenement ; void rien(void) {}; pAction a[nEtats][nEvenements] = daction */ /* Action nulle */ /* matrice
40
{ { { { { } void main(void) { ... (*a[marche,porte])() ; porte */ ... } rien,rien,rien,rien,rien rien,rien,rien,rien,rien rien,rien,rien,rien,rien rien,rien,rien,rien,rien }, }, }, } /* /* /* /* etat etat etat etat initial */ marche */ arret */ panne */
Note: Les nombres dtats et dvnements (nEtats et nEvenements) sont dclares avec une petite astuce commode qui consiste dclarer ces constantes comme membre terminal dune numration.
E/S console
Les entres/sorties console permettent les changes entre le programme et lutilisateur qui travaille sur une console alpha-numrique. Ces e/s peuvent tre ralises de diffrentes manires: - les e/s formattes qui offrent de nombreuses possibilits, - les e/s caractres, - les e/s lignes (chanes de caractres). Lusage des e/s console ncessite linclusion de <stdio.h>. Pour plus de dtail sur les fonctions de/s voir leurs descriptions dans la librairie ANSI.
/* saisie de a et b */ /* affichage de a et b */
Formats
La plupart des formats ont la mme signification pour scanf et printf. On signalera les particularits de scanf et de printf dans les paragraphes qui leur sont consacrs.
41
TYPE int int int long long long float float double double char char *
FORME en dcimal en octal en hexadcimal en dcimal en octal en hexadcimal notation virgule fixe (ex: -12.345) notation virgule flottante (ex: -1.234e01) idem %f idem %e un caractre plusieurs caractres
criture formatte
produit:
a b a+b
La somme de 12 et 23 est 35
Les formats de base sont %d (dcimal), %o (octal), %x (hexadcimal). Il est possible de fixer la taille de la fentre daffichage en indiquant le nombre de caractres aprs le %. Si la valeur noccupe pas toute la fentre, laffichage est cadr droite et complt par des espaces gauche. Si la valeur ne tient pas dans la fentre, celle-ci est automatiquement ajuste pour que le nombre ne soit pas tronqu. Un nombre de caractres ngatif provoque un cadrage gauche dans le fentre. Exemples:
printf(%-6d,m); printf(%4x,n); m=100 n=30 -> -> sortie = 100^^^ sortie = ^^1e
Pour les entiers longs, un l ou L doit tre plac en prfixe au format: %ld,%lo,%lx.
Les formats de base sont %f (virgule fixe) et %e (notation exponentielle). Comme pour les entiers, il est possible de fixer la taille de la fentre. On peut prciser en outre le nombre de dcimales (par dfaut 6).
42
Exemples:
printf(%6.2f,pi); printf(%12.4e,m); ^^1.0000e+03 pi=3.14159 m=1000 -> -> sortie = ^^3.14 sortie =
Le format est %c pour les caractres et %s pour les chanes. Dans le cas des chanes, une taille de fentre peut tre indique. Exemple:
printf(%c,c); printf(%s,s); printf(%4s,s); c=A s=hello s=hello -> -> -> sortie = A sortie = hello sortie = hell
Elle indique le nombre de caractres effectivement affichs. Le plus souvent cette valeur de retour de printf nest pas utilise. Cependant elle peut tre utile pour dtecter un dpassement de format daffichage. Exemple:
n = printf(%10d %12.4f\n,a,x); if (n!=25) { /* pb de format */ }
lecture formate
On spare habituellement les valeurs entres par un ou plusieurs espaces. Si les valeurs 12 et 3 taient attaches scanf aurait pris 123 pour a et scanf continuerait attendre la valeur de b ... On aurait obtenu le mme rsultat en sparant les valeurs dune tabulation ou dune fin de ligne. Exemple 2: Saisie de deux entiers (prsentation sous forme de couple)
scanf ((%d,%d),&a,&b); entre = (12,3) -> a=12 ; b=3
Ici le guide oblige taper des caractres en plus des valeurs. Ces caractres ne servent pas grandchose mais amliorent la prsentation des donnes lcran.
43
Pour les entiers longs, un l ou L doit tre plac en prfixe au format: %ld,%lo,%lx. Exemple:
scanf(%lx,&a); entre = ff01 -> a=0xff01
Dans le cas des chanes un nombre maximum de caractres peut tre indiqu. Ceci peut tre utile pour ne pas quune chane dborde de la variable qui lui a t alloue. Exemple:
char m[6]; scanf(%5s,m); entre = bonjour -> m = bonjo
Le scanf sest termin prmaturment quand x a t dtect alors quun dcimal tait attendu. Les caractres xyz 11 restent dans le buffer et pourront tre lus dans une prochaine lecture.
De manire gnrale un espace dans une chane guide de scanf force le passage sur tous les caractres sparateurs: lespace, la tabulation, le saut de ligne. Exemple:
scanf(%d %d,&a,&b); = 11 entre = 10^^^^^^11 -> a = 10 ; b
En fait, dans ce cas, la chane guide %d%d produit le mme effet car le format %d demande dj llimination de tous les sparateurs qui prcdent la valeur dcimale. Il en va de mme pour tous les formats sauf pour le format %c. Exemple:
scanf(%c%c,&c1,&c2); c2 = ^ scanf(%c %c,&c1,&c2); c2 = b entre = a^^^^^^^b entre = a^^^^^^^b -> -> c1 = a ; c1 = a ;
44
E/S caractres
Les e/s caractres permettent de lire ou crire un seul caractre. Ceci peut tre fait avec les e/s formates en utilisant le format %c, mais avec plus de lourdeur. putchar getchar Exemple:
#include <stdio.h> main() { char c; c = getchar(); putchar(c); }
E/S lignes
Les e/s lignes permettent de lire ou crire une ligne. puts gets Exemple:
#include <stdio.h> main() { char ligne[40]; gets(ligne); puts(ligne); }
Ces fonctions transforment ligne en chane et vice-versa: gets: puts: au revoir\n au revoir\0 -> -> au revoir\0 au revoir\n
Programmes interactifs
Un programme interactif est un programme qui dialogue avec lutilisateur: lutilisateur gnralement donne des commandes, le programme agit et donne un rsultat. Un programme interactif est typiquement de la forme suivante:
#include <stdio.h> void main(void) { char com; float memoire,x; memoire=0; do { printf(\n>); scanf( %c,&com); switch (com) { case -: scanf(%f,&x); case +: scanf(%f,&x); case *: scanf(%f,&x); case /: scanf(%f,&x);
45
} while ( com != q ) ;
On notera dans la saisie de la variable com le format %c (format caractre prcd dun espace) qui permet que les espaces (au sens dfini un peu plus haut) taps avant la commande soient ignors.
Dsynchronisation clavier/cran
Il arrive que lon ait des problmes de dsynchronisation clavier/cran du fait que les entres sont bufferises. Voici un exemple:
#include <stdio.h> void main(void) { int a,b,c; printf(entrez a: ); scanf(%d,&a); printf(entrez b: ); scanf(%d,&b); printf(entrez c: ); scanf(%d,&c); printf(fin\n); }
La dsynchronisation constate dans le second exemple provient de ce que deux valeurs on t tapes en rponse au premier scanf. La premire seulement a t lue par ce scanf, la seconde est reste dans le buffer. Quand le second scanf est excut, il prend la valeur prsente dans le buffer sans attendre quune valeur soit tape au clavier. Dans ce cas, la dsynchronisation peut tre vite en vidant le buffer jusqu la fin de ligne aprs avoir lu la valeur souhaite. Voici le mme programme en version in-dsynchronisable:
#include <stdio.h> void main(void) { int a,b,c; printf(entrez a: ); scanf(%d,&a); while (getchar()!=\n){};
46
Cependant, si cette nouvelle version ne se dsynchronise plus, il peut y avoir encore des problmes. Par exemple, en cas derreur de frappe, on peut se retrouver avec une variable non affecte:
> entrez a: 10 > entrez b: a1 > entrez c: 4
Le second scanf ne peut accepter la valeur a1 qui reste donc dans le buffer et se trouve limine par le vidage de ligne. Le programme se termine avec a=10, b=NON_AFFECTEE et c=4... Pour remdier il faut encore btonner le programme en testant la valeur retour du scanf qui doit ici tre 1:
#include <stdio.h> void main(void) { int a,b,c; int n; do { printf(entrez a: ); n = scanf(%d,&a); while (getchar()!=\n){}; } while (n != 1); do { printf(entrez b: ); n = scanf(%d,&b); while (getchar()!=\n){}; } while (n != 1); do { printf(entrez c: ); n = scanf(%d,&c); while (getchar()!=\n){}; } while (n != 1); printf(fin\n); }
Fichiers
Un fichier est un ensemble de valeurs ranges sur un disque. Sous UNIX, les priphriques de/s peuvent tre considrs comme des fichiers. Les e/s consoles apparaissent ainsi comme des cas particulier de la gestion de fichiers. Bien quil soit possible daccder aux priphriques par des primitives de bas niveau, cest un usage qui est dconseill car dune part cest complexe (transfert par blocs, interruptions...), dautre part cest dlicat car une erreur de programmation peut tre catastrophique (par exemple dgts sur la disque...).
47
Cest pour cela que des librairies de fonctions de plus haut niveau sont fournies. On distingue deux niveaux de fonctions: - les librairies de/s dUNIX - les librairies de/s du C ANSI Les librairies de/s dUNIX fournissent un bon niveau de scurit, mais leur maniement est encore assez complexe. Et surtout, leur usage dans un programme ne permet pas de porter ce programme sur un ordinateur non-UNIX. Les librairies de/s du C ANSI sont indpendantes du systme dexploitation: un programme qui les utilise peut tre port sur tout ordinateur qui possde un compilateur C ANSI. De plus ces librairies sont de plus haut niveau que les librairies UNIX donc encore plus simples utiliser. Nous nous limitons dans cet expos la librairie de/s du C ANSI. Son utilisation ncessite dinclure <stdio.h>. Pour plus de dtail sur les fonctions de/s voir leurs descriptions dans la librairie ANSI.
On constate dans cet exemple plusieurs tapes: 1) La variable df est dclare: Cette variable est un pointeur sur un objet de type FILE. Un objet de ce type est un descripteur de fichier. Un descripteur de fichier contient des informations relatives un fichier en cours dutilisation: nom, tat, buffers, ... Pour linstant df ne pointe vers rien. 2) Ouverture du fichier: Lopration douverture fopen consiste demander laccs un fichier. En rponse cette demande le systme prpare laccs au fichier, remplit un descripteur et en renvoie ladresse en retour de fonction. Dans notre cas, louverture est effectue en vue de lecture (paramtre r). Maintenant df pointe vers le descripteur du fichier nom et servira pour dsigner ce fichier dans les oprations suivantes.
48
3) Lecture du fichier: Cette lecture est ici ralise par fscanf. Cette fonction est analogue la fonction scanf mais opre sur un fichier ouvert dont on indique ladresse dun descripteur en premier paramtre. 4) Fermeture du fichier: La fermeture, ralise par fclose, dsalloue le descripteur de fichier et les structures de donnes qui y sont lies (buffers...).
Le type FILE
Le type FILE, dfini dans <stdio.h>, est le modle pour une structure de donnes appele descripteur de fichier. Cette structure de donnes contient des informations relatives au fichier manipul: nom, tat, buffers, ... Lors de louverture dun fichier, le descripteur de fichier est cr. Il est alors possible doprer sur le fichier (lecture, criture, etc...). A chacune de ces oprations le descripteur est indiqu en paramtre pour identifier le fichier sur lequel lopration doit sappliquer. Les descripteurs de fichiers sont manipuls via des pointeurs car ce sont des structures volumineuses qui seraient lourdes passer en paramtre. Lutilisateur na pas se soucier du contenu du descripteur de fichier: cest une structure de donne prive gre par la librairie de/s.
Ouverture et fermeture
fopen fclose Usage de fopen: pointeur = fopen ( nom, mode_douverture ) On indique en paramtre le nom du fichier ouvrir et le mode douverture qui peut tre: r : lecture. Le fichier doit exister. w: criture. Si le fichier existe il est perdu; si il nexiste pas il est cr. a: criture. Si le fichier existe, criture la suite (append); sinon il est cr. et ladresse du descripteur est renvoye. Usage de fclose: fclose ( pointeur ) ouverture dun fichier fermeture dun fichier
Lecture et criture
Les oprations de lecture/criture sont analogues celles sur console. On peut distinguer diffrents types doprations: - e/s formates - e/s ligne - e/s caractre
49
criture formate lecture formate criture ligne lecture ligne criture caractre lecture caractre
De manire gnrales ces fonctions sutilisent comme les fonctions de/s console. Un paramtre indique simplement ladresse du descripteur du fichier sur lequel il faut oprer.
Les e/s console ne sont finalement pas autre chose que les e/s fichiers appliques ces fichiers prdfinis. Exemple: fprintf(stdout,Salut\n) quivaut printf(Salut\n)
local
global
50
Si la dclaration est ralise comme cest lusage en dbut fichier, lobjet est visible depuis toutes les fonctions du fichier. Do le nom global. Les objets globaux ont une allocation statique, donc existent pendant toute la dure du programme. Note: Les fonctions sont considres comme des objets globaux.
Variables locales
Un objet local est dclar dans une fonction. Il est visible depuis sa dclaration jusqu la fin de la fonction. Si la dclaration est effectue comme cest lusage en dbut de fonction, lobjet est visible de tous les points de celle-ci, mais seulement delle. Do le nom local. Les objets locaux ont par dfaut une allocation automatique, donc nexistent que pendant la dure de vie de la fonction qui les contient. Une variable locale peut tre dclare statique. Dans ce cas, sa visibilit est toujours restreinte la fonction mais sa dure de vie est celle du programme. Ceci permet pour des usages particuliers de disposer de variables locales persistantes.
Exportation et importation
Tous les objets globaux, y compris les fonctions, sont exports par dfaut et peuvent tre imports dans un autre fichier pour y tre utiliss. Lexportation dun objet global est supprime si sa dclaration spcifie lattribut static. Pour importer un objet global dfini dans un autre fichier, on doit le dclarer en prcisant lattribut extern.
Allocation automatique
Les variables locales dune fonction sont par dfaut alloues dans la pile au dbut de lexcution de la fonction et sont dsallous la fin. Cette allocation est prise en charge par le compilateur, do le mot automatique. Si une fonction est appele plusieurs fois au cours dun programme, ses variables locales automatiques ont donc peu de chances de se trouver la mme adresse. Bien des compilateurs optimisent lutilisation des variables automatiques en leur allouant un registre. Ceci nest possible que si ce sont des variables simples et si lon ne leur applique pas loprateur & pour prendre leur adresse. Il est possible de forcer lallocation dune variable automatique dans un registre en spcifiant lattribut register lors de sa dclaration. Exemple:
register int v ;
51
Allocation dynamique
Un objet dynamique est implant dans une autre partie de la mmoire: le tas. Cest l que lon dpose les objets pour une dure quelconque. Ce type dallocation est dynamique car il est ralis par la volont du programmeur laide des fonctions de librairie malloc et free: - malloc ralise lallocation dun objet dans le tas, - free le dsalloue, cest dire libre la place.
Objets statiques
- soit par dfaut 0 - soit par une valeur constante donne la dclaration.
Objets automatiques
Les objets automatiques ne sont jamais initialiss par dfaut. Il peuvent recevoir une valeur, constante ou non, au moment de la dclaration. Cependant cette possibilit est interdite aux agrgats (tableaux, structures, unions). Note: Ces diffrences concernant les objets automatiques sexpliquent car ces objets sont crs donc initialiss en cours dexcution, alors que les objets statiques sont crs et initialiss ds la compilation.
Objet volatile
Les compilateurs ralisent frquemment des optimisations qui visent simplifier les boucles. Exemple:
while (1) { v = port; if (v==0) ... ... }
juste titre car la variable port semble ne pas changer de valeur pendant le cours de la boucle. Dans la plupart des cas cette optimisation ne pose pas de problme, mais le programmeur peut ne pas la souhaiter car il a prvu que la variable port sera modifie par un autre processus ou une interruption. Dans un tel cas on dit que la variable est volatile et il est ncessaire de prciser cela la dclaration en spcifiant lattribut volatile.
52
Exemple:
volatile short port ;
Objet constant
Lattibut de dclaration constant, spcifie que lobjet ne doit pas tre modifi. Ceci est vrifi par le compilateur. Exemple:
constant pi = 3.14 ;
Les fonctions sont dfinies (et donc du coup dclares) AVANT toute utilisation.
int somme (int a, int b) { return a+b; } void main(void) { ... z = somme(x,y); .... }
Pour une obscure raison, le programmeur souhaite spcifier le corps des fonctions en fin de fichier... Dans ce cas, cest possible, mais pour pouvoir appeler les fonctions il faut au moins que le prototype soit indiqu AVANT appel. Cest cette indication de prototype qui dclare la fonction et la rend utilisable.
int somme (int a, int b); void main(void) { ... z = somme(x,y); .... } int somme (int a, int b) { return a+b; }
Programme multifichier
53
Dans une programmation multifichiers, on programme souvent des fonctions dans des fichiers spars qui peuvent constituer une librairie, tandis que le programme qui les utilise se trouve dans un autre. Ici le fichier somme.c dfinit un fonction qui est utilise par test.c... Fichier test.c
extern int somme (int a, int b); void main(void) { ... z = somme(x,y); .... } int somme (int a, int b) { return a+b; }
Fichier somme.c
La librairie ANSI
La librairie (ou bibliothque) ANSI est un ensemble de fonctions utilitaires que lon retrouve dans tout compilateur C ANSI. Elle assure donc un certaine portabilit des programmes qui lutilisent. Pour utiliser une fonction de librairie, il est ncessaire dinclure le fichier header qui en contient la dfinition. Pour des raisons de commodit, la librairie C ANSI, a rparti les dfinitions des fonctions dans plusieurs headers.
stdio.h
s
FOPEN FFLUSH FCLOSE FEOF REMOVE RENAME
Gestion
FILE *fopen ( const char * nomfichier, const char * mode ) int fflush ( FILE * pointeur ) int fclose ( FILE * pointeur ) int feof ( FILE * pointeur ) int remove ( const char * nomfichier ) int rename ( const char * anciennom, const char * nouveaunom )
s
PRINTF
E/S formattes
int printf ( const char * guide, ... ) int fprintt ( FILE * pointeur, const char * guide, ... ) int sprintt ( char * chaine, const char * guide, ... ) int scanf ( const char * guide, ... ) int fscanf ( FILE * pointeur, const char * guide, ... ) int sscanf ( char * chaine, const char * guide, ... )
E/S caractres
int putchar ( int c )
PUTCHAR
54
int fputc ( int c, FILE * pointeur ) int getchar ( void ) int fgetc ( FILE * pointeur ) int ungetc ( int c, FILE * pointeur )
s
PUTS FPUTS GETS FGETS
E/S ligne
int puts ( const char * chaine ) int fputs ( const char * chaine, FILE * pointeur ) char * gets ( char * chaine ) char * fgets ( char * chaine, FILE * pointeur )
s
FSEEK
REWIND FTELL
ctype.h
ISALNUM ISALPHA ISCNTRL ISDIGIT ISLOWER ISPRINT ISPUNCT ISSPACE ISUPPER ISXDIGIT int isalnul ( char c ) int isalpha ( char c ) int iscntrl ( char c ) int isdigit ( char c ) int islower ( char c ) int isprint ( char c ) int ispunct ( char c ) int isspace ( char c ) int isupper ( char c ) int isxdigit ( char c )
string.h
STRCPY STRNCPY STRCAT STRNCAT STRCMP STRNCMP STRLEN char * strcpy ( char * destination, const char * source ) char * strncpy ( char * destination, const char * source, int size ) char * strcat ( char * destination, const char * source ) char * strncat ( char * destination, const char * source, int size ) int strcmp ( char * destination, const char * source ) int strncmp ( const char * destination, const char * source, int size ) size_t strlen ( const char * chaine )
55
math.h
SIN COS TAN ASIN ACOS ATAN ATAN2 SINH COSH TANH EXP LOG LOG10 POW SQRT CEIL FLOOR FABS double sin (double x) double cos (double x) double tan (double x) double asin (double x) double acos (double x) double atan (double x) double atan2 (double y, double x) double sinh (double x) double cosh (double x) double tanh (double x) double exp (double x) double log (double x) double log10 (double x) double pow (double x, double y) double sqrt (double x) double ceil (double x) double floor (double x) double fabs (double x)
stdlib.h
RAND int rand ( void ) SRAND void srand ( unsigned int graine ) MALLOC FREE EXIT SYSTEM GETENV ABS LABS DIV LDIV void malloc ( size_t taille ) void free ( void * adresse ) void exit ( int etat ) int system ( const char * commande_unix ) char * getenv ( const char * nom ) int abs ( int n ) long labs ( long n ) div_t ldiv ( int numerateur, int denominateur ) ldiv_t ldiv ( long numerateur, long denominateur )
assert.h
ASSERT void assert ( int expression )
56
time.h
CLOCK clock_t clock ( void ) TIME time_t time ( time_t adresse )
57