Sunteți pe pagina 1din 107

Programmation en Langage C/C++ par Sylvain Ferey pour le Forum des Programmeurs Francophones

Tables des matires


Introduction............................................................................................................................. 5 Ralisation de notre premier programme C............................................................................... 6 Les types du C / C++ .............................................................................................................. 9
Le type caractre .................................................................................................................9
Un signe de caractre ..............................................................................................................................................9

Les types entiers.................................................................................................................13


Les entiers courts...................................................................................................................................................13 Les entiers longs....................................................................................................................................................13

Les types flottants...............................................................................................................15 Conversion de types............................................................................................................15 Le type Pointeur .................................................................................................................17 Le type Tableau..................................................................................................................23
Les tableaux statiques ...........................................................................................................................................23 Les tableaux dynamiques......................................................................................................................................25 Les tableaux de caractres ....................................................................................................................................27

Les oprateurs....................................................................................................................... 28
Les oprateurs mathmatiques.............................................................................................28 Les oprateurs logiques et relationnels..................................................................................30 Les oprateurs dincrmentation et dcrmentation...............................................................31 Les oprateurs binaires .......................................................................................................32 Les oprateurs contracts ...................................................................................................33 Les piges viter..............................................................................................................33

Les instructions de tests.......................................................................................................... 35


Linstruction if.................................................................................................................35
Lexpression condition..........................................................................................................................................37 Des conditions avec et et ou........................................................................................................................39

Loprateur ternaire ?: .....................................................................................................39 Linstruction switch .........................................................................................................41

Les instructions de boucles..................................................................................................... 43


Linstruction while ...........................................................................................................43 Linstruction break...........................................................................................................43 Linstruction do ... while( ) ...............................................................................................45 Linstruction for...............................................................................................................47
Lexpression dinitialisation .................................................................................................................................47 Lexpression de test...............................................................................................................................................47 Lexpression dincrmentation.............................................................................................................................49

Linstruction continue.......................................................................................................51

Tables des matires


Les fonctions ......................................................................................................................... 53
Dclaration.........................................................................................................................53 Dfinition ...........................................................................................................................55 Librairie .............................................................................................................................59 La fonction main..............................................................................................................60
Code de retour........................................................................................................................................................60 Paramtres reus ....................................................................................................................................................61

La sortie des donnes ............................................................................................................ 63


La fonction printf.............................................................................................................63
Dfinition des donnes .........................................................................................................................................63 Modificateur de description.................................................................................................................................65 Largeur daffichage................................................................................................................................................67 Prcision daffichage.............................................................................................................................................69 Modificateur de taille.............................................................................................................................................71

Les fonctions ?printf ........................................................................................................71

La saisie des donnes............................................................................................................. 73


La fonction scanf.............................................................................................................73
Dfinition des donnes .........................................................................................................................................73 Suppression daffectation ....................................................................................................................................75 Largeur de lecture ..................................................................................................................................................77 Modificateurs de taille...........................................................................................................................................77

Les fonctions ?scanf........................................................................................................79

Les chanes de caractres....................................................................................................... 80


Un nombre sign ou non......................................................................................................80 Les tableaux de caractres..................................................................................................82 Les fonctions standards de manipulation de chanes...............................................................88
Comparaison...........................................................................................................................................................88 Concatnation ........................................................................................................................................................88 Copie ........................................................................................................................................................................88 Longueur.................................................................................................................................................................90 Recherche................................................................................................................................................................90

Les Fichiers........................................................................................................................... 91
Le type FILE ..................................................................................................................91 Dsignation du fichier..........................................................................................................93
Ecriture de fichier...................................................................................................................................................94 Lecture de fichier....................................................................................................................................................96

Les fonctions gnriques dentre sortie ...............................................................................98 Les fonctions orientes fichiers............................................................................................98

Conclusion........................................................................................................................... 102

Liste des programmes


Code 1 : Hello world ...............................................................................................................6 Code 2 : Mauvais passage de paramtres variants .....................................................................21 Code 3 : Bon passage de paramtres variants............................................................................21 Code 4 : Cration dun tableau de taille quelconque ....................................................................25 Code 5 : Boucle while(..) { ... } .............................................................................................43 Code 6 : Boucle while(..) { ... } avec sortie par break..........................................................43 Code 7 : Boucle do { ... } while(..) ........................................................................................45 Code 8 : Boucle for ..............................................................................................................49 Code 9 : Boucle for avec break et continue......................................................................51 Code 10 : Fichier de dclaration : geometry.h .........................................................................55 Code 11 : Fichier de dfinition : geometry.c ............................................................................57 Code 12 : Utilisation de la librairie : geometry.a.......................................................................59 Code 13 : Valeur retourne par la fonction main .....................................................................60 Code 14 : Lecture du code retourn par la fonction main .........................................................60 Code 15 : Paramtres reus par la fonction main ....................................................................61 Code 16 : Affichage format de nombres dcimaux ...................................................................69 Code 17 : Stockage de donnes formates dans une chane ........................................................71 Code 18 : Saisie avec cho.......................................................................................................75 Code 19 : Lecture de donnes formates depuis une chane........................................................79 Code 20 : Table ASCII restreinte..............................................................................................80 Code 21 : Table ASCII complte ..............................................................................................80 Code 22 : Table ASCII vraiment complte ................................................................................82 Code 23 : Ecriture de fichier.....................................................................................................94 Code 24 : Lecture de fichier .....................................................................................................96 Code 25 : Carnet dadresses................................................................................................... 102

Introduction
Ce document fait la synthse des cours de C proposs sur le Forum des Programmeurs Francophones de CompuServe. Cette synthse se veut un guide dapprentissage efficace du langage C ANSI. Tous les chapitres ont t peu ou prou rcrits pour tre les plus comprhensibles possibles et vous permettront de comprendre la mise en uvre et lutilisation du C. Dautres chapitres ont t ajouts par rapport la parution hebdomadaire initiale. Enfin de nouveaux exemples illustrent cette prsentation.

Ce cours est destin aux dbutants, bien quil puisse servir de rfrence aux inities. Si vous ne disposez pas de compilateur C, vous trouverez, sur le Forum des Programmeurs Francophones de CompuServe, une distribution du compilateur GNU adapte au processeur Intel par DJGPP; ce compilateur ligne de commande fournit du code binaire 32 bits de bonne qualit.

Je tiens remercier Julien Hardelin pour sa relecture efficace, ainsi que tous ceux qui, par leurs remarques, ont contribu amliorer ce cours. Ce document est libre dutilisation et de diffusion non commerciale sous une forme imprime qui respecte lintgralit du document. Toute diffusion lectronique - autre que celles ralises par lauteur lui -mme - est interdite sans laccord de lauteur. Lauteur ne garantit pas que ce document ne contient aucune erreur ou inexactitude, malgr les diffrentes rele ctures. De mme, lauteur ne pourra tre tenu pour responsable de tout dommage, implicite ou explicite, survenant de lutilisation de ce document et des exemples de codes quil contient. Lauteur, Sylvain FEREY sferey@compuserve.com

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

Ralisation de notre premier programme C


En C, plus quavec dautres langages, le premier programme traditionnel est : Code 1 : Hello world
#include <stdio.h> void main(){ printf("hello world!\n"); }

Pour excuter ce premier programme C, enregistrez le source ci -dessus dans un fichier et sauvegardez le sous le nom hello.c (ou ce que vous souhaitez, mais si vous utilisez le compilateur GNU lextension doit tre .c); ensuite dans une fentre Dos - si vous tes sous Windows - ou avec votre shell prfr (zsh, csh, bash, ...), placez vous dans le rpertoire qui contient ce fichier et lancez la compilation et ldition de liens grce la commande, si vous tes sous Dos/Windows :
G:\>gcc hello.c -o hello.exe

ou si vous tes sous unix ou Linux.


> gcc hello.c -o hello

Par cette commande, nous demandons loutil gcc de compiler notre source hello.c et de crer un excutable nomm hello.exe (ou hello); loption -o (pour output) permettant de dfinir ce nom. Nous obtenons un programme que nous pouvons lancer, sur lcran (la console) saffiche :
hello world!

Dtaillons ce petit programme : ?? la premire ligne inclut le fichier nomm stdio.h. Ce fichier contient les dfinitions pour les oprations standards dentres (input) partir du clavier ou dun fichier, et de sorties (output) vers lcran ou dans un fichier. stdio.h dfinit notamment une fonction nomme printf(). Cette fonction peut tre appele avec une chane de caractres (entre guillemets - ou double quote) comme unique paramtre; cette chane est alors affiche lcran. ?? nous trouvons ensuite une unique fonction appele main. Cette fonction est le point dentre du programme; cest dire l o dbutera son excution; elle est appele fonction principale. Le nom de cette fonction - main - est impos, si vous en utilisez un autre, lditeur de liens (linkeur) vous dira quil na pas trouv de fonction main et la construction de lexcutable chouera. main est une fonction, nous devons donc placer une paire de parenthses ouvrante et fermante - ( et ) - aprs son nom. Dans cet exemple simple, la fonction ne reoit aucun paramtre, rien ne figure donc entre ces parenthses.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

Une fonction doit dfinir le type des informations quelle retourne, comme ici aucune valeur nest retourn e par la fonction main, nous utilisons le mot cl void (littralement rien) plac devant le nom de la fonction.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

Enfin le corps dune fonction est toujours dfini entre accolades { et }. Ici, il est constitu dun simple appel la fonction printf() qui nous communiquons le texte afficher. Notez dans notre message que le caractre \n sert insrer un retour la ligne la fin du texte. Notez que la syntaxe est importante. Le C et le C++ distinguent les minuscules des majuscules. Dans ce source, seul le texte entre parenthses aurait pu avoir une syntaxe diffrente, tout le reste doit tre tap en minuscule. En effet, include et void font parti des mots cl du langage et leur syntaxe est impose. La routine printf est dclare avec cette orthographe dans le fichier stdio.h et est implmente avec ce nom dans la librairie dentre/sortie. Enfin la librairie de dmarrage (le runtime) sattends trouver une fonction nomme main. Remarque: le systme Dos/Windows ne distinguant pas la casse des noms de fichiers stdio.h aurait pu tre saisi autrement. Cependant une des forces du C/C++ tant sa portabilit, il est trs recommand de respecter la syntaxe des noms de fichiers afin de pouvoir recompiler vos sources sur dautres plates-formes (sous unix, par exemple).

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

Les types du C / C++


Les langages C et C++ sont des langages typs. Cela signifie que pour utiliser une variable, vous devez, avant de pouvoir lutiliser, dclarer cette variable en prcisant son type. Il existe trois sortes de types fondamentaux : le type caractre, le type entier et le type rel ou flottant. Nous allons tudier chacun de ces types. Notons avant cela lexistence dun autre type particulier et que nous venons de rencontrer : le type void, ce type ne peut tre utilis que comme type dune fonction (qui ne renvoie rien); en effet il est impossible de dclarer une variable qui ne serait stock sur aucun octet et qui ne permettrait de stocker aucun nombre.

Le type caractre
Le type caractre permet de dfinir des variables stockant une lettre. Le nom de ce type est char; on dfinit donc ainsi une variable de type caractre :
char maLettre;

Nous pouvons ensuite fixer la valeur dune telle variable avec une lettre donne entre apostrophes (ou simple quote), voici un exemple :
maLettre = 'a';

Notez dans cet exemple que le signe = sert dfinir la valeur dune variable ( lui affecter une valeur). Nous dirons que = est loprateur daffectation. Nous pouvons galement dclarer une variable et fixer sa valeur en mme temps:
char maLettre = 'A';

Nous pouvons galement dfinir la valeur dune variable de type char avec une valeur numrique. En effet, dans le dernier exemple la lettre 'A' est un caractre appartenant au jeu de caractres ASCII a pour valeur le nombre 65. Nous pouvons donc coder la ligne ci-dessus :
char maLettre = 65;

Plus gnralement, le type char supporte in extenso larithmtique des nombres entiers; ceci est trs commode et vite les surcharges (modifications de types) ou appels des fonctions manipulant les caractres comme cest le cas en Pascal; par exemple le code suivant affecte un caractre le caractre suivant dans le jeu de caractres utilis :
char uneLettre = 'A'; uneLettre = uneLettre + 1; /* lettre contient 'B' */

Notez que les symboles /* et */ permettent dinsrer des commentaires.

Un signe de caractre
Le type char stockant des caractres est cod sur un octet, il accepte donc 256 valeurs diffrentes, mais quelles valeurs ?

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

Il existe en fait deux types char, lun est sign et peut contenir des valeurs comprises entre -128 et +127; le second est non sign et ne contient donc que des valeurs positives soit les nombres compris entre 0 et 255. Ces deux types sont dfinis par les noms signed char et unsigned char.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

10

Le type char (sans plus de prcision) comme nous lavons utilis prcdemment est gnralement quivalent au type signed char, mais cela peut changer selon la machine hte. En effet, ce type a en quelque chose t dfini pour reprsenter tous les caractres disponibles sur un systme donn. Cette dfinition date un peu; cette poque la plupart des systmes ne dfinissaient que les caractres dont la valeur est comprise entre 0 et 127, cest notamment le cas du jeu de caractres ASCII primordial et du jeu de caractres EBCDIC (des IBM 370, par exemple), le problme de la reprsentation du type char (sign ou non sign) ne se posait alors pas. Aujourdhui tous les compilateurs permettent de dfinir explicitement un type caractre sign (grce signed char) et non sign (grce unsigned char), le type char restant le format natif du systme hte. Cette distinction permet, grce aux contrles des types effectus par le compilateur de traiter ces types distinctement et ceci peut savrer trs utile. Illustrons ce point par un exemple: les chanes de caractres en C sont des tableaux de char. Ces chanes sont termines par un caractre de valeur nulle (ou encore de valeur ASCII zro, on parlera de chane ASCIIZ); imaginons un programme qui aurait besoin de stocker et manipuler de s chanes qui, comme en Pascal, contiennent la longueur de la chane comme premier caractre, nous pouvons reprsenter de telles chanes comme un tableau de unsigned char; grce cela le compilateur fera bien la diffrence entre ces deux sortes de chanes et il nous avertira dune ventuelle erreur si nous utilisons une chane ASCIIZ l o une chane Pascal est attendue et vice versa. La distinction entre une variable signe et non signe ninfluence pas le stockage de la valeur de cette variable, ainsi si nous codons :
signed char unsigned char lettreSigne = 'a'; lettreNonSigne = 'a';

alors ces deux variables contiennent, du point de vue binaire, exactement les mmes donnes; par contre larithmtique applique ces variables sera diffrente; on distinguera ainsi une arithmtique signe et une autre non signe; expliquons cela : Soit la dfinition:
signed char unsigned char lettreSigne = 127; lettreNonSigne = 127;

si maintenant nous ralisons :


lettreSigne lettreNonSigne = lettreSigne + 1; = lettreNonSigne + 1;

alors la variable lettreSigne est maintenant interprte par une opration arithmtique comme -128, tandis que la variable lettreNonSigne serait interprte comme 128. Voici un autre exemple, nous dfinissons :
signed char unsigned char lettreSigne = 0; lettreNonSigne = 0;

si maintenant nous ralisons :


lettreSigne lettreNonSigne = lettreSigne - 1; = lettreNonSigne - 1;
11

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

alors la variable lettreSigne est interprte comme -1, tandis que lettreNonSigne est interprte comme +255. Notez enfin que le type char est bien un type distinct de signed char et de unsigned char; cette rgle ne sapplique pas aux types entiers que nous allons dtailler et pour lesquels si la spcification signed ou unsigned est omise, le type est considr comme signed.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

12

Les types entiers


Les types entiers sont tous bass sur le type fondamental int; cependant le codage de ce type dpend de la machine et du compilateur utilis. Aussi, et bien que lemploi du mot cl int soit suffisante pour dclarer une variable entire, nous utiliserons les modificateurs de types short et long pour dfinir le format des nombres entiers dune manire qui ne dpende plus de lenvironnement

Les entiers courts


Le modificateur short, utilis en association avec int, permet de dfinir un entier cod sur 16 bits ou deux octets. Nous pouvons ici aussi utiliser en plus les modificateurs signed et unsigned, ceci nous conduit aux types suivants : signed short int unsigned short int nombres compris entre -32 768 et +32767 nombres compris entre 0 et 65 535

pour simplifier lcriture le symbole int peut tre omis; ainsi les deux dfinitions de types prcdentes sont quivalentes signed short et unsigned short; de plus, comme nous venons de le voir avec le type char, la reprsentation signed est implicite pour des entiers si rien nest prcis; nous utiliserons donc: short (nombre de -32 768 +32767) et unsigned short (nombre de 0 65 535). Voici des dfinitions valides de variables entires :
short unEntier; signed short unNombre; /* quivalent short unNombre signed short int unChiffre; /* quivalent short unChiffre */ unsigned short unEntierPositif; */

Les entiers longs


Le modificateur long, utilis en association avec int, permet de dfinir un entier cod sur 32 bits ou quatre octets, cet entier peut galement tre sign ou non, soit les types : signed long int unsigned long int nombres compris entre -2 147 483 648 et 2 147 483 647 nombres compris entre 0 et 4 294 967 295

Comme prcdemment, les mots cl int et signed sont optionnels, nous utiliserons les dfinitions de type long et unsigned long. Voici des dfinitions valides de variables entires longues :
long signed long signed long int unsigned long unGrandEntier; unGrandNombre; /* quivalent long unGrandNombre unGrandChiffre; /* quivalent long unGrandChiffre unGrandEntierPositif; */ */

Le modificateur long peut tre appliqu deux fois au type int, ainsi nous dfinissons un type entier cod sur 64 bits ou 8 octets, soit les types : signed long long int unsigned long long int

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

13

que nous utiliserons comme long long et unsigned long long; le type long long accepte des valeurs comprises entre -9 223 372 036 854 775 808 et 9 223 372 036 854 775 807; soit presque de quoi compter le nombre de particules prsentes dans tout lunivers.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

14

Les types flottants


Ces types servent reprsenter des nombres en virgule flottante, cest dire des rels. La prcision de telles variables (cest dire le nombre de chiffres significatifs) et leurs valeurs absolues minimale et maximale dpendent du processeur sur lequel tourne le code ainsi que, dans certains cas, du compilateur. Les types dfinis sont : float double long double flottant simple prcision flottant double prcision flottant quadruple prcision

Le type float correspond gnralement la taille des registres du processeur, le type double est deux fois plus large (ncessite deux fois plus doctets) et le type long double quatre fois plus. Ainsi sur les processeurs Intel rcents, le type float est cod sur 4 octets et permet de stocker des nombre dont la valeur absolue est comprise entre 1.403e-45 et -3.403e+38. Le type double est lui cod sur 8 octets et reprsente une valeur absolue comprise entre 4.941e-324 et 1.798e+308. Par contre, sur un Cray utilisant des processeurs 64 bits le type float est stock sur 8 octets (et correspond donc au type double utilisable avec un processeur Intel) tandis que le type double est cod sur 16 octets. Enfin le type long double est totalement dpendant du compilateur (car certain ne le dfinisse pas) et du processeur utilis (il peut ainsi tre cod sur 10, 16 ou 32 octets). Notez que les modificateurs signed et unsigned ne sont pas applicables aux types flottants; ceux-ci dfinissent toujours des variables ayant un signe.

Conversion de types
Les langages C et C++ supportent la modification de types (typecasting) des valeurs. Cette modification est implicite (automatique) entre les types fondamentaux que nous venons dtudier; ainsi le code suivant est valide :
float short char monReel monEntier maLettre = 97.85; = monReel; = monEntier; /* monEntier vaut 97 */ /* maLettre est 'a' */

cependant le compilateur risque dmettre des avertissements durant la compilation de ce code afin de nous avertir des risques de pertes de donnes. Pour viter ces messages et lorsque nous savons que le codage ralis est correct, nous pouvons raliser une conversion explicite (demande) des donnes, ainsi nous cririons:
float short char monReel monEntier maLettre = 97.85; = (short) monReel; = (char) monEntier;

avec une telle criture nous demandons une conversion du type de donnes de la variable vers le type prcis. La variable dorigine et son type demeure inchangs (monReel est toujours une variable de type float) mais lexpression rsultat que
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 15

reprsente son valuation (cest dire sa valeur) est convertie lors de la ralisation de laffectation. Rptons-le cest exactement ce que le compilateur a ralis lors dune conversion implicite mais maintenant la compilation est plus propre puisque quaucun avertissement ne sera affich pour ces lignes, ce qui nous aidera apporter plus dattention aux autres messages du compilateur.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

16

Le type Pointeur
Les variables de type pointeur permettent de stocker non plus une donne (comme une lettre, un entier ou un nombre rel) mais ladresse dune autre variable. Pour utiliser une telle variable, nous dfinirons donc sa valeur avec ladresse dune variable existante; pour cela nous utiliserons loprateur & (oprateur rfrence) qui appliqu une variable fournit son adresse; cest dire lendroit o la valeur de cette variable est stocke en mmoire. Si nous avons dfini :
short unEntier;

alors :
&unEntier

fournit ladresse de cette variable. Notez que loprateur & ne peut pas tre appliqu sur des expressions values. Par exemple &(unEntier + 1) est interdit, de mme que &32; en effet, dans ces expressions (unEntier + 1 ou le nombre 32) nexistent pas comme des emplacements mmoires dfinis, il est donc impossible den valuer ladresse. La variable pointeur elle -mme sera dfinie en utilisant le symbole * qui associ un type dfinit le pointeur sur ce type. Nous dirons en effet que cette variable pointe sur une donne, car contrairement une variable non -pointeur, elle ne contient pas directement la donne mais seulement ladresse o cette donne est stocke. Voici des dfinitions de variables pointeurs :
short* char* unsigned long* adresseDunEntier; adresseDunCaractere; adresseDunEntierPositif;

Notez que le fait de coller le symbole * droite du nom du type est arbitraire, en effet les dclarations suivantes sont rigoureusement identiques aux prcdentes :
short char unsigned long *adresseDunEntier; *adresseDunCaractere; *adresseDunEntierPositif;

Selon votre propre prfrence vous utiliserez la premire en explicitant littralement linstruction short* adresseDunEntier comme adresseDunEntier est une variable dont le type est short* cest dire pointeur sur un short; ou bien considrant la forme short *adresseDunEntier vous pourrez vous dire *adresseDunEntier est une variable pointeur qui pointe sur une variable de type short; les deux sont tout fait quivalents. Pour stocker ladresse de notre variable unEntier nous faisons donc :
short short* unEntier; adresseDunEntier;

adresseDunEntier = &unEntier;

Comme pour une variable non-pointeur, nous pouvons dclarer notre variable pointeur et fixer sa valeur en mme temps, soit :

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

17

short short*

unEntier; adresseDunEntier = &unEntier;

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

18

Pour lire la valeur pointe par une variable pointeur, nous utiliserons le signe * gauche du nom de la variable pointeur; cette opration sappelle drfrencer le pointeur et le symbole * est loprateur dadressage. Ainsi pour obtenir la valeur pointe par adresseDunEntier nous utilisons :
*adresseDunEntier

cette criture est lorigine de la prfrence souvent rencontre pour la seconde forme de dfinitions des pointeurs vue ci-dessus. Appliquons cela :
short short short* unEntier; unNombre; unPointeur; /* dclaration dun entier court */ /* un autre entier court /* un pointeur sur un entier court /* fixe la valeur de unEntier /* stocke ladresse de unEntier */ /* rcupre la donne pointe: 5 */ */ */ */

unEntier = 5; unPointeur = &unEntier; unNombre = *unPointeur;

Notez que ce code est quivalent :


short short unEntier; unNombre; /* dclaration dun entier court */ /* un autre entier court /* fixe la valeur de unEntier /* prend directement la valeur */ */ */

unEntier = 5; unNombre = unEntier;

Cet exemple montre un cas o il semble inutile dutiliser un pointeur, dans un vrai code son usage se rvlerait indispensable; imaginez par exemple quau lieu daffecter la variable unPointeur avec ladresse de unEntier nous devons raliser un test pour choisir parmi plusieurs variables celle dont nous stockerons ladresse; lutilisation dun pointeur permet alors dutiliser la donne de la variable choisie sans devoir raliser ce test chaque fois. De plus nous pouvons utiliser la variable pointeur pour modifier le contenu de la variable pointe, par exemple :
short short short etc... short* entier1; entier2; entier3; unPointeur; /* dclaration dun entier court */ /* un autre entier court /* un autre entier court /* un pointeur sur un entier court */ */ */

un test conduirait par exemple : unPointeur = &entier2; /* stocke ladresse de entier2 *unPointeur = 5; /* fixe la donne pointe 5 maintenant entier2 vaut 5 */

*/

Une autre mise en oeuvre incontournable des pointeurs est lutilisation de paramtres variants avec une fonction. Paramtre variant signifie que vous dsirez passer une variable une fonction et vous souhaitez que cette fonction puisse modifier le contenu de cette variable. Un exemple pourrait tre une fonction utilise lors dun tri; nous souhaitons crire une fonction qui reoit deux nombres a et b et le cas chant permute ces deux nombres afin que a soit infrieur ou gal b.
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 19

Si nous dfinissons simplement le type des paramtres transmis cette routine comme tant des types non pointeur, nous ne recevrons que la valeur de la variable et le traitement effectu sera perdu la sortie de la fonction. Ainsi le programme suivant ne marche pas !

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

20

Code 2 : Mauvais passage de paramtres variants


#include <stdio.h> void permute(short a, short b) { short temp; if (a > b){ temp = a; a = b; b = temp; } } void main(){ short x, y;

/* dclaration de deux entiers courts */

x = 5; y = 2; printf("%hd %hd\n", x, y); /* affiche les nombres */ permute(x, y); /* appel de notre fonction */ printf("%hd %hd\n", x, y); /* affiche les nombres */ }

A lexcution, nous obtenons deux fois laffichage de :


5 2

Remarque : ne vous effrayez pas de lexpression printf("%hd %hd\n", x, y). Cette commande permet dafficher les deux nombres x et y; nous tudierons cela plus loin. Pour obtenir, le rsultat souhait nous devons transmette notre fonction ladresse des variables afin que ce soit bien le contenu de ces variables qui soit chang. Pour cela notre routine dclare des paramtres de type short* et non plus short, nous sommes donc oblig de drfrencer ces variables afin de lire la valeur pointe et lors de lappel cette routine nous devons transmettre les adresses grce loprateur &. Voila la version corrige du programme : Code 3 : Bon passage de paramtres variants
#include <stdio.h> void permute(short* a, short* b) { short temp; if (*a > *b){ temp = *a; *a = *b; *b = temp; } } void main(){ short x, y;

/* dclaration de deux entiers courts */

x = 5; y = 2; printf("%hd %hd\n", x, y); /* affiche les nombres permute(&x, &y); /* appel de notre fonction */ printf("%hd %hd\n", x, y); /* affiche les nombres }

*/ */

A lexcution, nous obtenons laffichage suivant :


Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 21

5 2

2 5

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

22

Le type Tableau
Un tableau est une variable qui permet de stocker non plus une seule valeur mais un nombre fini et dtermin de valeurs. Il est possible de crer des tableaux dlments quelconques, par exemple des tableaux de caractres - nous y reviendrons en dtail plus loin - ou encore des tableaux dentiers ou de rels; nous pouvons galement dfinir des tableaux de variables pointeurs et mme des tableaux de fonctions, mais commenons par le dbut.

Les tableaux statiques


Un tableau statique est un tableau dont la taille (le nombre dlments) est fige et dclare dans le source. Un tel tableau est dfini en utilisant les symboles [ et ] aprs le nom de la variable et en faisant figurer le nombre dlments dsirs entre ces crochets; par exemple :
short tableau_d_entier[5];

dfinit une variable nomme tableau_d_entier qui est un tableau de 5 entiers de type short. Les lments du tableau sont numrots de 0 (taille du tableau - 1); par exemple nous pouvons raliser :
short tableau_d_entier[5]; tableau_d_entier[0] tableau_d_entier[1] tableau_d_entier[2] tableau_d_entier[3] tableau_d_entier[4] = = = = = 1; 3; 5; 3; 1;

Notez que nous pouvons dfinir les valeurs des lments du tableau lors de sa dclaration, pour cela nous placerons les diffrentes valeurs entre accolades en les sparant par des virgules; la dfinition suivante est identique au tableau prcdent :
short tableau_d_entier[5] = {1, 3, 5, 3, 1};

Nous pouvons galement dfinir des tableaux multi-dimensions en rptant la spcification de taille faites avec les crochets, par exemple voici une matrice 2*2 :
float matrice[2][2]; matrice[0][0] matrice[0][1] matrice[1][0] matrice[1][1] = = = = 1.0; 2.0; 3.0; 4.0;

cette variable reprsente en quelque sorte la matrice :

10 . 2.0 3.0 4.0

Notez quici aussi nous pouvons dfinir les valeurs des lments lors de la dclaration, nous imbriquerons simplement des accolades pour dfinir chacune des dimensions du tableau; ainsi la dfinition suivante est identique la matrice prcdente :
short matrice[2][2] = {{1.0, 2.0}, {3.0, 4.0}};

Il existe une forte similitude en C/C++ entre un type tableau et le type pointeur sur ce mme type. Ainsi tant dfini le tableau_d_entier ci -dessus nous pouvons raliser :
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 23

short* ptr_entier = tableau_d_entier;

ou encore, cest identique :


short* ptr_entier = &tableau_d_entier[0];

En effet une variable tableau contient ladresse du premier lment de ce tableau.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

24

Les tableaux dynamiques


Un tableau dynamique est un tableau dont la taille nest pas fige dans le source. La remarque prcdente concernant la similitude entre une variable tableau et un pointeur sur un type quivalent nous permet de dclarer aisment un tel tableau de taille inconnue, nous utiliserons simplement un pointeur et stockerons ladresse de ce tableau dans la variable pointeur. Pour illustrer cela, nous devons savoir que le fichier den-tte (ou header) stdlib.h dclare la fonction malloc() avec le prototype (lAPI) suivant :
void* malloc(size_t size);

cela signifie quelle attend un paramtre de type size_t (qui est galement dfini dans ce fichier stdlib.h comme lquivalent de unsigned long int) qui indique le nombre doctets allouer et quelle renvoie un pointeur void*. Nous connaissions le type void que signifie void* ? Littralement cest un pointeur sur rien du tout, dans la pratique cest le contraire : cela dcrit un pointeur sur nimporte quelle type de donne, un pointeur universel en quelque sorte. Il est important de noter que le paramtre taille transmis malloc() (la variable nomme size dans le prototype) est une taille en octets. Ainsi si nous ne souhaitons pas un tableau doctets - de char - nous devrons prciser le nombre dlments multipli par la taille dun lment. Pour cela nous pouvons utiliser loprateur sizeof() - ce nest pas une fonction car le rsultat est valu la compilation - qui fournit la taille du type ou de la variable utilis comme paramtre. Par exemple, lexpression sizeof(short) est remplace par la valeur 2 durant la phase de compilation sur machine Intel. Enfin, pour stocker le rsultat (de type void*) de la fonction malloc() dans notre variable, nous devons raliser une conversion explicite de void* vers le type qui nous intresse. Voyons comment raliser tout cela : Code 4 : Cration dun tableau de taille quelconque
#include <stdio.h> #include <stdlib.h> void main(){ short i; short taille; long* tableau; taille = 5; /* /* pour la fonction printf() */ /* pour la fonction malloc() */

/* une variable utilise pour une boucle */ /* mmorise la taille du tableau */ /* pointeur pour stocker ladresse du tableau /* saisi par l'utilisateur dans un vrai code

*/ */

allocation (dynamique) de notre tableau */ tableau = (long*) malloc(taille * sizeof(long)); remplissage du tableau, on utilise les indices 0 (taille -1) */ for (i = 0; i < taille; i++) tableau[i] = i; affichage du contenu du tableau */ for (i = 0; i < taille; i++) printf("%lu\n", tableau[i]); destruction du tableau : libration de la mmoire rserve */ free(tableau);
25

/*

/*

/*

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

Notez que tout bloc de mmoire allou avec malloc() doit tre libr par un appel free(). Le prototype de free() est void free(void* ptr); ici il nest pas besoin de convertir explicitement notre tableau de type long* en void*. En effet, un type pointeur gnrique peut recevoir nimporte quel pointeur puisque cette conversion perd de linformation.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

26

Les tableaux de caractres


Dans notre premier programme nous avons utilis la fonction printf() avec une chane de caractres; notez quen C/C++ il nexiste pas de type chane de caractres quivalent au type String du Pascal; ce type est obtenu en utilisant un tableau de caractres. Pour dclarer une chane statique, nous utiliserons donc un tableau - dont la taille sera ou non prcise. Si elle ne lest pas le compilateur rservera un tableau pour y stocker toute la chane, si par contre une taille N est prcis le compilateur ne retiendra que les N premiers caractres de cette chane; exemple :
char chaine1[] = "hello"; char chaine2[5]= "bonjour"; /* dclaration correcte */ /* correct mais chane2 contient "bonjo" */

Que font ces instructions ? Lorsque nous dclarons char chaine1[] = "hello", le compilateur rserve dans une zone mmoire (gnralement dans le segment de donnes) six octets et il y range notre chane en y ajoutant un caractre ascii zro marquant la fin de cette chane : h e l l o 0

ensuite il fixe la valeur de chaine1 avec ladresse du premier caractre de la chane. Notez que la dclaration :
char* chaine1 = "hello";

est compltement quivalente la dfinition de chaine1 prcdente. Except cet ajout automatique dun caractre ascii zro de terminaison de chanes, les tableaux de caractres sont semblables aux tableaux dautres types. Nous pourrons par exemple crer des chanes dynamiquement en utilisant malloc(); nous devrons cependant dans ce cas tre vigilant ajouter nous mme le caractre zro lors de la construction de nos chanes.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

27

Les oprateurs
Les oprateurs sont nombreux en C et C++, ceci est d au fait que certaines oprations ralises par des fonctions dans dautres langages sont faites ici par des oprateurs et galement au fait que le C et C++ permettent des formes dcritures doprations impossibles dans dautres langages.

Les oprateurs mathmatiques


les oprateurs disponibles sont : Oprateur Syntaxe + * / % a+b a-b a*b a/b a%b -a Description calcule la somme de a et b calcule la diffrence de a moins b calcule la multiplication de a par b calcule la division de a par b calcule le reste de la division entire de a par b value loppos de a

Notez que le rsultat dune opration numrique dpend du type de la variable rsultat, en effet si ce nombre dpasse lintervalle de dfinition du type de la variable rsultat il sera tronqu, exemple:
unsigned short unsigned short a = 160, b = 500; c = a * b; /* affiche 14464 soit 80000 - 65536 */

printf("%hu\n", c);

cette opration aurait d tre code :


unsigned short unsigned long a = 160, b = 500; c = a * b; /* affiche 80000 */

printf("%u\n", c);

De plus lutilisation de nombres signs ou non signs peut galement produire des rsultats diffrents, par exemple :
signed short signed short unsigned short a = 80, b = 500; c = a * b; d = a * b; /* /* affiche -25536 affiche 40000 */ */

printf("%hd\n", c); printf("%hu\n", d);

Dans cet exemple les variables c et d contiennent en fait exactement la mme valeur, cest linterprtation que lon en fait qui provoque la diffrence des rsultats. Enfin, le rsultats dpend du type des variables oprandes, ainsi le code :
short float a = 80, b = 500; c = b / a; /* affiche 6 au lieu de 6.25 */
28

printf("%g\n", c);
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

naffiche pas le (vrai) rsultat de la division (non entire) de b par a; en effet a et b tant entiers, une division entire est ralise.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

29

Pour obtenir le rsultat correct il faut, soit introduire un nombre flottant dans les arguments de la division, exemple :
short float a = 80, b = 500; c = 1.0 * b / a; /* affiche 6.25 */

printf("%g\n", c);

soit retyper une des variables signed short en float, ce changement de type est ralis en indiquant avant la variable altrer un nom de type entre parenthses :
short float a = 80, b = 500; c = (float) b / a; /* affiche 6.25 */

printf("%g\n", c);

Les oprateurs logiques et relationnels


Les oprateurs suivant produisent tous un rsultat logique vrai (gnralement cod 1) ou faux (cod 0). Oprateur Syntaxe == != && || ! > >= <= < a == b a != b a && b a || b !a a>b a >= b a <= b a<b Description value a gal b value a diffrent de b effectue un ET logique entre a et b effectue un OU logique entre a et b value la ngation logique de a value a plus grand que b value a plus grand ou gal b value a plus petit ou gal b value a plus petit que b

On notera que les oprateurs relationnels ( >, >=, <= et < ) ont une priorit suprieure aux oprateurs logiques ( ==, !=, && et || ), ainsi :
(a > b && c < d)

est quivalent :
( (a > b) && (c < d) )

De plus tous les oprateurs ont une priorit infrieure aux oprateurs mathmatiques, ainsi :
(a < b - 1 && c % d)

est quivalent, puisque quun test logique est vrai pour toutes valeurs diffrentes de zro, :
( ( a < (b - 1) ) && ((c % d) != 0) )

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

30

Loprateur unaire de ngation logique ! appliqu une valeur logique vrai ou faux renvoie son contraire; appliqu une valeur numrique ou une adresse non nulle il renvoie 0, enfin appliqu un oprande nul il renvoie 1; cet oprateur est couramment utilis dans des tests :
long* a_ptr; .... a_ptr = (long*) malloc( 10 * sizeof(long) ); if (!a_ptr) { /* lallocation a choue */ /* a_ptr est un pointeur nul */ }

short c; .... if (!c){ .... }

/*

quivalent : if (c == 0)

*/

Les oprateurs dincrmentation et dcrmentation


Loprateur ++ est loprateur dincrmentation: il ajoute 1 la variable sur lequel il est appliqu. L'oprateur -- est loprateur de dcrmentation: il diminue de 1 la valeur de la variable sur lequel il est appliqu. Ces deux oprateurs produisent deux rsultats diffrents selon quils sont placs gauche ou droite dune expression. Placs gauche, ils agissent avant que la variable ne soit utilise dans linstruction qui la contient; placs droite, ils agiront aprs que la variable ait t utilise. Illustrons ceci :
short a = 5; a++; short b = a++; /* dclare a avec la valeur 5 /* fixe la valeur de a 6 /* fixe b a soit 6 /* PUIS incrmente a (a gal 7) */ /* incrmente a (soit 8) /* PUIS affecte c (c gal 8) */ */ */ */ */

short c = ++a;

La position de loprateur (avant ou aprs loprande) induit donc des effets de bord (ou effets rebond), ces effets peuvent tre trs sensibles et produiront un rsultat imprvisible lorsque loprande apparat plusieurs fois dans lexpression; par exemple linstruction suivante cherche remplir llment dindice i dun tableau avec cette valeur de i :
short a[10]; .... short i = 3; a[ i ] = i++; /* un tableau de 10 entiers courts */

/* qu'avons-nous ralis ??? */

aprs cette dernire instruction, i vaut 4 mais a ton fix a[ 3 ] ou a[ 4 ] ? En fait cela dpend du compilateur et la seule rponse certaine est quil ne faut pas abuser des oprateurs ou pour le moins quune telle expression ne doit jamais tre code.
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 31

Les oprateurs binaires


Ces oprateurs oprent sur la reprsentation binaire des nombres, ils ne peuvent donc tre utiliss quavec des oprandes de type entiers. Oprateur Syntaxe & | ^ << >> ~ a&b a|b a^b a << b a >> b ~a Description effectue un ET bit bit effectue un OU inclusif bit bit effectue un OU exclusif bit bit (appel aussi XOR) effectue un dcalage gauche de b bits sur le nombre a effectue un dcalage droite de b bits sur le nombre a effectue un complment un (ngation binaire)

Les oprateurs de dcalage de bits seront utiliss avec prudence sur les types signs, en effet lors dun dcalage droite les bits dcals seront remplis par des zros pour un oprande non sign alors quun nombre sign sera complt de bits de signe (dcalage arithmtique) ou par des zros (dcalage logique) en fonction de la machine utilise et / ou du compilateur. Rappel pour les personnes non rompues aux oprations binaires par une longue pratique dassembleur: un dcalage de bits gauche revient multiplier le nombre par une pu issance de 2; formellement a << b est quivalent a * 2^b; ainsi 5 << 2 est identique (5 * 2^2 = 5 * 4); cette criture apporte une vitesse de traitement optimale : le dcalage est ralis par le microprocesseur beaucoup plus rapidement quune multiplication nest ralis par lunit de traitement des entiers; cependant si nous ntes pas habitu ces techniques, laissez le compilateur faire son uvre : la plupart savent reconnatre les expressions pouvant bnficier dun dcalage de bits en lieu et place dune multiplication, ainsi le code :
a = b * 80

sera souvent cod :


a = (b * 64) + (b * 16)

soit :
a = (b << 6) + (b << 4)

Note : rappelez-vous quen assembleur plus le code est long, plus a va vite... Rappel des tables vrits des oprations binaires : a
0 0 1 1

~a
1 1 0 0

b
0 1 0 1

a&b
0 0 0 1

a|b
0 1 1 1

a^b
0 1 1 0

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

32

Les oprateurs contracts


La plupart des oprations de la forme a = a oprateur b peuvent tre crites de manire plus concise sous la forme a oprateur= b. Ainsi:
a a a a = = = = a a a a + * & b 5 (b + 2) 0x0F

peut scrire peut scrire peut scrire peut scrire

a a a a

+= += *= &=

b 5 b + 2 0x0F

Remarque : il nexiste pas de forme contracte pour les oprateurs << et >>, ni, videmment, pour les oprateurs unaires.

Les piges viter


Lors de votre apprentissage du langage C et C++ vous risquez de rencontrer des bugs (incomprhensibles) ds des inversions entre diffrents oprateurs ressemblants. Le premier pige viter, surtout si vous avez lexprience du Pascal, est la confusion entre les oprateurs de tests (==) et daffectation (=), ainsi si vous crivez
a == 5;

au lieu de
a = 5;

le compilateur ne dtectera aucune erreur (sauf pour une expression comme short a == 5; ) et votre initialisation se rsumera un test (dont le rsultat est ignor). Cette erreur est rarement commise, par contre linverse peut se produire, par exemple lors dun test :
if (a = 2) ... /* possible erreur de codage */

ici, non seulement le test sera toujours vrifi pour des valeurs diffrentes de zro, mais en plus vous altrez le contenu de la variable; certains compilateurs mettent un avertissement (affectation non dsire possible) pour ce type dcriture, dautres ne vous diront rien. Une solution pour contourner ce problme est dcrire, quand cela est possible, la constante gauche du signe, ainsi :
if (2 = a) ... /* possible erreur de codage MAIS erreur de syntaxe */

provoquera toujours une erreur de compilation; cependant vous comparerez souvent deux variables entre elles et dans ce cas cette astuce est inutilisable; dans dautres cas vous voulez prcisment raliser cette affectation et tester si la valeur transmise est non nulle, cest le cas notamment lorsque vous stockez le rsultat dune fonction retournant un code derreur, on crirait alors :
if ( err = maFonction() ) ...

ceci est valide et pour le coup on sera peut tre agac par les avertissement mis par le compilateur, la meilleure faon dcrire une telle instruction serait :
if ( ( err = maFonction() ) == noErr ) ... /* o noErr est une constante */
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 33

Lautre erreur souvent commise et linversion entre les oprateurs ET et OU logiques versus binaires, ainsi:
short a = 5, b = 3; if (a & 4 && b & 1) ...

est valu comme if ( 4 et 1 ) soit if ( vrai et vrai ), donc le test est vrifi. Par contre :
short a = 5, b = 3; if (a && 4 & b && 1) ...

est valu comme if ( 5 et (4 et binaire b) et 1 ) soit if ( 5 et 0 et 1), donc le test nest pas vrifi.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

34

Les instructions de tests Linstruction if


Linstruction de test if ... else ... sert adapter un traitement toutes sortes de conditions pouvant se ramener une expression vrai ou faux; notez que les mots cls if et else doivent tre saisis en minuscule, comme tous les mots cls. La syntaxe de linstruction if est :
if ( condition ) instruction ; else instruction ;

Remarque : ?? la condition test est toujours place entre parenthses. ?? la clause else et le bloc dinstruction li sont optionnels. ?? toute instruction se termine par un point virgule, y compris celle prcdant linstruction else (contrairement la syntaxe du Pascal). Nous supposons dans les exemples suivants que A, B et C dsignent des valeurs boolennes. Voici un premier exemple de test simple :
if ( A ) printf("A est vrai\n"); else printf("A est faux\n");

Les blocs dinstructions excuts si le test est vrifi ou non peuvent tre constitus de plusieurs instructions, nous utiliserons alors des accolades pour dlimiter ces blocs; exemple :
if ( A ) { printf("A\n"); printf("est vrai\n"); } else { printf("A\n"); printf("est faux\n"); }

Souvent nous aurons dterminer le traitement raliser parmi plus de deux choix, pour cela nous pouvons enchaner les tests if ... else ; exemple :
if ( A ) printf("A est vrai\n"); else if ( B ) printf("B est vrai\n"); else printf("A et B sont faux\n");

Lorsque nous enchanons plusieurs tests, il est important dtre vigilant sur la porte de la clause else , considrons par exemple le bloc suivant :

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

35

if ( A ) if ( B ) printf("A et B sont vrais\n"); else if ( C ) printf("C est vrai, A est-il vraiment faux ?\n"); else printf("A et C seraient faux\n");

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

36

Les propositions faites par ce code sont fausses; en effet le test else if ( C ) sapplique si B est faux et non si cest A qui est faux. Correctement indent code ci -dessus est en fait quivalent :
if ( A ) { if ( B ) printf("A et B sont vrais\n"); else if ( C ) printf("C est vrai, A est-il vraiment faux ?\n"); else printf("A et C seraient faux\n"); }

avec cette criture nous voyons clairement que le test est mal cod, nous devons crire:
if ( A ) { if ( B ) printf("A et B sont vrais\n"); } else if ( C ) printf("C est vrai, A est vraiment faux !\n"); else printf("A et C sont faux\n");

ce codage est meilleur et nous permet de voir que le cas A vrai, B faux a t oubli de lanalyse; la bonne implmentation sera donc :
if ( A ) { if ( B ) printf("A et B sont vrais\n"); else printf("A est vrai mais B est faux\n"); } else if ( C ) printf("C est vrai, A est faux\n"); else printf("A et C sont faux\n");

Lexpression condition
La condition applique linstruction if peut tre une expression plus complique quune simple valeur boolenne mais doit pouvoir tre value comme une valeur boolenne; notons que les compilateurs C/C++ considrent comme faux la valeur zro et comme vrai toute autre valeur, ce qui signifie que mis part le rsultat dune fonction de type void, tout peut tre interprt comme une valeur bo olenne; par exemple le code suivant affichera vrai (cest un exemple extrme : qui coderait cela ?).
if ( "une chane" ) printf("vrai\n"); else printf("faux\n");

Les exemples suivants illustrent un peu plus ce principe :


short n; ....... if ( n )
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 37

printf("n est non nul\n"); else printf("n est nul\n");

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

38

est quivalent :
short n; ....... if ( n != 0 ) printf("n est non nul\n"); else printf("n est nul\n");

Des conditions avec et et ou


La condition test peut galement tre constitue de plusieurs conditions associes par des critres et logique et ou logique. Loprateur et logique scrit &&; loprateur ou logique scrit || ([Alt Gr] + 6); exemple :
if ( A && B ) printf("A et B sont vrais\n"); else if ( A || C ) printf("A ou C ou les deux sont vrais\n"); else printf("A et C sont faux, B est peut tre vrai\n");

Loprateur ternaire ?:
On parle doprateur ternaire car celui-ci ncessite trois arguments, contrairement aux oprateurs binaires qui prennent deux arguments (exemple loprateur de test ==, on crit: arg1 == arg2) et aux oprateurs unaires qui nutilisent quun argument (exemple loprateur de ngation -, on crit: -arg). Cet oprateur scrit: (condition) ? (instruction_1) : (instruction_2) si la condition est vrifie le bloc instruction_1 est excut, sinon cest le bloc instruction_2 qui lest. Cet oprateur se prte bien aux affectations conditionnelles, par exemple nous voudrions crire :
short choix; if ( A ) choix = 1; else choix = 2;

ce code peut tre crit grce loprateur ? : :


short choix = ( A ) ? 1 : 2;

Pour des raisons de lisibilit, on vitera son emploi dans des blocs de codes trop compliqus. Retenez cependant que partout o nous utilisons une squence :
if ( condition ) { expression 1 } else {
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 39

expression 2 }

nous pouvons crire :


( condition ) ? { expression 1 } : { expression 2 }

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

40

Linstruction switch
Imaginons que nous souhaitons crire un bloc de tests portant de nombreuses conditions, avec des critres ou logique et des tests imbriqus, par exemple :
short X; ... if ( X == 1 ) [instruction A]; else if ( X == 2 || X == 3 ) [instruction B]; else if ( X == 4 || X == 5) { [instruction C]; if ( X == 4 ) [instruction D]; } else [instruction E];

pour simplifier lcriture de tel bloc nous utiliserons linstruction switch ( ... ) { ... } associe plusieurs tiquettes case. Nous utilisons comme argument de la clause switch lexpression dont nous aurions teste la valeur; chaque valeur-test fait lobjet dune tiquette case suivie dun bloc dinstructions et du mot cl break ds que le traitement raliser pour cette valeurtest est effectu; enfin ltiquette default permet de grer tous les cas nayant pas t traits. Cela conduit :
short X; ... switch ( X ) { case 1: [instruction A]; break; case 2: case 3: [instruction B]; break; case 4: [instruction C]; /* et continue avec l'instruction suivante */ case 5: [instruction D]; break; default: [instruction E]; } /* linstruction break nous mne ici */

Explications : ?? notez la prsence dun : (deux points) aprs la valeur des tiquettes case ?? ltiquette case 1: intercepte le cas o X est gal la valeur 1, le bloc [instruction A] est alors excut puis linstruction break nous fait sortir du bloc switch . Cette
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 41

instruction break est indispensable, sans elle lexcution continuerait linairement avec le bloc [instruction B]. ?? ltiquette case 2: atteinte si X est gal 2 ne ralise aucune opration si ce nest de poursuivre avec les instructions suivantes. ?? ltiquette case 3: est atteinte si X est gal 3 mais galement pour X gal 2 puisqu ltiquette prcdente nous navons pas effectu de break. ?? ltiquette case 4: provoque lexcution de l [instruction C]; nous ne faisons pas suivre ce bloc dinstruction du mot cl break, lexcution se poursuit donc avec le bloc [instruction D]. ?? ltiquette case 5: est atteinte directement si X est gal 5 et par poursuite du traitement si X tait gal 4; nous ralisons lexcution de l [instruction D] et nous quittons le bloc switch. ?? enfin, tous les cas non traits (cest dire dans tous les cas o X est plus petit que 1 ou plus grand que 5) nous effectuons le bloc [instruction E]. Remarques : ?? ltiquette default est optionnelle. ?? une tiquette ne peut pas dfinir un intervalle de valeur, par exemple case 1..3: est interdit; on crira la place plusieurs case comme pour les valeurs 2 et 3 dans lexemple ci -dessus. ?? la valeur dune tiquette doit pouvoir tre value au moment de la compilation et tre une valeur entire. Cest donc une valeur immdiate (comme une constante), une opration sur des constantes (exemple: maConstante + 2), mais cela ne peut pas tre le rsultat dune fonction ou tout autre expression value lexcution uniquement. ?? les valeurs numriques des tiquettes nexigent ni dtre ordonnes (de la plus petite valeur la plus grande) ni de former une srie discrte (1 puis 2 puis 3 ...).

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

42

Les instructions de boucles Linstruction while


Linstruction de boucle while permet de raliser un traitement tant quune condition boolenne est vraie. La syntaxe de linstruction while est :
while ( condition ) instruction ;

Remarque : la condition test est toujours place entre parenthses. Si le bloc instruction est compos de plusieurs instructions, on placera ces instructions entre les symboles de dbut et de fin de bloc: { et } soit :
while ( condition ) { instruction 1; instruction 2; ... }

Exemple de boucle : Code 5 : Boucle while(..) { ... }


#include <stdio.h> /* Saisie d'un nombre avec cho de ce nombre sur la console */

void main() { short nombre = 1; while ( nombre != 0 ) { printf("Tapez un nombre (0 pour sortir) : "); scanf("%hd", &nombre); printf("Votre nombre est %hd\n", nombre); } }

Le contenu de la boucle (affichage du message, saisie du nombre et raffichage du nombre) est rpt tant que lutilisateur ne rentre pas la valeur zro.

Linstruction break
Linstruction de sortie de boucle break permet de quitter la boucle en cours. Ceci peut tre utile si le traitement est irralisable ou si une erreur se produit dans la boucle. Notez cependant que linstruction break quitte la boucle immdiatement parente; mais en cas de boucle imbrique il reste dans la boucle prcdente. Rcrivons le code prcdent pour limiter le traitement des nombres infrieurs 100 : Code 6 : Boucle while(..) { ... } avec sortie par break
#include <stdio.h> void main() { short nombre = 1; while ( nombre != 0 ) { printf("Tapez un nombre (0 pour sortir) : ");
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 43

scanf("%hd", &nombre); if (nombre > 100) break; printf("Votre nombre est %hd\n", nombre); } }

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

44

Bien sr, nous aurions pu crire while ( nombre != 0 && nombre <= 100) ..., nous serions sortis de la boucle si nombre est plus grand que cent. Cependant linstruction printf("Votre nombre ... aurait t galement excut. Ici ce nest pas dramatique mais on peut facilement imaginer un code o le traitement naurait pas d tre effectu.

Linstruction do ... while( )


Un bloc dinstruction contrl par une instruction while peut ne jamais tre excut, en effet si la condition de test est (logiquement) fausse, lexcution se poursuit avec linstruction immdiatement suivante au bloc, par exemple :
#include <stdio.h> void main() { short nombre; ... nombre = 0;

/* un traitement met nombre zro */

... while ( nombre != 0 ) { printf("Tapez un nombre (0 pour sortir) : "); scanf("%hd", &nombre); printf("Votre nombre est %hd\n", nombre); } printf("Vous avez rat la boucle\n"); }

naffichera jamais rien dautre que simplement Vous avez rat la boucle. Linstruction do ... while ( ) permet de raliser des boucles dont le test de sortie est valu la fin; ceci implique que le corps de la boucle sera excut au moins une fois. La syntaxe de linstruction do ... while ( ) est :
do instruction ; while ( condition );

Ici aussi, on placera les instructions entre les symboles de dbut et de fin de bloc: { et } si plusieurs instructions doivent tre excutes, soit :
do { instruction 1; instruction 2; ... } while ( condition );

Reprenons notre exemple, il devient : Code 7 : Boucle do { ... } while(..)


#include <stdio.h> void main() { short nombre; /* linitialisation n'est plus ncessaire */ do { printf("Tapez un nombre (0 pour sortir) : "); scanf("%hd", &nombre); printf("Votre nombre est %hd\n", nombre); } while ( nombre != 0 ) }
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 45

ces boucles (avec contrle la fin) sont moins utilises, cependant il existera toujours un problme pour lequel le meilleur algorithme utilisera cette forme de boucle.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

46

Linstruction for
Linstruction de boucle for est peut tre la plus souple du langage C, on dit couramment que nimporte quel programme C peut se ramener une boucle for; la syntaxe de linstruction for est :
for ( initialisation ; test ; incrmentation ) instruction ;

Au nouveau, si plusieurs instructions doivent tre excutes chaque itration, on crira :


for ( initialisation ; test ; incrmentation ) { instruction 1; instruction 2; ...

Les trois expressions dinitialisation, de tests et dincrmentation sont facultatives, ainsi le bloc :
for ( ; ; ) { }

est un bloc dinstruction valide, vide et rpt linfini.

Lexpression dinitialisation
La premire instruction sert initialiser des variables, vous pouvez aussi dfinir une variable lors de cette initialisation (en C++ et sous certaines conditions en C), par exemple une boucle contrle par une variable compteur commencerait par :
for ( short compteur = 0 ; . . .

Remarque : dans le cas o la variable est dfinie dans linstruction for, celle-ci nest plus visible ds que lon sort de la boucle (son utilisation provoquera une erreur signale par le compilateur). De plus les dernires normes du langage C++ recommandent ou imposent lemploi dune variable locale comme variable de contrle de linstruction for et la visibilit de cette variable est rduite cette boucle; ainsi dans :
for } for } ( short compteur = 0 ; . . .) { quelque chose; ( short compteur = 0 ; . . .) { autre chose

lidentifi cateur compteur dcrit bien deux variables diffrentes, chacune ntant utilisable que dans son instruction for; cependant encore de nombreux compilateurs ignorent cette norme et signaleraient, par un avertissement ou une erreur, que lidentificateur compteur est dj dclar lors de la compilation de la seconde boucle.

Lexpression de test
La condition de test est vrifie au dbut de lexcution du bloc, comme ctait le cas pour linstruction while, ainsi si notre boucle commence par :
for ( short compteur = 0 ; compteur < 0 ; . . .

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

47

le corps de la boucle ne sera jamais excut car compteur (initialis zro) ne vrifie pas la condition test (zro nest pas strictement infrieur zro). De plus le test peut tre complexe et mettre en jeu des oprateurs logiques, par exemple nous pouvons dfinir :
for ( short compteur = 0 ; compteur < ValeurMax && variableLogique ; . .

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

48

Lexpression dincrmentation
La troisime partie de linstruction for est gnralement utilise pour incrmenter ou dcrmenter la variable de contrle, ainsi pour raliser cinq itrations nous codons:
for } ( short compteur = 0 ; compteur < 5 ; compteur++ ) { printf("%hd\n", compteur);

ce code affichera: 0, 1, 2, 3, 4 et 5. Nous pouvons galement faire varier le compteur avec un pas ngatif, ainsi pour aller de 10 2 par pas de -2 nous codons :
for } ( short compteur = 10 ; compteur >= 2 ; compteur -= 2 ) { printf("%hd\n", compteur);

ce code affichera: 10, 8, 6, 4 et 2. Cette expression dincrmentation peut en fait contenir toute expression devant tre excute la fin de la boucle, avant que litration suivante ne soit ralise. Remarque : il nest pas possible dinsrer un bloc dinstruction dlimit par les signes { et } comme expression dinitialisation, de test ou dincrmentation dune boucle for. Il est nanmoins possible de combiner plusieurs tests dans lexpression de test en utilisant les oprateurs logiques (et logique et ou logique). De mme, lexpression dincrmentation peut tre constitue de plusieurs instructions, il suffit pour cela de sparer les diffrentes instructions par des virgules la place du point-virgule habituel; cela dfinira alors une seule instruction valide. Voila un exemple avec notre programme de saisie de nombre Code 8 : Boucle for
#include <stdio.h> void main() { short pairs = 0; short impairs = 0; short compteur, nombre; /* Saisie de 10 nombres maximum */ /* avec dcompte des nombres pairs et impairs */ for ( compteur = 0; compteur < 10 && nombre != 0; compteur++ , ((nombre % 2) == 0) ? pairs++ : impairs++ ) { printf( "Tapez votre %hd%s nombre (0 pour sortir) : ", compteur, (compteur == 0) ? "ier" : "ime"); scanf("%hd", &nombre); printf("Votre nombre est %hd\n", nombre); } printf("Vous avez tap %hd nombre(s) pair(s)", pairs); printf(" et %hd nombre(s) impair(s)\n", impairs); }

Dans cet exemple, lexpression test combine deux vrifications nombre infrieur 10 et nombre diffrent de zro grce loprateur et logique not &&.
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 49

De mme, lexpre ssion dincrmentation incrmente compteur (compteur++) et, selon le rsultat du test nombre % 2 qui calcule nombre modulo 2 et donc retourne 0 si nombre est pair et 1 sil est impair, incrmente pairs ou impairs.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

50

Revenons notre exemple dorigine, avec une boucle for nous crivons :
void main() { short nombre = 1 for ( ; nombre != 0 ; ) { printf("Tapez votre nombre (0 pour sortir) : "); scanf("%hd", &nombre); printf("Votre nombre est %hd\n", nombre); } }

ici nous navons aucune opration dinitialisation ou dincrmentation raliser, seule lexpression de test est alors dfinie. Pour illustrer la polyvalence de la boucle for, remarquons que lexpression :
for ( expression 1 ; .expression 2 ; expression 3 ) { instructions; ...

est quivalente lexpression :


expression 1; while ( expression 2 ) { instructions; ... expression 3; }

Linstruction continue
Nous avons vu tout lheure que linstruction break quittait la boucle en cours dexcution; si en fait nous souhaitions d e pas appliquer un traitement pour certaines valeurs mais autoriser la poursuite de la boucle, nous pouvons utiliser linstruction continue pour forcer valuer litration suivante sans quitter la boucle (dans lexemple prsent ici, nous pouvions certes utiliser un test), exemple : Code 9 : Boucle for avec break et continue
#include <stdio.h> /* Saisie de 10 nombres maximum avec cho de ce nombre sur la console */ /* si le nombre saisi est suprieur 10, il nest pas affich */ /* si le nombre saisi est suprieur 100, la boucle est interrompue */ void main() { short compteur; short nombre = 1; for ( compteur = 0; compteur < 10 ; compteur++ ) { printf("Tapez votre nombre (0 pour sortir) : "); scanf("%hd", &nombre); if (nombre > 100) break; /* finit le programme pour une valeur > 100 */ if (nombre > 10) continue; /* finit cette itration pour une valeur > 10 */ printf("Votre nombre est %hd\n", nombre); /* linstruction "continue" nous mne ici, compteur++ va tre excut, le test compteur < 10 sera valu et le cas chant
51

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

/* }

litration suivante sera excute. } linstruction "break" nous mne ici */

*/

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

52

Les fonctions
Les fonctions sont omniprsentes en C; elles prsentent le moyen de diviser des taches complexes en oprations plus simples et, avec le temps, les bibliothques de fonctions que vous aurez crites constitueront votre capital-codage qui vous permettra de construire de nouveaux programmes sans passer votre temps tout reconstruire chaque fois. Pour grer efficacement vos bibliothques de fonctions, nous vous conseillons de maintenir des fichiers pas trop volumineux o les fonctions sont regroupes par thme. Pour construire une bibliothque, vous devrez crer deux fichiers: 1. Un fichier den-tte (avec lextension .h) sera utilis pour stocker les dclarations ou prototypes - des fonctions. 2. Un fichier de dfinition (ayant le mme nom mais utilisant lextension .c sil sagit de C pur et .cpp sil sagit de C++) contiendra limplmentation de ces fonctions. Cette organisation assurera, vos bibliothques, une compatibilit maximale avec le plus grand nombre de compilateurs.

Dclaration
La dclaration gnrique dune fonction est :
type_resultat nom_de_fonction ( type_parametre [nom_parametre] );

1. le type_resultat est un type fondamental, un type utilisateur ou un type pointeur sur lun de ces types. Sil sagit dun type utilisateur il doit tre connu - cest dire avoir t dclar - au moment de la dfinition de la fonction. La valeur void prcise que la fonction ne renvoie rien (cest alors une procdure au sens pascalien du terme); ce type_resultat dfinit le type de la fonction. 2. le type des paramtres reus est quelconque mais doit galement tre connu au moment de la dfinition; on ne peut pas transmettre void mais void* est valide et dcrit un pointeur universel. 3. le nom des paramtres est facultatif dans la dclaration de la fonction : vous pouvez vous contenter de fournir uniquement leur type. De plus le nom que vous utiliserez votre dcrire ce paramtre dans la dfinition de la fonction (son codage) peut tre diffrent du nom que vous utiliseriez ici. Une bonne rgle est de choisir un nom dcrivant au mieux ce que reprsente ce paramtre. Le fichier de dclaration que vous allez crer sera inclus dans tous les fichiers de codages (les .c ou .cpp) qui auront besoin dappeler ces fonctions; pour viter quun fichier ne soit inclus plusieurs fois - par suite dinclusion en cascade de fichiers utilisant les mmes dfinitions - ce qui pourrait provoquer des erreurs de redfinition de symboles, nous utilisons une dfinition de constantes dcrivant ce fichier. Pour cela nous utiliserons #ifndef (if not defined) afin de tester si un symbole - une directive - est dfini, #define pour dfinir un symbole et #endif pour finir le #ifndef. Remarque: il existe aussi #ifdef pour tester lexistence dune directive.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

53

Grce ces instructions nommes pragma, nous pourrons en quelque sorte signer notre fichier de dfinition en dfinissant une constante dont lexistence ne dure que le temps de la compilation dun fichier, lhabitude veut que cette signature soit le nom du fichier en majuscules avec un double soulign au dbut et la fin de ce nom.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

54

Nous souhaitons dfinir une bibliothque de fonctions gomtriques, pour cela nous allons crer deux fichiers geometry.h et geometry.c; voici le fichier de dfinition :

Code 10 : Fichier de dclaration : geometry.h


/* Dclaration de la bibliothque gomtrique #ifndef __GEOMETRY__ #define __GEOMETRY__ /* Dfinition de constantes */ /* on utilise: #define symbole_a_dfinir valeur /* la ligne ne comporte pas de point-virgule #define #define CERCLE CARRE 1 2 */

*/ */

/* /* /* /*

Dfinition de types utilisateurs */ on utilise: typedef type_connu type_a_dfinir */ la ligne est complte par un point-virgule les types utilisateurs finissent souvent par _t

*/ */

typedef unsigned short int forme_t; /* forme_t est maintenant un synonyme de unsigned short int */ /* Dfinition de fonctions */ /* const signifie que le paramtre est constant */ float surface_cercle( const float rayon ); float surface_carre ( const float cote ); float surface_forme ( const float taille, forme_t forme ); #endif /* __GEOMETRIE__ */

Dfinition
La dfinition gnrique dune fonction est :
type_resultat nom_de_fonction ( type_parametre [nom_parametre] ) { dclarations de variables instructions [return <expression de type "type_resultat">] }

1. le prototype de la fonction doit tre identique celui dclar dans le fichier de dclaration. 2. le nom des variables paramtres est facultatif si vous nutilisez pas ce paramtre dans la routine, de plus ce nom (uniquement le nom) peut tre diffrent de celui utilis lors de la dfinition. 3. linstruction return quelque chose est obligatoire si la fonction nest pas de type void. Notre fichier de dfinitions va implmenter toutes nos fonctions, afin de connatre les dclarations faites dans le fichier .h nous inclurons celui -ci.
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 55

Nous pouvons galement inclure dautres fichiers de dfinitions si cela est ncessaire.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

56

Le fichier doit dfinir toutes les fonctions dclares, dans le cas contraire il demeurera parfaitement compilable mais si un programme utilise ce fichier il risque de ne pas pouvoir tre construit du fait du manque de nos fonctions; dans un tel cas, il est prfrable de ne pas inclure les fonctions non encore codes dans le fichier dclaration; elles y seront ajoutes lorsque leur codage sera ralis. Voici notre fichier dfinition pour notre librairie gomtrique :

Code 11 : Fichier de dfinition : geometry.c


/* Dclaration de la bibliothque gomtrique */

#ifndef __GEOMETRY__ #include "geometry.h" /* les fichiers utilisateurs sont prciss entre guillemets */ #endif #include <math.h> /* les fichiers de systme sont prciss entre symboles < et > */ /* Dfinition des fonctions non exportes */

/* calcul du carr d'un nombre */ inline float sqr(const float x) /* inline signifie que la fonction ne sera pas rellement appele mais que son code sera recopie l o la fonction est appele, cela acclre le traitement pour des petites fonctions */ { return ( x * x ); } /* Dfinition des fonctions publiques */ float surface_cercle( const float rayon ) { /* M_PI est dfini dans math.h */ return M_PI * sqr( rayon ); } float surface_carre( const float cote { return sqr( cote ); } )

float surface_forme( const float taille, forme_t forme ) { switch ( forme ){ case CERCLE: return surface_cercle( taille ); case CARRE: return surface_carre( taille ); default: return 0.0; } }

Ces deux fichiers dfinissent notre bibliothque. Le fichier geometry.h sera inclus par tout fichier ayant besoin de ses fonctions, tandis que geometry.c devra tre inclus dans la liste des fichiers du projet. Cest dire prsent sur la ligne de commande lors de
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 57

lappel au compilateur GNU ou insr dans votre projet si vous utilisez un gestionnaire de projets.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

58

Librairie
Nous pouvons simplifier un peu cela en construisant une vritable librairie. Pour cela, nous compilons (une fois pour toute - jusqu sa prochaine modification) le fichier geometry.c en utilisant loption -c de GNU gcc afin de demander seulement la compilation du fichier (et non la cration dun excutable) :
> gcc -c geometry.c -o geometry.o

ceci cr un fichier geometry.o; loption -o geometry utilise pour lancer gcc est optionnelle selon lenvironnement; nous convertissons maintenant ce fichier binaire au format objet en une librairie :
> ar -rc geometry.a geometry.o

Sur systme unix et Linux, vous devrez peut tre excuter en plus :
> ranlib geometry.a

Ceci a cr notre librairie, voyons comment lutiliser, pour cela nous crons un petit programme - ce sera un programme et non plus une bibliothque car il contiendra une fonction main - ce fichier doit inclure geometry.h et utiliser geometry.a durant sa construction. Voici le programme, enregistrez-le sous test-geo.c : Code 12 : Utilisation de la librairie : geometry.a
/* Utilisation de la bibliothque gomtrique #include <stdio.h> #include "geometry.h" void main() { float carre = surface_forme ( 10.0, CARRE ); float cercle = surface_forme ( 5.0, CERCLE ); float rapport= cercle / carre; printf( "un cercle inscrit dans un carre " "occupe %g %% de sa surface\n", rapport ); } */

Notez que la diffrenciation selon la casse permet de dfinir une variable carre qui est bien un symbole diffrent de la constante CARRE dfinie dans geometry.h. Nous compilons ce programme en incluant notre librairie dans la liste des fichiers :
> gcc test-geo.c geometry.a -o test-geo.exe

Nous pouvons alors lancer ce programme et nous obtenons :


> un cercle inscrit dans un carre occupe 0.7854 % de sa surface

Notez que notre programme aurait pu appeler directement les fonctions surface_cercle et surface_carre puisque celles-ci sont exportes par le fichier de dclaration. Par contre la fonction sqr, implmente dans geometry.c, mais non dfinie dans len-tte reste cache et ne peut tre utilise qu lintrieur de geometry.c.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

59

La fonction main
La fonction principale main est obligatoire pour crer un programme. Elle doit tre unique et ce nom (ainsi que la casse) est impos.

Code de retour
La valeur retourne par la fonction main est soit de type int soit de type void. En fait main retourne toujours une valeur car le systme qui a lanc votre programme attend cette valeur de compte rendu dexcution; si vous avez dclar void, la valeur retourne est imprvisible ou dpend de votre compilateur. Sous Dos vous pouvez tester la valeur retourne avec la variable Dos errorlevel, en voici un exemple : Code 13 : Valeur retourne par la fonction main
int main() { return 1; }

Enregistrez ce fichier sous testmain.c et compilez-le pour crer testmain.exe. Nous utilisons ensuite ce programme dans un fichier batch : Code 14 : Lecture du code retourn par la fonction main
@echo off testmain if errorlevel 2 goto option2 if errorlevel 1 goto option1 if errorlevel 0 goto option0 :option0 echo l'option 0 est atteinte goto fin :option1 echo l'option 1 est atteinte goto fin :option2 echo l'option 2 est atteinte goto fin :fin

Enregistrez ce fichier sous testIt.bat et lancez-le, nous obtenons :


> l'option 1 est atteinte

Cette fonctionnalit permet de faire des programmes de type menu ou de construire des systmes de traitement qui excutent leurs tapes via plusieurs programmes spars. On testera alors le code de sortie de chaque programme avant de lancer le suivant; la valeur zro est gnralement utilise pour signifier une excution correcte. Remarque: ce principe existe galement sur unix et Linux, reportez-vous la documentation du shell utilis pour savoir comment utiliser ce code retour.
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 60

Paramtres reus
La fonction main accepte plusieurs interfaces (prototypes) permettant ou non de recevoir des informations depuis linterprteur (shell). Les deux interfaces suivantes sont reconnues par la plupart des compilateurs :
main() main(int argc, char* argv[])

avec un code de retour de type int ou void. La premire version nous est familire; la seconde permet, en dclarant une variable de type int et un tableau de chanes de caractres (char* []), de rcuprer les arguments passs au programme via la ligne de commande. Une troisime version, disponible sur certains systmes, permet galement de rcuprer les variables denvironnement de ce systme :
main(int argc, char* argv[], char* env[])

Voici un exemple de lecture des diffrents paramtres : Code 15 : Paramtres reus par la fonction main
#include <stdio.h> #include <stdlib.h> void main(int argc, char* argv[], char* env[]) { int i; printf("Nombre de parametres recus : %d\n", argc); printf("\nLes arguments de la ligne de commande sont:\n"); for (i = 0; i < argc; i++) printf("argument[%d] = %s\n", i, argv[i]); printf("\nLes variables d'environnements sont:\n"); for (i = 0; env[i] != NULL; i++) printf("variable[%d] = %s\n", i, env[i]); }

Enregistrez ce programme sous testarg.c et compilez-le pour crer testarg.exe; nous pouvons alors tester la lecture des arguments; notez avant cela que le caractre espace utilis sur la ligne de commande sert sparer les diffrents arguments; si vous dsirez transmettre une phrase incluant des espaces, vous devez la quoter (avec des simples quotes - apostrophes - ou des doubles quotes - guillemets) :
> testarg premier "plusieurs mots" 3 4

affichera :
Nombre de parametres recus : 5 Les arguments argument[0] = argument[1] = argument[2] = argument[3] = argument[4] = de la ligne de commande sont: c:/src/cours/cpp/testarg.exe premier plusieurs mots 3 4

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

61

Les variables variable[0] = variable[1] = ... variable[8] = etc....

d'environnements sont: DIRCMD=/OGN /L TMP=C:\SYS\SYSTEME\TEMP GNU_PATH=c:\dev\gnu

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

62

La sortie des donnes La fonction printf


Nous avons dj utilis la fonction printf(), sa dfinition relle (dans stdio.h) est :
int printf(const char* format[, argument, ...]);

Cela signifie que cette fonction attend un moins un paramtre de type const char* (le modificateur const sert ici prciser que le paramtre est trait comme constant, il ne pourra pas tre modifi par la fonction); aprs ce paramtre obligatoire on peut ajouter un nombre arbitraire dautres paramtres, ils correspondent des valeurs (un nombre, une chane, une constante, ...) ou des variables dont on souhaite afficher la valeur. La valeur retourne par la fonction est le nombre de caractres affichs si lopration se droule correctement, et la constante EOF (gnralement -1) en cas dchec. Le premier paramtre, la chane de caractre, sert prciser la nature des valeurs constituant les autres paramtres (le type de ces variables ou le type selon lequel traiter ces valeurs) ainsi que le format utiliser pour afficher ces valeurs; cette chane inclura galement le texte devant tre affich en plus des variables. Le nombre de valeurs affiches dpend du nombre de descriptions prsents dans cette chane de formatage et non du nombre de paramtres fournis; si vous fournissez plus de valeurs quil ny a des descriptions, les valeurs supplmentaires seront ignores, si vous fournissez plus de descriptions quil ny a de valeurs le rsultat est imprvisible (gnralement des valeurs alatoires seront affiches). Le format gnrique dune description de valeur est le suivant (les arguments entre crochets sont optionnels) :
% [modificateur] [largeur] [.prcision] [N|F|h|l|L] caractre_de_type

Dfinition des donnes


le caractre de type sert dfinir le type de la donne afficher, en voici la liste : valeur d ou i u o x X f e g E signification valeur entire (dcimale) signe valeur entire (dcimale) non signe valeur entire octale non signe (base sur une arithmtique nutilisant que les chiffres de 0 7 ) valeur entire hexadcimale non signe (base sur une arithmtique utilisant les chiffres de 0 9 et les lettres a f ) valeur entire hexadcimale non signe (base sur une arithmtique utilisant les chiffres de 0 9 et les lettres A F ) nombre rel affich sous la forme [+/-] xxxx.xxxx nombre rel affich sous la forme [+/-] x.xxxx e[+/-]xxx laisse la fonction choisir entre le format f et e selon la valeur relle nombre rel affich sous la forme [+/-] x.xxxx E[+/-]xxx
63

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

G c s p

idem format g avec le caractre E pour lexposant du format e un caractre unique (variable de type char ou caractre entre apostrophes) une chane de caractre (variable char* ou chane entre guillemets) un pointeur sous la forme segment:offset ou offset seul selon la spcification de taille N ou F

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

64

Le format dune description de valeur commenant par le symbole %, vous devez utiliser le caractre de type % pour afficher le symbole pour-cent lui -mme, voici un exemple :
printf( "Un tiers est 33.33 %%" );

ceci affichera bien :


Un tiers est 33.33 %

Voici dautres exemples avec diffrents caractres de types :


printf( "%d", 12 );

affiche:

12

printf( "%d %d", 12, 34 );

affiche:

12 [espace] 34

printf( "%d\t%d\n", 12, 34 );

affiche:

12 [tabulation] 34 [retour ligne]

int a = 12, b = 34; printf( "%d %d", a, b );

affiche:

12 [espace] 34

int a = 12, b = 34; printf( "%d plus %d font %d", a, b, a + b );

affiche:

12 plus 34 font 46

int a = 62; printf("%d vaut %x en hexa", a, a);

affiche:

62 vaut 3e en hexa

int un = 1, deux = 2; char* eyes[2] = {"oeil", "yeux"}; printf("%d %s, %d %s", un, eyes[un-1], deux, eyes[deux-1]);

affiche:

1 oeil, 2 yeux

Noubliez pas que les tableaux commencent lindice zro.

Modificateur de description
valeur signification la valeur sera aligne gauche et complte droite par des espaces; par dfaut, la valeur est aligne droite en insrant des zros ou des espaces avant la donne; loption largeur doit tre utilise avec ce modificateur les types signs seront prcds du caractre + ou -

(espace) les valeurs positives seront prcdes dun espace et non de + # largument caractre_de_type subira une interprtation diffrente, ainsi : avec d, i, u, c et s aucune modification dinterprtation nest ralise avec o le caractre 0 (zro) est affich au dbut du nombre avec x et X les nombres sont affichs avec le prfixe 0x ou 0X
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 65

avec e, E et f le point dcimal est toujours affich, habituellement il lest uniquement sil existe au moins un chiffre significatif (non nul) avec g et G le point dcimal sera galement ajout ainsi que des zros

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

66

Voici des exemples dutilisation de modificateurs : Le code :


int a = 12, b = -34; printf( "<%+d>\n<%+d>", a, b );

affiche:
<+12> <-34>

Le code :
int a = 12, b = -34; printf( "<%+u>\n<%+u>", a, b );

affiche:
<12> <4294967262>

Le modificateur + a t ignor car il ne sapplique que sur des types signs; or, u provoque une interprtation non sign. De mme b a t interprt comme non -sign, il est ici exprim comme un unsigned long int puisque int est quivalent short int sur systme 16 bits et long int sur systme 32 bits.
int a = 12, b = -34; printf( "<% d>\n<% d>", a, b );

affiche:
< 12> <-34>

Le code :
int a = 62; printf("%d vaut %#X en hexa et %#o en octal", a, a, a);

affiche:
62 vaut 0X3E en hexa et 076 en octal

Largeur daffichage
Loption largeur permet de fixer le nombre minimum de caractres afficher, si le nombre prcis commence par un zro, les donnes, si elles sont aligns droite, sont compltes par des zros sinon des espaces sont insrs. La largeur peut galement tre fournie comme parmi la liste des variables en utilisant ici un astrisque * (cf plus loin). Le code :
int a = 1, b = -12, c = 123; printf( "<%5d>\n<%5d>\n<%5d>", a, b, c );

affiche:
< < < 1> -12> 123>

Le code :
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 67

int a = 1, b = -12, c = 123; printf( "<%-5d>\n<%-5d>\n<%-5d>", a, b, c );

affiche:
<1 <-12 <123 > > >

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

68

Prcision daffichage
Loption prcision, toujours prcde dun point, permet de fixer le nombre maximum de caractres afficher. La prcision par dfaut est de 1 pour les types entiers dcimaux (d, i et u), octal (o) et hexadcimal (x et X), de 6 pour les types rels exposants (e et E) et flottant (f), tous les chiffres significatifs pour les formats g et G, tous les caractres (jusquau caractre nul) pour les chanes (s) enfin il est sans effet pour un caractre. Vous pouvez prciser : .0 (zro) pour supprimer le point dcimal affich avec les formats e, E et f .n * o n est un nombre, pour afficher n dcimales ou les n premiers caractres dune chane comme pour la spcification de la largeur, la prcision figurera, sous forme dentier, parmi les arguments et sera place juste avant la donne utilisant le format en cours de description; dans le cas o la largeur est aussi fournie en paramtre, on fournira la largeur, la prcision puis la donne afficher

Le code :
char* question = "2 + 2 font 4 !"; printf("%.10s ?\n", question); printf("%s\n", question);

affiche:
2 + 2 font ? 2 + 2 font 4 !

Le code : Code 16 : Affichage format de nombres dcimaux


#include <stdio.h> #include <math.h> void main() { int larg, prec; char* blanc = "

";

printf("largeur"); for (larg = 8; larg <= 10; larg++) { /* affichage centr sur 2 colonnes parmi 'larg' */ prec = (larg - 2) / 2; printf(" | %.*s%2d%.*s", prec, blanc, larg, prec + larg % 2, blanc); } printf(" |\n"); for (prec = 3; prec < 7; prec++) { printf("prec: %d", prec); /* affichage align gauche sur 'larg' colonnes et 'prec' chiffres significatifs */ for (larg = 8; larg <= 10; larg++) printf(" | %-*.*f", larg, prec, M_PI); printf(" |\n"); } }
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 69

affiche:
largeur prec: 3 prec: 4 prec: 5 prec: 6 | | | | | 8 3.142 3.1416 3.14159 3.141593 | | | | | 9 3.142 3.1416 3.14159 3.141593 | | | | | 10 3.142 3.1416 3.14159 3.141593 | | | | |

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

70

Modificateur de taille
Ce modificateur optionnel est choisi parmi: N, F, h, l (L minuscule) ou L valeur N F signification largument pointeur (de type %p ou %s) est un pointeur proche (NEAR). interprte largument pointeur comme un pointeur long (FAR). par dfaut les pointeurs correspondent au modle mmoire utilis et sont donc des pointeurs longs avec GNU qui gnrent du code 32 bits. h l uniquement avec les caractres de type d, i et u, il spcifie que la donne est un entier court, soit short int signifie quil sagit dun type long, soit long int si le caractre de type spcifie un entier (d, i, u, o, x ou X) ou un double si le caractre de type spcifie un rel (e, E, f, F, g, G) sans ce modificateur les rels sont assimils float signifie un double long, cest dire un entier long long int ou un rel long double selon le caractre de type

Bien quoptionnel, le modificateur de taille sera trs souvent utilis car sans lui les variables autres que int et float pourraient tre mal interprtes, ou leur interprtation prsumerait du compilateur ou de lenvironnement.

Les fonctions ?printf


Ils existent des variantes de la fonction printf(), toutes fonctionnent sur le mme principe que printf() mais ont des particularits que nous allons tudier. La fonction fprintf() :
int fprintf(FILE* stream, const char* format[, argument, ...]);

permet dcrire dans un fichier; nous utilisons pour cela un descripteur de fichier de type FILE*, ce type et les fichiers sont tudis plus loin. Notez que stdio.h dfinit une variable stdout de type FILE* qui est connecte la sortie standard, cest dire habituellement lcran. Vous pouvez donc utiliser fprintf() la place de printf(), en prcisant stdout comme premier paramtre pour afficher lcran ou un vrai descripteur de fichier pour enregistrer sur disque; cela vous permet de rutiliser une mme routine de sortie indpendamment de la destination relle. La fonction sprintf() :
int sprintf(char* s, const char* format[, argument, ...]);

permet dcrire la chane formate dans une chane passe en premier paramtre; ce tampon doit tre assez grand pour contenir lensemble des donnes formates car il nexiste aucun moyen de prciser le nombre maximum de caractres y stocker. Exemple dutilisation : (noubliez pas dutiliser geometry.a avec gcc) Code 17 : Stockage de donnes formates dans une chane
#include <stdio.h> #include <math.h> #include "geometry.h" void main() { char buffer[256];
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 71

sprintf(buffer, "Un cercle de rayon %g a pour surface %f * %g^2, " "soit %f\n", 1.5, M_PI, 1.5, surface_cercle(1.5)); printf("%s\n", buffer); /* pour vrifier */ }

affiche (et donc stocke dans buffer) :


Un cercle de rayon 1.5 a pour surface 3.141593 * 1.5^2, soit 7.068583

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

72

La saisie des donnes La fonction scanf


La saisie directe de donnes est ralise grce la fonction scanf() (dfinie dans stdio.h) :
int scanf(const char* format[, adresse, ...]);

Cette fonction attend un paramtre de type const char* (il ne pourra pas tre modifi par la fonction); aprs ce paramtre obligatoire on ajoutera un nombre arbitraire darguments; ces arguments sont obligatoirement des pointeurs donnant les adresses des variables dans lesquelles une valeur doit tre stocke. La valeur retourne par la fonction est le nombre de donnes effectivement lues, ventuellement converties et stockes dans nos variables-arguments. Si aucune donne nest stocke, la valeur retourne est zro; si la fonction rencontre une fin de fichier (variante fscanf()) ou la fin dune chane (variante sscanf()) la constante EOF est retourne (gnralement dfinie comme -1). Le premier paramtre de la fonction scanf(), une chane de caractre, sert prciser la nature des donnes lire. Le nombre de valeurs lues et/ou stockes dpend du nombre de descriptions prsentes dans cette chane de formatage et non du nombre dadresses fournies; en effet si vous fournissez plus de pointeurs quil ny a de descriptions, la valeur des variables pointes supplmentaires ne sera pas d finie; si vous stockez plus de donnes que vous ne fournissez de pointeurs, le rsultat est imprvisible et dangereux (risque de blocage svre), en effet la fonction va crire des valeurs des adresses mmoires indfinies. En rsum, soyez srs de fournir une description de format pour chaque donne lire et un pointeur pour chaque donne stocker; ce nombre peut tre diffrent; en effet nous pouvons demander que des donnes soient lues mais que celles-ci ne soient pas stockes. Le format gnrique dune description de donnes est le suivant :
% [*] [ largeur ] [F|N] [h|l|L] caractre_de_type

les arguments entre crochets sont optionnels, dtaillons ces arguments :

Dfinition des donnes


le caractre de type sert dfinir le type de la donne lire, en voici la liste : valeur d D u U o O signification pointeur int* soit ladresse dun entier sign pointeur long* soit ladresse dun entier long sign (quivalent ld) pointeur unsigned int* soit ladresse dun entier non sign pointeur unsigned long* soit ladresse dun entier long non sign pointeur int*; la valeur lue doit tre une valeur octale pointeur long*; la valeur lue doit tre octale
73

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

x X i I

pointeur int*, la valeur lue est hexadcimale et crite sous la forme 0x???? pointeur int*, la valeur lue est hexadcimale et crite sous la forme 0X???? pointeur int*, la valeur lue peut tre dcimale, octale ou hexadcimale pointeur long*, la valeur lue peut tre dcimale, octale ou hexadcimale

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

74

valeur e E f g G c s

signification pointeur float*, soit ladresse dun rel court, la valeur lire doit tre crite sous la forme [+/-]x.xxxe[+/-]xxx pointeur float*, la valeur doit tre crite sous la forme [+/-]x.xxxE[+/-]xxx pointeur float*, la valeur doit tre code sous la forme [+/-]xxx.xxx pointeur float*, la valeur doit tre code sous la forme [+/-]x.xxxe[+/-]xxx ou [+/-]x.xxxE[+/-]xxx ou [+/-]xxx.xxx pointeur float*, la valeur doit tre code sous la forme [+/-]x.xxxe[+/-]xxx ou [+/-]x.xxxE[+/-]xxx ou [+/-]xxx.xxx pointeur char*, par dfaut linterprtation faite est celle de ladresse dun caractre (unique) et non pas ladresse dun tableau de caractres. pointeur char*, linterprtation faite est celle de ladresse dune chane de caractres; la taille nest pas vrifie, vous devez donc fournir un tableau de caractres suffisamment large pour contenir tout le texte lire pointeur sur type pointeur, soit ladresse dun pointeur; une adresse sera lue pointeur int*, la valeur lire est crite sous forme de caractres, le nom bre de caractres stocks dpend de la taille relle de la variable (cf modificateur de taille) ce type de saisie nest pas support par toutes les implmentations versions de stdio le caractre % lui -mme est stock

p n

Exemple dutilisation : Code 18 : Saisie avec cho


#include <stdio.h> void main() { char char short long

aChar; aStr[32 + 1]; /* +1 pour stocker le zro ascii */ int16; int32; : "); : %c\n", aChar);

printf("Tapez une lettre scanf ("%c", &aChar); printf("votre lettre

printf("Tapez une chaine (32 car. max.) :"); scanf ("%s", aStr); printf("votre chaine : %s\n", aStr); printf("Tapez un entier signe scanf ("%hd", &int16); printf("votre entier : "); : %hd\n", int16);

printf("Tapez un long non signe : "); scanf ("%U", &int32); printf("votre nombre : %lu\n", int32); }

Suppression daffectation
le caractre *, dit caractre de suppression daffectation, permet quune donne lue ne soit pas stocke; ceci est particulirement intressant dans le cas o vous nutilisez
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 75

quune partie des donnes reues, aucun pointeur nest fourni pour une telle donne. Voir le code 19 - ci-aprs - pour un exemple dutilisation.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

76

Largeur de lecture
Loption largeur permet de fixer le nombre maximum de caractres lire. Utilisable avec un pointeur sur caractres, cette option permet de contrler le nombre de caractres transfrs dans la chane argument. Cette option ne garantit pas que ce nombre de caractres sera lu; en effet le buffer de saisie peut contenir moins de caractres; mais il se peut galement quil y ait plus de caractres lus que la valeur demande. En effet, en cas de caractres espace ou non convertibles, des caractres supplmentaires seront lus. Par contre ces caractres non pris en compte ne sont pas crits dans votre tampon. Cette caractristique a deux usages courants : 1. tout dabord cela permet de contrler le nombre de caractres lus vis vis de la taille du buffer fourni. Ainsi la saisie de texte ci -dessus aurait du tre code :
char aStr[32 + 1]; /* +1 pour stocker le zro ascii */ printf("Tapez une chaine (32 car. max.) :"); scanf ("%32s", aStr); printf("votre chaine : %s\n", aStr);

2. le fait que le caractre espace ne soit pas pris en compte est galement utile pour saisir un caractre qui, justement, doit tre diffrent de espace et imprimable; si nous codons :
char aChar; printf("Tapez une lettre scanf ("%c", &aChar); printf("votre lettre : "); : %c\n", aChar);

lutilisateur peut saisir un espace ou taper directement un retour ligne; si par contre nous codons :
char aChar; printf("Tapez une lettre scanf ("%1s", &aChar); printf("votre lettre : "); : %c\n", aChar);

nous sommes srs que aChar sera un caractre valide (et diffrent despace).

Modificateurs de taille
Nous pouvons utiliser deux paramtres de spcification de taille. Le premier N ou F redfinit la taille du pointeur reu en argument; avec N il est considr comme un pointeur proche (near pointer), avec F il est uti lis comme un pointeur lointain (far pointer). Le second modificateur redfinit la taille de la variable pointe : valeur h l signification signifie quil sagit dun pointeur sur un type court, applicable uniquement avec le type int; il spcifie donc un pointeur sur un short signifie quil sagit dun pointeur sur un type long, soit long int* si le caractre de type spcifie un entier, soit un double* sil sagit dun rel

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

77

signifie quil sagit dun pointeur sur un double long, cest dire un long long* ou un long double* selon le caractre de type.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

78

Les fonctions ?scanf


A lidentique de ce que nous avons vu pour les variantes de printf() il existe :
int fscanf(FILE* stream, const char* format, ...);

qui utilise un premier paramtre supplmentaire de type FILE (un descripteur de fichier) pour y lire des donnes. Et une fonction :
int sscanf(const char* s, const char* format, ...);

qui lit des donnes, non plus issues de la console (le clavier), mais dans un tableau de caractres que nous fournissons en premier paramtre supplmentaire. Exemple dutilisation : Code 19 : Lecture de donnes formates depuis une chane
#include <stdio.h> #include <math.h> #include "geometry.h" void main() { char float float double

buffer[256]; rayon; surface; pi_arrondi;

/* cration de la chaine contenant les donnes */ sprintf(buffer, "Un cercle de rayon %g a pour surface %f * %g^2, " "soit %f\n", 1.5, M_PI, 1.5, surface_cercle(1.5)); /* lecture des donnes de la chaine */ sscanf(buffer, "Un cercle de rayon %f a pour surface %lf * %*f^2, " "soit %f\n", &rayon, &pi_arrondi, &surface); /* affichage des donnes relues */ printf("Le rayon est : %g\n", rayon); printf("La surface est : %g\n", surface); printf("avec pi valant : %lg\n", pi_arrondi); }

affiche :
Le rayon est : 1.5 La surface est : 7.06858 avec pi valant : 3.14159

Remarque: nous lisons et stockons le rayon (%f), la valeur de pi (%lf), lisons mais ignorons la rptition du rayon (%*f) et enfin nous lisons et stockons la surface.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

79

Les chanes de caractres Un nombre sign ou non


Nous avons annonc que le type caractre peut tre considr comme un nombre. Pour illustrer cette quivalence, voici un code qui affiche les caractres dont le code ASCII est compris entre 32 (lespace) et 127 (non compris) : Code 20 : Table ASCII restreinte
#include <stdio.h> void main() { char c; char* separateur[2] = {"\t", "\n"}; for (c = ' '; c < 127; c++) printf("%hd = '%c' %s", c, c, separateur[(c - 31) % 5 == 0]); }

Explications : 1. dans linstruction for nous initialisons c avec une lettre et testons sa valeur par rapport un nombre 2. le caractre c est affich comme un entier (format %hd) et comme la lettre quil reprsente (format %c) 3. le test sur le reste de la division entire de (c - 31) par 5 renvoie 0 ou 1 qui est utilis comme indice du tableau de chanes afin dinsrer un retour la ligne (\n) ou une marque de tabulation (\t). Pourquoi ne pas afficher le caractre 127 en ralisant une boucle qui testerait c <= 127 (ou c < 128) ? Voila un cas o la distinction sign, non sign du type char pourrait produire des effets indsirables. Supposons que le compilateur traite le type char comme signed char (cest le cas de GNU C/C++) : dans un tel cas le test c <= 127 serait toujours vrifi, puisque le compilateur effectue un test sign entre un nombre prenant les valeurs comprises entre -128 et +127 et le nombre 127, et le programme partirait dans une boucle infini. En effet aprs avoir imprim le caractre 127, linstruction c++ affecterait -128 c qui vrifie bien c <= 127, imprimerait ce caractre -128 (?!) puis -127, -126 etc. jusqu +127 ... et on repart pour un tour ... indfiniment. Pour imprimer tous les caractres imprimables du jeu de caractres ASCII tendu, sans risquer la mauvaise surprise, on coderait : Code 21 : Table ASCII complte
#include <stdio.h> void main() { unsigned char c; char* separateur[2] = {" ", "\n"}; for (c = ' '; c < 255; c++) printf("%3hd = '%c' %s", c, c, separateur[(c - 31) % 6 == 0]); }
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 80

ici encore on ne cherchera pas imprimer le caractre de code ASCII 255 (qui dailleurs nest pas imprimable) sous peine de calculer 255 + 1 soit 0 et de produire une autre boucle infinie.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

81

Remarque : cette dernire remarque ne signifie nullement que lon ne puisse pas coder des boucles sexcutant sur tout le domaine de dfinition dun nombre; il conviendrait juste dcrire la boucle autrement, par exemple : Code 22 : Table ASCII vraiment complte
#include <stdio.h> void main() { unsigned char c, x; char* separateur[2] = {"\t", "\n"}; for (c = 0; ; c++) { x = ((c > 6 && c < 11) || c == 13 || c == 26) ? ' ' : c; printf("%3hd = '%c' %s", c, x, separateur[(c + 1) % 6 == 0]); if (c == 255) break; /* sort de la boucle */ } }

Les tableaux de caractres


Les chanes de caractres du C/C++ sont des pointeurs ! Nous lavons dj dit et rpt mais ceci est peut tre la premire source derreur lorsque lon dbute en C/C++, donc insistons. Une chane statique est cre et initialise de la faon suivante :
char* chaine = "mon texte entre guillemets";

ou
char chaine[] = "mon texte entre guillemets";

dans les deux cas, le compilateur rserve la place ncessaire pour stocker les caractres prsents et un caractre de valeur nulle insr la fin de la chane; et affecte la variable chaine ladresse mmoire du premier caractre. Toutes les oprations sur les chanes que vous utilisez (le cas chant) habituellement en Pascal ou Basic sont ici non valides; par exemple si sont dfinis :
char* str1 = "premiere chaine"; char* str2 = "second message";

vous ne pouvez pas dfinir le contenu de str1 avec celui de str2 en utilisant loprateur daffectation:
str1 = str2;

ceci fait pointer str1 sur lemplacement mmoire o est stock second message; str1 et str2 deviennent donc deux pointeurs quivalents (pointant sur la mme donne). De mme, des affectations hors initialisations modifient ladresse de ce pointeur, mais pas le contenu de la chane pointe, exemple :
str1 = "autre chaine";

provoque la rservation dune nouvelle zone mmoire par le compilateur durant la phase de compilation pour y stocker les caractres de la chane et, lors de lexcution, le pointeur str1 est chang pour dsigner cet autre emplacement.
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 82

Ceci montre clairement que nous ne pouvons pas utiliser une telle syntaxe pour modifier le contenu de chanes utilises, par exemple, comme paramtres dune routine ayant pour fonction de dfinir ce contenu.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

83

Voici un exemple pour illustrer cela :


#include <stdio.h> void main() { char* str1 = "premiere chaine"; char* str2 = "second message"; printf("ptr\tcontenu\n"); printf("%p\t%s\n", str1, str1); str1 = str2; printf("%p\t%s\n", str1, str1); str1 = "autre chaine"; printf("%p\t%s\n", str1, str1); }

lexcution donne (les adresses de la premire colonne peuvent tre diffrentes) :


ptr 1550 1560 1583 contenu premiere chaine second message autre chane

o on observe que la variable str1 change de valeur, cest dire pointe sur un emplacement diffrent. Vous pourriez penser que tout cela na pas trop dimportance puisque la chane affiche est bien celle que nous attendions; tant que nous manipulons des chanes statiques cela semble en effet ne pas tre gnant; cependant, comme nous lavons dj dit, si str1 bien que statique est suppose pointer toujours sur le mme bloc de caractres dont nous cherchons modifier le contenu, cela ne fonctionne plus; un problme plus grave va apparatre avec les chanes dynamiques - dont lespace mmoire utilis pour stocker les caractres est allou durant lexcution et non rserv durant la compilation. Voyons cela. Une chane est cr dynamiquement en utilisant malloc( ) :
char* aStr = (char*) malloc(32); /* alloue 32 octets */

Notre chane dynamique est cre, nous utilisons alors loprateur daffectation comme dans lexemple prcdent :
aStr = "autre chaine";

ceci a modifi ladresse dcrite par aStr ... comment allons-nous maintenant librer la zone de 32 octets prcdemment alloue ? ... impossible ! et force votre code finira en out of memory. Comment alors fixer le contenu dune chane avec une autre chane ? nous devons recopier un un tous les caractres de la nouvelle chane dans le bloc mmoire reprsentant la premire. Voici un codage possible dune routine strcpy (String Copy) qui raliserait une telle tche :
char* strcpy(char* destination, const char* source) { int i; for (i = 0; source[ i ] != 0; i++)
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 84

destination[ i ] = source[ i ]; /* recopie un caractre destination[ i ] = 0; /* marque la fin */ destination; /* retourne ce pointeur */ }

*/ return

cette routine est un peu sale : on ne vrifie pas que source et destination existe et on suppose que le bloc point par destination contient suffisamment de place pour stocker tous les caractres de source.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

85

De plus cette routine peut tre largement optimise, les accs aux i -ime lments des chanes sont coteux en cpu; une premire amlioration consisterait utiliser larithmtique des pointeurs : en effet puisque source est un pointeur sur caractre, *source est le premier caractre et *(source + 1) le second, etc. Ainsi nous pouvons remplacer laccs par indice par une drfrence dun pointeur parcourant la chane, comme nous ne pouvons modifier le pointeur source puisquil est constant, et que nous voulons conserver la valeur de destination pour la retourner la fin du traitement, nous crons deux variables locales de type char* et nous balayons ensuite ces pointeurs :
char* strcpy(char* destination, const char* source) { char* sour = (char*) source; /* conversion explicite en char* */ char* dest = destination; for (; *sour; sour++, dest++) *dest = *sour; *dest = 0 return destination; }

/* recopie un caractre /* insertion du zro terminal

*/ */

Remarques : 1. le test de sortie *sour est quivalent *sour != 0 soit est-ce que le caractre dont ladresse est stock dans sour est diffrent de zro. 2. linstruction dincrmentation utilise loprateur virgule de liaison dinstruction pour raliser les deux actions : incrmenter le pointeur sour et le pointeur dest. Vous avez suivi ? alors allons y franchement, vous vous rappelez que loprateur ++ plac gauche dune variable incrmente son contenu avant son valuation; pour tirer le meilleur parti du cache du micro processeur nous coderons, afin dvaluer les termes quune seule fois par itration :
char* strcpy(char* destination, const char* source) { char* sour = (char*) source - 1; /* conversion en char* char* dest = destination - 1; /* pointeur local while (*++dest = *++sour) ; return destination; }

*/ */

/* instruction nulle

*/

Remarques: les pointeurs locaux sont initialiss avec loctet prcdent ladresse relle puisque nous utilisons ++ gauche; ainsi la premire valuation les pointeurs seront incrments dun octet et dsigneront donc bien le premier caractre. Sachez que le compilateur optimise une partie du code pour vous et vous naurez pas habituellement chercher crire des expressions le plus incomprhensibles possibles. Cependant : 1. vitez les accs par loprateur crochet et prfrez larithmtique des pointeurs 2. cette recherche de lexpression cryptique est un jeu qui a fait la rputation du C et qui vous permettra aussi de vous familiariser avec le C et de le domestiquer.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

86

Enfin, les librairies fournies avec un compilateur dfinissent les fonctions utiles aux traitement des chanes (dont strcpy); vous naurez donc pas crire toutes ces fonctions.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

87

Les fonctions standards de manipulation de chanes


Les fonctions suivantes sont dfinies dans string.h.

Comparaison
int strcmp(const char* s1, const char* s2);

retourne une valeur ngative si s1 < s2, zro si les chanes sont identiques, une valeur positive si s1> s2; la comparaison se fait caractre par caractre sur les codes ASCII des caractres.
int stricmp(const char* s1, const char* s2);

retourne une valeur ngative si s1 < s2, zro si les chanes sont identiques, une valeur positive si s1> s2; la comparaison se fait caractres par caractres sans tenir compte de la casse (A est gal a).
int strncmp(const char* s1, const char* s2, size_t n);

traitement identique strcmp mais portant que sur les n premiers caractres. Rappel : le type size_t est dfini comme unsigned long int.
int strnicmp(const char* s1, const char* s2, size_t n);

traitement identique stricmp mais portant que sur les n premiers caractres.

Concatnation
char* strcat(char* s1, const char* s2);

retourne la chane forme de s1 et s2; ralloue s1 pour stocker les caractres de s1 et ceux de s2 quivalent Pascal : s1 := Concat(s1, s2); ou s1 := s1 + s2;
char* strncat(char* s1, const char* s2, size_t n);

retourne la chane forme de s1 et dau maximum n caractres de s2; ralloue s1 pour stocker les caractres de s1 et les n premiers caractres de s2 (ou la taille relle de s2). quivalent Pascal : s1 := s1 + Copy( s2, 1 n );

Copie
char* strcpy(char* s1, const char* s2);

recopie le contenu de s2 dans le bloc point par s1; s1 doit avoir une taille suffisante pour lopration quivalent Pascal : s1 := s2;
char * strncpy(char* s1, const char* s2, size_t n);

recopie au maximum les n premiers caractres de s2 dans le bloc point par s1; s1 doit avoir une taille suffisante pour stocker n caractres; s1 peut ne pas tre nul termin si s2 contient n ou plus caractres. quasi-quivalent Pascal : s1 := Copy( s2, 1, n );
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 88

char* strdup(const char* s);

retourne une copie de la chane s; la routine alloue lespace ncessaire, lutilisateur doit librer ce bloc quand il nen a plus besoin.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

89

Longueur
size_t strlen(const char* s);

retourne le nombre de caractres dans s (le caractre nul nest pas compt) quivalent Pascal : Length(s)

Recherche
char* strchr(const char* s, int c);

retourne un pointeur sur la premire occurrence du caractre c dans s; la chane retourne est un sous-ensemble de s, rien nest allou. quivalent Pascal : Copy( s, Pos( Chr( c ), s ), Length( s ));
char* strrchr(const char* s, int c);

retourne un pointeur sur la dernire occurrence du caractre c dans s; la chane retourne est un sous-ensemble de s, rien nest allou.
size_t strcspn(const char* s1, const char* s2);

retourne le nombre de caractres, pris depuis le dbut de s1 ne contenant aucun caractre de s2


size_t strspn(const char* s1, const char* s2);

retourne une sous chane de s1 forme uniquement par les caractres de s2.
char* strpbrk(const char* s1, const char* s2);

retourne un pointeur sur la premire occurrence dun des caractres de s2 dans s1; la chane retourne e st un sous-ensemble de s1, rien nest allou. quivalent Pascal : Copy( s, Pos( Chr( c ), s ), Length( s ));
char* strstr(const char* s1, const char* s2);

retourne une sous chane de s1 identique s2

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

90

Les Fichiers
Les entres sorties ralises en C/C++ sur des fichiers ne sont pas trs diffrents de celles ralises avec la console (cest dire le clavier en entre, lcran, en mode texte, en sortie). En effet lorsque nous travaillons avec la console, nous utilisons des priphriques automatiquement pris en compte par le systme; cest dire que ce systme a ouvert les accs en entre ou sortie, insre ou rcupre les donnes, etc.; pour travailler avec des fichiers nous allons avoir besoin de grer nous mmes ces oprations, mais le travail sur le fichier va ensuite tre quasi identique lutilisation de la console.

Le type FILE
Le type FILE, dclar dans le fichier stdio.h, permet de dclarer une rfrence un fichier (un flux de donnes); les fonctions utilisant des rfrences une vari able de ce type sont indpendantes du systme; nous manipulerons donc un fichier de la mme faon quel que soit le systme de gestion implment par le systme lui -mme; le revers de cette transparence est que certaines fonctions ne seront pas disponibles sur certains systmes (par exemple sous window il est impossible de dfinir des droits daccs un fichier comme cela se fait sous unix). La premire fonction qui va nous intresser est fopen(); son prototype est :
FILE* fopen(const char* nom_du_fichier, const char* mode_d_ouverture);

Le second paramtre est une chane de caractres qui contiendra tout dabord une des options suivantes : r w ouvre le fichier en mode de lecture, il est donc interdit dy crire et le fichier doit exister. ouvre le fichier en criture, tout fichier de mme nom existant sera cras; il est videmment impossible dy lire des donnes puisque le fichier est toujours vide aprs lappel de la fonction. ouvre le fichier en mode ajout ou en mode criture si aucun fichier de ce nom nexiste; toute criture se fait la fin du fichier. ouvre un fichier en mode lecture, donc le fichier doit exister et la position dans le fichier est fixe au dbut de ce fichier, mais il est autoris dy crire. Si lcriture est ralis la fin du fichier, les donnes seront ajoutes. Si par contre les donnes sont crites partir dune position diffrente de la fin, toutes les anciennes donnes situes entre cette position et la fin sont perdues. En rsum il nexiste aucun moyen dinsrer directement des donnes dans un fichier.

a r+

w+ ouvre un nouveau fichier en criture. Ce fichier remplace, comme avec loption w, tout autre fichier de mme nom. Cependant il sera possible de relire les donnes du fichier en se repositionnant dans le fichier; cette dernire opration sera ncessaire car videmment lorsque nous utilisons un fichier en criture, la position courante aprs louverture du fichier est toujours la fin de ce fichier. a+ ouvre le fichier, ou cre un nouveau fichier, en mode ajout. Lusage est identique que lors de lutilisation de loption r+, ceci prs que la position courante est la fin du fichier au retour de la fonction fopen.
91

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

A chacune de ces options, nous pouvons ajouter, la fin de la chane, le caractre b ou t; le caractre t assignera un comportement texte au fichier, tandis que b provoquera un mode binaire.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

92

Cette distinction ne sert qu savoir comment sont enregistrs les symboles de fin de ligne dans le fichier; si vous ninscrivez que des nombres (par exemple quatre octets pour un long) cette option ne vous concerne pas et vous ntes pas tenu de prciser t ou b; si vous enregistrez des fichiers contenant du texte mais que le format de ces fichiers est un format propre votre application, prcisez b afin quaucune interprtation de ces symboles de fin de ligne ne soit ralise. Vous retrouverez ainsi le contenu exact de ce que vous avez inscrit, y compris pour une application destine sexcuter sur diffrentes plates-formes o le symbole nest pas toujours le mme. Si enfin vous enregistrez des fichiers texte qui doivent tre relus par nimporte quel diteur sur nimporte quelle plate-forme, prcisez t. Cette option (texte ou binaire) est optionnelle car le systme a toujours un choix par dfaut, dsign par la variable globale dclare par fcntl.h dont le nom est _fmode; la valeur par dfaut de cette variable est gnralement de considrer les fichiers en mode texte, vous pouvez dans votre programme modifier sa valeur pour ladapter votre utilisation la plus courante. Les valeurs autorises pour cette variable sont les constantes O_TEXT et O_BINARY dfinies galement dans le fichier fcntl.h.

Dsignation du fichier
Nous avons dfini le second paramtre de la fonction fopen(), et le premier alors ? Vous laviez devin, ce premier paramtre dsigne le nom du fichier crer ou ouvrir. Ce nom peut dsigner uniquement un nom de fichier ou un nom prcd dun chemin relatif ou absolu; la chane fournie doit bien sr respecter les rgles du systme, notamment pour la syntaxe dun chemin daccs. Ainsi sous systme Unix, le sparateur / doit tre utilis entre chaque nom de rpertoire. Sur Macintosh, on utilisera le signe :. Enfin avec DOS/Windows, le sparateur tant \, on utilisera \\ car le caractre simple \ sert dfinir des caractres spciaux tandis que le double anti-slash \\ dsigne le caractre \ lui-mme. La validit du nom porte galement sur le nombre de caractres maximal autoris pour le nom des rpertoires et fichiers ainsi que les caractres autorises dans ce nom. Enfin, selon le compilateur, vous pourrez ou non profiter des possibilits de codage des noms. Ainsi si vous souhaitez crer des noms de fichiers longs sous Windows 95 ou NT, vous devrez utiliser un compilateur comme MetroWerks CodeWarrior ou Microsoft Visual C++. Le compilateur GNU ne sachant pas grer de tels noms tronquera le nom fourni ses huit premiers caractres pour le nom et trois premiers pour lextension. Vous avons donc vu que la fonction fopen() sert tout la fois crer un nouveau fichier, relire un ancien fichier, en remplacer un, ajouter des donnes un fichier existant. Ajoutons que la fonction fclose() sert fermer le fichier pass comme unique argument et nous sommes prts crer notre premier fichier; nous allons tout dabord demander un nom de fichier lutilisateur puis nous remplirons le fichier texte ouvert (aprs sa cration ou le remplacement dun ancien fichier) avec les chanes de caractres saisies par lutilisateur; nous nous arrterons quand nous recevrons une chane vide (cest dire un simple retour chariot).

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

93

Ecriture de fichier
Code 23 : Ecriture de fichier
/* Saisie de texte et recopie dans un fichier */ #include <stdio.h> int main() { char buffer[256]; /* tampon pour la saisie */ FILE* sortie = NULL; /* priphrique de sortie */ printf("Nom du fichier a crer : "); if (gets(buffer) == NULL || *buffer == 0) { /* pas de nom saisi, arrtes le programme */ printf("\nVous deviez saisir un nom pour continuer\n"); return -1; } /* Cration du fichier avec le nom fourni en mode criture et texte */ sortie = fopen(buffer, "wt"); testons si le fichier a pu tre cr */ if (sortie == NULL){ printf("\nErreur durant la cration du fichier %s\n", buffer); return -1; } un peu d'aide pour l'utilisateur (le compilateur colle les morceaux de chaines tout seul) */ printf("\nLe fichier a t correctement ouvert,\n" "Tapez des lignes de texte qui seront stockes dans ce fichier\n" "Pour terminer tapez simplement un retour chariot\n\n"); Saisie de lignes de texte et insertion la fin du fichier */ while (gets(buffer) != NULL && *buffer != 0) /* gets(char*) retourne un pointeur sur le buffer en cas de succs ce pointeur dsigne le caractre zro si la chane est vide */ { /* nous connaissons dj "printf" qui crit sur la console "fprintf" crit dans le fichier prcis en premier paramtre les autres paramtres sont identiques ceux de "printf" */ fprintf(sortie, "%s\n", buffer); } ferme le fichier */ fclose(sortie); return 0; }

/*

/*

/*

/*

Remarques :
if (gets(buffer) == NULL || *buffer == 0)

est quivalent :
char* dummy = gets(buffer); if ( (dummy == NULL) || (dummy[0] == 0) )

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

94

cest dire que nous testons si le pointeur retourn par gets est non nul et si le premier caractre du bloc de caractre retourn est diffrente du caractre zro de fin de chane; ici gets retourne le mme pointeur que celui transmis comme paramtre, ceci nous dispense dune variable intermdiaire; avec dautres fonctions retournant un pointeur diffrent (un nouveau bloc allou par exemple) nous nous pourrions pas utiliser un tel code.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

95

Lecture de fichier
En utilisant fprint() et fscanf() en lieu et place de prinf() et scanf() nous pouvons travailler sur les fichiers de la mme manire quavec la console. De mme lorsque nous travaillons avec la console, nous pouvons utiliser toutes les fonctions prvues pour les fichiers en utilisant les flux prdfinis stdin pour la saisie et stdout pour les sorties. Voici un autre exemple qui relit un fichier, et recopie son contenu sur la console ou dans un fichier. Notez comment un mme code peut sadapter diffrents besoins. Code 24 : Lecture de fichier
/* Lecture d'un fichier et recopie dans un fichier ou sur la console */ #include <stdio.h> int main() { char buffer[256]; char* dummy = buffer; FILE* entree = NULL; FILE* sortie = NULL;

/* /* /* /*

tampon pour la lecture fgets attends un char* priphrique d'entree priphrique de sortie

*/ */ */ */

printf("Nom du fichier a lire : "); if (gets(buffer) == NULL || *buffer == 0) { /* pas de nom saisi, arrtes le programme */ printf("\nVous deviez saisir un nom pour continuer\n"); return -1; } /* /* Ouverture du fichier avec le nom fourni en mode lecture et texte et test sur le succs de l'ouverture */ if ((entree = fopen(buffer, "rt")) == NULL) { printf("\nErreur durant l'ouverture du fichier %s\n", buffer); return -1; } printf("\nTapez un nom de fichier crer pour recopier ce fichier\n" "ou simplement un retour chariot pour afficher le contenu\n\n"); if (gets(buffer) == NULL || *buffer == 0) { /* pas de nom saisi, utilises la console */ sortie = stdout; } /* Cration du fichier en mode criture et test de succs */ else if ((sortie = fopen(buffer, "wt")) == NULL) { printf("\nErreur durant la cration du fichier %s\n", buffer); fclose(entree); /* n'oublions pas de faire le mnage */ return -1; } /* Lecture de la source et recopie */ while (fgets(dummy, 255, entree) != NULL && *dummy != 0) /* fgets(char*, size n, FILE*) retourne un pointeur sur le buffer pass en premier paramtre aprs avoir lu au plus n caractres depuis le flux pass en troisime paramtre */ { fprintf(sortie, "%s", dummy); /* le symbole "\n" a t lu par fgets
96

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

*/ } /* ferme les fichiers */ fclose(entree); fclose(sortie); /* la fermeture de stdout sera ignor */ return 0; }

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

97

Les fonctions gnriques dentre sortie


Nous avons not que les fonctions fichiers peuvent tre utilises avec la console grce aux flux C ANSI; cette similitude existe galement pour les fonctions dentre sortie. En effet, pour la plupart il existe une version fichier de la routine console dont le comportement est identique; dans quasiment tous les cas le nom de la routine travaillant sur un fichier aura pour nom celui de la fonction console prfix du caractre f et recevra un premier paramtre supplmentaire de type FILE*. Nous trouvons ainsi les fonctions :
int fgetc(FILE* flux);

qui retourne un caractre lu depuis flux en cas de succs ou la valeur EOF en cas derreur. La position courante est incrmente dune position; fgetc( stdin ) lit un caractre saisi au clavier.
char* fgets(char* s, int n, FILE* flux);

qui remplit le buffer s avec au plus n caractres lus depuis flux et insre un caractre zro aprs ces n caractres (cest pour cela que nous avons pass la valeur 255 alors que notre buffer avait une taille de 256 caractres); la fonction retourne s en cas de succs et la valeur NULL si la fin du flux a t atteinte; fgets( s, n, stdin ) lira une chane au clavier (tout caractre jusqu lappui de la touche Entre)
int fputc(int c, FILE* flux);

qui insre le caractre c dans le flux et retourne ce caractre en cas de succs ou la valeur EOL en cas derreur; fputc( c, stdout ) affiche le caractre sur la console.
int fputs(const char* s, FILE* flux);

qui injecte la chane s dans le flux (qui peut tre stdout) et renvoie une valeur positive ou nulle en cas de succs (cela peut tre le nombre de caractres effectivement injects) et EOF en cas derreur. La fonction najoute pas automatiquement de symbole de retour la ligne (\n).

Les fonctions orientes fichiers


Les fonctions suivantes sappliquent sur des flux correspondant rellement des fichiers :
int feof(FILE* flux);

retourne zro si la fin du fichier na pas t atteinte et une valeur diffrente de zro dans le cas contraire. Remarque : une telle dfinition de la valeur retourne peut paratre un peu floue, mais cest la norme ANSI dfinie pour cette fonction et selon la librairie utilise la valeur rellement reue peut tre diffrente; il est donc impratif de ne se baser que sur une valeur nulle pour crire un code portable. Ainsi nous aurions pu crire dans notre second exemple :
/* Lecture de la source et recopie */ while (!feof(entree)) { if (fgets(dummy, 255, entree) != NULL)
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

/* lecture de la source

*/
98

fputs(dummy, sortie); }

/* criture en sortie */

ce code serait meilleur pour une recopie de fichier car il ne sarrte pas sur la premire ligne vide que contiendrait la source (en effet, nous ne souhaiterions pas quil sarrte).

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

99

Et partant de la dfinition de fgets qui doit renvoyer non NULL tant que la fin du flux na pas t atteinte, nous pourrions supprimer le test redondant et coder directement :
while (!feof(entree)) fputs(fgets(dummy, 255, entree), sortie);

ceci tant, mfiez-vous du codage ralis dans les librairies dites standards et utilisez, dans les oprations sensibles, la ceinture et les bretelles afin de dtecter toutes erreurs. La position courante dans le flux peut tre manipule grce :
int fgetpos(FILE* flux, fpos_t* pos);

qui stocke dans notre variable pos la position courante et renvoie 0 en cas de succs et une valeur diffrente de zro en cas derreur. La valeur stocke dans pos est dite opaque. On ne cherchera pas linterprter et ne sera utiliser quavec la fonction :
int fsetpos(FILE* flux, const fpos_t* pos);

qui restaurera la position courante avec les informations contenues dans pos et retourne 0 ou diffrent de 0, respectivement en cas de succs ou derreur. Voici un exemple dutilisation :
fpos_t une_position; ... /* sauvegarde de la position */ if (fgetpos(flux, &une_position) == 0) { /* traitement */ .... /* restauration de la position */ if (fsetpos(flux, &une_position) != 0) { /* la situation est bancale ... pas moyen de restaurer l'tat initial, prvoir une sortie de secours !!! */ } }

Un autre moyen de fixer la position est de le faire autoritairement en donnant une distance par rapport la position courante, au dbut du fichier ou la fin de ce fichier; on utilise par cela :
int fseek(FILE* flux, long offset, int mode);

qui on donne le flux sur lequel oprer, le dcalage et une constante parmi : ?? SEEK_SET ?? SEEK_CUR ?? SEEK_END pour prciser un dcalage par rapport au dbut du fichier pour un dcalage par rapport la position courante pour un dcalage par rapport la fin du fichier

fseek devrait retourner zro en cas de succs et non zro dans le cas contraire; malheureusement sous DOS cette valeur de retour nest pas toujours fiable car le systme peut rinitialiser la position en cours. Nous pouvons galement sauvegarder la position courante, par rapport au dbut du fichier grce :
long ftell(FILE* flux);

qui renvoie la taille en octets ou -1 en cas derreur. Enfin :


Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 100

int

fflush(FILE* flux);

force lcriture des donnes dans le fichier en vidant le tampon associ,


void rewind(FILE* flux);

rembobine le fichier en fixant la position courante au dbut du fichier.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

101

Conclusion
Maintenant que vous connaissez tout du C, je vous propose un codage possible pour un carnet dadresses; tudiez cet exemple et inspirez-vous en pour raliser des petits programmes du mme genre. Code 25 : Carnet dadresses
#include <stdio.h> #include <stdlib.h> #include <string.h> /* Definition d'une structure */

struct StructureAdresse { char Nom[30]; char Prenom[30]; char Adresse[50]; long CodePostal; char Ville[20]; }; /* le typedef suivant permet d'utiliser directement le type 'TAdresse' a la place de 'struct StructureAdresse' */

typedef struct StructureAdresse TAdresse; /* nom du fichier de donnees en constante globale */

const char* gFileName = "Adress.dta"; /* verification de l'existence d'un fichier */

int ExisteFichier(const char* aFileName) { FILE* dummy = fopen(aFileName, "rt"); if (dummy != NULL) { fclose(dummy); return 1; } else return 0; } /* saisie d'une ligne de texte via un tampon surdimensionne */

size_t SaisieTexte(char* container, short maxSize) { char dummy[256]; /* tampon de saisie */ size_t size; /* verification des parametres */ if (container == NULL) return 0; le resultat est une chaine vide par defaut */ *container = 0; if (maxSize <= 0) /* rien ne pourra etre recopie */ return 0;
102

/*

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

/*

saisie */ gets(dummy); taille de la saisie */ size = strlen(dummy); if (size > maxSize - 1) size = maxSize - 1;

/*

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

103

/*

recopie des donnees saisies */ memcpy(container, dummy, size); container[size] = 0; return size;

} /* saisie d'un nombre entier long utilise prealablement une saisie en texte puis retourne le nombre converti ou 0 si la saisie est vide ou si une erreur se produit long atol(const char*) convertit une chaine en long int */ long SaisieLong() { char dummy[32]; /* tampon de saisie */ if (SaisieTexte(dummy, 32) > 0) return atol(dummy); else return 0; } void Affiche(TAdresse* adresse) { printf("%s%s%s%5lu %s\n", adresse->Nom, adresse->Prenom, adresse->Adresse, adresse->CodePostal, adresse->Ville); } TAdresse* Relit(TAdresse* adresse, FILE* source) { char dummy[9]; if (fgets(adresse->Nom, 29, source) == NULL) return NULL; fgets(adresse->Prenom, 29, source); fgets(adresse->Adresse, 49, source); fgets(dummy, 8, source); adresse->CodePostal = atol(dummy); fgets(adresse->Ville, 19, source); return adresse; } void Ajoute() { TAdresse FILE*

adresse; calepin;

if ((calepin = fopen(gFileName, "a+t")) == NULL) return; printf("\nSaisie d'une nouvelle fiche :\n"); printf("Nom (30 printf("Prenom (30 printf("Adresse (50 printf("Code postal printf("Ville (20 /* car.) : "); car.) : "); car.) : "); : "); car.) : "); SaisieTexte(adresse.Nom, 30); SaisieTexte(adresse.Prenom, 30); SaisieTexte(adresse.Adresse, 50); adresse.CodePostal = SaisieLong(); SaisieTexte(adresse.Ville, 20);

ecriture de adresse dans le fichier */ fprintf(calepin, "%s\n%s\n%s\n%lu\n%s\n",


104

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

adresse.Nom, adresse.Prenom, adresse.Adresse, adresse.CodePostal, adresse.Ville); fclose(calepin); }

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

105

void Liste() { TAdresse FILE* long

adresse; calepin; compteur = 0;

if ((calepin = fopen(gFileName, "rt")) == NULL) return; printf("\nContenu du fichier :\n"); while (!feof(calepin)) { if (Relit(&adresse, calepin) != NULL) { printf("fiche %5lu\n-----------\n", ++compteur); Affiche(&adresse); } } fclose(calepin); } void Cherche() { TAdresse FILE* char size_t long

adresse; calepin; cherche[30]; nbDeCarac; compteur = 0;

if ((calepin = fopen(gFileName, "rt")) == NULL) return; printf("\nIndiquez le nom (ou le debut du nom) a chercher : "); nbDeCarac = SaisieTexte(cherche, 30); if (nbDeCarac > 0) while (!feof(calepin)) { if (Relit(&adresse, calepin) != NULL) /* test sur le nom lu */ if (strnicmp(adresse.Nom, cherche, nbDeCarac) == 0) /* le nom lu contient 'cherche' */ { printf("\nfiche %5lu\n-----------\n", ++compteur); Affiche(&adresse); } } fclose(calepin); } short Menu() { printf("\nCommandes disponibles\n\n"); printf("1 - Ajout d'une adresse\n"); if (ExisteFichier(gFileName)) { printf("2 - Recherche d'une adresse\n"); printf("3 - Liste des adresses\n"); } printf("4 - Fin\n"); printf("\nVotre choix : "); return SaisieLong(); }
Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones 106

void main() { short choice; do { switch (choice = Menu()) { case 1: Ajoute(); break; case 2: Cherche(); break; case 3: Liste(); break; } } while (choice != 4); }

Bonne lecture et merci de me communiquer vos questions, impressions et attentes. Sylvain Ferey - sferey@compuserve.com pour le forum des programmeurs francophones.

Sylvain Ferey (sferey@compuserve.com) Forum des Programmeurs Francophones

107