Vous êtes sur la page 1sur 25

Support de cours Langage C

Prof : M. KOUME Andr

Chapitre 1 : Considrations gnrales


1. Bref Historique
Le langage C a t mis au point par D. Ritchie et B.W.Kernighan au dbut des annes 70. Leur but tait de permettre de dvelopper un langage qui permettrait d'obtenir un systme d'exploitation de type UNIX portable. D. Ritchie et B.W.Kernighan se sont inspirs des langages B et BCPL, pour crer un nouveau langage : le langage C. La premire dfinition de ce langage a t donne dans leur livre commun The C programming language . Toutefois, suite l'apparition de nombreux compilateurs C, l'ANSI (American National Standards Institute) a dcid de normaliser ce langage pour donner ce que l'on appelle le C-ANSI. Suite cette norme, Ritchie et Kernighan ont sorti une deuxime dition du livre en intgrant les modifications apportes par l'ANSI.

2. Compilation
La transformation d'un texte crit en langage C en un programme excutable par l'ordinateur se fait en deux tapes : la compilation et l'dition de liens. La compilation est la traduction des fonctions crites en C en des procdures quivalentes crites dans un langage dont la machine peut excuter les instructions. Le compilateur lit toujours un fichier, appel fichier source, et produit un fichier, dit fichier objet. L'dition de liens est l'opration par laquelle plusieurs fichiers objets sont mis ensemble pour se complter mutuellement : un fichier apporte des dfinitions de fonctions et de variables auxquelles un autre fichier fait rfrence et rciproquement. L'diteur de liens (ou linker) prend en entre plusieurs fichiers objets et bibliothques (une varit particulire de fichiers objets) et produit un unique fichier excutable. L'diteur de liens est largement indpendant du langage de programmation utilis pour crire les fichiers sources, qui peuvent mme avoir t crits dans des langages diffrents. Les langages qui utilisent cette approche sont appels langages compils. Exemple de langes compils : C/C++, Pascal, VB, Linterprtation est une autre approche de traduction dun programme source en excutable. Linterprteur traduit chaque ligne du programme source et lexcute, puis passe la ligne suivante. Linterprtation ne produit pas de fichier intermdiaire. Exemple de langages interprt : PERL, PHP, Certains langages combinent les deux approches : JAVA

3. Structure dun programme en langage C


Le langage C ne respecte pas la structure dun programme telle que dfinie en algorithme, comme le fait le langage Pascal ; un programme n'est qu'une collection de fonctions assortie d'un ensemble de variables globales. D'o la question : par ou l'excution doit-elle commencer ? La rgle gnralement suivie par l'diteur de liens est la suivante : parmi les fonctions donnes il doit en exister une dont le nom est main. C'est par elle que l'excution commencera ; le lancement du programme quivaut l'appel de cette fonction par le systme d'exploitation. Notez bien que, part cela, main est une fonction comme les autres, sans aucune autre proprit spcifique ; en particulier, les variables internes main sont locales, tout comme celles des autres fonctions. Nanmoins, on peut distinguer, dans un programme crit en langage C les parties suivantes : - Les directives du prprocesseur (prcompilation) : elles commencent par # - Dclaration des variables (globales) - Dfinition des fonctions, dont la fonction main

Exemple /*Directives du prprocesseur*/ #include <sdtio.h> #define N 50 /*Dclaration de variables globales*/ int a, b ; printf(Entrez un entier) ; scanf(%d, &a) ; b=a+N ; printf(Le rsultat est %d, b) ;

4. Quelques caractristiques du langage C


Le langage C reste un des langages les plus utiliss actuellement. Cela est d au fait que le langage C est un langage comportant des instructions et des structures de haut niveau (contrairement l'assembleur par exemple) tout en gnrant un code trs rapide grce un compilateur trs performant. Cest un langage trs portable. Un programme crit en C en respectant la norme ANSI est portable sans modifications sur n'importe quel systme d'exploitation disposant d'un compilateur C : Windows, UNIX, VMS (systme des VAX) ou encore OS/390 ou z/Os (l'OS des mainframes IBM). - Le langage C est un compil - langage est sensible la case -

Chapitre 2 : Structures lmentaires & syntaxe du langage C


1. Syntaxe
Le programmeur est maitre de la disposition du texte du programme. Des blancs, des tabulations et des sauts la ligne peuvent tre placs tout endroit ou cela ne coupe pas un identificateur, un nombre ou un symbole compos.

1.1. Commentaires
Les commentaires commencent par /* et se terminent par */ : /* Ce texte est un commentaire et sera donc ignor par le compilateur */ Les commentaires ne peuvent pas tre imbriqus : crit dans un programme, le texte /* voici un grand /* et un petit */ commentaire */ est erron, car seul /* voici un grand /* et un petit */ sera vu comme un commentaire par le compilateur. Les langages C et C++ cohabitant dans la plupart des compilateurs actuels, ces derniers acceptent galement comme commentaire tout texte compris entre le signe // et la fin de la ligne ou ce signe apparait :

1-2 - Mots-cls
Les mots suivants sont rservs. Leur fonction est prvue par la syntaxe de C et ils ne peuvent pas tre utiliss dans un autre but : 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

1.3 - Identificateurs
Un identificateur est une suite de lettres et chiffres contigus, dont le premier est une lettre. Lorsque seul le compilateur est concern, c'est--dire lorsqu'il s'agit d'identificateurs dont la porte est incluse dans un seul fichier (nous dirons de tels identificateurs qu'ils sont privs) : en toute circonstance une lettre majuscule est tenue pour diffrente de la lettre minuscule correspondante ; dans les identificateurs, le nombre de caractres discriminants est au moins de 31. Le caractre _(appel blanc soulign ) est considr comme une lettre ; il peut donc figurer n'importe quelle place dans un identificateur. Cependant, par convention un programmeur ne doit pas utiliser des identificateurs qui commencent par ce caractre. Cela assure qu'il n'y aura jamais de conflit avec les noms introduits ( travers les fichiers .h ) pour les besoins des bibliothques, car ces noms commencent par un tel blanc soulign.

1.4. Variables
Une variable est un emplacement mmoire. Elle est caractrise par un nom (identificateur) et un type (ensemble de valeur quelle peut prendre). Dclarer une variable consiste prcise lidentificateur et le type Dclaration <type_varaible> <identicateur> ; Ex : int a ; double resultat ; Les dclarations de variables peuvent se trouver : - en dehors de toute fonction, il s'agit alors de variables globales ; l'intrieur d'un bloc, il s'agit alors de variables locales; dans l'en-tte d'une fonction, il s'agit alors d'arguments formels.
3

Contrairement une variable, le contenu dune constante est invariable. On les dclare en C laide de directive #define

1.5. Oprateurs
Les oprateurs servent former des expressions. On distingue :

Oprateurs arithmtiques
+-*/%

Oprateurs de comparaison
<, >, <=, >=, ==, !=

Oprateurs logiques
&&, ||, !

Incrmentation/ Dcrmentation
Prfixe (++a, --a) Postfixe (a++, a++)

Affectation
simple= largie : a=+10 ; r=%2

Oprateur dindirection
*

Oprateur dadresse
&

Expressions
Une expression est une combinaison didentificateurs t doprateurs. On utilise les parenthses pour prciser parfois les priorits des certains oprateur sur dautres. Une expression peut conteur un appel de fonction. Ex : r= 2+a*cos(x). Une expression termine par un point virgule (;) est une instruction. Au total, une instruction peut tre : - une dclaration de variable - une expression suivie dun point virgule

1.6 Types de donnes simples


Les types de base en C sont : - int entier (32 bits) - float rel (32 bits) - char caractres (8 bits) A ces types de base, on peut ajouter des modificateurs pour modifier la taille respective. Ainsi : Unsigned int pour les entiers non signs Short int entiers courts (16 bits) long int entiers longs (32 bits) long float (double) rels longs (64 bits) Remarque : le type boolen nest pas en C. dans la pratique, on le remplace par un entier (int). La valeur FAUX correspondant 0 et la valeur VRAI toute valeur diffrente de 0.
4

2. Entre / Sortie sur lentre / sortie standards


Les fonctions dentre / sortie sont pour la plupart dans la bibliothque <stdio.h>

2.1. Ecriture
La fonction d'criture sur l'unit de sortie standard (en principe l'cran du poste de travail) avec conversion et mise en forme des donnes est : printf( format , expr1 , expr2 , ... exprn ) format est une chaine qui joue un rle particulier. Elle est forme de caractres qui seront crits normalement, mlangs des indications sur les types des expressions expr1, expr2, ... exprn et sur l'aspect sous lequel il faut crire les valeurs de ces expressions. Cela fonctionne de la manire suivante : la fonction printf parcourt la chaine format et reconnait certains groupes de caractres comme tant des spcifications de format. C'est le caractre % qui dclenche cette reconnaissance. La fonction printf recopie sur le flot de sortie tout caractre qui ne fait pas partie d'une telle spcification. La ime spcification dtermine la manire dont la valeur de expri sera crite.

2.2. Lecture
La fonction de lecture avec format l'unit d'entre standard (en principe le clavier du poste de travail) est scanf( format , adresse1 , adresse2 , ... adressen ) L'argument format est une chaine qui indique la manire de convertir les caractres qui seront lus ; adresse1, adresse2, ... adressen indiquent les variables devant recevoir les donnes lues. Ce sont des sorties de la fonction scanf, par consquent il est essentiel que ces arguments soient des adresses de variables lire. Quelques valeurs de largument format : Format Conversion en criture %d Int dcimale signe %ld long int dcimale signe %u unsigned int dcimale non signe %lu %o %lo %x %lx %f %lf %e %le %g %lg unsigned long int unsigned int unsigned long int unsigned int unsigned long int double long double Double long double double Long dcimale non signe octale non signe octale non signe hexadcimale non signe hexadcimale non signe dcimale virgule fixe dcimale virgule fixe dcimale notation exponentielle dcimale notation exponentielle dcimale, reprsentation la plus courte parmi %f et %e double dcimale, reprsentation la plus courte parmi %lf et %le caractres chane de caractres

%c %s

unsigned char char*

2.3. Affichage et lecture de caractres


5

Les fonctions getchar et putchar permettent respectivement de lire et dimprimer des caractres. Il sagit de fonctions dentres-sorties non formates. La fonction getchar retourne un int correspondant au caractre lu. Pour mettre le caractre lu dans une variable caractre, on crit : caractere = getchar(); Lorsquelle dtecte la fin de fichier, elle retourne lentier EOF (End Of File), valeur dfinie dans la librairie stdio.h. En gnral, la constante EOF vaut -1. La fonction putchar crit caractre sur la sortie standard : putchar(caractere); Elle retourne un int correspondant lentier lu ou la constante EOF en cas derreur.

3. Structures de contrle
Un bloc est une suite de dclarations et d'instructions encadre par les deux accolades { et }. Du point de vue de la syntaxe il se comporte comme une instruction unique et peut figurer en tout endroit ou une instruction simple est permise.

3.1. Structures alternatives


If [else] if (<expression>) instructions <expression> est value : si elle est vraie (i.e. non nulle) instruction1 est excute Si instructions est compose de plus dune instruction, l faut les encadre par les accolades {} If peut tre suivi de else if (<expression>) instruction1 else instruction2 Dans ce cas, expression est value : si elle est vraie (i.e. non nulle) instruction1 est excute ; si elle est fausse (nulle) instruction2 est excute On notera que l'expression conditionnelle doit figurer entre parenthses. Celles-ci font partie de la syntaxe du if , non de celle de l'expression. Lorsque plusieurs instructions if sont imbriques, il est convenu que chaque else se rapporte au dernier if pour lequel le compilateur a rencontr une condition suivie d'exactement une instruction. Le listing du programme peut (et doit !) traduire cela par une indentation (marge blanche) expressive, mais il ne faut pas oublier que le compilateur ne tient pas compte des marges. Par exemple, le programme suivant est probablement incorrect ; en tout cas, son indentation ne traduit pas convenablement les rapports entre les if et les else : if (nombrePersonnes != 0) if (nombrePersonnes != nombreAdultes) printf("Il y a des enfants!"); else printf("Il n'y a personne!"); TD : 1. Ecrire un programme qui teste la parit dun entier 2. Ecrire un programme qui affiche la plus grande valeur parmi 3 entiers. 3. Ecrire un programme qui affiche dans lordre croissant trois entiers lus au clavier switchcase
6

Syntaxe switch ( expression ) corps Le corps de l'instruction switch prend la forme d'un bloc {...} renfermant une suite d'instructions entre lesquelles se trouvent des constructions de la forme case expression-constante : ou bien default : Le fonctionnement de cette instruction est le suivant : expression est value ; Sil existe un nonc case avec une constante qui gale la valeur de expression, le contrle est transfr l'instruction qui suit cet nonc ; si un tel case n'existe pas, et si l'nonc default existe, alors le contrle est transfr l'instruction qui suit l'nonc default ; si la valeur de expression ne correspond aucun case et s'il n'y a pas d'nonc default, alors aucune instruction n'est excute. TD : appliquer les oprateurs arithmtiques sur deux entiers lus au clavier selon la valeur que lutilisateur aura choisi.

3.2. Structures itratives


Boucle while La syntaxe de while est la suivante : while ( expression ) instruction Tant que expression est vrifie (i.e., non nulle), instruction est excute. Si expression est nulle au dpart, instruction ne sera jamais excute. instruction peut videmment tre une instruction compose. Par exemple, le programme suivant imprime les entiers de 1 9. i = 1; while (i < 10) { printf("\n i = %d",i); i++; }

Boucle do---while Il peut arriver que lon ne veuille effectuer le test de continuation quaprs avoir excut linstruction. Dans ce cas, on utilise la boucle do---while. Sa syntaxe est : do <instruction> while ( <expression>); Ici, instruction sera excute tant que expression est non nulle. Cela signifie donc que <instruction> est toujours excute au moins une fois. Par exemple, pour saisir au clavier un entier entre 1 et 10 : int a; do { printf("\n Entrez un entier entre 1 et 10 : "); scanf("%d",&a); } while ((a <= 0) || (a > 10)); TP: Ecrire un programme qui calcule la PGCD de deux entiers Boucle for
7

La syntaxe de for est : for ( expr 1 ; expr 2 ; expr 3) <instruction> Par exemple, pour imprimer tous les entiers de 0 9, on crit : for (i = 0; i < 10; i++) printf("\n i = %d",i); Les trois expressions utilises dans une boucle for peuvent tre constitues de plusieurs expressions spares par des virgules. Cela permet par exemple de faire plusieurs initialisations la fois. Par exemple, pour calculer la factorielle dun entier, on peut crire: int n, i, fact; for (i = 1, fact = 1; i <= n; i++) fact *= i; printf("%d ! = %d \n",n,fact); On peut galement insrer linstruction fact *= i; dans la boucle for ce qui donne : int n, i, fact; for (i = 1, fact = 1; i <= n; fact *= i, i++); printf("%d ! = %d \n",n,fact); On vitera toutefois ce type dacrobaties qui napportent rien et rendent le programme difficilement lisible. TP : 1. Simuler lopration de multiplication en nutilisant que laddition. 2. Calculer la somme des n premiers positifs

Chapitre 3 : Structures de donnes composes


A partir des types prdfinis du C (caractres, entiers, flottants), on peut crer de nouveaux types, appels types composs, qui permettent de reprsenter des ensembles de donnes organises. 3.1. Les tableaux Un tableau est un ensemble fini dlments de mme type, stocks en mmoire des adresses contiges. Tableaux unidimensionnel (Vecteur) La dclaration dun tableau une dimension se fait de la faon suivante : <type> <nom_du_tableau>[nombre_elements]; o nom_du_tableau> est une expression constante entire positive. Par exemple, la dclaration : int tab[10]; indique que tab est un tableau de 10 lments de type int. Cette dclaration alloue donc en mmoire pour lobjet tab un espace de 10 4 octets conscutifs. Pour plus de clart, il est recommand de donner un nom la constante <nombre_elements> par une directive au prprocesseur, par exemple # nombre_elements 10 On accde un lment du tableau en lui appliquant loprateur []. Les lments dun tableau sont toujours numrots de 0 nom_du_tableau-1 TP : 1. Lecture des lments dun tableau 2. Affichage des lments dun tableau

Dclaration de tableaux avec initialisation On peut initialiser un tableau lors de sa dclaration par une liste de constantes de la faon suivante : type nom_du_tableau[N] = {constante_1,constante_2,...,constante_N}; Par exemple, on peut crire #define N 4 int tab[N] = {1, 2, 3, 4}; main() { int i; for (i = 0; i < N; i++) printf("tab[%d] = %d\n",i,tab[i]); } Si le nombre de donnes dans la liste dinitialisation est infrieur la dimension du tableau, seuls les premiers lments seront initialiss. Les autres lments seront mis zro si le tableau est une variable globale (extrieure toute fonction) ou une variable locale de classe de mmorisation static TP : 1. Ecrire un algorithme qui convertit un entier en binaire. (Le rsultat pourra tre rang dans un tableau) 2. Ecrire un algorithme qui calcule la somme des lments dun tableau dentiers 3. Ecrire un algorithme qui recherche la plus grande valeur du tableau. 4. Ecrire un algorithme qui insre un lment lindice i dans un tableau 5. Ecrire un algorithme qui insre un lment dans un tableau tri 6. Ecrire un algorithme qui fusionne deux tableaux 7. Ecrire un algorithme qui fusionne deux tableaux tris (le tableau rsultat doit tre tri) 8. Ecrire un algorithme qui affiche lindice de la plus longue squence de nombre rang dans lordre croissant dans un tableau dentiers 9. Ecrire un algorithme qui affiche lindice de la plus longue monotonie dans un tableau dentiers
9

Quelques algorithmes de tri Tri bulle Tri direct Tri fusion Tri shell Tri par la mthode du tas (Heap short) Tableaux multidimensionnels Il sagit des tableaux plus dune dimension. La dclaration est <type_element><nom_tableau>[<nb_elt_dim1>][<nb_elt_dim2>][<nb_elt_dimn>] ; Exemple : int matrice[10][7] ; float A[3][5][2] ; TP : Lecture / Affichage des lments dune matrice #include <sdtio.h> #define M 10 #define N 5 int main(){ float matrice[M][N] ; int i, j ; /*Lecture de la matrice*/ for(i=0;i<M;i++) for(j=0;j<N;j++){ printf(matrice[%,%]=,i,j); scanf(%f,&matrice[i][j]; } /*Affichage de la matrice*/ for(i=0;i<M;i++) for(j=0;j<N;j++) printf(matrice[%,%]=%f,i,j,matrice[i][j]); return 0; } TP :

1. Afficher les coefficients du polynme (a+b)n (triangle de Pascal) 2. triangulariser une matrice carre dordre n 3. rsolution de systme dquation linaire

3.2. Chaines de caractres Une chaine de caractres est un tableau dont les lments sont des caractres. La dclaration est donc la suivante : char <nom_chaine>[<nombre_caractere>] ; Exemple : char nom[30] ; scanf(%s, nom) ; char msg[50]=Bonjour , je suis dbutant en C ; Contrairement aux tableaux de nombres pour lesquels la lecture / lcriture se fait lment par lment, la lecture / lcriture dun tableau de caractres (chaine de caractres) se fait de manire globale laide des fonctions scanf et printf avec le format %s. La fin dune chaine de caractre est matrialis par le caractre \0, affect automatiquement par le compilateur. Les fonctions gets et puts permettent de lire et afficher les chaines de caractres. Il sagit de fonctions dentre sortie non formates.
10

Exemple char ch[40] ; gets(ch) ; /*Lecture de la chaine de caractres, quivalente scanf(%s,ch)*/ puts(ch) ; /*Affichage de la chaine de caractres, quivalente printf(%s,ch)*/ TP : Ecrire un programme qui dtermine la longueur dune chaine de caractres lu. Remarque : la fonction strlen de la bibliothque <stdlib.h> fournit la longueur dune chaine de caractre passe en argument. TP : 1. Ecrire un algorithme caractres. 2. Ecrire un inverse les 3. Ecrire un algorithme chaine de caractre. 4. Ecrire un algorithme 5. Ecrire un algorithme caractres. qui compte le nombre de voyelle et de consonne dans une chaine de caractres dune chaine qui compte le nombre doccurrence dun caractre dans qui teste sui un mot est palindrome (ex. LAVAL, KAYAK, ) qui teste si un caractre est prsent dans une chaine de

une

3.3. Les structures


Une structure est une suite finie dobjets de types diffrents. Contrairement aux tableaux, les diffrents lments dune structure noccupent pas ncessairement des zones contiges en mmoire. Chaque lment de la structure, appel membre ou champ, est dsign par un identificateur. On distingue la dclaration dun modle de structure de celle dun objet de type structure correspondant un modle donn. La dclaration dun modle de structure dont lidentificateur est modele suit la syntaxe suivante : struct modele { type-1 membre-1; type-2 membre-2; ... type-n membre-n; }; Pour dclarer un objet de type structure correspondant au modle prcdent, on utilise la syntaxe : struct modele objet; ou bien, si le modle na pas t dclar au pralable : struct modele { type-1 membre-1; type-2 membre-2; ... type-n membre-n; } objet; On accde aux diffrents membres dune structure grce loprateur membre de structure, not .. Le i-me membre de objet est dsign par lexpression objet.membre-i
11

On peut effectuer sur le i-me membre de la structure toutes les oprations valides sur des donnes de type type-i. Par exemple, le programme suivant dfinit la structure complexe, compose de deux champs de type double ; il calcule la norme dun nombre complexe. #include <math.h> struct complexe { double reelle; double imaginaire; }; main() { struct complexe z; double norme; ... norme = sqrt(z.reelle * z.reelle + z.imaginaire * z.imaginaire); printf("norme de (%f + i %f) = %f \n",z.reelle,z.imaginaire,norme); } 3.4. Les unions Une union dsigne un ensemble de variables de types diffrents susceptibles doccuper alternativement une mme zone mmoire. Une union permet donc de dfinir un objet comme pouvant tre dun type au choix parmi un ensemble fini de types. Si les membres dune union sont de longueurs diffrentes, la place rserve en mmoire pour la reprsenter correspond la taille du membre le plus grand. Les dclarations et les oprations sur les objets de type union sont les mmes que celles sur les objets de type struct. Dans lexemple suivant, la variable hier de type union jour peut tre soit un entier, soit un caractre. union jour { char lettre; int numero; }; main() { union jour hier, demain; hier.lettre = J; printf("hier = %c\n",hier.lettre); hier.numero = 4; demain.numero = (hier.numero + 2) % 7; printf("demain = %d\n",demain.numero); } 3.5 Les numrations Les numrations permettent de dfinir un type par la liste des valeurs quil peut prendre. Un objet de type numration est dfini par le mot-clef enum et un identificateur de modle, suivis de la liste des valeurs que peut prendre cet objet : enum modele {constante-1, constante-2,...,constante-n}; En ralit, les objets de type enum sont reprsents comme des int. Les valeurs possibles constante-1, constante-2,...,constante-n sont codes par des entiers de 0 n-1. Par exemple, le type enum booleen dfini dans le programme suivant associe lentier 0 la valeur faux et lentier 1 la valeur vrai. main() { enum booleen {faux, vrai};
12

enum booleen b; b = vrai; printf("b = %d\n",b); } On peut modifier le codage par dfaut des valeurs de la liste lors de la dclaration du type numr, par exemple : enum booleen {faux = 12, vrai = 23}; 3.6 Dfinition de types avec typedef Le programmeur peut dfinir ses propres types de donnes laide de typedef : typedef type <synonyme>; Exemple : struct complexe { double reelle; double imaginaire; }; typedef struct complexe complexe; main() { complexe z; ... }

13

Chapitre 3 : Fonctions
Beaucoup de langages distinguent deux sortes de sous-programmes: les fonctions et les procdures. L'appel d'une fonction est une expression, tandis que l'appel d'une procdure est une instruction. Ou, si on prfre, l'appel d'une fonction renvoie un rsultat, alors que l'appel d'une procdure ne renvoie rien. En C on retrouve ces deux manires d'appeler les sous-programmes, mais du point de la syntaxe le langage ne connait que les fonctions. Autrement dit, un sous-programme est toujours suppos renvoyer une valeur, mme lorsque celle-ci n'a pas de sens ou n'a pas t spcifie. C'est pourquoi on ne parlera ici que de fonctions. 3.1 Dfinition dune fonction La dfinition dune fonction est la donne du texte de son algorithme, quon appelle corps de la fonction. Elle est de la forme <type> <nom-fonction> ( <type_1> <arg_1>,..., <type_n> <arg_n>) { [declarations de variables locales] <liste dinstructions> } Le corps de la fonction dbute ventuellement par des dclarations de variables, qui sont locales cette fonction. Il se termine par linstruction de retour `a la fonction appelante, return, dont la syntaxe est return (<expression>); La valeur de <expression> est la valeur que retourne la fonction. Son type doit tre le mme que celui qui a t spcifi dans len-tte de la fonction. Si la fonction ne retourne pas de valeur (fonction de type void), sa dfinition sachve par return; Exemple : int produit (int a, int b) { return(a*b); } TP : 1. Ecrire la fonction pgcd pour le calcul du PGCD de deux entiers 2. Ecrire la fonction factorielle pour le calcul de la factorielle dun entier 3 Ecrire la fonction somme pour le calcul de la somme des n entiers positifs

3.2. Appel dune fonction Lappel dune fonction se fait par lexpression nom-fonction(para-1,para-2,...,para-n) Lordre et le type des paramtres effectifs de la fonction doivent concorder avec ceux donnes dans len-tte de la fonction. Les paramtres effectifs peuvent tre des expressions. La virgule qui spare deux paramtres effectifs est un simple signe de ponctuation ; il ne sagit pas de loprateur virgule. Cela implique en particulier que lordre devaluation des paramtres effectifs nest pas assur et dpend du compilateur. Il est donc dconseill, pour une fonction plusieurs paramtres, de faire figurer des oprateurs dincrmentation ou de dcrmentation (++ ou --) dans les expressions dfinissant les paramtres effectifs . 3.3 Dclaration dune fonction Le langage C nautorise pas les fonctions imbriques. La dfinition dune fonction secondaire doit donc tre place soit avant, soit aprs la fonction principale main. Toutefois, il est indispensable que le compilateur connaisse la fonction au moment o celle-ci est appele. Si une fonction est dfinie aprs son premier appel (en particulier si sa dfinition est place aprs la fonction aprs la fonction
14

main), elle doit imprativement tre dclare au pralable. Une fonction secondaire est dclare par son prototype, qui donne le type de la fonction et celui de ses paramtres, sous la forme : <type> <nom-fonction>(<type-1>,...,<type-n>); Exemple : #include <stdio.h> float discriminant (float,float,float); void main(){ float a, b,c discriminant ; printf(entrez les coefficients) ; scanf(%f%f%f, &a ;&b,&c) ; disc=discriminant(a, b, c) ; printf(%f, disc) ; } float discriminant (float a, float b, float c){ return (b*b-4*a*c); } 3.4. Transmission des paramtres dune fonction Les paramtres dune fonction sont traites de la mme manire que les variables locales de classe automatique : lors de lappel de la fonction, les paramtres effectifs sont copis dans le segment de pile. La fonction travaille alors uniquement sur cette copie. Cette copie disparat lors du retour au programme appelant. Cela implique en particulier que, si la fonction modifie la valeur dun de ses paramtres, seule la copie sera modifie ; la variable du programme appelant, elle, ne sera pas modifie. On dit que les paramtres dune fonction sont transmis par valeurs. Par exemple, le programme suivant void echange (int, int ); void echange (int a, int b) { int t; printf("debut fonction :\n a = %d \t b = %d\n",a,b); t = a; a = b; b = t; printf("fin fonction :\n a = %d \t b = %d\n",a,b); return; } main() { int a = 2, b = 5; printf("debut programme principal :\n a = %d \t b = %d\n",a,b); echange(a,b); printf("fin programme principal :\n a = %d \t b = %d\n",a,b); } imprime debut programme principal : a=2b=5 debut fonction : a=2b=5 fin fonction : a=5b=2 fin programme principal : a=2b=5 Pour quune fonction modifie la valeur dun de ses arguments, il faut quelle ait pour paramtre ladresse de cet objet et non sa valeur. Par exemple, pour changer les valeurs de deux variables, il faut crire :
15

void echange (int *, int *); void echange (int *adr_a, int *adr_b) { int t; t = *adr_a; *adr_a = *adr_b; *adr_b = t; return; } } main() { int a = 2, b = 5; printf("debut programme principal :\n a = %d \t b = %d\n",a,b); echange(&a,&b); printf("fin programme principal :\n a = %d \t b = %d\n",a,b); }

16

Chapitre 4 : Allocation dynamique


4.1. Les pointeurs Toute variable manipule dans un programme est stocke quelque part en mmoire centrale. Cette mmoire est constitue doctets qui sont identifis de manire univoque par un numro quon appelle adresse. Pour retrouver une variable, il suffit donc de connatre ladresse de loctet o elle est stocke (ou, sil sagit dune variable qui recouvre plusieurs octets contigus, ladresse du premier de ces octets). Pour des raisons videntes de lisibilit, on dsigne souvent les variables par des identificateurs, et non par leur adresse. Cest le compilateur qui fait alors le lien entre lidentificateur dune variable et son adresse en mmoire. Toutefois, il est parfois trs pratique de manipuler directement une variable par son adresse. Adresse et valeur dun objet On appelle Lvalue (left value) tout objet pouvant tre plac gauche dun oprateur daffectation. Une Lvalue est caractrise par : son adresse, cest- -dire ladresse mmoire partir de laquelle lobjet est stock ; sa valeur, cest-a-dire ce qui est stock cette adresse. Deux variables diffrentes ont des adresses diffrentes. Laffectation i = j; nopre que sur les valeurs des variables. Les variables i et j tant de type int, elles sont stockes sur 4 octets. Ainsi la valeur de i est stocke sur les octets dadresse 4831836000 `a 4831836003. Ladresse dun objet tant un numro doctet en mmoire, il sagit dun entier quelque soit le type de lobjet considr. Loprateur & permet daccder ladresse dune variable. Toutefois &i nest pas une Lvalue mais une constante : on ne peut pas faire figurer &i gauche dun oprateur daffectation. Pour pouvoir manipuler des adresses, on doit donc recourir un nouveau type dobjets, les pointeurs. Notion de pointeur Un pointeur est un objet (Lvalue) dont la valeur est gale ladresse dun autre objet. On dclare un pointeur par linstruction : <type> *<nom-du-pointeur>; o type est le type de lobjet point. Cette dclaration dclare un identificateur, nom-du-pointeur, associ un objet dont la valeur est ladresse dun autre objet de type type. Lidentificateur nomdu-pointeur est donc en quelque sorte un identificateur dadresse. Comme pour nimporte quelle Lvalue, sa valeur est modifiable. Mme si la valeur dun pointeur est toujours un entier (ventuellement un entier long), le type dun pointeur dpend du type de lobjet vers lequel il pointe. Cette distinction est indispensable linterprtation de la valeur dun pointeur. En effet, pour un pointeur sur un objet de type char, la valeur donne ladresse de loctet o cet objet est stocke. Par contre, pour un pointeur sur un objet de type int, la valeur donne ladresse du premier des 4 octets o lobjet est stock. Dans lexemple suivant, on dfinit un pointeur p qui pointe vers un entier i : int i = 3; int *p; p = &i; Loprateur unaire dindirection * permet daccder directement la valeur de lobjet point. Ainsi, si p est un pointeur vers un entier i, *p dsigne la valeur de i. Par exemple, le programme main() { int i = 3; int *p; p = &i; printf("*p = %d \n",*p); } Arithmtique des pointeurs La valeur dun pointeur tant un entier, on peut lui appliquer un certain nombre doprateurs arithmtiques classiques. Les seules oprations arithmtiques valides sur les pointeurs sont :
17

laddition dun entier un pointeur. Le rsultat est un pointeur de mme type que le pointeur de dpart ; la soustraction dun entier un pointeur. Le rsultat est un pointeur de mme type que le pointeur de dpart ; la diffrence de deux pointeurs pointant tous deux vers des objets de mme type. Le rsultat est un entier.

Notons que la somme de deux pointeurs nest pas autorise. Si i est un entier et p est un pointeur sur un objet de type type, lexpression p + i dsigne un pointeur sur un objet de type type dont la valeur est gale la valeur de p incrmente de i*sizeof(type). Il en va de mme pour la soustraction dun entier un pointeur, et pour les oprateurs dincrmentation et de dcrmentation ++ et --. Par exemple, le programme main() { int i = 3; int *p1, *p2; p1 = &i; p2 = p1 + 1; printf("p1 = %ld \t p2 = %ld\n",p1,p2); } affiche p1 = 4831835984 p2 = 4831835988. Par contre, le mme programme avec des pointeurs sur des objets de type double : main() { double i = 3; double *p1, *p2; p1 = &i; p2 = p1 + 1; printf("p1 = %ld \t p2 = %ld\n",p1,p2); } Les oprateurs de comparaison sont galement applicables aux pointeurs, condition de comparer des pointeurs qui pointent vers des objets de mme type. Pointeurs et tableau Tout tableau en C est en fait un pointeur constant. Dans la dclaration int tab[10]; tab est un pointeur constant (non modifiable) dont la valeur est ladresse du premier lment du tableau. Autrement dit, tab a pour valeur &tab[0]. On peut donc utiliser un pointeur initialis tab pour parcourir les lments du tableau. #define N 5 int tab[5] = {1, 2, 6, 0, 7}; main() { int i; int *p; p = tab; for (i = 0; i < N; i++) { printf(" %d \n",*p); p++; } } On accde llment dindice i du tableau tab grce `a loprateur dindexation [], par lexpression tab[i]. Cet oprateur dindexation peut en fait sappliquer tout objet p de type pointeur. Il est li loprateur dindirection * par la formule p[i] = *(p + i) Pointeurs et tableaux se manipulent donc exactement de mme manire.
18

TP : Reprendre lexercice de recherche du maximum dun tableau en utilisant les pointant pour le parcours du tableau. Pointeurs et tableaux multidimensionnels Un tableau `a deux dimensions est, par dfinition, un tableau de tableaux. Il sagit donc en fait dun pointeur vers un pointeur. Considrons le tableau deux dimensions dfini par : int tab[M][N]; tab est un pointeur, qui pointe vers un objet lui-mme de type pointeur dentier. tab a une valeur constante gale ladresse du premier lment du tableau, &tab[0][0]. De mme tab[i], pour i entre 0 et M-1, est un pointeur constant vers un objet de type entier, qui est le premier lment de la ligne dindice i. tab[i] a donc une valeur constante qui est gale &tab[i][0]. Exactement comme pour les tableaux une dimension, les pointeurs de pointeurs ont de nombreux avantages sur les tableaux multi-dimensionnels. On dclare un pointeur qui pointe sur un objet de type type* (deux dimensions) de la mme manire quun pointeur, cest--dire type **nom-du-pointeur. De mme un pointeur qui pointe sur un objet de type type ** (quivalent un tableau 3 dimensions) se dclare par type ***nom-du-pointeur; 4.2. Allocation dynamique Avant de manipuler un pointeur, et notamment de lui appliquer loprateur dindirection *,il faut linitialiser. Sinon, par dfaut, la valeur du pointeur est gale une constante symbolique note NULL dfinie dans stdio.h. En gnral, cette constante vaut 0. Le test p== NULL permet de savoir si le pointeur p pointe vers un objet. On peut initialiser un pointeur p par une affectation sur p. Par exemple, on peut affecter p ladresse dune autre variable. Il est galement possible daffecter directement une valeur *p. Mais pour cela, il faut dabord rserver *p un espace-mmoire de taille adquate. Ladresse de cet espace-mmoire sera la valeur de p. Cette opration consistant rserver un espace-mmoire pour stocker lobjet point sappelle allocation dynamique. Elle se fait en C par la fonction malloc de la librairie standard stdlib.h. Sa syntaxe est : malloc(nombre-octets) Cette fonction retourne un pointeur de type char * pointant vers un objet de taille nombreoctets octets. Pour initialiser des pointeurs vers des objets qui ne sont pas de type char, il faut convertir le type de la sortie de la fonction malloc laide dun cast. Largument nombre-octets est souvent donn laide de la fonction sizeof() qui renvoie le nombre doctets utiliss pour stocker un objet. Ainsi, pour initialiser un pointeur vers un entier, on crit : #include <stdlib.h> int *p; p = (int*)malloc(sizeof(int)); La fonction calloc de la librairie stdlib.h a le mme rle que la fonction malloc mais elle initialise en plus lobjet point *p zro. Sa syntaxe est : calloc(nb-objets,taille-objets) Ainsi, si p est de type int*, linstruction p = (int*)calloc(N,sizeof(int)) est strictement quivalente p = (int*)malloc(N * sizeof(int)); for (i = 0; i < N; i++) *(p + i) = 0; Lemploi de calloc est simplement plus rapide. Enfin, lorsque lon na plus besoin de lespace-mmoire allou dynamiquement (cest--dire quand on nutilise plus le pointeur p), il faut librer cette place en mmoire. Ceci se fait laide de linstruction free qui a pour syntaxe free(nom-du-pointeur); A toute instruction de type malloc ou calloc doit tre associe une instruction de type free.

19

Chapitre 5 : Structures de donnes rcursives


On a souvent besoin en C de modles de structure dont un des membres est un pointeur vers une structure de mme modle. Cette reprsentation permet en particulier de construire des listes chanes. En effet, il est possible de reprsenter une liste dlments de mme type par un tableau (ou un pointeur). Toutefois, cette reprsentation, dite contigu, impose que la taille maximale de la liste soit connue a priori (on a besoin du nombre delements du tableau lors de lallocation dynamique). Pour rsoudre ce problme, on utilise une reprsentation Chaine : 5.1. Listes chaines Dfinition et reprsentation Une liste chaine est une collection dlments appels cellule qui contient la valeur dun lment de la liste et un pointeur sur llment suivant. Le dernier lment pointe sur la liste vide NULL. La liste est alors dfinie comme un pointeur sur le premier lment de la chane. NULL L Pour reprsenter une liste dentiers sous forme chane, on cre le modle de structure cellule qui a deux champs : un champ valeur de type int, et un champ suivant de type pointeur sur une struct cellule. Une liste sera alors un objet de type pointeur sur une struct cellule. Grce au mot-clef typedef, on peut dfinir le type liste, synonyme du type pointeur sur une struct cellule. struct cellule { int valeur; struct cellule *suivant; }; typedef struct cellule *liste; Un des avantages de la reprsentation chane est quil est trs facile dinsrer un lment un endroit quelconque de la liste. Opration sur les listes chaines - recherche dun lment dans une liste - - insertion dun lment (En fin de liste, en tte, avant un lment, aprs un lment) - Suppression dun lment - 5.2. Les arbres binaires Un arbre binaire est reprsent par une adresse, qui est : - soit l'adresse nulle ; - soit l'adresse d'une structure constitue de trois champs : une information et deux descendants qui sont des adresses d'arbres binaires. Typiquement, la dclaration d'une telle structure sera donc calque sur la suivante : struct noeud { int valeur; struct noeud *fils_gauche; struct noeud *fils_droit; }; typedef struct noeud *arbre;

20

Chapitre 6 : Fichier
Les entres/sorties en langage C se font par l'intermdiaire d'entits logiques, appels flux, qui reprsentent des objets externes au programme, appels fichiers. En langage C, un fichier n'est donc pas ncessairement un fichier sur disque (ou priphrique de stockage pour tre plus prcis). Un fichier dsigne en fait aussi bien un fichier sur disque (videmment) qu'un priphrique physique ou un tube par exemple. Selon la manire dont on veut raliser les oprations d'E/S sur le fichier, qui se font travers un flux, on distingue deux grandes catgories de flux savoir les flux de texte et les flux binaires. Les flux de texte sont parfaits pour manipuler des donnes prsentes sous forme de texte. Un flux de texte est organis en lignes. En langage C, une ligne est une suite de caractres termine par le caractre de fin de ligne (inclus) : '\n'. Malheureusement, ce n'est pas forcment le cas pour le systme sous-jacent. Sous Windows par exemple, la marque de fin de ligne est par dfaut la combinaison de deux caractres : CR (Carriage Return) et LF (Line Feed) soit '\r' et '\n' (notez bien que c'est CR/LF c'est--dire CR suivi de LF pas LF suivi de CR). Sous UNIX, c'est tout simplement '\n'. On se demande alors comment on va pouvoir lire ou crire dans un fichier, travers un flux de texte, de manire portable. Et bien c'est beaucoup plus simple que ce quoi vous-vous attendiez : lorsqu'on effectue une opration d'entre/sortie sur un flux de texte, les donnes seront lues/crites de faon ce qu'elles correspondent la manire dont elles doivent tre reprsentes et non caractre pour caractre. C'est--dire par exemple que, dans une implmentation o la fin de ligne est provoque par la combinaison des caractres CR et LF, l'criture de '\n' sur un flux de texte va provoquer l'criture effective des caractres '\r' et '\n' dans le fichier associ. Sur un flux binaire les donnes sont lues ou crites dans le fichier caractre pour caractre. 6.1. Les fichiers sur disque La communication avec une ressource externe (un modem, une imprimante, une console, un priphrique de stockage, etc.) ncessite un protocole de communication (qui peut tre texte ou binaire, simple ou complexe, ...) spcifique de cette ressource et qui n'a donc rien voir le langage C. La norme dfinit tout simplement les fonctions permettant d'effectuer les entres/sorties vers un fichier sans pour autant dfinir la notion de matriel ou mme de fichier sur disque afin de garantir la portabilit du langage (ou plus prcisment de la bibliothque). Cependant, afin de nous fixer les ides, nous n'hsiterons pas faire appel ces notions dans les explications. Les fichiers sur disque servent stocker des informations. On peut naviguer l'intrieur d'un tel fichier l'aide de fonctions de positionnement (que nous verrons un peu plus loin). A chaque instant, un pointeur indique la position courante dans le fichier. Ce pointeur se dplace, quelques exceptions prs, aprs chaque opration de lecture, d'criture ou appel d'une fonction de positionnement par exemple. Bien entendu, cette notion de position n'est pas une spcificit exclusive des fichiers sur disque. D'autres types de fichiers peuvent trs bien avoir une structure similaire. 6.2. Les messages d'erreur En langage C il est coutume de retourner un entier mme quand une fonction n'est cense retourner aucune valeur. Cela permet au programme de contrler les erreurs et mme d'en connatre la cause. Certaines fonctions comme malloc par exemple retournent cependant une adresse, NULL en cas d'erreur. Pour fournir d'amples informations quant la cause de l'erreur, ces fonctions placent alors une valeur dans une variable globale appele errno (en fait la norme n'impose pas qu'errno doit tre forcment une variable globale !), cette valeur bien entendu est priori dpendante de l'implmentation. Ces valeurs doivent tre dclares dans le fichier errno.h. Par exemple, une implmentation peut dfinir un numro d'erreur ENOMEM qui sera place dans errno lorsqu'un appel malloc a chou car le systme manque de mmoire. La fonction strerror, dclare dans string.h, permet de rcuprer une chane priori dfinie par l'implmentation dcrivant l'erreur correspondant au numro d'erreur pass en argument. La fonction perror, dclare dans stdio.h, quant elle utilise cette chane, plus une chane fournie en
21

argument, pour afficher la description d'une erreur. Le texte sera affich sur l'erreur standard (stderr). Pour rsumer : perror(msg); est quivalent : fprintf(stderr, "%s: %s\n", msg, strerror(errno)); Si nous n'avons pas parl de ces fonctions auparavant, c'est parce que nous n'en avions pas vraiment jusqu'ici besoin. A partir de maintenant, elles vont tre trs utiles car les fonctions d'entres/sorties sont sujettes de trs nombreuses sortes d'erreurs : fichier non trouv, espace sur disque insuffisant, on n'a pas les permissions ncessaires pour lire ou crire dans le fichier, ... 6.3. Ouverture d'un fichier Toute opration d'E/S dans un fichier commence par l'ouverture du fichier. Lorsqu'un fichier est ouvert, un flux lui est associ. Ce flux est reprsent par un pointeur vers un objet de type FILE. Pendant l'ouverture d'un fichier, on doit spcifier comment en dsire l'ouvrir : en lecture (c'est-dire qu'on va lire dans le fichier), en criture (pour crire), ou en lecture et criture. La fonction fopen : FILE * fopen(const char * filename, const char * mode); permet d'ouvrir un fichier dont le nom est spcifi par l'argument filename selon le mode, spcifi par l'argument mode, dans lequel on souhaite ouvrir le fichier. Elle retourne l'adresse d'un objet de type FILE qui reprsente le fichier l'intrieur du programme. En cas d'erreur, NULL est retourn et une valeur indiquant la cause de l'erreur est place dans errno. En pratique, dans le cas d'un fichier sur disque, l'argument nom peut tre aussi bien un chemin complet qu'un chemin relatif. Notez bien que les notions de chemin, rpertoire (qui inclut galement les notions de rpertoire courant, parent, etc.), ... sont dpendantes du systme, elles ne font pas partie du langage C. La plupart du temps, le rpertoire courant est par dfaut celui dans lequel se trouve le programme mais, si le systme le permet, il est possible de spcifier un rpertoire diffrent. Fondamentalement, les valeurs suivantes peuvent tre utilises dans l'argument mode : "r" : ouvrir le fichier en lecture. Le fichier spcifi doit dj exister. "w" : ouvrir le fichier en criture. S'il n'existe pas, il sera cr. S'il existe dj, son ancien contenu sera effac. "a" : ouvrir le fichier en mode ajout, qui est un mode dans lequel toutes les oprations d'criture dans le fichier se feront la fin du fichier. S'il n'existe pas, il sera cr. Quel que soit le cas, le fichier sera associ un flux de texte. Pour spcifier qu'on veut ouvrir le fichier en tant que fichier binaire, il suffit d'ajouter le suffixe 'b' (c'est--dire "rb", "wb" ou "ab"). Sauf dans le cas du mode "ab" et drivs, dans lequel le pointeur pourrait, selon l'implmentation, tre positionn la fin du fichier, il sera positionn au dbut du fichier. On peut galement ajouter un '+' (par exemple : "wb+") dans l'argument mode ce qui aura pour effet de permettre d'effectuer aussi bien des oprations de lecture que d'criture sur le fichier. On dit alors que le fichier est ouvert en mode mise jour. Il y a cependant une remarque trs importante concernant les fichiers ouverts en mode mise jour : avant d'effectuer une opration de lecture juste aprs une opration d'criture, il faut tout d'abord appeler fflush ou une fonction de positionnement avant d'effectuer une opration d'criture juste aprs une opration de lecture, il faut d'abord appeler une fonction de positionnement, moins d'avoir atteint la fin du fichier dans de nombreuses implmentations, tout fichier ouvert en mode mise jour est suppos tre un fichier binaire qu'on ait mis oui ou non la lettre 'b'. Personnellement, je recommande donc de n'utiliser le mode mise jour qu'avec les fichiers binaires. Lorsqu'on n'en a plus besoin, il faut ensuite fermer le fichier : int fclose(FILE * f); Le programme suivant cre un fichier, hello.txt, pour y crire ensuite une et une seule ligne : Hello, world.
22

#include <stdio.h> int main() { FILE * f; f = fopen("hello.txt", "w"); if (f != NULL) { fprintf(f, "Hello, world\n"); fclose(f); } else perror("hello.txt"); return 0; } En supposant que pour le systme la fin de ligne est reprsente par la combinaison des caractres CR et LF, ce programme est aussi quivalent au suivant : #include <stdio.h> int main() { FILE * f; f = fopen("hello.txt", "wb"); if (f != NULL) { fprintf(f, "Hello, world\r\n"); fclose(f); } else perror("hello.txt"); return 0; } 6.3. Exemple : copier un fichier Il y a plusieurs moyens de raliser la copie d'un fichier, le plus simple est peut-tre de copier la source vers la destination octet par octet. Voici un programme qui ralise une telle copie : #include <stdio.h> #include <stdlib.h> #include <string.h> char * saisir_chaine(char * lpBuffer, int buf_size); int main() { FILE * fsrc, * fdest; char source[FILENAME_MAX], dest[FILENAME_MAX]; int c; printf("source : "); saisir_chaine(source, sizeof(source)); fsrc = fopen(source, "rb"); if (fsrc == NULL) { perror(source);
23

exit(EXIT_FAILURE); } printf("dest : "); saisir_chaine(dest, sizeof(dest)); fdest = fopen(dest, "wb"); if (fdest == NULL) { perror(dest); exit(EXIT_FAILURE); } while ((c = getc(fsrc)) != EOF) putc(c, fdest); fclose(fdest); fclose(fsrc); } return 0;

char * saisir_chaine(char * lpBuffer, int buf_size) { char * p; fgets(lpBuffer, buf_size, stdin); if ((p = strchr(lpBuffer, '\n')) != NULL) *p = '\0'; else { int c; do c = getchar(); while (c != EOF && c != '\n'); } } return lpBuffer;

24