Vous êtes sur la page 1sur 47

I-Organisation de lordinateur 1) fonctionnement interne de base

Vous pouvez voir les documents prsents en cours sur certains points de l'histoire de l'informatique. L'ide de base a t de reprsenter par 1 ou 0 le fait qu'il y ait du courant ou non, dans circuit lectronique (au dpart un "tube vide" ou un relais, dsormais des semi-conducteurs), et de les combiner pour reprsenter des nombres en binaire (voir /pat/autom/autom07.htm). Au centre de l'ordinateur : la carte mre :

Pour plus de dtails, voir coeur-ordi.htm CPU (Central Processing Unit) : le processeur. Il commande tout le systme. Il sait faire des calculs arithmtiques de base (addition, soustraction, multiplication) mais uniquement sur des nombres entiers, des oprations logiques (et, ou, complment), et tester si le rsultat de ces calculs est positif, ngatif ou nul. Il sait lire un nombre en mmoire ou dans un port, ou l'crire (via le bus). Deux grandeurs caractrisent le CPU :

la taille de l'accumulateur (8, 16, 32, 64 bits) : c'est le nombre de chiffres maximum d'un calcul (en binaire). Mais un processeur 8 bit sait faire des calculs 32 bits, il lui suffit de poser le calcul (en donc mettre 4 fois plus de temps) sa frquence : c'est le nombre d'oprations lmentaires qu'il peut faire la seconde. Un quartz envoie des tops intervalle rguliers, cette frquence, ce qui permettra de cadencer les calculs et de synchroniser les changes d'informations entre les diffrents composants.

Mmoire centrale : elle est partage en deux parties :

la mmoire vive (RAM Random Access Memory) : on peut y mmoriser (crire) des nombres binaires pour les relire plus tard. Il existe des mmoires statiques (SRAM) qui mmorisent les nombres aussi longtemps qu'elles sont alimentes, et les dynamiques (DRAM) qui sont plus rapides mais doivent tre rcrites intervalles rguliers. Mais toutes oublient tout ds qu'on coupe l'alimentation la mmoire morte (ROM, Read Only Memory) : ce sont des composants qui par fabrication donneront toujours les mmes valeurs (du 5V ou 0V). Mme aprs coupure d'alimentation. Par contre il est impossible de modifier ces valeurs. Il existe aujourd'hui des ROM programmables (PROM) qui peuvent tre crites, en une fois, hors de l'ordinateur, l'aide d'un appareil spcial (programmateur de PROM). Certaines peuvent tre rinitialises pour pouvoir tre rcrites, en une fois (EPROM, Erasable PROM)

Les interfaces : ils sont vus par le CPU comme des mmoires, il peut y crire ou lire des nombres binaires. Ils permettent la carte mre de dialoguer avec des dispositifs externes nomms "priphriques". En gnral l'interface est ralise par une carte lectronique, qui reoit des commandes du CPU, les traite et rend la rponse si ncessaire. Il y en a trois grands types :

la mmoire de masse : permet de stocker des informations (depuis la mmoire centrale), et de les rcuprer plus tard (mme voire surtout aprs coupure du courant). Ce sont les disques durs, disquettes, bandes, CD, ... les interfaces homme machine permettent l'homme de donner des ordres et de visualiser les rsultats. Ils ont beaucoup volu, de l'interrupteur et le voyant (0/1), les cartes perfores, le clavier et l'cran, la souris, l'imprimante, le joystick, le scanner, le micro et la carte son, la camra numrique... Jusqu' il y a peu de temps, le traitement des informations ne surchargeait pas le CPU (temps de rponse trs suprieurs). Les interfaces machine - machine : modem, carte rseau, bus de terrain...

Le bus permet de transfrer des nombres. Il relie tous ces composants, c'est ce qui fait que lorsque deux d'entre eux dialoguent, les autres doivent attendre. L'ordinateur ne peut de ce fait faire qu'une chose la fois. Pendant longtemps, l'horloge du processeur cadenait tous les composants. Ils fallait qu'ils soient tous aussi rapides. A la fin des annes 80, le PC tournait 4,77 MHz. Aujourd'hui on peut faire fonctionner un processeur plusieurs Ghz, mais pas les mmoires, encore moins les cartes d'interfaces. On va donc prvoir des frquences diffrentes, et entre les deux une "zone tampon" appele "cache". Celui-ci mmorise les informations qui arrivent grande vitesse pour les transmettre la vitesse du destinataire. Evidement, quand le cache est plein il prvient l'metteur qui devra alors soit attendre soit en profiter pour faire quelque chose d'autre. Il y a dsormais des caches multiples (cache mmoire, cache disque,...) et diffrentes frquences de composants. Vous pouvez insrer par exemple une carte PCI sur tout Pentium, elle fonctionnera toujours la mme vitesse.

2) Les 5 gnrations de langages de programmation


Comment donne-t-on des ordres un ordinateur ? Grce un programme. Le CPU va chercher dans la mmoire centrale des "instructions". Chaque action de base que sait faire le processeur est dsigne par un "code opration" qui est un nombre (en binaire). C'est ce qu'on appelle le "langage machine". Bien que tous les processeurs soient capables de faire peu prs la mme chose, chaque marque voire type a "numrot" diffremment les

oprations. Vous ne pourrez donc pas faire tourner un programme PC sur un Mac (sauf si vous avez un mulateur, programme qui pour chaque code op d'un processeur donne son quivalent pour un autre, mais c'est extrmement lent et limit). Exemple : sur un PC, je veux ajouter 5 dans la mmoire n 1234. Ce numro de mmoire (on dit une "adresse") a trop de chiffres en binaire pour entrer dans un octet, on en utilise deux (le PC met toujours les derniers chiffres en premier). Je note tous les nombres en hexa, en binaire ce serait trop long : lire le contenu d'une mmoire (un octet), et le stocker dans le processeur (dans une "mmoire" nomme accumulateur) est cod A0, ajouter un nombre l'accumulateur est cod 04, recopier l'accumulateur dans un mmoire est cod A2. Le programme sera donc : A0 34 12 04 05 A2 34 12 et fait 8 octets. C'est ainsi qu'on programmait les premiers ordinateurs (langage de premire gnration). Pour simplifier la programmation, on a dcid de donner un nom aux diffrentes instructions, c'est le langage mnmonique : MOV pour transfrer un nombre d'une mmoire, ADD pour additionner, AL pour l'accumulateur,... Voici ce que donne notre programme prcdent :

On permet galement au programmeur de dfinir ses propres noms (appels "identificateurs"). Par exemple on pourrait dfinir que la mmoire d'adresse 1234 a pour nom "memoire", la premire instruction deviendrait MOV AL,memoire. La traduction d'un programme mnmonique (un peu plus clair pour le programmeur) en langage machine (seul comprhensible par le processeur) s'appelle "assemblage". Par abus de langage, on regroupe sous le nom "assembleur" indiffremment chacun de ces deux langages. (voir /pat/memo8088.htm) Trs rapidement, on est pass une troisime gnration de langages : les langages dits "volus" et "structurs". Ici on a prvu des instructions plus complexes, qui seront traduites en plusieurs codes op. On y a galement prvu des types numriques plus complexes (au minimum les rels, appels aussi flottants). Plutt que de les numroter, on nomme les mmoires dont on a besoin (variables), on peut aussi regrouper plusieurs instructions sous un seul nom (sous-programmes). Les deux premiers langages volus ont t le FORTRAN (FORmula TRANslator) cr pour le calcul scientifique, et COBOL pour les applications de gestion. Puis BASIC, langage volontairement peu puissant mais prvu pour l'initiation la programmation (les premiers PC se programmaient en basic, les premiers MSDOS taient

livrs avec BasicA, les suivants avec GW-Basic ou QBasic, prcurseurs du Visual Basic). Pour l'enseignement de l'informatique de plus haut niveau, on a cr PASCAL. Le C quand lui, un langage prvu pour les informaticiens, est plus efficace et plus puissant, mais aussi plus complexe. (voir /pat/program/tpc.htm /pat/program/pascal.htm /pat/program/fortran) La quatrime gnration de langages correspond une organisation du programme non plus sous forme d'une suite ordonnes d'instructions, mais les instructions sont regroupes autour des donnes les utilisent. Ce sont les langages de bases de donnes et les langages orients objets (LOO). Pour ces derniers, de nombreux langages ont t cres, dont par exemple SmalTalk. Mais c'est C++ (voir /pat/program/cpp.htm), pourtant moins bon, qui s'est impos, car il est compatible avec C, et a permis une migration progressive. Les nouveaux langages crs aujourd'hui ont une syntaxe proche du C++ (voir /pat/program/javascript). Les langages de 5 gnration sont ceux de l'intelligence artificielle. Ils ne se sont pas dvelopps autant qu'on le prdisait dans les annes 80. Ici, on ne programme plus, mais on donne, progressivement mais en vrac, de la connaissance sur le domaine traiter, au systme de se dbrouiller pour l'organiser (sous une forme qui pourrait par exemple approcher notre rseau de neurones) et puiser dans sa base de connaissances ce qui lui permettra de rsoudre les problmes qu'on lui pose. (voir /pat/program/prolog).

3) La gestion de l'ordinateur
Pour viter de devoir donner des ordres en binaire l'ordinateur, on a cr pour chaque ordinateur un programme appel systme d'exploitation (OS operating system), qui est lanc au dmarrage de l'ordinateur. Il doit connatre comment accder aux interfaces, attend qu'on lui donne des ordres (on dit lancer une commande) et se dbrouille pour les excuter. Au dbut, l'OS tait spcifique chaque machine, vendu et maintenu par le fabriquant de l'ordinateur. Il ne prenait en charge que des priphriques du mme fabricant (et pas moyen d'changer une console BULL avec une IBM par exemple). Certains universitaires, lasss de devoir rcrire les programmes pour tous les OS, ont crit un OS multi-plateforme (Multics puis UNIX), mais longtemps les fabriquants ont refus de l'implanter, mme pour utiliser UNIX il fallait acheter l'OS du constructeur. Pour viter de devoir modifier l'OS chaque modification de priphriques, on en a extrait et fig une partie, qui sait grer un minimum de priphriques, en gnral une console en mode texte et le support qui contient le systme d'exploitation. Dans les premiers ordinateurs, ce programme tait enregistr sur une bande de papier perfore (en binaire) qu'on appelait "bootstrap". Aujourd'hui il est plutt log en ROM, sur un PC on l'appelle BIOS (Basic Input Output System), il sait vrifier la mmoire disponible, puis rechercher s'il y a des disques durs (un seul type, IDE, pas les SCSI par exemple) ou une disquette, puis il y recherche un systme d'exploitation, le charge en mmoire puis lui passe la main. Il connat un type de console de base (pour le PC, en mode texte uniquement, mais certains fabriquants y ont incorpor un fond graphique, par ex Compaq). L'OS lui aussi doit tre volutif. En particulier, si l'on veut permettre de choisir parmi une multitude de priphriques diffrents, il faut soit que l'OS les connaisse absolument tous, soit qu'on spare la partie concernant chaque priphrique possible, et que l'OS ne charge que celles qui correspondent la configuration demande (c'est le pilote ou driver). Le driver est donc spcifique un priphrique et un systme d'exploitation. Aujourd'hui, les priphriques sont capables de donner leur nom et type, ils sont dits "plug and play", chaque allumage

l'OS va vrifier les composants disponibles, ce qui va rallonger l'initialisation mais pas ralentir le fonctionnement normal. Toute modification importante de la configuration de l'OS ou des priphriques ncessite dont de rebooter l'ordinateur. Les premiers ordinateurs personnels contenaient un OS minimal, log en ROM, et contenant en gnral le langage Basic. Il ne savait que grer le clavier et l'cran, en mode graphique ou semi-graphique (pavs de couleur), et permettait des jeux au graphisme simplifi. J'en ai trouv quelques exemples :

La seule mmoire de masse disponible tait la cassette audio. Puis les OS ont permis de grer des disques (et disquettes) et se sont appels DOS (Disk Operating System). Au dbuts du PC, le DOS le plus courant tait CP/M (on a aussi vu DR-DOS de Digital, PC-DOS d'IBM, QDOS,...) avant l'hgmonie du MSDOS (Microsoft DOS). Il existe mme un freeware FreeDOS . Comment grent-ils les disques ? Un disque est un ensemble de secteurs, tous tant numrots. On appelle fichier un ensemble de donnes, regroupes sous un nom. L'OS doit donc disposer d'une table associant le nom du fichier aux secteurs qu'il utilise (FAT File Allocation Table). Au dbut, la FAT tait crite sur les premiers secteurs du disque, et tait assez limite. Il a fallu grer des fichiers de taille variable, et donc stocks sur une suite non continue de secteurs. On a dcompos la FAT en deux parties :

un rpertoire (directory) contenant par exemple 256 noms de fichiers, associs chacun au numro de son premier secteur, et quelques informations supplmentaires (date de cration, taille ou nombre de secteurs...) une table de chanage des secteurs : pour chaque secteur, le numro du secteur suivant pour le mme fichier. Un code spcial est utilis pour le dernier secteur d'un fichier.

Les secteurs libres sont grs comme un gros fichier, donc une suite de secteurs, ce qui permet de trouver facilement des secteurs libres. Chaque systme d'exploitation a dcid d'une gestion de disque (avec ses avantages et limites). Sous MSDOS, la FAT12 stockait les numros de secteurs sur 12 bits (donc 4096 secteurs maxi), avec des noms de fichiers de 8+3 caractres. La FAT16 permet 65000 secteurs, la FAT32 beaucoup plus. Depuis Windows 95, une table annexe associe les noms longs au vrai nom d'un fichier (par exemple "program files" pour PROGRA~1). Les diffrents systmes d'exploitation ont tous une mthode de gestion des disques (File System FS) diffrents (FAT16 sous DOS, FAT32 sous Windows, NTFS pour Windows NT, UFS sur d'anciens Unix, ExFS Extended FS, NFS via rseau (network),...). Mais il y a deux grandes classes : les mono et multi-utilisateurs. Un FS multi-utilisateurs connat le propritaire de chaque fichier, et peut donc grer des droits d'accs diffrents. Sur les FS mono-utilisateur, tout utilisateur ( condition d'avoir pu entrer sur le systme) a les mmes accs aux fichiers. Si l'on veut pouvoir crer beaucoup de fichiers, il faut prvoir de grosses tables, et donc rserver une grosse partie du disque pour cela (surtout du temps des petits disques). La solution a t de ne crer qu'un petit rpertoire, appel racine (root), et permettre de crer dans un rpertoire des fichiers particuliers, nomms sous-rpertoire, avec exactement la mme structure (pouvant donc contenir des fichiers normaux et des sous-rpertoires). Les disques sont donc grs de manire arborescente. Windows a choisi d'appeler "poste de travail" la racine de cet arbre. Sous cette racine, uniquement et obligatoirement les disques, nots A:, C:,... Dans ces disques, un rpertoire (Windows) contient les commandes du systme d'exploitation et ses fichiers de configuration, Program Files regroupe les programmes supplmentaires, Mes Documents les fichiers utilisateur. Sous Unix, la racine est nomme /, les commandes du systme sont dans /bin, les fichiers de configuration dans /etc, les programmes supplmentaires dans /usr, les fichiers utilisateurs dans /home. Les disques peuvent correspondre n'importe quel rpertoire (mais tout ce qui est dans le disque est obligatoire dans ou sous ce rpertoire). On associe un disque un rpertoire par un montage (mount). Si par exemple on monte /home sur un autre disque (ou une autre partition) que le reste, les utilisateurs, mme en saturant leur zone personnelle ne pourront pas saturer le disque systme. Exemple d'arborescence sous Windows98 (PC) et sous Solaris (Station Sun):

Vous pouvez galement regarder le document fourni en TD d'IUP1 concernant les systmes d'exploitation. Commandes de base d'un systme d'exploitation : DOS - Telnet vers Windows afficher la liste des fichiers (dtaille) changer de rpertoire de travail remonter la racine effacer un fichier effacer tous les fichiers crer un rpertoire DIR /V CD rpertoire CD \ DEL fichier DEL *.* MD rpertoire UNIX ls -l cd rpertoire cd / rm fichier rm * mkdir rpertoire rmdir rpertoire cp source dest

supprimer un rpertoire (vide) RD rpertoire copier un fichier COPY source dest

dplacer (ou renommer) un fichier ou rpertoire copier un rpertoire et ses sous-rpertoires grer les accs aux fichiers aide sur une commande

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

mv src dest cp -R src dest chmod man commande

Pour plus de dtails sur les commandes Unix (mais en se limitant aux commandes de base) : les notes d'Hanna Klaudel.

4) Proposition de liens :
En local, une introduction au fonctionnement du PC sous Windows par des tudiants de l'IPST. A l'extrieur, je vous conseille le Cours d'Informatique d'Y. Mairesse (le fonctionnement de l'ordinateur, prsentation Windows et logiciels Micro$oft courants, le tout simple, progressif, avec des petits exercices). Ou regardez les cours du CMIC (Windows, Word, Excel, PowerPoint...).

II -Langages de programmation
Un ordinateur est une machine bte, ne sachant qu'obir, et trs peu de choses :

addition, soustraction, multiplication en binaire, uniquement sur des entiers, sortir un rsultat ou lire une valeur binaire (dans une mmoire par exemple), comparer des nombres.

Sa puissance vient du fait qu'il peut tre PROGRAMM, c'est dire que l'on peut lui donner, l'avance, la squence (la suite ordonne) des ordres effectuer l'un aprs l'autre. Le grand avantage de l'ordinateur est sa rapidit. Par contre, c'est le programmeur qui doit TOUT faire, et surtout tout prvoir. L'ordinateur ne comprenant que des ordres cods en binaire (le langage machine), des langages dits "volus" ont t mis au point pour faciliter la programmation, au dbut des annes 60, en particulier FORTRAN (FORmula TRANslator) pour le calcul scientifique et COBOL pour les applications de gestion. Puis, pour des besoins pdagogiques principalement, ont t crs le BASIC, pour une approche simple de la programmation, et PASCAL au dbut des annes 70. Ce dernier (comme le C) favorise une approche mthodique et discipline (on dit "structure"). Le C a t dvelopp conjointement au systme d'exploitation UNIX, dans les Laboratoires BELL, par Brian W Kernigham et Dennis M Ritchie, qui ont dfini en 78, dans "The C Language", les rgles de base de ce langage. Le but principal tait de combiner une approche structure (et donc une programmation facile) avec des possibilits proches de celles de l'assembleur (donc une efficacit maximale en excution, quitte passer plus de temps de programmation), tout en restant standard (c'est dire pouvoir tre implant sur n'importe quelle machine). Puis ce langage a t normalis en 88 (norme ANSI), cette norme apportant un nombre non ngligeable de modifications au langage. Le C est un langage compil, c'est dire qu'il faut :

entrer un texte (le programme source) dans l'ordinateur ( l'aide d'un programme appel EDITEUR), le traduire en langage machine (c'est dire en codes binaires comprhensibles par l'ordinateur) : c'est la compilation et, si plusieurs modules ont t compils sparment, l'dition de liens (LINK ou BIND), l'excuter.

Contrairement un langage interprts (Basic autrefois, javascript...), l'excution sera beaucoup plus rapide puisqu'il n'y a plus de traduction effectuer, mais la phase de mise au point sera plus complexe. Bien que le langage soit normalis, un certain nombre de points dpendent de la machine et du compilateur utilis (par exemple comment appeler le compilateur). Ces indications ne seront pas donnes ici.

III- Connaissances de base


regardons ce petit programme :
#include <stdio.h> #define TVA 18.6 void main(void) { float HT,TTC; puts ("veuillez entrer le prix H.T."); scanf("%f",&HT); TTC=HT*(1+(TVA/100)); printf("prix T.T.C. %f\n",TTC); }

On trouve dans ce programme :

* des directives du pr processeur (commenant par #) #include : inclure le fichier dfinissant (on prfre dire dclarant) les fonctions standard d'entres/sorties (en anglais STanDard In/Out), qui feront le lien entre le programme et la console (clavier/cran). Dans cet exemple il s'agit de puts, scanf et printf. #define : dfinit une constante. A chaque fois que le compilateur rencontrera, dans sa traduction de la suite du fichier en langage machine, le mot TVA, ces trois lettres seront remplaces par 18.6. Ces transformation sont faites dans une premire passe (appele pr compilation), o l'on ne fait que du "traitement de texte", c'est dire des remplacements d'un texte par un autre sans chercher en comprendre la signification. * une entte de fonction. Dans ce cas on ne possde qu'une seule fonction, la fonction principale (main function). Cette ligne est obligatoire en C, elle dfinit le "point d'entre" du programme, c'est dire l'endroit o dbutera l'excution du programme. * un "bloc d'instructions", dlimit par des accolades {}, et comportant :

IV-Fonctions d'entres/sorties les plus utilises


Le langage C se veut totalement indpendant du matriel sur lequel il est implant. Les entres sorties, bien que ncessaires tout programme (il faut lui donner les donnes de dpart et connatre les rsultats), ne font donc pas partie intgrante du langage. On a simplement prvu des bibliothques de fonctions de base, qui sont nanmoins standardises, c'est dire que sur chaque compilateur on dispose de ces bibliothques, contenant les mme fonctions (du moins du mme nom et faisant apparemment la mme chose, mais programmes diffremment en fonction du matriel). Ces bibliothques ont t dfinies par la norme ANSI, on peut donc les utiliser tout en restant portable. Nous dtaillerons la bibliothque STDIO.H. Sous Turbo C on utilise en plus CONIO.H Je dtaille beaucoup ces fonctions, et si vous n'tes qu'au dbut du cours vous ne pourrez pas tout comprendre (je vous autorise mme passer au chapitre suivant). Sachez simplement qu'au fur et mesure de votre progression vous pourrez retourner ici pour trouver plus de dtails. Dans un premier temps, seuls printf et scanf vous seront ncessaires. En fait, ds les premiers cours, les tudiants me demandent toujours "comment afficher ceci...", c'est pourquoi je mets ces dtails ici. De toute faon je n'ai pas besoin de me justifier, bon sang qui c'est le chef ici ? char putchar(char) : affiche sur l'cran (ou du moins stdout) le caractre fourni en argument (entre parenthses). stdout est l'cran, ou un fichier si on a redirig l'cran (en rajoutant ">nomfichier" derrire l'appel du programme, sous DOS ou UNIX). Si besoin est, cette fonction retourne le caractre affich ou EOF en cas d'erreur. char getchar(void) : attend le prochain appui sur le clavier, et rend le caractre qui a t saisi. Mais attention certains systmes d'exploitation ne transmetent au programme les informations du clavier qu'aprs l'appui de la touche entre (retour chariot). Le retour chariot est lui aussi transmis. De mme, si l'on vient de saisir une valeur numrique, le prochain getchar prendra en compte le retour chariot ou espace qui terminait le nombre. Je conseille donc en gnral la boucle :
do reponse=getchar(); while (reponse<=32);

qui limine le retour chariot (et les autres caractres spciaux).

Sous Turbo C, char getch(void) : attend le prochain appui sur le clavier, et rend le caractre qui a t saisi. (sans attendre le retour chariot), getche(void) fait en plus un cho du caractre l'cran. Dans STDIO.H, on trouve des fonctions plus volues, pouvant traiter plusieurs caractres la suite, et les transformer pour en faire une chane de caractres ou une valeur numrique, entire ou relle par exemple. Les entres sont dites "buffrises", c'est dire que le texte n'est pas transmis, et peut donc encore tre modifi, avant le retour chariot. puts(chane) affiche, sur stdout, la chane de caractres puis positionne le curseur en dbut de ligne suivante. puts retourne EOF en cas d'erreur. gets(chane) lecture d'une chane sur stdin. Tous les caractres peuvent tre entrs, y compris les blancs. La saisie est termine par un retour chariot. Gets retourne un pointeur sur le premier caractre entr (donc gal son paramtre d'entre, ou le pointeur NULL en cas

d'erreur. Attention, gets ne vrifie pas s'il y a eu dbordement de buffer, contrairement fgets (le dbordement est une mthode utilise pour pirater votre ordinateur). On crira donc plutt : fgets(chaine,sizeof(chaine),stdin); printf(format,listevaleurs) affiche la liste de valeurs (variables ou expressions) dans le format choisi. Le format est une chane de caractres entre guillemets (double quote "), dans laquelle se trouve un texte qui sera crit tel quel, des spcifications de format (dbutant par %) qui seront remplaces par la valeur effective des variables, dans l'ordre donn dans listevaleurs, et de caractres spciaux (\). Printf retourne le nombre de caractres crits, ou EOF en cas de problme. Une spcification de format est de la forme :
% [flag][largeur][.prcision][modificateur]type (entre [] facultatifs) Le flag peut valoir : - (cadrage gauche, rajout de blancs si ncessaire droite), + (impression du signe, mme pour les positifs), blanc (impression d'un blanc devant un nombre positif, la place du signe), 0 (la justification d'un nombre se ferra par rajout de 0 gauche au lieu de blancs). D'autres Flags sont possibles mais moins utiles, pour plus de dtails voir l'aide en ligne de Turbo C.

La largeur est le nombre minimal de caractres crire (des blancs sont rajouts si ncessaire). Si le texte crire est plus long, il est nanmoins crit en totalit. En donnant le signe * comme largeur, le prochain argument de la liste de valeurs donnera la largeur (ex printf("%*f",largeur,rel)). La prcision dfinit, pour les rels, le nombre de chiffres aprs la virgule (doit tre infrieur la largeur). Dans la cas d'entiers, indique le nombre minimal de chiffes dsir (ajout de 0 sinon), alors que pour une chane (%s), elle indique la longueur maximale imprime (tronqu si trop long). La prcision peut, comme la largeur, tre variable en donnant .* Le modificateur peut tre : h (short, pour les entiers), l (long pour les entiers, double pour les rels), L (long double pour les rels). Le type est : c (char), s (chane de caractres, jusqu'au \0), d (int), u (entier non sign), x ou X (entier affich en hexadcimal), o (entier affich en octal), f (rel en virgule fixe), e ou E (rel en notation exponentielle), g ou G (rel en f si possible, e sinon), p (pointeur), % (pour afficher le signe %). Les caractres spciaux utilisables dans le format sont \t (tabulation), \n (retour la ligne), \\ (signe \), \nb tout code ASCII, en dcimal, hexa ou octal (\32=\040=\0x20=' '). Essayez printf("\007"); scanf(format,listeadresse) lecture au clavier de valeurs, dans le format spcifi. Les arguments sont des pointeurs sur les variables rsultats (dans le cas de variables scalaires, les prcder par l'oprateur &). Scanf retourne le nombre de valeurs effectivement lues et mmorises (pas les %*). Le format peut contenir (entre ") : du texte (il devra tre tap exactement ainsi par l'utilisateur, et ne sera pas stock), des sparateurs blancs (l'utilisateur devra taper un ou plusieurs blancs,

tabulations, retours la ligne), et des spcifications de format, sous la forme %[*][largeur][modificateur] type. * signifie que la valeur sera lue mais ne sera pas stocke (ex scanf("%d%*c%d",&i,&j) : lecture de deux entiers spars par n'importe quel caractre) la largeur est la largeur maximale lue, scanf s'arrte avant s'il trouve un sparateur (blanc, tab, CR). les modificateurs et types sont les mmes que pour printf (except d,o,x : pour entiers short, D,O,X pour long). Dans le cas des chanes (%s), le blanc est galement un sparateur. On ne pourra donc pas entrer une chane avec des blancs par scanf, il faut utiliser gets. Scanf lit dans stdin en considrant les retours chariot (CR) comme des blancs. on peut donc sparer par des CR plusieurs valeurs demandes dans le mme scanf. Mais de mme, si scanf a pu lire tous ses arguments sans arriver la fin de la ligne, la suite servira au prochain scanf. Utilisez "fflush(stdin)" ou mme "gets(bidon)" avant (pour tre sur de commencer sur une nouvelle ligne) ou aprs scanf (pour ignorer la fin de la ligne) si ncessaire. On dispose aussi de sprintf(chane, format, listevaleurs) qui permet d'crire dans une chane plutt que sur l'cran, et donc faire des conversions numriques ->ASCII; et de sscanf(chane, format,l isteadresse) qui lit dans une chane plutt qu'au clavier. On possde encore d'autres fonctions dans STDIO, en particulier pour grer les fichiers. Sous Linux vous en trouverez la liste par "man 3 stdio".

V-La syntaxe du C

Second exemple, dfinitions Variables / identificateurs / adresse / pointeurs Expressions / oprateurs o Arithmtiques unaires deuxaires o Relationnels comparaisons logique boolenne binaires o Affectation affectation simple = incrmentation / dcrmentation affectation largie o Oprateurs d'adresses o Autres conditionnel ? : squentiel , o Ordre de priorit et associativit Instructions Structures de contrle o Boucles While (tant que)

Do While (faire tant que) For (pour) o Branchements conditionnels If - Else (Si - Sinon) Switch - Case (brancher - dans le cas) o Branchements inconditionnels Break (interrompre) Continue (continuer) Goto (aller ) Return (retourner) Exit (sortir) Dclaration et stockage des variables o Dclarations locales o Dclarations globales o Dclaration de type Fonctions o Dfinitions gnrales o Rcursivit, gestion de la pile o Arguments passs par adresse o La fonction main o Fonction retournant un pointeur et pointeur de fonction Les types de donnes du C o Variables scalaires char : caractre (8 bits) int : entier float : rel Tailles et plages Conversions de type / cast Enumrations Tableaux o Tableaux unidimensionnels o Tableaux et pointeurs / arithmtique des pointeurs o Chanes de caractres o Bibliothques de fonctions pour tableaux et chanes o Allocation dynamique de mmoire o Tableaux multidimensionnels Structures et unions o Dclaration o Utilisation o Champs de bits o Unions o Structures chanes

Second exemple, dfinitions


Considrons le programme ci-dessous :
#include <stdio.h> void affiche_calcul(float,float); /* prototype */ float produit(float,float); int varglob;

int main(void) { float a,b; /* dclaration locale */ varglob=0; puts("veuillez entrer 2 valeurs"); scanf("%f %f",&a,&b); affiche_calcul(a,b); printf("nombre d'appels produit : %d\n",varglob); } float produit(float r, float s) { varglob++; return(r*s); } void affiche_calcul(float x,float y) { float varloc; varloc=produit(x,y); varloc=produit(varloc,varloc); printf("le carr du produit est %f\n",varloc); }

Un programme C est compos (voir le schma complet) :


de directivess du pr-processeur, commenant par #, termines par un retour la ligne (pas de ;). Dans l'exemple il y en a une. de dclarations globales (termines par un ;). Ici on en a trois, deux prototypes et une variable globale. d'une suite de fonctions, crites les unes aprs les autres (sans imbrication comme on le ferait en Pascal). Ici il y en a 3 : main, produit et affiche_calcul.

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

Le corps est compos de dclarations de variables locales, et d'instructions.

Variables / identificateurs / adresse / pointeurs


On appelle variable une mmoire de l'ordinateur (ou plusieurs), laquelle on a donn un nom, ainsi qu'un type (on a prcis ce qu'on mettra dans cette variable (entier, rel, caractre,...), pour que le compilateur puisse lui rserver la quantit de mmoire ncessaire. Dans cette variable, on pourra y stocker une valeur, et la modifier au cours du programme. Exemple : int a; a est une variable entire, le compilateur va lui rserver en mmoire la place ncessaire un entier (2 octets en Turbo C, 4 sous gcc). Le nom de cette variable est choisi par le programmeur. On prfre utiliser le terme identificateur plutt que nom, car il permet d'identifier tout objet que l'on voudra utiliser (pas seulement les variables). Les identificateurs

doivent suivre quelques rgles de base : il peut tre form de lettres (A Z), de chiffres et du caractre _ (soulign). Le premier caractre doit tre une lettre (ou _ mais il vaut mieux le rserver au compilateur). Par exemple valeur1 ou prem_valeur sont possibles, mais pas 1ere_valeur. En C, les minuscules sont diffrentes des majuscules (SURFace et surFACE dsignent deux objets diffrents). Le blanc est donc interdit dans un identificateur (utilisez _). Les lettres accentues sont galement interdites. La plupart des compilateurs acceptent n'importe quelle longueur d'identificateurs (tout en restant sur la mme ligne) mais seuls les 32 premiers caractres sont significatifs. J'appelle "blanc" : soit un espace, soit un retour la ligne, soit une tabulation, soit un commentaire, soit plusieurs de ceux-ci. Les commentaires sont une portion de texte commenant par /* et finissant par le premier */ rencontr. Les commentaires ne peuvent donc pas tre imbriqus. Mais un commentaire peut comporter n'importe quel autre texte, y compris sur plusieurs lignes. La plupart des compilateurs acceptent les commentaires commenant par // et se finissant la fin de la ligne, qui eux peuvent tre inclus dans un commentaire /*...*/ (c'est en fait une extension du C++). Un identificateur se termine soit par un blanc, soit par un signe non autoris dans les identificateurs (parenthse, oprateur, ; ...). Le blanc est alors autoris mais non obligatoire. L'endroit o le compilateur a choisi de mettre la variable est appel adresse de la variable (c'est en gnral un nombre, chaque mmoire d'un ordinateur tant numrote de 0 ? ). Cette adresse ne nous intresse que rarement de manire explicite, mais souvent de manire indirecte. Par exemple, dans un tableau, compos d'lments conscutifs en mmoire, en connaissant son adresse (son dbut), on retrouve facilement l'adresse des diffrentes composantes par une simple addition. On appelle pointeur une variable dans laquelle on place (mmorise) une adresse de variable (o elle est) plutt qu'une valeur (ce qu'elle vaut). Les types de variables scalaires simples que l'on utilise le plus couramment sont le char (un caractre), l'int (entier) et le float (rel). Le char est en fait un cas particulier des int, chaque caractre tant reprsent par son numro de code ASCII.

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

Pour former une expression, les oprateurs possibles sont assez nombreux, nous allons les dtailler suivant les types de variables qu'ils grent.
Arithmtiques

Ces oprateurs s'appliquent des valeurs entires ou relles.


unaires

Ce sont les oprateurs un seul argument : - et + (ce dernier a t rajout par la norme ANSI). Le rsultat est du mme type que l'argument.
deuxaires

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

Relationnels comparaisons

Ces oprateurs sont deuxaires : = = (galit), != (diffrent), <, >, <=, >=. Des deux cts du signe opratoire, il faut deux oprandes de mme type (sinon, transformation implicite) mais

numrique (les caractres sont classs suivant leur numro de code ASCII). Le rsultat de l'opration est 0 si faux, 1 si vrai (le rsultat est de type int). Exemple : (5<7)+3*((1+1)= =2) donne 4. Attention, le compilateur ne vous prvient pas si vous avez mis = au lieu de = = (= est aussi un oprateur, voir plus loin), mais le rsultat sera diffrent de celui prvu.
logique boolenne

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

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

En C, l'affectation (signe =) est une opration comme une autre. Elle ncessite deux oprantes, un droite, appel Rvalue, qui doit tre une expression donnant un rsultat d'un type donn, et un gauche (Lvalue) qui doit dsigner l'endroit en mmoire o l'on veut stocker la Rvalue. Les deux oprandes doivent tre de mme type, dans le cas d'oprandes numriques si ce n'est pas le cas le compilateur effectuera une conversion implicite (la Lvalue doit tre de type "plus fort" que la Rvalue). L'opration d'affectation rend une valeur, celle qui a t transfre, et peut donc servir de Rvalue. Exemples : a=5 (met la valeur 5 dans la variable a. Si a est float, il y a conversion implicite en float); b=(a*5)/2 (calcule d'abord la Rvalue, puis met le rsultat dans b); a=5+(b=2) (Le compilateur lit l'expression de gauche droite. la premire affectation ncessite le calcul d'une Rvalue : 5+(b=2). Celle ci comporte une addition, dont il value le premier oprande (5) puis le second (b=2). Il met donc 2 dans b, le rsultat de l'opration est 2, qui sera donc ajout 5 pour tre mis dans a. A vaut donc 7 et b, 2. Le rsultat de l'expression est 7 (si l'on veut s'en servir). Remarque : il ne faut pas confondre = et = =. Le compilateur ne peut pas remarquer une erreur (contrairement au Pascal ou Fortran) car les deux sont possibles. Exemple : if (a=0) est

toujours faux car quelle que soit la valeur initiale de a, on l'crase par la valeur 0, le rsultat de l'opration vaut 0 et est donc interprt par IF comme faux.
incrmentation / dcrmentation

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

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

Ces oprateurs sont utilises avec des pointeurs. On utilise


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

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

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

Cet oprateur permet de regrouper deux sous expressions en une seule. On effectue le premier oprande puis le second, la valeur finale de l'expression tant celle du second oprande. On l'utilise pour valuer deux (ou plus) expressions l o la syntaxe du C ne nous permettait que

d'en mettre une, exemple : for(i=j=0;i>10;i++,j++). Dans le cas d'une utilisation de cet oprateur dans une liste, utilisez les parenthses pour distinguer les signes , : exemple (inutile) : printf("%d %d",(i++,j++),k) i est modifi mais sa valeur n'est pas affiche.
Ordre de priorit et associativit oprateurs () [] -> . ! ~ ++ -- - + & * (cast) */% +>> << < <= > >= = = != & ^ | && || ?: = += -= *= etc. , associativit -> <-> -> -> -> -> -> -> -> -> -> -> <<unaires (* pointeurs) multiplicatifs addition dcalages relations d'ordre galit binaire binaire binaire logique logique conditionnel (ternaire) affectation squentiel description

Dans ce tableau, les oprateurs sont classs par priorit dcroissante (mme priorit pour les oprateurs d'une mme ligne). Les oprateurs les plus prioritaires seront valus en premier. L'associativit dfinit l'ordre d'valuation des oprandes. La plupart se font de gauche droite ( 4/2/2 donne (4/2)/2 donc 1 (et pas 4/(2/2))). Les seules exceptions sont :

les oprateurs unaires, crits gauche de l'oprateur. L'oprande est valu puis l'opration est effectue, le rsultat est celui de l'opration; sauf dans le cas de l'incrmentation /

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

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

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

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


While (tant que)

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

scanf("%f",&nombre); while (racine*racine<nombre) racine+=0.01; printf("la racine de %f vaut %4.2f 1%% prs\n", nombre, racine); }

Exercice (while_puiss) : faire un programme qui affiche toutes les puissances de 2, jusqu' une valeur maximale donne par l'utilisateur. On calculera la puissance par multiplications successives par 2. Cliquez ici pour une solution.

Exercice (while_err) : que fait ce programme ?


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

Cliquez ici pour une solution. Do While (faire tant que)

structure : do instruction while (expression); (attention au ; final) comme while, mais l'instruction est au moins faite une fois, avant la premire valuation de l'expression. exemple :
#include <stdio.h> int main(void) { int a; do { puts("veuillez entrer le nombre 482"); scanf("%d",&a); } while (a!=482); puts("c'est gentil de m'avoir obi"); }

Les boucles sont la solution employer pour effectuer plusieurs fois la mme instruction (ou bloc d'instructions). L'exemple ci-dessus montre l'utilisation d'un do-while pour une action qu'on a prvu

d'effectuer une seule fois normalement, mais que peut-tre on effectuera plusieurs fois. Donc en C on ne dit jamais "faire X, si erreur recommencer X", mais "faire X tant que erreur".

Exercice (do_while) : crivez un programme de jeu demandant de deviner un nombre entre 0 et 10 choisi par l'ordinateur. On ne donnera pas d'indications avant la dcouverte de la solution, o l'on indiquera le nombre d'essais. La solution sera choisie par l'ordinateur par la fonction rand() qui rend un entier alatoire (dclare dans stdlib.h). Cliquez ici pour une solution.
For (pour)

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

Une ou plusieurs des trois expressions peuvent tre omises, l'instruction peut tre vide. for(;;); est donc une boucle infinie.

exemple :
{ char c; for(c='Z';c>='A';c--)putchar(c); }

Exercice (for) : faire un programme qui calcule la moyenne de N notes. N et les notes seront saisies par scanf. Le calcul de la moyenne s'effectue en initialisant une variable 0, puis en y ajoutant progressivement les notes saisies puis division par N. Cliquez ici pour une solution. Branchements conditionnels

On a souvent besoin de n'effectuer certaines instructions que dans certains cas. On dispose pour cela du IF et du SWITCH.
If - Else (Si - Sinon)

structure : if (expression) instruction1 ou : if (expression) instruction1 else instruction2 Si l'expression est vraie (!=0) on effectue l'instruction1, puis on passe la suite. Sinon, on effectue l'instruction 2 puis on passe la suite (dans le cas sans else on passe directement la suite).

Exercice (jeu) : modifier le jeu de l'exercice (do_while) en prcisant au joueur chaque essai si sa proposition est trop grande ou trop petite. Cliquez ici pour une solution. L'instruction d'un if peut tre un autre if (imbriqu) exemple :
if(c1) i1; else if (c2) i2; else if (c3) i3; else i4; i5;

si c1 alors i1 puis i5, sinon mais si c2 alors i2 puis i5, ... Si ni c1 ni c2 ni c3 alors i4 puis i5.

Le else tant facultatif, il peut y avoir une ambigut s'il y a moins de else que de if. En fait, un else se rapporte toujours au if non termin (c'est dire qui on n'a pas encore attribu de else) le plus proche. On peut aussi terminer un if sans else en l'entourant de {}. exemple : if(c1) if(c2) i1; else i2; : si c1 et c2 alors i1, si c1 et pas c2 alors i2, si pas c1 alors (quel que soit c2) rien. if (c1) {if (c2) i1;} else i2; : si c1 etx>=0) if (y>=0 c2 alors i1, si c1 et pas c2 alors rien, si pas c1 alors i2. Un programmeur expriment utilisera toujours un maximum de "else" :
if(x>=0 && y>=0)....; if(x<=0 && y>=0)....; if(x>=0 && y<=0)....; if(x<0 && y<0)....; if(x>=0) if (y>=0)....; else ....; /*x>=0 et y<0*/ else if (y>=0)....; /*x<0 de toute faon*/ else ....; /*x<0 et y<0*/

mauvais : toujours 8 tests quelques risques d'erreur (si nul)

bon : toujours 2 tests et impossibilit de faire deux cas ou aucun

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

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

Cette structure de contrle correspond un "goto calcul". structure :

switch (expression_entire) { case cste1:instructions case cste2:instructions ........ case csteN:instructions default :instructions }

L'expression ne peut tre qu'entire (char, int, long). L'expression est value, puis on passe directement au "case" correspondant la valeur trouve. Le cas default est facultatif, mais si il est prvu il doit tre le dernier cas.

exemple : fonction vrifiant si son argument c est une voyelle.


int voyelle(char c) { switch(c) { case 'a': case 'e': case 'i': case 'o': case 'u': case 'y':return(1); /* 1=vrai */ default :return(0); } }

Remarque : l'instruction break permet de passer directement la fin d'un switch (au } ). Dans le cas de switch imbriqus on ne peut sortir que du switch intrieur.

Exemple :
switch (a) { case 1:inst1;inst2;....;break; case 2:....;break; default:..... } /*endroit o l'on arrive aprs un break*/

Exercice (calcul) : faire un programme simulant une calculatrice 4 oprations. Cliquez ici pour une solution. Branchements inconditionnels

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

Break (interrompre)

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

*/

Continue (continuer)

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

peut tre remplac par :


for (i=0;i<10;i++) if (i!=j) { ...... }

Goto (aller )

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

Return (retourner)

Permet de sortir de la fonction actuelle (y compris main), en se branchant son dernier }. Return permet galement (et surtout) de rendre la valeur rsultat de la fonction. structure : return; ou return(expression);

exemple :
int max(int a, int b) {if (a>b) return(a); else return(b);}

Exit (sortir)

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

Dclaration et stockage des variables


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

Dans tout bloc d'instructions, avant la premire instruction, on peut dclarer des variables. Elles seront alors "locales au bloc" :elles n'existent qu' l'intrieur du bloc. Ces variables sont mises en mmoire dans une zone de type "pile" : quand, l'excution, on arrive sur le dbut du bloc ({), on rserve la mmoire ncessaire aux variables locales au sommet de la pile (ce qui en augmente la hauteur), et on les retire en quittant le bloc. L'intrt consiste n'utiliser, un instant donn, que la quantit ncessaire de mmoire, qui peut donc resservir par aprs pour d'autres variables. Le dsavantage (rarement gnant et pouvant tre contrecarr par la classe STATIC) est qu'en quittant le bloc (par } ou un branchement), et y entrant nouveau plus tard (par son {), les variables locales ne sont plus ncessairement recres au mme endroit, et n'auront plus le mme contenu. De plus la libration/rservation de la pile aura fait perdre un peu de temps. Par contre, lorsque l'on quitte temporairement un bloc (par appel une fonction), les variables locales restent rserves. La sortie d'un bloc par un branchement gre la libration des variables locales, mais seule l'entre dans un bloc par son { gre leur cration. Exemple :
#include <stdio.h> int doubl(int b) {int c; c=2*b; b=0; return(c); } int main(void) { int a=5; printf("%d %d\n",doubl(a),a); }

A l'entre du bloc main, cration de a, qui l'on donne la valeur 5. Puis appel de doubl : cration de b au sommet de la pile, on lui donne la valeur de a. Puis entre dans le bloc, cration sur la pile de c, on lui donne la valeur b*2=10, on annule b (mais pas a), on rend 10 la fonction appelante, et on libre

le sommet de la pile (c et b n'existent plus) mais a reste (avec son ancienne valeur) jusqu' la sortie de main. On affichera donc : 10 5.

Une variable locale est cre l'entre du bloc, et libre la sortie. Cette priode est appele sa dure de vie. Mais pendant sa dure de vie, une variable peut tre visible ou non. Elle est visible : dans le texte source du bloc d'instruction partir de sa dclaration jusqu'au }, mais tant qu'une autre variable locale de mme nom ne la cache pas. Par contre elle n'est pas visible dans une fonction appele par le bloc (puisque son code source est hors du bloc). autre exemple :
int main(void); {int a=1; {int b=2; {int a=3; fonction(a); } fonction(a); } } int fonction (int b) {int c=0; c=b+8; } [1] [2] [3] [4] [5] [6] [7] [8] [a] [b] [c] [d]

analysons progressivement l'volution de la pile au cours du temps (en gras : variable visible) :

[1] a=1 [2] a=1 | b=2 [3] a=1 | b=2 | a=3 : seul le a le plus haut est visible (a=3), l'autre vit encore (valeur 1 garde) mais n'est plus visible. [4a] a=1 | b=2 | a=3 | b=3 : entre dans la fonction, recopie de l'argument rel (a) dans l'argument formel (b). Mais a n'est plus visible. [4b] a=1 | b=2 | a=3 | b=3 | c=0 [4c] a=1 | b=2 | a=3 | b=3 | c=11 : quand le compilateur cherche la valeur de b, il prend la plus haute de la pile donc 3 (c'est la seule visible), met le rsultat dans le c le plus haut. L'autre b n'est pas modifi. [4d] a=1 | b=2 | a=3 : suppression des variables locales b et c du sommet de la pile [5] a=1 | b=2 : sortie de bloc donc libration de la pile [6a] a=1 | b=2 | b=1 : l'argument rel (a) n'est plus le mme qu'en [4a] [6b] a=1 | b=2 | b=1 | c=0 [6c] a=1 | b=2 | b=1 | c=9 [6d] a=1 | b=2 : suppression b et c [7] a=1 [8] la pile est vide, on quitte le programme Notez que la rservation et l'initialisation prennent un peu de temps chaque entre du bloc. Mais ne prsumez jamais retrouver une valeur sur la pile, mme si votre programme n'a pas utilis la pile entre temps (surtout sur systme multitche ou avec mmoire virtuelle). Une dclaration a toujours la structure suivante : [classe] type liste_variables [initialisateur]; (entre [] facultatif)

Le type peut tre simple (char, int, float,...) ou compos (tableaux, structures..., voir plus loin). La liste_variables est la liste des noms des variables dsires, spares par des virgules s'il y en a plusieurs. Chaque nom de la liste peut tre prcds d'une *, ceci spcifiant un pointeur. L'initialisateur est un signe =, suivi de la valeur donner la variable lors de sa cration ( chaque entre du bloc par exemple). La valeur peut tre une constante, mais aussi une expression avec des constantes voire des variables (visibles, dj initialises). La classe peut tre :

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

Dclarations globales

Une dclaration faite l'extrieur d'un bloc d'instructions (en gnral en dbut du fichier) est dite globale. La variable est stocke en mmoire statique, sa dure de vie est celle du programme. Elle est visible de sa dclaration jusqu' la fin du fichier. Elle sera initialise une fois, l'entre du programme (initialise 0 si pas d'autre prcision). Le format d'une dclaration globale est identique une dclaration locale, seules les classes varient. Par dfaut, la variable est publique, c'est dire qu'elle pourra mme tre visible dans des fichiers compils sparment (et relis au link). La classe static, par contre, rend la visibilit de la variable limite au fichier actuel. La classe extern permet de dclarer une variable d'un autre fichier (et donc ne pas lui rserver de mmoire ici, mais la rendre visible). Elle ne doit pas tre initialise ici. Une variable commune plusieurs fichiers devra donc tre dclare sans classe dans un fichier (et y tre initialise), extern dans les autres.

Toute fonction, pour pouvoir tre utilise, doit auparavant tre soit dclare, soit dfinie. On dtaillera sa dfinition au paragraphe suivant. Une dclaration de fonction est gnralement globale, et alors connue des autres fonctions. Une dclaration de fonction est appele "prototype". Le prototype est de la forme :
[classe] type_retourn nom_fonction(liste_arguments); elle est donc identique l'entte de la fonction mais : - est termine par un ; comme toute dclaration - les noms des arguments n'ont pas besoin d'tre les mmes, il peuvent mme tre omis (les types des arguments doivent tre identiques).

Sans prcision de classe, la fonction est publique. Sinon, la classe peut tre extern (c'est ce que l'on trouve dans les fichiers .H) ou static (visibilit limite au fichier). Le prototype peut tre utilis pour utiliser une fonction du mme fichier, mais avant de l'avoir dfinie (par exemple si l'on veut main en dbut du fichier). En gnral, lorsque l'on cre une bibliothque (groupe de fonctions et variables regroupes dans un fichier compil sparment), on prpare un fichier regroupant toutes les dclarations extern, not .H, qui pourra tre inclus dans tout fichier utilisant la bibliothque. exemples de dclarations globales :
int i,j; /* publiques, initialises 0 */ static int k=1; /* prive, initialise 1 */ extern int z; /* dclare (et initialise) dans un autre fichier */ float produit(float,float); /* prototype d'une fonction dfinie plus loin dans ce fichier */ extern void change(int *a, int *b); /* prototype d'une fonction dfinie dans un autre fichier */

Avant la norme ANSI, le prototype n'existait pas. Une fonction non dfinie auparavant tait considre comme rendant un int (il fallait utiliser un cast si ce n'tait pas le cas).
Dclaration de type

La norme ANSI permet de dfinir de nouveaux types de variables par typedef. structure : typedef type_de_base nouveau_nom; Ceci permet de donner un nom un type donn, mais ne cre aucune variable. Une dclaration typedef est normalement globale et publique. exemple :
typedef long int entierlong; /* dfinition d'un nouveau type */ entierlong i; /* cration d'une variable i de type entierlong */ typedef entierlong *pointeur; /* nouveau type : pointeur = pointeur d'entierlong */ pointeur p; /* cration de p (qui contiendra une adresse), peut tre initialis par =&i */

Remarques : Le premier typedef pouvait tre remplac par un #define mais pas le second. L'utilisation du typedef permet de clarifier les programmes, puisqu'on peut dfinir un nom de type pour dclarer des variables dont la fonction sera clairement dtaille :

typedef float prix; typedef float quantite; prix a,b,c; quantite x,y;

Une autre utilisation importante est pour dfinir un type complexe, o l'on dfinira petit petit les composants (exemple tableau de pointeurs de structures, on dfinit d'abord la structure, puis le pointeur de structure, puis le tableau de tout cela).

Fonctions
Dfinitions gnrales

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

Une fonction possde ses propres variables locales (ici i), mais n'a pas accs aux variables locales des autres fonctions, pas mme celles de main (mais aux variables globales, que je dconseille fortement au dbutant)

2) fonction recevant un (ou plusieurs) argument(s), et ne retournant rien. Une telle fonction a besoin d'informations pour effectuer sa tche, ce qui est le cas le plus courant.
void insiste(int combien_de_fois,char * compliment) { int i; printf("Oh que tu es "); for(i=0;i<combien_de_fois;i++)printf("%s ",compliment); printf("!!!!! \n); } On appelle cette fonction par exemple par insiste(10,"belle"); ou int j=3; char i[]="fatiguant"; insiste(j*2,i); Tout ce qui importe l'appel est qu'on donne en premier

argument une valeur entire et en second une chane de caractres. Leurs noms n'ont aucun rapport avec ceux utiliss dans la fonction.

3) fonction recevant un (ou plusieurs, ou aucun) argument(s), et retournant une valeur. Une telle fonction a besoin (ou non) d'informations pour effectuer sa tche, mais quand la tche est termine (en gnral c'tait un calcul), elle rend (retourne) le rsultat (une et une seule valeur). Je vous rapelle que les variables locales de la fonction ne sont pas visibles l'extrieur de la fonction, sans cela les calculs auraient t inutiles.
float produit(float a;float b) { float z; z=a*b; return(z); }

On appelle cette fonction dans une expression ncessitant un float : x=produit(2,4.5); ou y=2+produit(sin(x)+produit(1.12,X),produit(2,2)) ou printf("le double de %f
est %f\n",z,produit(z,2));

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

Une fonction peut s'appeler elle-mme :


int factorielle(int i) { if (i>1) return(i*factorielle(i-1)); else return(1); }

analysons la pile en appelant factorielle(3) :

i=1
i=2 i=2 i=2

i=3 i=3 i=3 i=3 i=3 (a) (b) (c) (d) (e)

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

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

Arguments passs par adresse

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

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

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

En conclusion, pour effectuer un passage d'argument par adresse, il suffit d'ajouter l'oprateur & devant l'argument rel ( l'appel de la fonction), et l'oprateur * devant chaque apparition de l'argument formel, aussi bien dans l'entte que le bloc de la fonction.
La fonction main

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

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

type*fonc(arguments) est une fonction qui renvoie un pointeur. exemple :


int *max(int tableau[], int taille) { int i,*grand; for(grand=tableau,i=1;i<taille;i++) if(tableau[i]>*grand) grand=tableau+i; return(grand); }

Cette fonction rend l'adresse du plus grand entier du tableau.

type (*fonc)(arguments) est un pointeur sur une fonction exemple :


int max(int,int); int min(int,int); int main(void); {

int (*calcul)(int,int); /* calcul est un pointeur donc une variable qui peut tre locale */ char c; puts("utiliser max (A) ou min (I) ?"); do c=getchar(); while ((c!='A')&&(c!='I')); calcul=(c=='A')?&max:&min; printf("%d\n",(*calcul)(10,20)); } int max(int a,int b) {return(a>b?a:b);} int min(int a,int b) {return(a<b?a:b);}

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

Les types de donnes du C


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

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

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

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

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

Si l'on dsire une taille prcise, utiliser short int (16 bits) ou long int (32 bits). Sans prcision, int donnera les programmes les plus rapides pour une machine donne (int = short

sur PC, mais long sur les stations 32 bits). Par dfaut, les int sont signs, mais on peut prciser unsigned int. Dsormais certains compilateurs considrent short comme 16 bits, int comme 32 bits et long comme 64 bits.
float : rel

Un flottant est un nombre stock en deux parties, une mantisse et un exposant. La taille de la mantisse dfinit le nombre de chiffres significatifs, alors que la taille de l'exposant dfinit le plus grand nombre acceptable par la machine. Les oprations sur les rels sont plus lents que sur les entiers. Pour une addition par exemple, il faut d'abord dcaler la mantisse pour galiser les exposants puis faire l'addition. Les rels sont toujours signs. On peut par contre utiliser le spcificateur long pour des rels avec une prcision accrue. On peut galement utiliser le nom double au lieu de long float. Certains compilateurs acceptent mme des long double (quadruple prcision).
Tailles et plages

type
char unsigned char short (short int) unsigned short long (long int) unsigned long float double (long float) long double (non standard)

taille (en bits) plage de valeurs 8 8 16 16 32 32 32 64 80 ou 128 -128 +127 0 255 -32768 32767 0 65535 -2.147.483.648 2.147.483.647 0 4.294.967.295 -3.4e38 3.4e38 (7 chiffres significatifs) -1.7e308 1.7e308 (15 chiffres significatifs) a dpend

Conversions de type / cast

Dans les calculs, les char sont automatiquement transforms en int. Quand un oprateur possde des arguments de type diffrent, une transformation de type est effectue automatiquement, suivant l'ordre : char -> int -> long -> float -> double signed -> unsigned

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

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

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

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

On peut aussi prvoir une codification non continue :


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

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

Tableaux

Tableaux unidimensionnels

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

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

On peut galement dclarer un tableau sans en donner sa dimension. Dans ce cas l le compilateur ne lui rserve pas de place, elle aura du tre rserve autre part (par exemple tableau externe ou argument formel d'une fonction). Exercice (moyenne) : Ecrire le programme qui lit une liste de Nb nombres, calcule et affiche la moyenne puis l'cart entre chaque note et cette moyenne. Cliquez ici pour une solution.
Tableaux et pointeurs / arithmtique des pointeurs

En dclarant, par exemple, int TAB[10]; l'identificateur TAB correspond en fait l'adresse du dbut du tableau. Les deux critures TAB et &TAB[0] sont quivalentes (ainsi que TAB[0] et *TAB). On dfinit l'opration d'incrmentation pour les pointeurs par TAB+1=adresse de l'lment suivant du tableau. L'arithmtique des pointeurs en C a cette particularit que l'opration dpend du type de variable pointe, ajouter 1 consistant ajouter l'adresse la taille de l'objet point. On dfinit donc l'addition (pointeur+entier): TAB+i=&TAB[i], la soustraction (pointeur - entier), mais galement la soustraction (pointeur - pointeur) qui donne un nombre d'lments. Les oprations de comparaisons entre pointeurs sont donc galement possibles. Dclarons : int TAB[10],i,*ptr; Ceci rserve en mmoire - la place pour 10 entiers, l'adresse du dbut de cette zone est TAB, - la place pour l'entier i, - la place pour un pointeur d'entier (le type point est important pour dfinir l'addition). Analysons les instructions suivantes :

ptr=TAB; /*met l'adresse du dbut du tableau dans ptr*/ for(i=0;i<10;i++) { printf("entrez la %dime valeur :\n",i+1); /* +1 pour commencer 1*/ scanf("%d",ptr+i); /* ou &TAB[i] puisque scanf veut une adresse*/ } puts("affichage du tableau"); for(ptr=TAB;ptr<TAB+10 /* ou &TAB[10] */;ptr++) printf("%d ",*ptr); puts(" "); /* attention actuellement on pointe derrire le tableau ! */ ptr-=10; /* ou plutt ptr=TAB qui lui n'a pas chang */ printf("%d",*ptr+1); /* affiche (TAB[0])+1 */ printf("%d",*(ptr+1)); /* affiche TAB[1] */ printf("%d",*ptr++); /* affiche TAB[0] puis pointe sur TAB[1] */ printf("%d",(*ptr)++); /* affiche TAB[1] puis ajoute 1 TAB[1]*/

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

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

Exercice (rotation) : Ecrire un programme qui lit une liste de Nb nombres, la dcale d'un cran vers le haut (le premier doit se retrouver en dernier), l'affiche puis la dcale vers le bas. On pourra dcomposer le programme en fonctions. Cliquez ici pour une solution.

Exercice (classer) : Classer automatiquement un tableau de Nb entiers puis l'afficher dans l'ordre croissant puis dcroissant. On pourra utiliser des fonctions de l'exercice prcdent. On pourra crer un (ou plusieurs) tableau temporaire (donc local). Si vous vous en sentez la force, prvoyez le cas de valeurs gales. Cliquez ici pour une solution.

Chanes de caractres

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

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

Le compilateur cre la chane en mmoire de code (constante) et une variable strptr contenant l'adresse de la chane. Le programme pourra donc changer le contenu de strptr (et donc pointer sur une autre chane), mais pas changer le contenu de la chane initialement cre. Exercice (chanes) : crire un programme qui dtermine le nombre et la position d'une souschane dans une chane (exemple ON dans FONCTION : en position 1 et 6). Cliquez ici pour une solution.
Bibliothques de fonctions pour tableaux et chanes

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

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

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

int memcmp(void *s1,void *s2,int longueur); void *memcpy(void *dest,void *src,int longueur);

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

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

J'utilise galement sscanf et sprintf pour les conversions, qui offrent un maximum de possibilits.

Dans ctype.h, on trouve des fonctions utiles (limites au caractres) :


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

Allocation dynamique de mmoire

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

malloc et calloc ncessitent un cast pour que le compilateur ne signale pas d'erreur.

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

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

On peut remarquer que ces tableaux ne sont pas totalement dynamiques : la dimension doit tre connue (et l'allocation faite) avant la premire utilisation du tableau. Elle est ensuite difficilement modifiable (du moins pour l'augmenter). On ne peut par exemple pas dire "entrez vos mots, terminez par une ligne vide", mais "combien de mots ?" puis "entrez vos mots".

Une erreur frquente consiste "perdre" l'adresse du dbut de la zone alloue (par tab=quelque chose, ou parce que tab tait une variable locale qui ne vit plus) et donc il devient alors impossible d'accder au dbut de la zone, ni de la librer.
Tableaux multidimensionnels

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

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

soit crer une matrice complte : allocation par t=malloc(NBLIG* NBCOL* sizeof(lment)), accs l'lment l,c par *(t+l*NBCOL+c).

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

Exercice (matrices) : faire le calcul de multiplication d'une matrice (M lignes, L colonnes) par une matrice (L,N) : rsultat (M,N). Cliquez ici pour une solution.

Exercice (dterminant) : crire un programme qui calcule le dterminant d'une matrice carre (N,N), sachant qu'il vaut la somme (sur chaque ligne) de l'lment de la ligne en 1re colonne par le dterminant de la sous-matrice obtenue en enlevant la ligne et la 1re colonne (en changeant le signe chaque fois). Le dterminant d'une matrice (1,1) est sont seul lment. On utilisera bien videment la rcursivit. Il existe (heureusement) d'autres mthodes plus rapides. Cliquez ici pour une solution. Vous trouverez d'autres exercices sur les tableaux dans les TP d'IUP2, par exemple celui-ci.

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

dclaration : struct nom_type {dclaration des champs} liste_variables ; exemple :


struct identite { char nom[30]; char prenom[20]; short int age; }jean,jacques,groupe[20];

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

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

Utilisation

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

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

Les structures sont tres importantes en C, et encore plus dans les langages plus rcents, les objets en dcoulent. Les champs d'une structure peuvent tre relativement complexes (tableaux, autres structures), et on peut crer des tableaux de structures. Exemple : dclarez le types et variables suivants : une date est compose de 3 entiers j, m et a. Une opration est une rfrence (entier long), une quantit et un prix unitaire. Une facture est une date, un numro de client, un nombre d'oprations et au maximum 20 oprrations. Un client est un numro, un nom et une adresse. Un produit est une rfrence, une dsignation, un prix unitaire, une quantit en stock. Une compta est un tableau de 1000 factures. Un stock est un tableau (dynamique) de produits. Et l'on peut continuer ainsi longtemps. Vous trouverez d'autres questions de ce genre dans mes sujets d'examens, plutt dans les dernires questions. En particulier IUP2 en 2003.
Champs de bits

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

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

Unions

dclaration : union nom_type {dclaration des champs} liste_variables ; Les diffrents champs commenceront tous la mme adresse (permet d'utiliser des variables pouvant avoir des types diffrents au cours du temps, mais un seul un instant donn). Les champs peuvent tre de tout type, y compris structures. On les utilise comme les structures, avec les oprateurs "." et "->". Exercice (tel) A l'aide d'un tableau de personnes (nom, prnom, numro dans la rue, rue, code postal, ville, numro de tlphone), faire un programme de recherche automatique de toutes les informations sur les personnes rpondant une valeur d'une rubrique donne (tous les PATRICK , tous ceux d'Obernai, travaillant l'IPST, etc...). On suppose que le tableau est dj initialis. Cliquez ici pour une solution.
Structures chanes

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

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

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


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

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


struct page *premier;

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


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

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


precedent->suivant=nouveau;

le dernier lment ne doit pas pointer sur n'importe quoi, on choisit gnralement soit le pointeur NULL, soit le premier (la liste est dite boucle). exemple :
#include <stdio.h> #include <conio.h> #include <ctype.h> #include <alloc.h> /*ou stdlib.h*/ struct page {int val; struct page *suivant; }; struct page *premier; int encore(void) /* demande si on en veut encore*/ { printf("encore (O/N) ? "); return(toupper(getche())= ='O'); } void lecture(void) { struct page *precedent,*nouveau; premier=(struct page *)malloc(sizeof(struct page)); puts("entrez votre premier entier"); scanf("%d",&premier->val); precedent=premier; while (encore()) { nouveau=(struct page *)malloc(sizeof(struct page)); precedent->suivant=nouveau; precedent=nouveau; puts("\nentrez votre entier"); scanf("%d",&nouveau->val); } precedent->suivant=NULL; } void affiche(struct page *debut) { printf("\nliste : "); while(debut!=NULL) { printf("%d ",debut->val); debut=debut->suivant; } printf("\n"); } int main(void) { lecture(); affiche(premier); }

Exercice (liste) : modifier la fonction lecture ci-dessus pour que la liste soit stocke dans l'ordre inverse de son introduction (chaque nouvel lment est plac devant la liste dj existante). Cliquez ici pour une solution de cet exercice (et du suivant).

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

Exercice (insertion) : ajouter au programme prcdent une procdure d'insertion d'une valeur dans la liste. La solution de l'exercice prcdent contient galement cette insertion

Ce type de donnes (structure pointant sur un mme type) est utilis dans d'autres cas. Par exemple, pour reprsenter un arbre, il suffit pour chaque lment de connatre l'adresse de chaque fils :

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