Professional Documents
Culture Documents
cole Nationale Suprieure des Mines de Saint-Etienne 158 Cours Fauriel 42023 Saint-tienne Cdex
Introduction
Avant-Propos
Lobjectif gnral de ce cours est lapprentissage de mthodes et outils permettant de rsoudre certains problmes de lingnieur ncessitant un traitement automatis de linformation. Lune des nalits est galement de proposer aux lves entrant en premire anne lcole des Mines de Saint-Etienne une mise niveau en programmation. Il sagit donc, dans un premier temps, danalyser un problme, den proposer un modle, et dtablir une mthode de rsolution possible. Cette phase relve des techniques gnrales de modlisation. Dans un second temps, le modle et la mthode de rsolution choisis sont implments en utilisant efcacement la technologie informatique retenue : lcriture dun programme de traitement en langage C.
0.1 Prambule
0.1.1 Le support de cours
Ce document est une brve introduction la programmation. Pour des raisons qui sont dtailles plus loin, cette introduction seffectue au travers du langage C. Le langage de programmation particulier utilis comme support de la pense dans un cours dinformatique ne devrait jouer quun rle assez secondaire vis vis des concepts enseigns. Ce nest malheureusement presque jamais le cas, et, en particulier dans le cas des initiations linformatique, ce choix du langage joue un rle prpondrant. Lexprience montre que lapprenant est en effet rapidement proccup (voire envahi) par les seuls aspects de mise en uvre du langage choisi, et que les concepts, parfois de haut vol, que lenseignant vise mettre sa disposition sont largement ignors (lorsquils ne sont pas traits par le mpris). Nous voulons construire des programmes solides, et le langage de programmation va nous permettre de crer nos briques. Mais il ne sert rien de construire sur du sable ; la comprhension de la nature et de la structure des donnes, la connaissance de lalgorithmique constituent les fondations de toute construction correcte. Cest un passage difcile, mais oblig. Pour cette raison, nous insistons beaucoup sur les aspects fondamentaux (et en particulier, algorithmiques) de la programmation. Un second cours lui fait suite ([1]), tourn vers les aspects plus formels de lalgorithmique. 5
Terme de remplissage, utilis ici en attendant la prochaine rvolution. . . Ne serait-ce que par sa syntaxe. . .
Cette prsentation sappuie largement sur des exemples, simples au dpart, puis de plus en plus complexes. Il est difcile en effet de prsenter demble la mthodologie universelle ( supposer quil en existe une) : ceci, notre avis, ne revt un intrt quaprs une certaine pratique effective de la rsolution de problmes de complexit non triviale. Dans cette prsentation sont successivement abordes diffrentes facettes de cette analyse, savoir lalgorithmique, la structuration par les traitements et la structuration par les donnes. Il faut bien comprendre que ce qui est prsent ne revt pas le caractre de rgles absolues : ne pas les suivre ne condamne pas ncessairement obtenir des programmes incorrects. Cependant, les suivre permet dobtenir des programmes plus facilement comprhensibles par quelquun qui ne les a pas crits3 , et amliore donc la maintenance de ceux-ci (que ce soit pour corriger ou pour amliorer le programme). On est donc ici plutt dans le domaine des bonnes pratiques.
Thorie
Pratique Partir dun problme simple, (calculer un PGCD, un sinus), en dcrire les tapes de la rsolution.
3
Thorie
Pratique tapes de la cration dune application : dcomposition en procdures. Procdures et compilations spares. Utilisation de procdures de bibliothque correspondant aux fonctions mathmatiques usuelles.
Thorie
Pratique Recherches de minimums, maximums, etc. Remplissage de tableau par des valeurs alatoires. Exemple du calcul des moyennes.
Thorie Analyse de problme, dcomposition en sous-problmes par les donnes ou par les traitements. Pratique Analyse, conception du problme trait en mini-projet.
10
Chapitre 1 Introduction
1.1 Cours
Ce premier chapitre introduit les diverses notions de linformatique pratiquement indispensables la comprhension de lactivit de programmation : ce quest un ordinateur, un chier, un compilateur, et comment mettre en uvre toutes ces choses. . . Aprs cette prsentation sommaire, le cours dcrit quelques lments de base du langage C ainsi que lutilisation de celui-ci sur les machines.
PROCESSEUR
A
bus
MMOIRE
F IG . 1.1 Larchitecture de Von Neumann Les ordinateurs actuels drivent dune architecture dnie en 1946, la Machine de von Neumann (c.f. gure 1.1). Les deux composantes principales sont le processeur (ou unit centrale) et la mmoire, relis entre eux par le bus. Dans cette machine, le problme rsoudre est dcrit sous la forme dinstructions excuter, le programme. Lexcution dun programme est dite processus. La mmoire de la machine contient la fois les instructions et les donnes du processus. Le jeu dinstructions du processeur nest pas universel, mais dpend du modle de la machine. Dautres lments sont connects au bus, ou des bus auxiliaires : les priphriques (cran, clavier, souris, imprimante, modem, scanner, disque dur, lecteur de disquette, de cdrom, etc.), qui permettent la communication avec le monde extrieur et le stockage de linformation. Ces lments sont commands par des instructions spciques du processeur. 11
12 1.1.1.2 La mmoire
CHAPITRE 1. INTRODUCTION
La mmoire dun ordinateur, dite parfois mmoire centrale, peut tre considre comme un espace linaire dlments identiques, les cellules de mmoire. Les lments dune mmoire de taille N sont dsigns par leurs index, entiers de lintervalle [0, N 1]. On appelle galement adresse mmoire lindex dsignant une cellule mmoire. Dans les architectures actuelles, ces cellules sont reprsentes par le regroupement, dit byte ou octet, de huit indicateurs lmentaires, les bits, pouvant avoir chacun la valeur 0 ou 1. Un byte peut donc coder 28 valeurs diffrentes. En langage C, une telle donne permet de reprsenter : soit les valeurs entires comprises entre -128 et +127 (ce qui est appel en C le type char)1 ; soit les valeurs entires comprises entre 0 et 255 (ce qui est appel type unsigned char en C). Selon les besoins du programmeur (et selon les langages de programmation), les cellules de mmoire peuvent tre regroupes (par 2, 3, 4, 8, 16...) pour reprsenter des donnes entires (pouvant prendre de plus grandes valeurs), mais galement des donnes virgule xe ou ottante, des caractres imprimables, des chanes de caractres... 1.1.1.3 Le processeur Le processeur excute des programmes situs en mmoire centrale. Ces programmes comportent des donnes et des instructions destines transformer ces donnes. Une partie du processeur est lunit arithmtique et logique, dont le but est deffectuer diverses instructions de calcul. Le processeur dispose dun petit nombre demplacements de mmoire accs trs rapide (dit registres, au nombre de 16, 32, etc.), qui lui permettent de stocker temporairement des valeurs dlments, et deffectuer des oprations sur ces valeurs (le processeur ne peut effectuer aucune opration directement sur le contenu dune cellule mmoire). 1.1.1.4 Communication entre processeur et mmoire Par lintermdiaire du bus, le processeur peut indiquer une adresse mmoire et lire le groupe dlments (des octets, 2, 4, 8, 16, etc., selon les machines) situs cette adresse. Les valeurs lues sont places dans un ou plusieurs registres du processeur. Celui-ci est ainsi capable de lire les instructions et les donnes lorsque ncessaire. Le processeur peut galement effectuer le transfert dune donne contenue dans un registre vers une cellule mmoire. 1.1.1.5 Les disques durs Les disques durs sont des units de stockage de linformation de grande capacit (dix cent fois la taille de la mmoire centrale), dont le rle est dassurer la conservation permanente des
Trs prcisment, nous devrions crire signed char ; lattribut signed est presque toujours pris par dfaut par le compilateur lorsque unsigned nest pas prcis.
1
1.1. COURS
13
programmes et donnes de lutilisateur. Ces informations sont organises sous la forme dun ou plusieurs systmes de chiers. 1.1.1.6 Les systmes de chiers Un systme de chiers (ceci est vrai pour Unix, Linux, Windows, etc.) est une organisation auto-dcrite, qui comprend deux types dobjets : les rpertoires et les chiers. Un chier est une squence doctets, dsigns globalement par un nom. Ces octets permettent de reprsenter des donnes de lutilisateur ou du systme. Un rpertoire contient des dsignations symboliques (les noms) de chiers ou dautres rpertoires. Il existe dans tout systme de chiers un rpertoire particulier, qui est la racine de ce systme de chiers. Tout chier est dsign par un nom rfrenc dans un rpertoire, et un seul. Tout rpertoire est dsign par un nom, rfrenc dans un autre rpertoire, ou dans la racine. Rpertoires et chiers constituent ainsi une arborescence. Une rfrence un chier particulier (lon parle parfois de cheminom ) se note par la liste des rpertoires quil faut traverser, depuis la racine, pour aboutir ce chier. La syntaxe de cette dsignation dpend des systmes dexploitation. Sous UNIX ou Linux, cest le caractre "/" (slash) qui sert de sparateur. On crira : /users/dupont/exemples/prog17.c Le systme de chiers porte ici le nom users ; le premier slash indique que lensemble a la syntaxe dune rfrence absolue. Sous Windows, cest le caractre "\" qui sert de sparateur ; la dsignation dbute par le nom du disque sur lequel se situe le systme de chiers : c:\users\dupont\exemples\prog17.c Un systme de chiers peut tre install sur un disque dur, mais aussi un CD-ROM, une disquette (sur laquelle lespace disponible est limit 1,44 millions doctets), etc. Fichiers et rpertoires sont dsigns par des noms, qui sont habituellement des suites de lettres et de chiffres. Certains systmes sont trs restrictifs, tels Dos, qui impose des dsignations de la forme XXXXXXXX.YYY , cest--dire huit caractres, le nom proprement dit, suivis dun point, suivis de trois caractres, l extension . Dautres systmes acceptent, avec certaines restrictions, des suites arbitraires de caractres (Mac OS, Windows). Sous UNIX ou Linux, il est possible dutiliser de tels noms, condition de les placer entre caractres quotes ou apostrophes . Nous conseillons pour notre part dutiliser des noms relativement courts (huit lettres, ou moins), constitus de lettres et de chiffres. On vitera en particulier dutiliser, pour les noms de chiers et de rpertoires, des blancs, caractres accentus, et autres signes exotiques. Notons que lextension (le sufxe de la dsignation dun chier) joue un rle important dans la comprhension par lutilisateur de la nature des chiers. Ainsi, les extensions ".c" et ".h" sont rserver pour les programmes crits en C, les chiers ".o" reprsentent des modules objets, rsultats de la compilation dun module source, les ".txt" des chiers de textes, les ".doc" et ".rtf" sont des chiers crs par lutilitaire Word , etc. Sous Windows, mais cette convention ne sapplique pas sous Unix ou Linux, les ".exe" reprsentent des programmes
14
CHAPITRE 1. INTRODUCTION
excutables. Dans certains cas, le systme dexploitation utilise cette extension pour savoir quel programme associer tel chier. Lon voit, par ces derniers exemples, que les chiers rpondent de multiples nalits. De manire trs gnrale, on peut dire quils constituent un support prne 2 linformation. Ils permettent de reprsenter le systme, les informations de lutilisateur, et tout un ensemble de donnes utilises temporairement par le systme ou les programmes de lutilisateur : les zones de travail des diteurs de texte et des compilateurs, les mails envoys ou reus, etc. 1.1.1.7 La programmation des ordinateurs La programmation dun ordinateur se ralise travers le langage machine, qui est complexe et propre chaque modle dordinateur. Lon utilise dans la pratique des langages de haut niveau, tels le C, le Fortran, le Java, etc., qui sont universels. Le plus souvent, un programme spcialis, dit traducteur ou compilateur, transforme le programme ralis en un programme en langage machine spcique lordinateur sur lequel on travaille : cest le processus de compilation. 1.1.1.8 Le systme dexploitation Le systme dexploitation est un complment indispensable lutilisation de tout ordinateur. Il a pour tche de grer les ressources de lordinateur (processeur, mmoire, et surtout priphriques) et de fournir un support lutilisateur, en contrlant lexcution des programmes et utilitaires (compilateur, diteur de texte, etc.). Le systme dexploitation est automatiquement charg en mmoire centrale lors de la mise sous tension de lordinateur, et va ds lors grer le fonctionnement de celui-ci. 1.1.1.9 Lenvironnement de travail Lenvironnement de travail, enn, est lindispensable intermdiaire entre lutilisateur et les ressources de la machine et du systme. Il permet lutilisateur de dclencher lexcution de programmes prexistant sur la machine, dont le rle est de rendre celle-ci utilisable sous une forme conviviale, que ces programmes aient pour but de consulter le Web, lire ou crire du courrier, crer des chiers, des documents, ou dautres programmes. Lenvironnement de travail peut offrir un systme de multi-fentrage (Windows, Gnome sous Linux, etc.) faisant grande consommation des interactions souris, ou un mcanisme bas sur des lignes de commandes telles que : gcc -o prog prog.c Lune des interfaces les plus simples permet uniquement de saisir des lignes tapes au clavier ; chaque ligne est cense reprsenter une commande excuter. Nous disposerons sous Windows dun interprte lmentaire (de nom invite de commande ou parfois bote MS-DOS ), et galement dun interprte plus volu, appel bash. Lon utilise souvent le terme gnrique de shell pour dsigner ces interfaces.
Plus simplement dit, leur contenu perdure lors de la mise hors-tension de lordinateur, alors que celui de la mmoire centrale et des registres de la machine disparat.
2
1.1. COURS
1.1.1.10 Fonctionnement gnral et processus dexcution
15
Parmi les registres du processeur, il en existe un, dit compteur programme, qui dsigne tout instant une adresse en mmoire centrale : cest celle de la prochaine instruction excuter. Lors de lexcution, le processeur effectue les oprations suivantes : 1. il va chercher, dans la mmoire centrale, linstruction dsigne par le compteur programme, et la place dans un registre (dit registre instruction) ; 2. il incrmente le compteur programme, an que celui-ci dsigne linstruction suivante ; 3. il dcode linstruction, vriant quelle est correcte ; 4. il va ventuellement rechercher en mmoire centrale le, ou les oprandes dsigns par linstruction, et les place dans des registres ; 5. il excute linstruction ; cette instruction peut impliquer la modication de certains registres, y compris le compteur programme ; on dit alors que linstruction est un saut, ou un branchement. 6. il va ventuellement crire en mmoire le contenu dun ou plusieurs registres ; 7. le cycle se continue par un retour ltape 1. Le cycle dexcution est le mme, que le processeur soit en train dexcuter des instructions du systme dexploitation, de lenvironnement de lutilisateur, ou du programme de lutilisateur. 1.1.1.11 Notion de variable La mmoire centrale ne contient pas que des instructions, mais sert aussi reprsenter les donnes manipules par le programmeur. Quand le programmeur souhaite utiliser une ou plusieurs cellules mmoire pour stocker une information utile pour le programme, il doit : dterminer le nombre de cellules utiles, cest--dire la taille de la donne ; trouver un emplacement non utilis dans la mmoire permettant de stocker cette donne (suite de cellules contigus en mmoire, de la taille de la donne stocker) ; mmoriser lemplacement (ladresse en mmoire) de la donne pour les manipulations venir ; rserver cet emplacement mmoire an dviter que dautres donnes nutilisent le mme emplacement ; utiliser les bonnes instructions dchange entre le processeur et la mmoire (transfrer le bon nombre doctets, utiliser le bon registre). Ces oprations deviennent trs fastidieuses lorsque la taille du programme (et donc, le nombre de donnes utilises) augmente. La plupart des langages de programmation intgrent donc une gestion des donnes par lintermdiaire dun moyen abstrait : la plupart du temps, ce moyen abstrait est un identicateur, qui est tout simplement un nom choisi par le programmeur. Dans le programme source (en langage de haut niveau), la donne est manipule par son nom ; cest au moment de la traduction en langage machine que le traducteur (le compilateur dans le cas du langage C) effectuera une association entre le nom et lemplacement effectif en mmoire. Toutes les instructions qui manipulent la donne utiliseront lemplacement mmoire associ cette donne. Ces associations nom-emplacement mmoire sont dsignes par le terme
16
CHAPITRE 1. INTRODUCTION
de variables : ce nom prcise que le contenu de la mmoire peut en particulier tre modi par le programme ; la valeur dune variable peut donc changer au cours de lexcution.
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
1.1. COURS
17
la composition et la codication ; les caractres de cette table sont reprsents par des octets, le premier bit de loctet tant 0, les trois suivants le numro de colonne et les quatre suivants le numro de ligne (par exemple, la lettre A est reprsente par la suite binaire 01000001). Un mot, une phrase, un texte sont ainsi reprsentables par une squence doctets. On parle souvent de chane de caractres pour dsigner de telles squences, et les langages de haut niveau disposent de notations spciques pour dnir de telles squences. Le code ASCII ne reprsente cependant quun sous-ensemble trs limit des caractres ncessaires lexpression crite, et dautres normes sont utilises pour la reprsentation des langages indo-europens, du japonais, etc. Malheureusement, ces normes sont multiples, et rien ne garantit la portabilit si ces caractres sont utiliss. Nous retiendrons donc que, pour ne pas avoir de souci, il est : impratif de nutiliser que des caractres du code ASCII pour construire notre code source (instructions, variables, procdures) ; prfrable dviter les caractres non-ASCII (et notamment les caractres accentus) dans les commentaires de programmes, ainsi que dans les chanes de caractres.
18
CHAPITRE 1. INTRODUCTION
deux tapes (compilation et dition des liens) peuvent senchaner directement par la commande unique : gcc prog.c -o prog Enn, lexcution proprement dite seffectue en entrant simplement le nom du programme excuter : prog On notera que si lon travaille sous Windows, le programme doit obligatoirement comporter un sufxe spcique, indiquant quil est excutable, .exe. La commande de compilation scrit alors : gcc prog.c -o prog.exe Le lancement peut seffectuer par le seul nom prog. Dans ce cas particulier, linterprte de commandes va rechercher le chier de nom prog.exe ou prog.com ; les deux sufxes correspondant deux formats dexcutables reconnus par Windows. Note Le compilateur C opre en fait en plusieurs phases : la compilation proprement dite est prcde dune excution dun prprocesseur, dont le rle sera explicit ultrieurement, et qui sert principalement insrer automatiquement des dclarations permettant au programmeur de faire appel aux oprations dentres-sorties dans son programme.
1.1. COURS
19
dentres-sorties standards du langage C. Le sufxe .h indique quil sagit dun chier dinclusion (en anglais, header ). De mme, la ligne suivante demande linclusion des dclarations des procdures utilitaires de base du langage. Il est raisonnable de la placer systmatiquement dans tout programme C. Le programme C est constitu dinstructions places lintrieur de la construction, entre les accolades : int main(int argc, char * argv[]){ ... } Ces instructions vont tre excute en squence. La premire : puts("hello world") ; constitue un appel de la procdure4 puts qui afche sur le terminal une chane de caractres. Celle-ci est dnie, dans le langage C, par la suite des caractres qui la composent, place entre doubles apostrophes. Cette chane est transmise la procdure puts, ce que lon indique par les parenthses suivant le nom de la procdure. Le point virgule termine linstruction. La seconde instruction : return 0 ; permet larrt du programme. Cest en fait un ordre de sortie dune procdure (dans ce cas, la procdure main) et de retour lappelant (en loccurrence, de retour au systme dexploitation), qui permet ici lapplication de spcier un code de retour, qui est une valeur numrique entire. La valeur 0 indique une terminaison normale du programme. Les autres valeurs indiquent des ns anormales. Ce code de retour peut tre test par les langages de commandes. Insistons immdiatement sur le fait que linstruction return permet de quitter une procdure et de revenir la procdure appelante, et ce nest que lorsquelle est utilise depuis le main quelle permet darrter le programme. Une autre manire darrter un programme est dutiliser un appel de la procdure exit, sous la forme : exit(0) ; La procdure du systme exit transmet au systme une indication sur la terminaison du programme : le nombre 0 indique ici une terminaison juge normale par le programmeur. L galement, le point-virgule termine linstruction. Notes : nous respecterons la lettre la notation : int main(int argc, char * argv[])
Une procdure est un ensemble dinstructions rpondant une nalit prcise. Beaucoup sont prdnies dans le systme ; nous verrons au chapitre 3 comment les dnir en C.
4
20
CHAPITRE 1. INTRODUCTION
Elle permet en effet de dclarer quil sagit du programme principal : ceci permet au moment du lancement du programme de savoir quelle est la premire instruction excuter (ce nest pas la premire instruction qui apparat dans le code source). Ce programme principal communique avec son environnement au moyen des deux variables argc et argv, et que ce programme fournira au systme un code de retour sous la forme dun int. Tout ceci sera expliqu en dtail ultrieurement, mais retenons toutefois que cest ainsi que doit tre dclar tout programme C conforme. nous prendrons lhabitude de terminer systmatiquement nos procdures main par une instruction return x ; ou exit(x) ; Lexcution du programme sous Linux est prsente la gure 1.3, tandis que la gure 1.4 nous montre la mme opration sous Windows. [P]$ gcc prog1.c -o prog1 [P]$ prog1 hello world [P]$ F IG . 1.3 Excution du programme sous un shell Linux
C:\temp> gcc prog1.c -o prog1.exe C:\temp> prog1.exe hello world C:\temp> F IG . 1.4 Excution du programme sous un shell Windows
Notes : Limpression du message hello world est suivie dun passage la ligne ; celui-ci est automatiquement ajout par la procdure puts. Sous Windows, lutilisation du sufxe .exe est obligatoire, pour indiquer quun chier reprsente un programme excutable. Sous Linux, un autre mcanisme est utilis pour reprer les programmes excutables, et lutilisation dun tel sufxe nest pas ncessaire. Cependant, pour des raisons de scurit, Linux naccepte dexcuter que les programmes situs dans un ensemble prdnis de rpertoires (ensemble dsign par le contenu de la variable du Shell PATH. Sil nest pas dclar que le rpertoire courant fait partie de cet ensemble, il faut utiliser une notation telle que : [P]$ ./prog1 pour lancer lexcution du programme.
1.1. COURS
21
#include <stdio.h> int main(int argc, char * argv[]) { float diametre=12.25 ; float pi=3.1415926535 ; float surface ; surface = pi*(diametre/2)*(diametre/2) ; printf("La surface du disque de diamtre %f est %f\n", diametre, surface) ; return 0 ; } F IG . 1.5 Surface dun disque
22
CHAPITRE 1. INTRODUCTION
Lexpression elle-mme fait appel aux oprations de multiplication et division (reprsentes par * et / respectivement), et aux parenthses servant regrouper des calculs. Dans cette expression apparaissent galement des noms de variables : dans ce cas, on calcule la valeur de lexpression en remplaant le nom de la variable par le contenu de la case mmoire qui lui est associe. la seconde afche le rsultat au moyen dune procdure du systme, printf, qui permet limpression de messages et de valeurs numriques. Notons le fonctionnement de printf. Il y a ici trois paramtres, spars par des virgules, le tout tant entre parenthses. le premier paramtre, une chane de caractres, dcrit de quelle manire lafchage doit seffectuer ; dans cette chane : un caractre autre que % est imprim tel quel. le caractre % introduit la description dun afchage : %f indique que lafchage correspond un nombre de type float. enn, \n est une convention dcriture qui permet de reprsenter, lintrieur dune chane, le caractre LF (line-feed, ou passage la ligne), de code ASCII 00001010. Limpression de ce caractre provoque un passage la ligne. les autres paramtres reprsentent les valeurs imprimer ; la premire spcication dafchage de la chane, ici %f, dcrivant comment simprime la premire valeur (le contenu de la case mmoire associe la variable diametre), la seconde spcication (encore %f) sappliquant la seconde valeur (le contenu de la case mmoire associe la variable diametre, etc. Signalons, toujours propos de printf, que %f fournit un afchage par dfaut ; une notation telle que %12f indique que lon dsire que lafchage du nombre occupe 12 caractres au moins (mais plus si ncessaire), et %12.4f prcise que lon veut en outre exactement 4 chiffres aprs la virgule. la syntaxe des lignes est relativement souple ; des blancs, ou des passages la ligne, peuvent tre insrs (sauf lintrieur des mots-clefs, identicateurs de variables et constantes) an daugmenter la lisibilit des programmes. Le rsultat afch par le programme est : La surface du disque de diamtre 12.250000 est 117.858818 Note importante : on a indiqu que toutes les dclarations de variables doivent prcder toutes les instructions ; mme si certains compilateurs tolrent un entrelaage des instructions et des dclarations, cette pratique ne doit en aucun cas tre recommande !
1.1. COURS
Mots clefs int long short char long long float double long double Description entier entier entier entier entier ottant ottant ottant Taille sur nos machines 4 octets 4 octets 2 octets 1 octet 8 octets 4 octets 8 octets 12 ou 16 octets
23
TAB . 1.2 Types de donnes du langage C les tailles indiques dpendent des machines et des compilateurs. Lopration sizeof() permet de connatre la taille en nombre doctets dune donne ou dun type de donne. La norme du langage prcise simplement que : sizeof (char ) sizeof (short) sizeof (int) sizeof (long ) sizeof (f loat) sizeof (double) sizeof (longdouble) tout type entier peut tre prcd du mot-cl unsigned qui indique que le type doit tre considr comme non-sign , cest--dire positif ; un type sign sur p bits (8, 16, 32 ou 64) permet de coder les valeurs entires allant de 2p1 2p1 1, le type non-sign permet de coder les valeurs entires allant de 0 2p 1 ; long et short sont en fait des abrviations pour long int et short int, qui sont des critures autorises ; le type long long est une extension non normalise (le compilateur signale ce problme si lon utilise les options -ansi -pedantic -Wall) ; les types ottants utilisent une reprsentation normalise (dite IEEE-754) permettant de coder des nombres virgule ottante ; le type float et double sont des types standards, le type long double est une extension dont la taille dpend de lenvironnement de dveloppement (12 octets par dfaut sous Linux, 16 octets sous Solaris ou IRIX, 16 octets sous Linux avec option de compilation particulire) ; les prcisions relatives des ces reprsentations (qui sont des reprsentations approches des nombres rels) sont de lordre de 107 pour le type float, de 1016 pour le type double et de 1032 pour le long double sur 16 octets. 1.1.6.2 Constantes Le langage C permet dutiliser des constantes numriques dans le code source. Les formats de ces constantes sont prciss ci-dessous : constantes entires notation dcimale : succession de chiffres ventuellement prcde dun signe 12 -123 +345
24
CHAPITRE 1. INTRODUCTION
notation octale : succession de chiffres commenant par 0 0123 077770 notation hexadcimale : succession de chiffres prcde des caractres 0x ou 0X ; les chiffres hexadcimaux sont les chiffres de 0 9, ainsi que les lettres de a (ou A) f (ou F) 0x101 0XFADA 0xBeef notation caractres : succession de caractres entre quotes ; criture en base 256, chaque caractre reprsentant son code ASCII ; le plus souvent limite un seul caractre a ab 12b 1234 constantes ottantes un signe ventuel, une partie entire (succession de chiffres dcimaux), un point ., une partie dcimale (succession de chiffres dcimaux), un exposant ventuel constitu de la lettre e ou de la lettre E, suivi dun signe ventuel et dun entier dcimal 1.34 -1. +0.45 .37 1e-10 -1.23E+9 +.12E-2
1.1.6.3 Arithmtique mixte Le mlange de valeurs entires et ottantes (constantes, variables) est autoris dans une expression numrique. Lorsquun oprateur est utilis avec des oprandes de types diffrents (entier et ottant), la valeur entire est convertie en valeur ottante avant lapplication de loprateur. Ainsi, dans le programme de calcul de la surface dun disque (g. 1.5 page 21), lexpression diametre/2 est quivalente diametre/2.0. Cependant, dans un calcul tel que i/2*f, o i est un entier et f est un ottant, le calcul intermdiaire i/2 seffectue en entier, puisque les deux oprandes de la division sont des entiers, et cest une division entire qui est excute. Donc, si i a la valeur 7 et f la valeur 3.5, i/2*f fournit comme rsultat 10.5, tandis que f*i/2 fournit 12.25... 1.1.6.4 Variables et adresses On a indiqu que le compilateur, lorsquil trouve une dclaration de variable, va rserver un emplacement mmoire pour stocker la donne : cest que lon appelle ladresse mmoire de cette donne. Le langage C offre la possibilit davoir accs cette adresse : il suft pour cela dutiliser loprateur du langage & (ampersand en anglais, esperluette en franais, parfois dsign par la locution et commercial )5 devant le nom de la variable. La procdure printf prcdemment mentionne possde un format spcique %p permettant dafcher une adresse. Ainsi, le petit programme ci-dessous : #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[])
5
1.1. COURS
{ int i = 1, j = 2; printf("i=%d, adresse(i)=%p\n",i,&i); printf("j=%d, adresse(j)=%p\n",j,&j); exit(0); } donne lexcution : i=1, adresse(i)=7fff2ee0 j=2, adresse(j)=7fff2ee4
25
On notera que le dcalage entre les deux valeurs (afches dans un format hexadcimal) est de 4 octets, correspondant bien la taille en mmoire dun entier. De plus, le langage C permet galement de manipuler des variables de type adresse. Pour dclarer une telle variable, on fera prcder le nom de la variable du caractre *, comme par exemple : int i=2,*j; qui dnit une variable i de type int (de valeur initiale 2), et une variable j de type adresse en mmoire dune donne de type int . On pourra alors mmoriser la valeur dune adresse dentier dans la variable j comme par exemple : j = &i; Notons que le compilateur vrie la cohrence de type, cest--dire ici que ladresse de i est bien du type adresse dentier . Enn, le langage C dispose de loprateur *, qui est loprateur rciproque de loprateur &, cest--dire un oprateur qui, plac devant une variable de type adresse, permet de dsigner le contenu de la case mmoire dsigne par cette adresse. Cet oprateur est utilisable aussi bien dans une expression que dans une partie gauche daffectation, comme dans les exemples suivants : printf("*j=%d\n",*j); *j=4; printf("i=%d\n",i); qui, lexcution, afche : *j=2 i=4 Ceci sexplique comme suit : j contient, du fait de la premire affectation (j = &i;), ladresse mmoire de i, variable de type int contenant la valeur 2 ;
26
CHAPITRE 1. INTRODUCTION
la premire impression de message utilise comme second paramtre lexpression *j, ce qui signie le contenu de la case mmoire dont ladresse est dans la variable j ; cest donc bien la valeur de i (2) qui est afche ; linstruction *j=4; modie le contenu de la case mmoire dont ladresse est stocke dans la variable j, ce qui revient donc exactement effectuer linstruction i=4; la deuxime impression afche la valeur de i, qui est bien 4 ! Terminons ce paragraphe par quelques remarques : on utilise souvent la terminologie de pointeur comme synonyme dadresse mmoire ; lenvironnement de dveloppement garantit que ladresse mmoire 0, souvent dnomme NULL, ne peut dsigner une adresse mmoire contenant une donne dintrt pour le programme ; cette valeur particulire sera trs souvent utilise pour signier que la variable ne dsigne aucune adresse relle ; le langage C use beaucoup (abuse ?) de la notion dadresse mmoire : il est donc relativement important de bien la comprendre.
1.2 retenir
La procdure main correspond au programme excuter. Les inclusions du prprocesseur se placent en tte du programme #include <stdio.h> Toute variable doit tre dclare avant son utilisation ; elle peut recevoir une valeur initiale int toto ; int titi=530 ; float facteur=6.55957 ; Les instructions du langage : expressions arithmtiques entires, ottantes, mixtes. affectation : "variable" = "expression" ; impression : puts(" chane de caractres ") ; printf(" format \n", valeurs ) ;
27
1.3.1 Exercice 1
Tapez et testez les deux exemples de programmes donns en cours. Note : il nest pas ncessaire denvoyer ces deux programmes dans votre ml de compte-rendu.
1.3.2 Exercice 2
crire un programme ralisant une conversion de degrs Fahrenheit vers des degrs Celsius. La valeur convertir est 327,35 F.
1.3.3 Exercice 3
crire un programme convertissant des francs en euros et rciproquement. Les rsultats seront afchs avec deux chiffres signicatifs, en respectant les consignes lgales de conversion. Les valeurs convertir sont 592,25 francs et 155,76 euros.
28
CHAPITRE 1. INTRODUCTION
29
30
2.1.1.2 Passage au programme Les deux nombres sont reprsents par deux variables, notes A et B de type int. Dtaillons maintenant les tapes dcrites ci-dessus : tape 1 Comparer deux nombres seffectue au moyen dun oprateur de comparaison ; les notations <, <=, ==, >, >= et != reprsentent respectivement les oprations de comparaison infrieur, infrieur ou gal, gal, suprieur, suprieur ou gal, et enn diffrent. Ces oprations comparent leurs oprandes, et fournissent un rsultat dit boolen, dont les valeurs, vrai ou faux, sont reprsentes par les entiers 1 et 0 respectivement2. Elles peuvent sutiliser dans les instructions conditionnelles, dont les syntaxes sont : if (test) instruction-1 if (test) instruction-1 else instruction-2 Dans le premier cas, instruction-1 est excute si et seulement si test est la valeur vrai. Dans le second cas, si test est vrai, instruction-1 est excute, et elle seule ; si test est faux, instruction-2 est excute, et elle seule. La comparaison de A et B va donc pouvoir sutiliser dans une instruction dbutant par : if (A<B) ... Se pose maintenant la question de la permutation des A et B . La meilleure solution consiste utiliser une variable intermdiaire (appelons la C ), sous la forme : C=A ; A=B ; B=C ; Cependant, cet change se compose de trois instructions, alors que la syntaxe des instructions conditionnelles nous parle dune instruction unique. La solution ce problme consiste regrouper ces instructions en un bloc, en les plaant entre accolades. Le rsultat constitue une instruction compose unique. Notre test devient ainsi : if (A<B) { C=A ; A=B ; B=C ; } tape 2 Nous disposons de tous les lments ncessaires la transcription de cette tape, qui met en jeu une instruction conditionnelle, une impression, et un arrt de lexcution : if (A==B) { printf("La valeur du PGCD est %d\n", A) ; exit(0) ; } tape 3 Pas de difcult non plus pour ce fragment de lalgorithme, qui scrit :
A=A-B ;
Le langage C considre en ralit que toute valeur non nulle reprsente la valeur vrai et que la valeur 0 (sous la forme dun int, char ou oat) est la valeur faux.
2
2.1. COURS
31
tape 4 Une solution pour revenir la premire tape du programme consiste enclore lensemble des instructions de celui-ci dans une forme rptitive ; voici trois syntaxes au choix : for ( ; ;) instruction while(1) instruction do instruction while(1) ; On notera que le 1 suivant les while reprsente en fait la valeur boolenne vrai : lexpression teste qui conditionne la rptition de la boucle est donc toujours vraie. L encore, nous utiliserons des accolades pour transformer notre squence de trois tapes en une instruction compose unique : for ( ; ;) { if (A<B) { C=A ; A=B ; B=C ; } if (A==B) { printf("La valeur du PGCD est %d\n", A) ; exit(0) ; } A=A-B ; } 2.1.1.3 Le programme nal Nous pouvons maintenant proposer la version nale du programme, dont le texte source est donn dans la gure 2.1. Lexcution fournit la rponse suivante : La valeur du PGCD est 743 2.1.1.4 Un cas non prvu ! On na pas dmontr que lalgorithme ci-dessus se termine en un temps ni (dmonstration laisse au lecteur). Ceci est toutefois loccasion de prciser que, si lun des entiers est nul alors que lautre ne lest pas, lalgorithme boucle indniment. On pourrait traiter ce problme en modiant le test darrt, par exemple en testant si la valeur de B nest pas nulle : if ((A==B) || (B==0)) { ... }
32
Noter au passage que les oprateurs logiques en C scrivent && (et logique), et || (ou logique) ; ne pas oublier le doublement du symbole, car les oprateurs & et | existent galement, et ont un tout autre sens ! En fait, il conviendrait, au dbut du programme, de vrier que les valeurs A et B satisfont les conditions dapplication de lalgorithme, par exemple en insrant le fragment : if (A<=0 || B<=0) { printf("Algorithme non applicable\n"); exit(1); }
2.1. COURS
int N, S, i; ... S = 0; for (i=1; i<=N; i++) S = S+i;
33
Notes : Cet exemple nous permet de prsenter de manire informelle la syntaxe dune boucle for : for (initialisation ; continuation ; gestion ) instruction Dans cette syntaxe, la partie initialisation permet daffecter des valeurs initiales aux variables qui vont tre utilises au sein de la boucle. Lexpression continuation est un calcul dune valeur boolenne qui indique si la boucle se poursuit ou sinterrompt. Enn, la partie gestion permet la modication des variables utilises dans la boucle avant un nouveau test. Il ny a pas ncessairement un lien direct entre le test de continuation de la boucle et les variables modies dans lexpression de gestion de la boucle. Par ailleurs, lexpression ci-dessus i++ fait appel loprateur de post-incrmentation, ++, spcicit du langage C (et de ses descendants, tels C++ ou Java). Loprateur ++ permet dincrmenter une variable entire (cest--dire de lui ajouter 1). Les instructions : i++ ; ++i ; i=i+1 ; sont quivalentes et ont pour effet dajouter 1 la variable i. Cependant, les expressions i++ et ++i ont des sens diffrents : i++ est une expression qui fournit la valeur de i avant lincrmentation ; ++i est une expression qui fournit la valeur de i aprs lincrmentation. Ainsi, aprs lexcution du fragment de programme suivant : int i, j, k ; ... i=4 ; j=++i ; k=i++ ; les variables i, j et k ont pour valeurs respectives 6, 5 et 5. Signalons enn lexistence de loprateur -- de dcrmentation, qui linstar de ++, permet de retrancher 1 une variable. Seconde version Cependant, le problme de la sommation des N premiers entiers nous est connu depuis longtemps, et nous savons que :
N
i=
i=1
N (N + 1) 2
34
Cette criture est non seulement plus lgante que notre premire version, mais elle est aussi plus efcace ! Elle est, pour N = 100, environ 40 fois plus rapide. Mieux encore, alors que le temps dexcution de la premire version est clairement propotionnel N (plus N est grand, plus le programme est lent), le temps dexcution de la seconde version est constant, indpendant de la valeur de N . Cet exemple trivial fait apparaitre la ncessit de trouver lalgorithme qui sera le plus efcace avant de coder cet algorithme. Nous reviendrons naturellement sur cet aspect important de linformatique dans la suite de ce cours, ainsi que dans le cours consacr aux algorithmes et structures de donnes ([1]).
(1)i
Ceci ne donne pas proprement parler un algorithme, puisque par dnition, un algorithme doit se terminer aprs un nombre ni doprations ! Mais de toutes faons, on ne cherche en fait qu calculer une valeur approche du sinus : comment pourrait-il en tre autrement avec des machines qui ont une prcision limite, et qui commettent des approximations pour reprsenter les nombres. On cherche donc majorer lerreur commise en calculant la valeur du sinus. Pour cela, on peut remarquer que la srie est alterne (change de signe, valeur absolue dcroissante) pour | i > |x , et on sait que lerreur commise en tronquant une srie alterne au rang N (la diffrence 2 entre la limite mathmatique et la somme partielle au rang N ) est infrieure la valeur absolue du terme de rang N . Si est la prcision demande, on obtient donc comme formule de calcul :
N
sin(x) =
i=0
(1)i
La formule obtenue ci-dessus nest cependant pas satisfaisante : il ny a pas doprateur dexponentiation ou de factorielle en C. Il faut donc continuer lanalyse.
2.1. COURS
On peut remarquer que la somme recherche peut sexprimer par :
N
35
sin(x) = UN =
i=0
x2 (2i + 2)(2i + 3)
En posant wi = 2i(2i + 1) = 4i2 + 2i, on obtient : ui+1 = ui wi+1 wi+1 x2 wi+1 = w i + 8i + 6 = wi + zi+1
en posant zi = 8i 2. La valeur de zi peut se calculer par la rcurrence : z0 = 2 zi+1 = zi + 8 En regroupant tout cela, on obtient cette fois lalgorithme de calcul : z0 w0 u0 U0 zi+1 wi+1 ui+1 Ui+1 2 0 x x zi + 8 wi + zi+1 x2 = ui wi+1 = Ui + ui+1 = = = = = =
Comme dhabitude, on va utiliser pour chacune des suites dnies ci-dessus une seule variable : la valeur de la variable litration i reprsente la i-me valeur de la suite. Il est donc primordial de respecter lordre de calcul indiqu ! Rappelons encore que le test darrt consiste tester si la valeur de |ui+1| est infrieure la | , puisque ce nest que lorsque cette condition prcision demande (sans oublier de tester si i > |x 2 est remplie que la srie est alterne). Le programme complet est donn ci-dessous :
36
Notons : lutilisation de la fonction de la bibliothque mathmatique fabs, qui fournit la valeur absolue de son argument (argument et rsultat sont de type double) ; lutilisation de cette fonction ncessite linclusion du chier den-tte de cette bibliothque, nomm math.h ; lutilisation de la macro M_PI (valeur de ) galement dnie dans ce chier den-tte. lutilisation de (void) indique que lon nutilise pas le rsultat fourni par la procdure printf. Lexcution de ce programme donne le rsultat : sin(0.7853981634)=0.7071067812 [apres 5 iterations] ce qui est un rsultat tout fait acceptable ( comparer au rsultat thorique 22 soit autour de 0.707106781187). On peut noter aussi que ce rsultat est obtenu assez rapidement (5 itrations reprsentent une trentaine doprations). On peut constater sur cet exemple quon obtient un code trs compact, mais que la lecture du code ne permet que trs difcilement de retrouver ce que ce code calcule ! Do lutilit de documenter son code, sans oublier de prciser, ds que lingnierie inverse devient difcile, la fonction prcise du code en question.
2.1. COURS
2.1.3.3 Petit problme !
37
On veut calculer le sinus de 100. On modie donc le programme prcdent (en insrant linstruction x=100; en lieu et place de x=M_PI/4;), et on obtient le rsultat suivant : sin(100)=-8.261375665e+25 [apres 142 iterations] ce qui est plutt surprenant pour une valeur cense tre comprise entre -1 et 1 ! Comme cela arrive frquemment, limprcision numrique des ordinateurs est la cause du problme. La formule utilise est mathmatiquement exacte : cependant, elle ajoute des termes de valeurs absolues trs diffrentes. Si le dernier terme est infrieur la prcision demande, le terme de valeur absolue maximale, est (pour x = 100) le terme dindice 49, soit : 10099 1042 99! La prcision relative de la reprsentation des nombres ottants selon la norme IEEE-754 est de lordre de 1016 (253 pour tre prcis !) : sur une valeur absolue de lordre de 1042 , on obtient donc une erreur absolue de lordre de 1026 , cest bien lordre du rsultat obtenu ! Dans le cas particulier de la fonction sinus, on peut amliorer grandement les choses en utilisant une proprit mathmatique supplmentaire, savoir la priodicit : se ramener une valeur de x entre et permet dobtenir une prcision tout fait acceptable. On peut ainsi insrer dans les initialisations linstruction : |u49 | = x=fmod(x,2*M_PI); en utilisant la fonction fmod (calcul de modulo sur des donnes de type double), appartenant toujours la bibliothque mathmatique. Lexcution du programme ainsi modi donne alors : sin(100)=-0.5063656423 [apres 13 iterations] beaucoup plus raliste , et en mme temps plus rapide ! On pourrait encore amliorer le programme en utilisant dautres proprits de la fonction sinus (sin( + x) = sin(x) ou sin( x) = sin(x)) an de se ramener une valeur de x comprise entre 0 et . Ce quil faut retenir : il faut tre parfaitement conscient, lorsque lon programme une fonction, des limitations lies la programmation de cette fonction ! Ne pas en tenir compte peut amener des consquences non ngligeables, les personnes intresses par ce sujet pourront consulter avec intrt la page suivante : http://www.math.psu.edu/dna/disasters pour se convaincre du caractre parfois dramatique de tels oublis : chec dinterception dun missile Scud par un missile Patriot d une erreur de prcision sur le calcul du temps ; chec du tir inaugural dAriane V d la rutilisation dun code incorrect issu dAriane IV (et inutile dans le cas dAriane V ) ; naufrage de la plate-forme ptrolire Sleipner A d la rupture dun lment par mauvaise approximation dune loi dlasticit.
38
2.2 retenir
instruction compose { squence dinstructions } oprateurs de comparaison < <= == >= > != oprateurs logiques et logique &&, ou logique || boucle innie : for ( ; ;) instruction while (1) instruction do instruction while (1) ; boucle for : for (initialisation ; continuation ; gestion ) instruction
2.3.1 Exercice 1
On veut dterminer si un nombre est premier. Le nombre dont on teste la primalit est dni par une affectation dans le programme. Si ce nombre nest pas premier, le programme donne une dcomposition en deux termes non triviaux.
2.3.2 Exercice 2
Modier le programme prcdent pour imprimer tous les facteurs du nombre si celui-ci nest pas premier.
2.3.3 Exercice 3
On veut dterminer si un nombre est parfait, cest dire gal la somme de ses diviseurs, hormis lui-mme. Les deux premiers nombres parfaits sont 6 (1+2+3) et 28 (1+2+4+7+14).
2.3.4 Exercice 4
On se propose de calculer, 106 prs, la racine du polynme x4 + 3x2 x 1 = 0 situe dans lintervalle [0 1].
39
On oprera pour cela par dichotomies successives, en divisant par deux la taille de lintervalle jusqu obtention de la prcision dsire.
2.3.5 Exercice 5
Un banquier propose un placement sur le principe suivant : linvestissement initial est e 1 euros (e est la base des logarithmes nperiens) ; la premire anne, on prlve un euro de frais de gestion ; la seconde anne, le capital restant est multipli par deux, et on prlve toujours un euro de frais de gestion ; la n-ime anne, le capital restant est multipli par n, et on prlve toujours un euro de frais de gestion ; le placement est prvu pour une dure de 25 ans. Dterminer le capital restant au bout des 25 ans. Notes : le chier den-tte math.h dnit la macro M_E qui est la meilleure approximation de e sur la machine ; essayer plusieurs types ottants ; en C, sont disponibles les types float (32 bits), double (64 bits) et long double (128 bits) ; quel est le rsultat mathmatique ?
40
Chapitre 3 Procdures
3.1 Cours
Ce cours sintresse plus spciquement lintroduction de la notion de procdure, qui nous permettra de structurer et organiser efcacement le code dun programme.
41
42
CHAPITRE 3. PROCDURES
Nous avions crit lors dune prcdente sance le calcul du PGCD de deux nombres par lalgorithme dEuclide, dont le texte source est rappel dans la gure 3.1. Que faire si notre souhait est de calculer le PGCD de trois nombres A, B et C ? On peut naturellement calculer le PGCD des deux premiers, A et B , puis ayant obtenu le rsultat dans la variable A, recopier les mmes instructions, en remplaant B par C , pour obtenir le rsultat dsir. La ralisation est lourde, et source de nombreuses erreurs car on oublie frquemment de modier un nom de variable dans le code recopi ! Et que dire sil sagit de calculer le PGCD de dix nombres, ou encore de calculer le PGCD divers endroits dun programme ! Il existe naturellement de meilleures solutions ce problme, qui passent par lcriture de procdures, dites parfois sous-programmes, subroutines (anglicisme !), et de manire plus impropre encore, fonctions. Une procdure permet dcrire une seule fois un traitement particulier, utilis plusieurs reprises au sein dun programme. On verra que cette notion permet galement de structurer un programme complexe, en dcomposant le traitement en plusieurs sous-tches, chacune tant reprsente par une procdure (ou un ensemble de procdures).
3.1. COURS
43
44
CHAPITRE 3. PROCDURES
On peut contourner dans un premier temps cette difcult en rendant les variables globales, cest--dire connues par toutes les procdures. En langage C, ceci se fait en dclarant les variables en dehors de toute procdure. On peut par exemple les dnir en tte du programme, et toutes les procdures dnies ultrieurement dans le code source pourront y accder (la rgle gnrale est quune variable doit tre dclare avant dtre utilise). Notons toutefois que la variable C nest utile que pour changer les variables A et B (elle ne sert pas dans la procdure main) : on a donc tout intrt dclarer cette variable dans la procdure pgcd. La gure 3.2 donne la nouvelle version, code selon ces considrations, du programme. #include <stdio.h> int A, B ; void pgcd(void) { int C ; for ( ; ;) { if (A<B) { C=A ; A=B ; B=C ; } if (A==B) { return ; } A=A-B ; } } int main(int argc, char * argv[]) { A = 834389 ; B = 944353 ; pgcd() ; printf("La valeur du PGCD est %d\n", A) ; return 0 ; } F IG . 3.2 Procdure PGCD (premire version)
3.1. COURS
45
cdures (variables globales). Le programme principal positionne les deux valeurs A et B, puis appelle la procdure qui consulte (et modie) ces deux variables, fournissant dans lune delles (les deux, dailleurs) le rsultat. Au retour de la procdure, la programme principal retrouve donc ce rsultat et peut limprimer. Cette approche impose au programmeur dtre parfaitement conscient du rle de la procdure et des variables quelle consulte et modie, tche qui devient trs complexe lorsquun grand nombre de procdures sont utilises dans lapplication. Pour simplier cette gestion, les langages de programmation en gnral, et C en particulier, permettent de dclarer quune procdure reoit des paramtres et fournit un rsultat. Au niveau de la dclaration, on doit alors indiquer entre les parenthses suivant le nom de la procdure la liste des paramtres (nom et type), et indiquer (le cas chant) le type du rsultat (entier int, ottant double...). La dclaration de notre procdure pgcd devient alors : int pgcd(int X, int Y) Nous dclarons cette fois que la procdure rend un rsultat de type int, et admet deux paramtres de type int, connus sous les noms de X et Y lintrieur du code. Les noms choisis pour dsigner les paramtres de la procdure nont de sens qu lintrieur de celle-ci, et sont indpendants des noms choisis dans le reste du programme. Une procdure est ainsi plus proche, dans son aspect, dune fonction mathmatique, si son excution ne dpend que des seuls paramtres. Il convient galement modier le corps de la procdure, pour que celle-ci travaille sur les variables X et Y, ainsi que linstruction permettant la sortie de la procdure. Celle-ci rendant un rsultat, nous le fournissons dans linstruction return, qui scrit maintenant : return X ; Enn, le mode dappel de la procdure est modi. Nous crirons par exemple : R = pgcd(A,B) ; la variable R ayant t dclare de type entier. Comment fonctionne maintenant la procdure pgcd ? Dtaillons les tapes successives de lexcution. avant lappel, les paramtres sont calculs : ici, il sagit juste de rcuprer les valeurs contenues dans les variables A et B, mais il pourrait tre ncessaire deffectuer des oprations ; ces valeurs sont mmorises temporairement ; comme avant tout appel de procdure, le compteur programme est mmoris, puis modi avec ladresse de la premire instruction de la procdure appele ; avant mme lexcution de la premire instruction dnie par le programmeur, les valeurs mmorises pour les paramtres sont recopies dans les variables locales (ici, X et Y) ; lors de lexcution de linstruction return, on calcule la valeur du rsultat (lexpression aprs le return, ici simplement le contenu de la variable X mais qui pourrait tre plus complexe), puis on restaure le compteur programme ; dans le programme appelant, avant dexcuter linstruction suivante, le rsultat fourni par la procdure est conserv dans la variable R. Notons que si la valeur rendue par une procdure nest pas immdiatement utilise, elle est perdue !
46
CHAPITRE 3. PROCDURES
Comme ce sont les valeurs des paramtres qui sont passes la procdure appele, on dit que le langage C effectue un passage des arguments par valeur. Dautres langages peuvent utiliser dautres techniques (passage par adresse, par rfrence). La gure 3.3 donne la nouvelle version du programme.
#include <stdio.h> int pgcd(int X, int Y) { int C ; for ( ; ;) { if (X<Y) { C=X ; X=Y ; Y=C ; } if (X==Y) { return X ; } X=X-Y ; } } int main(int argc, char * argv[]) { int A, B, R ; A = 834389 ; B = 944353 ; R = pgcd(A,B) ; printf("La valeur du PGCD est %d\n", R) ; return 0 ; } F IG . 3.3 Procdure PGCD de deux nombres (deuxime version)
Questions : 1. Les variables A et B dclares dans la procdure main sont-elles modies lors de lappel de la procdure pgcd ? 2. Quen est-il si lon appelle A et B (au lieu de X et Y) les paramtres de la procdure pgcd ?
3.1. COURS
47
CHAPITRE 3. PROCDURES
Les paramtres dune procdure (on parle souvent darguments de la procdure) se comportent comme des variables locales la procdure : ils ne sont donc accessibles quau sein de la procdure elle-mme (notons que la procdure peut tout--fait modier la valeur de ces variables). Leur particularit est de recevoir une valeur initiale au moment de lappel de la procdure : ces valeurs peuvent donc tre diffrentes lors de deux appels diffrents au sein du programme ; ceci est donc foncirement diffrent des variables locales initialises de la procdure, qui recoivent les mmes valeurs chaque appel de la procdure. Enn, insistons sur le fait quil importe, lors dun appel, de passer le nombre correct de paramtres, ainsi que des paramtres dont les types sont conformes aux types attendus par la procdure. 3.1.5.3 Notion de prototype La description dune procdure incluant le type du rsultat et les types des arguments est dite prototype de la procdure. Cest une dclaration, au mme titre que int C ; . En principe, toute procdure doit tre dclare, la dclaration se composant du prototype de la fonction suivi dun point-virgule. La procdure PGCD se dclare ainsi : int pgcd(int A, int B) ; Il nest en fait pas ncessaire de fournir le nom des paramtres ; la dclaration peut donc scrire : int pgcd(int, int) ; Dans la pratique, la dclaration, toujours utile, nest indispensable que si la procdure est utilise dans le texte du programme source avant que napparaisse sa dnition. Pour cette raison, on place souvent dans le programme source la dnition des procdures avant leur premire utilisation. On notera que le mot-clef void dsigne un type de donnes particulier, de taille 0, qui indique quune fonction ne fournit pas de rsultat, comme dans void toto(int), ou na pas de paramtre, comme dans int clock(void). En rsum, un prototype ne dcrit pas comment une procdure est programme, mais simplement comment et dans quelles conditions elle peut tre utilise. Enn, on notera quun programme C comporte obligatoirement une procdure de nom main. Cest cette procdure (dont on ne peut pas choisir le nom) qui est appele lorsque lon demande lexcution du programme. En principe, le prototype de cette procdure est le suivant : int main(int argc, char * argv[]) ; Nous verrons ultrieurement ce que dsigne exactement une dclaration telle que char * argv[] . Notons toutefois que ce prototype impose un rsultat cette procdure : cest pour cette raison que lon doit toujours terminer la procdure main par une instruction de type return X ;
3.1. COURS
3.1.5.4 Compilation spare
49
Les grosses applications dveloppes en langage C peuvent atteindre quelques centaines de milliers de lignes de code, voire quelques millions de lignes. Ces applications sont dveloppes par des quipes pouvant comporter des dizaines de programmeurs. On ne peut gure envisager de voir un programmeur travailler sur un programme comportant des millions de lignes de code, tandis que les autres attendent quil ait ni ses modications pour maintenir leur tour les procdures dont ils sont responsables ! La solution consiste rpartir lensemble du programme source en plusieurs chiers spars, qui peuvent tre dits et compils sparment. Il est bien sr ncessaire de rassembler ensuite les rsultats de ces compilations pour pouvoir procder lexcution du programme. Imaginons que notre programme de calcul de PGCD soit jug assez complexe pour relever dune telle approche. Il serait possible de placer, par exemple, la procdure pgcd dans lun de ces chiers, et la procdure main dans un autre. Que faut-il faire pour que cette solution fonctionne ? Tout dabord, nous allons crer un chier source contenant le programme principal , celui dans lequel la procdure main est dclare, et puisque cette procdure main va faire appel la procdure pgcd, il faut quelle connaisse le prototype de cette fonction. Nous allons donc insrer, avant la dnition de main, le prototype suivant : extern int pgcd(int, int) ; Le mot-clef extern indique que la procdure pgcd nest pas dnie dans ce chier, mais dans un autre, tout en fournissant son prototype, qui permet au compilateur de vrier que lutilisation de cette procdure est conforme sa dnition. Si le chier contenant la procdure main a pour nom prog3.c, nous le compilerons par la commande : [P]$ gcc -c prog3.c Notons loption -c qui indique que lon dsire seulement compiler le chier prog3.c. Le rsultat de lopration est un nouveau chier, de nom prog3.o qui contient le code objet de notre programme. Il faut ensuite crer un second chier source, contenant le texte de la procdure pgcd, et que nous nommerons par exemple pgcd.c. De la mme manire que ci-dessus, nous compilerons ce chier par : [P]$ gcc -c pgcd.c L encore, un chier, de nom pgcd.o, va tre construit par le compilateur. Nous pouvons alors crer le programme excutable, par une commande telle que : [P]$ gcc prog3.o pgcd.o -o prog3 Notons que cette fois-ci, ce sont les noms des chiers contenant les codes objet (donc, se terminant par lextension .o ), que nous transmettons la commande gcc. Le compilateur gnrera le chier prog3, qui contient le programme directement excutable. Nous avons en fait dcompos la cration du programme excutable en deux tapes : la premire consiste compiler indpendamment les divers modules sources pour obtenir les modules
50
CHAPITRE 3. PROCDURES
objets correspondants ; la seconde rassemble ces modules objets (plus, ventuellement, des modules objets dits de bibliothque contenant les procdures dnies par le standard C) pour fournir le module excutable. Cette seconde tape est souvent dit dition des liens . Cette approche permet ainsi de modier lune ou lautre des procdures, sans avoir besoin de recompiler systmatiquement tout le code source de lapplication. 3.1.5.5 Fichiers dinclusion Nous avons dj prsent la commande include du prprocesseur. Cette commande a pour but daller chercher le chier prcis en paramtre, et de linsrer, dans le corps du programme, pour ltape de compilation. La commande admet les deux syntaxes suivantes : #include <stdio.h> #include "toto.h" Dans la premire forme, le chier de nom stdio.h est recherch dans les rpertoires contenant les dclarations des procdures prdnies du systmes, ici, certaines oprations dentressorties. Dans la seconde forme, le chier, ici toto.h est cherch dabord dans le rpertoire courant (puis dans les rpertoires du systme, comme ci-dessus) ; un tel chier a en gnral t cr par lutilisateur. De tel chiers, dits souvent chiers dinclusion, peuvent contenir nimporte quelles instructions du langage C. En gnral cependant, lon ny place que des dclarations et dautres instructions destines au pr-processeur. Il nous serait possible, pour simplier lutilisation de la procdure pgcd, de dnir son prototype dans un tel chier. Crons le chier pgcd.h , contenant la seule ligne : extern int pgcd(int, int) ; Dans le chier prog3.c , celui qui contient le programme principal, nous pouvons ds lors remplacer la ligne dnissant le prototype de la fonction par la ligne suivante : #include "pgcd.h" Notre programme principal devient : #include <stdio.h> #include "pgcd.h" int main(int argc, char * argv[]) { int A, B ; int R ; A = 834389 ; B = 944353 ; R = pgcd(A,B) ; printf("La valeur du PGCD est %d\n", R) ; return 0 ; }
3.1. COURS
51
Le gain despace est dans ce cas prcis assez rduit. En gnral un module objet va contenir plusieurs procdures, et le chier include correspondant va contenir lensemble des dclarations de ces procdures. On notera que les chiers dinclusion utilisent par convention lextension .h . Ceci simplie la vie de lutilisateur, qui sait que lorsquil a dans un rpertoire les trois chiers suivants : pgcd.c pgcd.h pgcd.o
Ceux-ci reprsentent en principe un module source, le chier dinclusion contenant les prototypes des procdures du module source, et enn le module objet correspondant.
52
CHAPITRE 3. PROCDURES
plus grand entier au paramtre cos double cos(double x) cosinus du paramtre cosh double cosh(double x) cosinus hyperbolique du paramtre exp double exp(double x) exponentielle du paramtre fabs double fabs(double x) valeur absolue du paramtre floor double floor(double x) plus petit entier au paramtre fmod double fmod(double x, reste de la division double y) de x par y . log double log(double x) logarithme du paramtre log10 double log10(double x) logarithme en base 10 du paramtre pow double pow(double x, xy double y) sin double sin(double x) sinus du paramtre sinh double sinh(double x) sinus hyperbolique du paramtre sqrt double sqrt(double x) racine carre du paramtre tan double tan(double x) tangente du paramtre tanh double tanh(double x) tangente hyperbolique du paramtre F IG . 3.4 Quelques procdures mathmatiques du Langage C
Prototype double acos(double x) double asin(double x) double atan(double x) double atan2(double x, double y) double ceil(double x)
Description arc cosinus du paramtre arc sinus du paramtre arc tangente du paramtre arc tangente de x/y
3.1. COURS
Nom abs atof atoi atol exit Prototype Description int abs(int p) Valeur absolue du paramtre double atof(char* s) Conversion caractres vers ottant int atoi(char* s) Conversion caractres vers entier long atol(char* s) Conversion caractres vers entier void exit(int cr) Arrt du programme, fourniture dun code de retour labs long labs(long p) Valeur absolue du paramtre rand int rand(void) Fournit un entier pseudo-alatoire srand void srand(int seed) Initialisation du gnrateur de nombres pseudo-alatoires F IG . 3.5 Quelques utilitaires de C
53
Le rsultat de certaines fonctions, lorsquil sort du domaine de dnition des procdures, peut tre reprsent par des congurations spciques qui simpriment sous la forme inf (inni, par exemple le rsultat dune division par zro) ou nan (not a number). 3.1.6.2 Oprations utilitaires Dautres procdures fournies par le systme sont vocation utilitaire. La gure 3.5 en prsente certaines. Lutilisation de ces oprations ncessite linclusion de leur dclaration, obtenue en insrant la ligne suivante en tte du programme : #include <stdlib.h> Notes Lopration rand fournit les lments successifs dune squence de nombres pseudoalatoires, calculs partir dun germe qui est 1 par dfaut. La mme squence est donc fournie chaque nouvelle excution du programme. Lopration srand permet de modier le germe (1 par dfaut) utilis par lopration rand. On peut initialiser ce germe avec une valeur diffrente chaque excution dun programme pour viter dobtenir, lors dune simulation, des rsultats strictement identiques chaque excution. Par exemple, la formule magique : time((time_t *)0) fournit le nombre de secondes coules depuis le 1er janvier 1970. Cette valeur peut tre utilise comme paramtre de srand(), en dbut dexcution du programme, pour obtenir des suites alatoires diffrentes chaque excution du programme (pour utiliser time, il faut inclure dans le programme les dclarations de time.h).
54
CHAPITRE 3. PROCDURES
3.2 retenir
dnition de procdure type-du-rsultat nom (liste des paramtres) { corps de la procdure } paramtre type-du-paramtre nom-du-paramtre type void utilis pour indiquer labsence de paramtres ou de rsultats sortie et fourniture de rsultat : return ; ( utiliser pour terminer une procdure sans rsultat) return expression ; (lexpression doit fournir une valeur du type dclar pour la procdure) variables locales elles sont dclares au dbut du bloc constituant la procdure. appel de procdure procdures sans paramtre nom() procdure avec paramtres nom(liste de valeurs) Un appel de procdure doit respecter le nombre, lordre, et les types des paramtres. dclaration de procdure : au moyen du prototype type-du-rsultat nom (liste des paramtres) ; Dans le prototype, les noms de paramtres sont facultatifs. dclaration de procdure externe : au moyen du mot-clef extern inclusion de dclarations : commande du prprocesseur #include "file.h" procdures prdnies du langages : la dclaration de leurs prototypes se trouve dans les chiers math.h (procdures mathmatiques) et stdlib.h (autres procdures standard )
55
3.3.1 Exercice 1
crire une fonction qui calcule le PPCM de deux nombres entiers. crire une fonction qui indique si deux nombres sont premiers entre eux.
3.3.2 Exercice 2
Reprendre la procdure PGCD dnie au paragraphe 3.3, page 46. Que se passe-t-il si lon effectue lappel : pgcd(0,11) Comment protger la procdure contre tout paramtre incorrect ? Comment une procdure peutelle signaler une erreur quelle dtecte au programme appelant ?
3.3.3 Exercice 3
On se propose de transformer en procdure le programme de recherche de racine de lquation x4 + 3x2 x 1 = 0 (exercice 4, chapitre 2). Cette procdure prendra comme paramtres les bornes de lintervalle de recherche, a et b, ainsi que la prcision dsire, epsilon. On utilisera cette procdure pour calculer, 106 prs, la racine du polynme situe dans lintervalle [0 1], puis, 107 prs, la racine situe dans lintervalle [1 0].
3.3.4 Exercice 4
crire (en vous basant sur un travail antrieur) une procdure premier(n) qui indique si son paramtre est un nombre premier. Raliser le programme qui afche les N premiers nombres premiers (avec N au moins gal 100).
3.3.5 Exercice 5
Consulter le manuel de rfrence de lopration rand() (par la commande man 3 rand), puis crire une procdure fournissant une valeur alatoire entre 1 et N, N tant un entier strictement positif.
3.3.6 Exercice 6
Identier, dans le projet, les parties qui vont se transformer en une (ou plusieurs) procdures. Rchir en particulier aux paramtres quil conviendra de transmettre ces procdures.
56
CHAPITRE 3. PROCDURES
Chapitre 4 Tableaux
4.1 Introduction
Ce cours va nous permettre daborder les tableaux, qui sont des collections dobjets de mme nature, les lments dun tableau de taille N tant dsigns par les entiers de lintervalle [0, N 1].
58
CHAPITRE 4. TABLEAUX
Un lment du tableau est alors dsign par lexpression notes[indice], dans laquelle indice est une expression quelconque dont la valeur doit tre entire et comprise entre 0 et 9. Attention : ne pas oublier que si le tableau est dclar de taille N , le dernier lment est celui dindice N 1 (ceci est source derreurs frquentes). Linitialisation des lments du tableau peut scrire : notes[0]=12 ; notes[1]=8 ; notes[2]=13 ; ... notes[9]=16 ; Comme pour toute variable, on peut, au moment de la dclaration, initialiser les valeurs du tableau : une telle initialisation seffectue par : int notes[10] = {12, 8, 23, 24, 6, 8, 19, 20, 9, 4} ; Le fragment de programme suivant permet de calculer, par une boucle, la moyenne des notes du tableau (les variables somme, moyenne et indice ayant t dclares en int) : somme = 0 ; indice = 0 ; while (indice < 10) { somme = somme+notes[indice] ; indice = indice+1 ; } moyenne = somme/10 ; Ou encore : somme = 0 ; for (indice=0 ; indice<10 ; indice=indice+1) somme = somme+notes[indice] ; moyenne = somme/10 ; On notera la syntaxe de cette dernire forme : for (valeur_initiale ; test_de_rptition ; gestion_de_lindice) instruction qui est quivalente . . . valeur_initiale ; while (test_de_rptition) tion_de_lindice ; } { instruction ges-
4.1. INTRODUCTION
4.1.2.2 Dclaration de tableau
59
Lors dune dclaration dun tableau dans un programme, la taille du tableau doit tre indique dune manire ou dune autre : int notes[10] ; int titi[10] = {2,3,5,6,7,8,10,13,14,19} ; int tutu[] = {1,2,3,4,5} ; On notera dans ce dernier cas que la taille du tableau nest pas indique, mais que le nombre dlments (5) peut tre dduit par le compilateur partir des valeurs indiques. Les dclarations suivantes, par contre, sont incorrectes ou provoqueront des erreurs lexcution : int jojo[] ; int juju[5]={1,2,3,8,10,12,15} ; La premire nindique pas la taille du tableau ; la seconde tente dinitialiser avec 7 valeurs un tableau dclar 5 lments. Enn, lexemple suivant est correct : int premiers[1000]={2,3,5,7} ; Dans le tableau ainsi dclar, de 1000 lments, seuls les 4 premiers reoivent une valeur. Il nest pas possible de prsumer de la valeur des lments non initialiss : certains compilateurs peuvent dcider daffecter une valeur par dfaut (0, pour des entiers), mais un programme ne doit en aucun cas considrer ceci comme un fait acquis. Notons enn que la taille du tableau doit tre connue au moment de la compilation. Il est ainsi incorrect dutiliser une variable pour dclarer la taille dun tableau, et le code suivant provoquera une erreur : int n ; int tab[n] ; 4.1.2.3 Accs aux lments des tableaux Dans une expression, la notation tab[i] permet de rfrencer llment i du tableau tab. Cette expression sapparente une rfrence un nom de variable, et peut donc tre utilise dans la partie gauche dune affectation (ce que lon appelle une left-value ou lvalue) : tab[i] = expression ; cette criture ayant pour effet de remplacer la valeur de llment i de tab par celle de lexpression. Notons aussi que des critures telles que tab[i]++ sont lgales et ont pour effet dincrmenter llment i du tableau tab. Cest naturellement une erreur que dutiliser comme indice une valeur non entire, ngative, ou encore suprieure ou gale la taille du tableau. Attention : cette erreur ne sera pas signale par le compilateur (ce nest pas une erreur de syntaxe, mais une erreur smantique ), mais peut entraner une erreur dexcution, puisque le programme accde ainsi (en lecture ou en criture), un emplacement quil nest pas suppos consulter ou modier.
CHAPITRE 4. TABLEAUX
On a vu que lon peut accder individuellement aux lments dun tableau. Il nest par contre pas possible de manipuler le tableau globalement ; en particulier, il nest pas possible daffecter une valeur un tableau, et les critures ci-dessous provoqueront des erreurs de compilation : int tab1[10],tab2[10]; tab1={0,1,2,3,4,5,6,7,8,9}; /* ERREUR !! */ tab1=tab2; /* ERREUR !! */ En fait, lorsque lon utilise le nom dune variable de type tableau sans faire rfrence un indice de ce tableau, cette expression dsigne en fait ladresse mmoire du premier lment. Ainsi, le code suivant est parfaitement correct : int tab[10],*adr,i; adr=tab; /* OK */ i=*adr; /* equivaut a i=tab[0] */ la premire instruction consistant stocker dans la variable adr de type adresse dentier ladresse mmoire du premier lment du tableau tab, qui est bien de type entier. Il est par ailleurs totalement licite dutiliser la notation indicielle (valeur entire entre crochets) avec une variable dnie non pas comme un tableau, mais comme une adresse. Ainsi, le code suivant est tout fait correct : int tab[10],*adr; adr=tab; if (adr[2]==4) /* si le deuxieme element du tableau vaut 4 */ ... Par contre, lcriture suivante est incorrecte : int tab[10],*adr; tab=adr; /* ERREUR !! */ car ceci reviendrait vouloir changer ladresse en mmoire o est stock le tableau tab, ce qui nest pas possible (cest le compilateur qui gre cette adresse). On a ainsi tradition de dire que le nom dun tableau est une adresse constante . On verra par la suite que lon mlange allgrement ces deux notions, utilisant selon les prfrences du programmeur la version tableau ou la version adresse. Il ne faut cependant pas oublier que ces deux notions ne sont pas tout fait identiques.
4.1. INTRODUCTION
4.1.2.5 Tableaux de caractres
61
Nous avons en fait dj manipul (sans le savoir) un type de tableau fort utile, les tableaux de caractres. Ceux-ci permettent la dnition de chanes de caractres, utilises pour la reprsentations des donnes alphanumriques, des messages, etc. Un tableau de caractres peut se dclarer sous des formes telles que : char char char char tab1[10] ; tab2[10]={0,1,2,3,4,5,6,7,8,9} ; tab3[10]={H,e,l,l,0x6f,48,49,50,51,52} ; tab4[10]="ABCD" ;
Les tableaux tab2 et tab3 sont initialiss avec des valeurs entires (qui doivent appartenir lintervalle [128, 127]), fournies ici sous forme dcimale (50), hexadcimale (0x6f) ou caractre (e), reprsentant en loccurrence les codes des caractres 2, o et e respectivement. Les premiers lments du tableau tab4 sont initialiss avec les valeurs reprsentes par la chane de caractres "ABCD". Attention ! Cette chane contient 5 valeurs, qui sont les quatre lettres A, B, C et D, et le nombre 0 que le compilateur utilise comme marqueur de n de chane. Nous reviendrons sur cette convention dans la section 4.1.3. Notons encore que dans ce cas prcis, les lments dindices 5 9 ne sont pas initialiss. 4.1.2.6 Tableaux et procdures Un tableau peut tre utilis comme paramtre dune procdure : int proc(int titi[10]) { ... } ... int toto[10] ; proc(toto) ; Dans cet exemple, le paramtre titi de la procdure proc est dclar en tant que tableau de 10 lments. La procdure est ensuite appele avec comme paramtre un tableau de taille adquate. Il a t indiqu que toute utilisation du seul nom du tableau fait rfrence ladresse mmoire du premier lment : cest donc ladresse mmoire du tableau qui est passe la procdure. Ceci a deux consquences immdiates : dans la compilation de la procdure proc, le compilateur ne tient pas compte de la taille dclare pour le paramtre (car il na pas besoin dallouer la place mmoire pour le tableau, seule ladresse est transmise et stocke dans la variable locale titi), et il est quivalent dcrire : int proc(int titi[]) Attention : cette notation nest possible que pour les arguments dune procdure, pas pour dclarer des variables locales ! On trouvera galement souvent la notation quivalente : int proc(int * titi)
62
CHAPITRE 4. TABLEAUX
nous avons dj signal la grande similitude entre tableau et adresse. puisque la procdure accde ladresse mmoire du tableau, toute modication du contenu du tableau dans la procdure modie effectivement le tableau pass en paramtre ; Enn, signalons quune procdure laquelle on a pass un tableau en argument na aucun moyen (par le langage lui-mme) de connatre la taille du tableau : il ny a pas doprateur taille de tableau, et loprateur sizeof() dj mentionn appliqu un tableau argument de procdure rendra comme rsultat la taille dune adresse mmoire, soit 4 octets dans nos implmentations. Ceci impliquera donc toujours que : soit la procdure connat a priori la taille du tableau ; soit un autre argument de la procdure en prcise la taille. 4.1.2.7 Exemples Voici un exemple de procdure calculant la somme des lments dun tableau reu comme paramtre. Comme indiqu ci-dessus, le deuxime argument prcise la taille du tableau. int pruc(int titi[], int n) { int i ; int somme=0 ; for (i=0 ; i<n ; i++) somme = somme+titi[i] ; return somme ; } Retour la procdure pruc Les deux paramtres permettent de transmettre la procdure le tableau (en fait, son adresse), ainsi que le nombre dlments de ce tableau. Des appels valides de la procdure sont par exemple : pruc(toto,10) ; printf("%d\n", pruc(toto,10)) ; Dans le premier cas, le rsultat fourni par la fonction nest pas utilis. Dans le second cas, il est imprim par la procdure printf. On notera que lappel : pruc(toto,8) ; permet de calculer la somme des 8 premiers lments du tableau, bien que toto ait t dclar de dimension 10. Note Une criture quivalente la premire est : pruc(&toto[0],10) ; Le premier paramtre se lit adresse de llment zro du tableau toto. Cette notation permet de transmettre ladresse dlments autres que le premier du tableau. Ainsi, puisque les lments sont rangs en mmoire par valeurs croissantes des indices, lexpression :
4.1. INTRODUCTION
pruc(&toto[2],8) ;
63
permet de calculer la somme des lments 2 9 du tableau. Voici un second exemple : la procdure minmax calcule le minimum et le maximum des lments dun tableau pass en paramtre. Le rsultat est plac dans un autre tableau, galement pass en paramtre : void minmax(int tab[], int res[], int n) { int i ; int max, min ; max = min = tab[0] ; for (i=1 ; i<n ; i++) { if (min > tab[i]) min = tab[i] ; if (max < tab[i]) max = tab[i] ; } res[0]=min ; res[1]=max ; } Voici un programme utilisant cette procdure #include <stdio.h> int main(int argc, char * argv[]) { int juju[10]={1,2,3,5,6,8,10,12} ; int res[2] ; minmax(juju, res, 10) ; printf("%d %d\n", res[0], res[1]) ; return 0 ; }
64
CHAPITRE 4. TABLEAUX
Lorsque cette convention est utilise, le caractre double-quote " lui-mme doit tre prcd dun caractre \, dit caractre dchappement, ou back-slash, ce dernier caractre, pour tre reprsent, devant lui-mme tre prcd dun autre back-slash. Le caractre dchappement permet la reprsentation de certains caractres dits de contrle : ainsi, le passage la ligne se note \n. Dans un programme C, une chane de caractres reprsente une rfrence un tableau. Une notation telle que "ABCDE"[3] reprsente llment dindice 3 du tableau, cest dire le caractre D dont la reprsentation dcimale est 68 ; "ABCDE"[5] est la valeur 0 marquant la n de la chane. Notons encore que les compilateurs C effectuent une concatnation implicite de chanes de caractres qui se suivent dans le texte dun programme C. Ainsi, les deux lignes suivantes dnissent-elles la mme chane : "ABC" "XY" "012345" "ABCXY012345" Ceci permet de disposer agrablement les longues chanes sur plusieurs lignes, an daugmenter la lisibilit des programmes. Enn, rappelons que la plus grande attention doit tre porte la manipulation de chanes contenant des caractres en dehors du code ASCII : caractres accentus, signes montaires. . . Les rsultats peuvent dpendre de lenvironnement : systme dexploitation, compilateur, variables denvironnement. 4.1.3.2 Oprations sur chanes de caractres La gure 4.1 dcrit certaines des oprations de base (procdures standards) relatives aux chanes de caractres. Les prototypes de ces fonctions sont dcrits dans le chier dinclusion string.h. Notes 1. ces oprations reposent sur la convention implicite quune chane de caractres est reprsente par une suite (ventuellement vide) doctets de valeur non nulle, suivie par un octet de valeur gale 0. Si lun des paramtres ne respecte pas ce schma, le comportement est indtermin (mais on peut prdire des problmes. . .) 2. dans les oprations entranant le transfert dlments (telles strcat, strcpy, etc.) la destination est le premier paramtre de la procdure. 3. le mot-clef const prcise que le paramtre transmis dsigne une zone de mmoire qui ne sera pas modie par la procdure. 4. les versions avec n (strncat, strncpy, strncmp) limitent lopration au plus n caractres ; attention : dans le cas de strncpy, si la chane source contient plus de n caractres, la chane rsultat ne contient pas le 0 nal ! 5. les oprations ne tiennent pas compte des tailles effectives des tableaux utiliss. Cest au programmeur de sassurer que la taille de loprande destination est sufsante pour recevoir lensemble des octets de la source.
4.1. INTRODUCTION
Nom strcat Prototype Description char * strcat(char * d, Concatnation de const char * s) deux chanes strcmp int strcmp(const char * d, Comparaison de const char * s) deux chanes strcpy char * strcpy(char * d, Copie dune chane const char * s) strlen int strlen(const char * s) Longueur dune chane strncat char * strncat(char * d, Concatnation de const char * s, int n) deux chanes strncmp int strncmp(const char * d, Comparaison de const char * s, int n) deux chanes strncpy char * strncpy(char * d, Copie dune chane const char * s, int n) F IG . 4.1 Quelques oprations sur chanes de caractres 4.1.3.3 Exemples de manipulation de chanes de caractres Dclaration dune chane char c1 [] = "Une suite doctets" ;
65
Le compilateur dduit ici que la taille du tableau est de 19 octets (incluant loctet nal de valeur 0). On aurait pu dclarer de faon quivalente : char *c1 = "Une suite doctets" ; Longueur dune chane Par convention, la longueur dune chane de caractres est le nombre doctets non nuls de la chane ; ainsi, la valeur de lexpression : strlen("ABCD") ; est 4 (le nombre doctets non nuls), et non 5, nombre doctets servant reprsenter la chane. Copie dune chane char c2[10] ; ... strcpy(c2,"ABCD") ; Dans ce fragment de programme, le tableau c2, de dix lments, reoit une copie des lments de la chane "ABCD". Aprs lexcution, les lments du tableau c2 ont pour valeurs : 65 66 67 68 0 ? ? ? ? ?
66
CHAPITRE 4. TABLEAUX
(Les points dinterrogation reprsentent les lments non modis du tableau c2.) Ce serait une erreur dexcuter : strcpy(c2,"ABCDEFGHIJKLMNOP") ; car le tableau c2 nest pas de taille sufsante pour recevoir une chane de 16 caractres, cest-dire 17 valeurs. On peut, pour se protger de tels dbordements, utiliser la procdure strncpy : strncpy(c2,"ABCD",10) ; qui permet dindiquer que 10 octets au plus doivent tre copis (en comptant loctet null de n de chane). Attention, si la seconde chane est de taille suprieure ou gale au nombre maximum doctets copier, il ny a pas insertion de null en n de chane, ce qui risque de provoquer des erreurs ultrieures. . . Concatnation Lopration de concatnation permet dajouter, au bout dune chane de caractres, une copie dune autre chane. Lopration modie son premier oprande. Aprs lexcution de : strcat(c2,"XYZ") ; les lments du tableau c2 ont maintenant pour valeurs : 65 66 67 68 88 89 90 0 ? ?
Impression La spcication de format %s permet limpression dune chane : printf("c2 vaut : \"%s\"\n", c2) ; imprimera : c2 vaut : "ABCDXYZ" Comparaison Lopration de comparaison sapplique deux chanes de caractres. Le rsultat est un entier qui est ngatif si la premire chane est infrieure la seconde, nul si les deux chanes sont gales, et positif si la premire est suprieure la seconde. strcmp("ABCD","ABD") Cette expression rend une valeur ngative, car la chane "ABCD" est situe avant la chane "ABD" dans lordre lexicographique (trs prcisment, C est situ avant D dans le code ASCII). Une criture possible en C de la procdure strcmp est la suivante :
4.1. INTRODUCTION
int strcmp(char s1[], char s2[]) { int c1, c2 ; int i ; for (i=0 ; ;i++) { c1=s1[i] ; c2=s2[i] ; if (c1 == 0 || c2 == 0 || c1 != c2) return c1-c2 ; } }
67
CHAPITRE 4. TABLEAUX
Il est bien sr possible dutiliser ces arguments. Ils permettent notamment de fournir au moment de lexcution (et sans aucune entre-sortie ou dialogue avec lutilisateur) des informations au programme. Dailleurs, chaque fois que nous utilisons un outil (diteur de texte, compilateur, dbogueur), nous faisons appel cette convention.
4.2 retenir
dclaration de tableau : type nom[dimension] ; dclaration avec initialisation : type nom[dimension]= {liste de valeurs} ; indice dun lment de tableau 0 valeur < dimension lment de tableau nom[indice] nom[indice]=valeur dclaration de pointeur type * nom ; chane de caractres notation spcique pour un tableau de caractres caractre null ajout la n de la chane "ABCD" quivaut au tableau contenant les cinq caractres de codes 65, 66, 67, 68 et 0. oprations sur chanes : dnies dans string.h tableau et pointeurs tableau et procdure passage des tableaux par adresse
4.3.1 Exercice 1
On se propose de dterminer la moyenne olympique dune suite de N notes. Celles-ci sont donnes sous la forme dun tableau. Il convient de supprimer de ces notes la plus leve (ou
69
lune des plus leves en cas de ex-aequo) et la plus faible, et de calculer la moyenne des N 2 restantes. crire une fonction dont le prototype est le suivant : double moy_olymp(int notes[], int N) ;
4.3.2 Exercice 2
crire une fonction qui ralise une permutation alatoire dun tableau dentiers (on peut utiliser la fonction rand). Si possible, cette fonction ne doit pas crer de copie du tableau, mais le permuter en place . Le prototype de cette fonction doit tre : void permute_tableau (int tab[],int N);
4.3.3 Exercice 3
On dsire manipuler des polynmes de degr arbitraire (infrieur une valeur maximale prdtermine, par exemple 31). imaginer une reprsentation, base sur un tableau, pour de tels polynmes ; implanter une procdure dvaluation de la fonction polynomiale en un point donn ; raliser la procdure daddition de deux polynmes.
4.3.4 Exercice 4
On se propose de construire un histogramme dcrivant la rpartition des valeurs des lments dun vecteur tel que celui-ci : { 2, 3, 7, 6, 9,11,12,15,18,17, 14,13,12, 7, 8, 7, 5, 3, 2, 1, 1, 0, 2, 8,11,13,12,11, 6, 3} ; Le programme afchera lhistogramme dans la fentre du terminal, en utilisant des blancs et des toiles, comme dans lexemple de la gure 4.2. crire une fonction dont le prototype est : void histog(int v[], int nb) Modier le programme an de pouvoir choisir la hauteur de lhistogramme. Le prototype devient : void histog(int v[], int nb, int H)
4.3.5 Exercice 5
On veut maintenant analyser le gnrateur de nombres alatoires fourni par le systme, rand(). On tirera 10000 valeurs alatoires, comprises entre 0 et 50, dont on tudiera la rpartition au moyen du gnrateur dhistogrammes de lexercice 2.
70 * ** ** *** **** ***** * ******* ** ******** **** ******** **** ********* **** ********* * ***** * ************ ***** ************** ****** *************** ****** *************** ****** ***************** ******* ******************* ******** ********************* ******** ****************************** F IG . 4.2 Histogramme
CHAPITRE 4. TABLEAUX
4.3.6 Exercice 6
Modier le programme prcdent, an de gnrer cette fois une distribution gaussienne qui sera calcule par le programme (on peut approcher une distribution gaussienne par une somme de douze valeurs alatoires uniformes de [-0.5,0.5]). On tracera lhistogramme de la rpartition.
4.3.7 Exercice 7
On se propose de lire une valeur numrique entire positive sur le terminal, en utilisant une procdure permettant de lire un caractre, getchar()1. La procdure lisant un caractre la fois, elle devra tre appele plusieurs fois de suite an de permettre de dcoder un nombre crit avec plusieurs chiffres. La procdure devra rejeter le caractre indiquant la n du nombre (tel un blanc, un passage la ligne, etc) au moyen de lopration ungetc().
4.3.8 Exercice 8
Modier le programme de lexercice prcdant, an quil permette de lire une valeur relle, exprime avec un point dcimal optionnel, telle que 152.11 ou 0.017, mais aussi 315, .5 ou encore 1..
1
On consultera naturellement le manuel dutilisation de cette opration sous Unix, par man getchar.
71
4.3.9 Exercice 9
On se propose maintenant (cet exercice constituant la suite des deux prcdents) dcrire un programme permettant de dcoder une dure, comportant plusieurs valeurs numriques reprsentant des heures, minutes et secondes. Le caractre h suivra le nombre indiquant lheure, le caractre : sparera les minutes des secondes, les secondes pouvant elles-mme tre exprimes avec une partie dcimale. Des exemples de valeurs acceptables sont : 3h25 (trois heures et vingt-cinq minutes), 17:21 (dix-sept minutes et vingt-et-une secondes), ou encore 11h27:11.45.
72
CHAPITRE 4. TABLEAUX
74
Maintenant, pour calculer ce nombre de secondes, on va en fait calculer, pour chaque instant, lintervalle (en nombre de secondes toujours) par rapport une date de rfrence T0 . On utilise donc la relation : T2 T1 = (T2 T0 ) (T1 T0 ) On choisit comme date de rfrence le 1er janvier 1970 0h00. On sait en effet que cest sous cette forme que sont stockes de nombreuses informations de date dans le systme Unix, et on dispose en plus dune fonction de bibliothque time qui permettra de tester notre propre version de cette fonction. On supposera donc dornavant que les instants T 1 et T2 sont postrieurs cette date. On peut ainsi dja crire une partie du code : int delta_secondes ( int J1, int M1, int A1, int h1, int m1, int s1, int J2, int M2, int A2, int h2, int m2, int s2) { return N(J1,M1,A1,h1,m1,s1)-N(J2,M2,A2,h2,m2,s2); } On ne tiendra pas compte (pour linstant) des problmes de passage entre heure dt et heure dhiver : toutes les dates sont supposes tre exprimes en temps universel. 5.1.1.2 Analyse de la fonction N On notera pour la suite J, M, A, h, m et s les jour (de 1 31), mois (de 1 12), anne (suprieure 1970), heure (de 0 23), minute (de 0 59), seconde (de 0 59)1 . On peut encore dcomposer le problme en remarquant que la quantit recherche est la somme : du nombre de secondes entre le 1er janvier 1970 0h00 et le 1er janvier de lanne A 0h00 (ce nombre ne dpend que de A, on le note N1 (A)) ; du nombre de secondes entre le 1er janvier de lanne A 0h00 et le 1er jour du mois M de lanne A 0h00 (ce nombre ne dpend que de A et M, on le note N2 (M, A)) ; du nombre de secondes entre le 1er jour du mois M de lanne A 0h00 et le jour J du mois M de lanne A 0h00 (ce nombre ne dpend que de J, on le note N3 (J )) ; du nombre de secondes entre le jour J du mois M de lanne A 0h00 et le jour J du mois M de lanne A lheure h :m.s (ce nombre ne dpend que de H, M et S, on le note N4 (h, m, s)). On peut ainsi crire : N (J, M, A, h, m, s) = N1 (A) + N2 (M, A) + N3 (J ) + N4 (h, m, s) On peut de plus remarquer que N1 , N2 et N3 sont des nombres de secondes correspondant des jours entiers : il est ainsi possible dcrire N i = Nji 86400, les Nji correspondant aux nombres de jours (et 86400 est le nombre de secondes par jour). On peut donc crire la fonction N:
dans un souci de simplicit, on ne tiendra pas compte des secondes supplmentaires parfois ajoutes pour corriger les dfauts de dure de rvolution terrestre.
1
5.1. COURS
int N (int J,int M,int A,int h,int m,int s) { return N_4(h,m,s)+86400*(Nj_1(A)+Nj_2(M,A)+Nj_3(J)); } 5.1.1.3 Calcul de N4 (h, m, s) Ce calcul est trs simple : on peut crire directement la fonction : int N_4 (int h,int m,int s) { return s+60*(m+60*h); } 5.1.1.4 Calcul de Nj3 (j ) Pas de difcult particulire non plus ! On crit : int Nj_3 (int j) { return j-1; } 5.1.1.5 Calcul de Nj1 On peut crire : Nj1 (A) =
A1 a=1970
75
nj (a)
o nj (a) dsigne le nombre de jours de lanne a. On sait que les annes comportent 365 jours, sauf les annes bissextiles qui en comportent 366. On rappelle que, dans le calendrier grgorien, sont bissextiles les annes divisibles par 4, sauf celles divisibles par 100, moins quelles ne soient aussi divisibles par 400 2 . On peut donc crire une fonction que lon note bissextile(A) permettant de tester si son argument est bissextile. Cette fonction permet ensuite dcrire lalgorithme de calcul de Nj 1 , crit ci-dessous en C : int n_j (int a) { if (bissextile(a)) return 366; return 365; }
Les lecteurs intresss par les problmes dajustement de calendrier peuvent consulter lURL http ://www.emse.fr/roelens/calendar.txt
2
76
int Nj_1 (int annee) { int nb,a; for (nb=0,a=1970;a<annee;a++) nb+=n_j(a); return nb; } Le code de la fonction bissextile peut lui-mme scrire : int bissextile (int annee) { if (annee % 4) return 0; if (annee % 100) return 1; if (annee % 400) return 0; return 1; } 5.1.1.6 Calcul de Nj2 Calcul galement non trivial. En effet, les mois ont un nombre de jours variable, et il nexiste pas vraiment de formule permettant de calculer ce nombre de jours en fonction du numro du mois. Il est alors plus pratique dcrire une fonction qui effectue un calcul spcique en fonction de chaque mois. Notons au passage que le calcul est modi partir du mois de mars si on est dans une anne bissextile, do lutilit de passer en paramtre lanne ! Voici une (ce nest bien sr pas la seule) faon dcrire cette fonction : int Nj_2 (int mois,int annee) { int nb=0; switch (mois) { case 12: /* on ajoute le nb de jours de novembre */ nb+=30; case 11: /* on ajoute le nb de jours doctobre */ nb+=31; case 10: /* on ajoute le nb de jours de septembre */
5.1. COURS
nb+=30; case 9: /* on ajoute le nb de jours daout */ nb+=31; case 8: /* on ajoute le nb de jours de juillet */ nb+=31; case 7: /* on ajoute le nb de jours de juin */ nb+=30; case 6: /* on ajoute le nb de jours de mai */ nb+=31; case 5: /* on ajoute le nb de jours davril */ nb+=30; case 4: /* on ajoute le nb de jours de mars */ nb+=31; case 3: /* on ajoute le nb de jours de fevrier */ /* 29 si annee bissextile, 28 sinon */ if (bissextile(annee)) nb+=29; else nb+=28; case 2: /* on ajoute le nb de jours de janvier */ nb+=31; case 1: return nb; } }
77
Remarque 1 : on rutilise ici la fonction bissextile, dj utilise pour la fonction Nj 1 . Cette rutilisation est lun des avantages du dcoupage en fonctions : une fonction reprsente une suite dinstructions utilisable autant de fois que souhait. Remarque 2 : de lanne. cette fonction dpend stricto sensu non pas de lanne, mais de la bissextilit
Remarque 3 : on a utilis la smantique particulire du switch ... case en C, attention bien la comprendre !
78
Remarque 4 : cette criture nest pas ncessairement la plus efcace ! 5.1.1.7 Correction dune inexactitude On signale alors que la rgle utilise pour le calcul des annes bissextiles est inexacte : en effet, les annes divisibles par 4000 et dont le quotient est impair (comme 4000, 12000...), ne sont pas bissextiles3 ! Il suft alors de modier la fonction bissextile, pour la version suivante : int bissextile (int annee) { if (annee % 4) return 0; if (annee % 100) return 1; if (annee % 400) return 0; if (annee % 4000) return 1; if ((annee / 4000) % 2) return 0; return 1; } On voit alors quaucun autre code nest modi, en particulier, pas le code de Nj 1 et Nj2 qui utilisent cette fonction. Cest aussi un avantage de cette conception : partir du moment o la smantique (ce quelle est cense faire) et linterface (son nom, ses paramtres, son type de rsultat) ne sont pas modies, il nest pas ncessaire de modier les fonctions utilisatrices. 5.1.1.8 Le bug du 19 janvier 2038 Si on teste le programme prcdent, avec la date du 19 janvier 2038 3 heures 14 minutes et 8 secondes (temps universel), on obtient un temps ngatif (pour tre prcis, on obtient -2147483648) ! Ceci est d au fait que les entiers (int) sont classiquement cods sur 32 bits : or, la date indique, le nombre de secondes est de 231 , qui dans le codage sur 32 bits, correspond 231 = 2147483648. Il faudra donc dici l trouver une parade ce problme : si lon se rfre au bug de lan 2000, on peut estimer que la majorit du travail de correction sera effectue en dcembre 2037... Plus srieusement, la parade est dj trouve : il suft de passer un codage des dates sur 64 bits (la majeure partie des systmes dits 64 bits le font dj). Ce codage ne posera problme que dans (approximativement) 292 milliards dannes. Encore une fois, il faut tre conscient des limitations de limplantation informatique des fonctions.
3
5.1. COURS
79
80
Note : pensez au fonctionnement dun compteur kilomtrique, qui afche des chiffres de 0 9. . . On trouvera ci-dessous un premier morceau de notre programme. #include <stdio.h> /* taille maximale de lechiquier */ #define NMAX 32 typedef int position[NMAX]; void init_position (int N,position p) { int i; for (i=0;i<N;i++) p[i]=0; } /* cette fonction rend 0 si on est a la derniere */ /* position, et 1 sinon */ int next_position (int N,position p) { int j; for (j=N-1;j>=0;j--) if (++p[j]==N) p[j]=0; else break; return j!=-1; } int main (int argc,char *argv[]) { position pos; int N,nb; if (argc==2) N=atoi(argv[1]); else N=8; init_position(N,pos); for (nb=0;;) { if (test_position(N,pos)==1) { dessine_position(N,pos);nb++;
5.1. COURS
} if (next_position(N,pos)==0) break; } (void) printf("%d positions trouvees\n",nb); return 0; } 5.1.2.3 Test dune position
81
Pour tester une position, on doit vrier que pour tout couple de reines, les positions correspondent des lignes diffrentes (cest vrai par construction !), des colonnes diffrentes et des diagonales diffrentes. Pour tester si ce sont des colonnes diffrentes, il suft de vrier que les valeurs des lments sont diffrentes. Pour tester si ce sont des diagonales diffrentes, il suft de tester si la valeur absolue de la diffrence entre les numros de lignes est diffrente de la valeur absolue de la diffrence entre les numros des colonnes (faire un dessin, si besoin). On en dduit le code de la fonction de test : int test_position (int N,position p) { int i,j; for (i=0;i<N;i++) for (j=0;j<i;j++) if (p[i]==p[j] || (p[i]-i)==(p[j]-j) || (p[i]+i)==(p[j]+j)) return 0; return 1; } 5.1.2.4 Dessin de lchiquier Laiss titre dexercice au lecteur. . . 5.1.2.5 Quelques rsultats On obtient les rsultats suivants, pour les petites valeurs de N : N=1 : 1 position N=2 : 0 position N=3 : 0 position N=4 : 2 positions N=5 : 10 positions N=6 : 4 positions N=7 : 40 positions
82
N=8 : 92 positions On notera que le temps de calcul pour N=8 commence tre long : ceci sexplique par la complexit en N N de lalgorithme, soit dj plus de 16 millions de positions tester ! Cette mthode est bien sre loin dtre efcace. . .
5.1. COURS
5.1.3.2 Analyse, modlisation des donnes
83
Paramtres Comme indiqu ci-dessus, on note m le nombre de couleurs de la combinaison, p le nombre de couleurs possibles, M le nombre maximal de combinaisons proposables par lutilisateur. On a galement besoin dun indicateur permettant de savoir si une couleur peut tre utilise une seule fois ou plusieurs fois : on notera multi cet indicateur, valant 1 si lon peut rpter une couleur, et 0 sinon. Note : une fois choisis, ces paramtres ne sont plus modis au cours du jeu.
Couleurs On a bien sr besoin de reprsenter informatiquement les couleurs ! Chaque couleur sera reprsente par un entier entre 0 et p 1. Puisque la valeur maximale de p est 8, cela donne donc un entier entre 0 et 7. On peut donc utiliser des variables de type char (entier sur 8 bits) pour coder la couleur. On a galement besoin dune reprsentation textuelle des couleurs (il est plus commode pour le joueur de lire rouge, vert ou bleu que 0, 3 ou 6. . .). On utilisera pour cela des chanes de caractres, stockes dans un tableau : lindice i du tableau correspond la description textuelle de la couleur i. Combinaison Comme une combinaison est une srie de couleurs, on va utiliser un tableau de couleurs (donc, un tableau de char) pour coder les combinaisons. Le nombre de couleurs de la combinaison tant limit 6, tous les tableaux seront de taille 6 (on utilisera toutefois une macro en C pour dnir cette valeur). Si le nombre m est infrieur 6, on nutilisera quune partie de chaque tableau. Tableau de jeu Il est ncessaire de conserver en mmoire toutes les combinaisons joues (an de pouvoir grer laide). Comme chaque combinaison est un tableau, on va donc utiliser un tableau deux dimensions pour grer ce tableau de jeu : le premier indice correspond au numro de combinaison, le deuxime indice correspond au numro de la couleur au sein de la combinaison. On va utiliser un tableau de taille 12x6 (toujours grce une macro) dont on nutilisera effectivement que la partie Mxm (le coin suprieur-gauche ou nord-ouest). Scores On a besoin, pour chaque proposition, de stocker le score . Ce score tant ici compos de deux valeurs, on utilisera un tableau deux dimensions, chaque ligne comportant deux valeurs : lindice 0 correspond au nombre de couleurs bien places, lindice 1 correspond au nombre de celles qui sont mal places. Encore une fois, le nombre de lignes du tableau sera pris comme une valeur maximale (nombre dessais) dont seule une partie sera effectivement utilise pour une valeur de M donne. Traduction en C Traduit en C, cela donne la version suivante :
typedef char mm_combi[MAXCOMBI]; typedef char mm_jeu[MAXESSAIS][MAXCOMBI]; typedef int mm_scores[MAXESSAIS][2]; int m,M,p,multi; Note : on a utilis la dnition de type en C (mot-cl typedef) permettant de donner un nom de type parlant nos variables. 5.1.3.3 Les traitements Une fois les donnes modlises, on peut sintresser aux diffrents traitements que le programme doit effectuer. Chaque traitement fera lobjet dune (ou plusieurs) procdure qui ralise informatiquement le traitement correspondant. On prcisera alors les donnes dentre et les rsultats en sortie de chaque procdure. 5.1.3.4 Tirage au sort de la combinaison Il sagit de choisir alatoirement m valeurs entre 0 et p, tirage effectu sans remise si on nautorise quune seule fois chaque couleur, avec remise si on peut utiliser plusieurs fois chaque couleur. On sait que lon dispose dune fonction rand() qui, daprs notre documentation, effectue un tirage pseudo-alatoire entre 0 et RAND_MAX. Pour obtenir un tirage pseudo-alatoire entre 0 et n 1, il suft deffectuer un modulo n (on ngligera les imperfections 4 de rpartition dun tel tirage). Si le tirage se fait avec remise, chaque couleur de la combinaison est obtenue par un tirage indpendant des autres. Si le tirage est fait sans remise, on peut utiliser une procdure ressemblant beaucoup un exercice du chapitre 4 (gnration dune permutation alatoire) en tronquant la gnration aprs le pe lment. Les paramtres de cette procdure sont donc : en entre, m, p et lindicateur multi ; en sortie, la combinaison initiale ; comme tout rsultat de type tableau, le paramtre est pass par adresse la procdure. do un prototype en C de cette procdure : int init_combi (int m,int p,int multi,mm_combi la_combi); La fonction rend 0 si le tirage a russi, -1 en cas de problme (tirage sans remise avec m suprieur p). On peut bien sr dcomposer cette fonction en deux, lune pour le tirage sans remise, lautre pour le tirage avec remise. Voici une faon dimplmenter ces deux fonctions :
4
5.1. COURS
int init_combi_remise (int m,int p,mm_combi la_combi) { int i; for (i=0;i<m;i++) la_combi[i]=(rand() % p); return 0; } int init_combi_sansremise (int m,int p,mm_combi la_combi) { char aux[MAXCOULEURS]; int i,j; if (m>p) return -1; for (i=0;i<p;i++) aux[i]=i; for (i=0;i<m;i++) { j=(rand() % (p-i)); la_combi[i]=aux[i+j]; aux[i+j]=aux[i]; } return 0; } int init_combi (int m,int p,int multi,mm_combi la_combi) { srand(time(0)); if (multi) return init_combi_remise(m,p,la_combi); return init_combi_sansremise(m,p,la_combi); } 5.1.3.5 Lecture de la proposition de lutilisateur
85
On souhaite que linterface permettre lutilisateur de donner sa proposition sous la forme de noms de couleurs. On utilisera pour sparer les couleurs des caractres spciaux comme les espaces, les tabulations, ou ventuellement les virgules. Sont ainsi des entres convenables (dans le cas m = 4 et p = 6) : rouge vert bleu jaune rouge, vert bleu,jaune ainsi que : rouge, vert bleu, jaune
86
On fera en sorte que, si le joueur entre une ligne vide ou un nombre de couleurs insufsant, le programme afche laide (impression des combinaisons prcdemment joues). On prendra galement garde ne pas lire trop de couleurs (on stoppe ds que lon en a lu m), et vrier que les couleurs sont bien les p premires. Notre fonction de lecture aura alors comme prototype : int lire_la_combi (int m,int p,mm_combi la_combi); la valeur retourne tant le nombre de couleurs effectivement lues. Note : ce niveau du cours, les entres-sorties nont pas t abordes, et on ne dtaillera pas limplmentation de cette procdure. Avertissement : on rappelle que les erreurs de conception des entres-sorties sont frquemment lorigine de dysfonctionnements des programmes. 5.1.3.6 Manipulation des couleurs On va avoir besoin pour imprimer les combinaisons, ou bien pour effectuer la saisie des combinaisons, de convertir le nom dune couleur en son numro et rciproquement. Il est alors souhaitable dcrire les deux fonctions de conversion suivantes : int numero_couleur (char *str_couleur); char *chaine_couleur (int num_couleur); la premire donnant le numro de la couleur dnie par la chane de caractres passe en argument (et -1 en cas derreur), la seconde donnant la description sous forme de chane de caractres de la couleur passe en argument (et 0 en cas derreur). On trouvera ci-aprs une (ce nest pas la seule) faon dimplmenter ces deux fonctions : char *tab_couleurs[MAXCOULEURS] = {"rouge","vert","bleu","jaune", "marron","violet","orange","rose"}; int numero_couleur (char *str) { int i; for (i=0;i<MAXCOULEURS;i++) if (!strcmp(str,tab_couleurs[i])) return i; return -1; } char *chaine_couleur (int num) {
5.1. COURS
if ((num<0)||(num>=MAXCOULEURS)) return 0; return tab_couleurs[num]; } 5.1.3.7 Calcul du score dune proposition
87
tant donn une combinaison et une proposition, on doit calculer le nombre de couleurs bien places et le nombre de couleurs mal places. Il faut bien prendre garde ce quune couleur ne soit jamais compte deux fois ! Pour cela, on commence par calculer le nombre de couleurs bien places : on compare les couleurs aux mmes indices dans la combinaison et dans la proposition. Si les couleurs sont identiques, on ajoute un au nombre de bien places. An dviter de recompter cette couleur en tant que mal place, on remplacera la couleur dans la combinaison par une valeur non attribue (MAXCOULEURS, par exemple) et dans la proposition par une valeur non attribue diffrente (MAXCOULEURS+1, par exemple). Il faut donc faire attention faire des copies pralables de la combinaison et de la proposition ! On peut calculer ensuite les couleurs mal places : il faut cette fois-ci tester les couleurs des positions diffrentes dans la combinaison et dans la proposition. On utilisera le mme mcanisme de remplacement des couleurs par des valeurs non attribues si lon trouve des couleurs concordantes. On obtient alors le code C suivant : /* le resultat est le nombre de "bien places" */ int teste_combi (int m,mm_combi la_combi,mm_combi essai, int score[2]) { mm_combi la_combibis,essaibis; int i,j; for (i=0;i<m;i++) { la_combibis[i]=la_combi[i]; essaibis[i]=essai[i]; } /* on calcule le nombre de bien places : on modifie */ /* alors la combi et lessai pour ne pas les */ /* recompter en tant que mals places */ for (i=0,score[0]=0;i<m;i++) if (la_combi[i]==essai[i]) { score[0]++; la_combibis[i]=MAXCOULEURS; essaibis[i]=MAXCOULEURS+1; } /* on calcule le nombre de mal places : on modifie */ /* encore la combi et lessai pour ne pas compter */ /* deux fois la meme couleur */
88
Note : la fonction rend un rsultat de type entier alors quon sattend ce quelle rende un score. Ceci est d au fait quen C, il nest pas possible quune fonction renvoie un rsultat de type tableau : on passe donc le tableau (ladresse du tableau pour tre prcis) en argument la fonction. Le rsultat est ici le nombre de couleurs bien places : on teste si ce rsultat est gal m pour savoir si lon a trouv la combinaison. 5.1.3.8 Gestion de laide Laide comprend deux parties : si le joueur ne saisit pas une proposition complte, on afche les propositions prcdentes et les scores ; si le joueur saisit une proposition correcte, on commence par vrier quelle est cohrente avec les propositions prcdentes ; cela signie que le score obtenu par chaque proposition prcdente doit tre identique au score que lon aurait obtenu si la combinaison tait identique la nouvelle proposition. Pour afcher une proposition, on peut tout--fait crire une fonction spcique, comme celle indique ci-aprs : void print_combi (int m,mm_combi la_combi) { int i; for (i=0;i<m;i++) (void) printf("%s%c",chaine_couleur(la_combi[i]), (i<(m-1)) ? , : \n); } Note : on utilise la fonction chaine_couleur dnie prcdemment ; noter galement lutilisation de lexpression x ? y : z, qui si x est vrai (non nul) vaut y, et z sinon. On peut alors crire la fonction dafchage des combinaisons prcdentes elle-mme : void print_aide (int m,int i,mm_jeu le_jeu,mm_score sc) { int j; for (j=0;j<i;j++) {
5.1. COURS
(void) printf("Prop. %d :",j); print_combi(m,le_jeu[j]); (void) printf("\t%d bien places, %d mal places\n", sc[j][0],sc[j][1]); } }
89
Pour la vrication de la cohrence par rapport aux propositions prcdentes, voici une faon de programmer la fonction : /* teste si la proposition i est coherente avec les */ /* precedentes : rend 1 sil y a incoherence, 0 sinon */ int teste_incoherence (int m,int i,mm_jeu le_jeu, mm_score le_score) { int j,tmp_score[2]; for (j=0;j<i;j++) { (void) teste_combi(m,le_jeu[i],le_jeu[j],tmp_score); if ((tmp_score[0]!=le_score[j][0])|| (tmp_score[1]!=le_score[j][1])) { (void) printf("proposition incoherente" " avec reponse %d\n",j); return 1; } } return 0; } 5.1.3.9 Le jeu complet On peut maintenant combiner les fonctions prcdentes pour obtenir le jeu. On va en fait crire une premire fonction qui dcrit une seule phase de jeu : saisie de la proposition de lutilisateur ; test de validit, afchage de laide si besoin ; test de cohrence, afchage de laide si besoin ; calcul et afchage du score de cette proposition. La fonction rend 1 si lutilisateur a trouv la solution, et 0 sinon. Voici une implmentation de cette phase de jeu : int phase_jeu (int m,int p,int i,mm_combi la_combi, mm_jeu le_jeu,mm_score le_score) { for (;;) { if (lire_combi(m,p,le_jeu[i])!=m) { (void) printf("erreur de saisie !\n");
90
On peut maintenant crire le jeu complet : gnration de la combinaison trouver ; rptition dune phase de jeu tant que le nombre dessais est infrieur au maximum et que la combinaison nest pas trouve ; si le joueur na pas trouv, afchage de la solution. ce qui peut se programmer comme suit : int partie (int m,int p,int multi,int M) { mm_combi la_combi; mm_jeu le_jeu; mm_score le_score; int i; if (init_combi(m,p,multi,la_combi)==-1) { (void) printf("ERREUR !\n"); return 2; } for (i=0;i<M;i++) if (phase_jeu(m,p,i,la_combi,le_jeu,le_score)) return 1; (void) printf("vous navez pas trouve," " la combinaison etait:\n"); print_combi(m,la_combi); return 0; }
5.2. RETENIR
91
5.2 retenir
avant tout choix dalgorithme, se poser la question de la nalit de lapplication raliser lorsque cest possible et utile, rchir longuement lalgorithme et aux structures de donnes mettre en uvre dcomposer le problme en sous-problmes plus simples, que lon programmera sous forme de procdures, et ventuellement de modules spars crire les programmes de manire claire, lisible et are, en privilgiant les commentaires signicatifs ; exemple a contrario : i=i+1 ; /* On ajoute 1 i */
92
93
A.2 Mmoire
Mmoire centrale Volatile Technologie : circuits silicium Espace linaire dlments identiques lments : des octets (byte) de 8 bits ; Un lment peut reprsenter 28 valeurs distinctes Dsigns par les entiers de lintervalle [0, N 1] Les lments sont directement accessibles par la CPU Les temps daccs sont trs rduits (108 secondes) Capacit : 256 Mo ( 108 octets) Mmoire secondaire 95
96
A.3 Bus
Un bus est un ensemble de ls connectant des units fonctionnelles au sein dun ordinateur bus interne CPU cache (300 bits Pentium Pro) bus donne Processeur Mmoire lignes adresses [16, 32, 48 bits] lignes donnes [8, 16, 32, 64 bits] signaux [contrle et logique] normes : ISA, PCI, etc.. . . bus externe : Ordinateur Priphrique arbitrage : centralis/dcentralis normes : IDE, SCSI, USB, etc.. . .
97
98
F IG . A.2 Pentium 4
100
101
F IG . B.2 Fentre bash sous DOS Note : DJGPP fournit un autre interprteur, nomm bash, driv du Bourne shell connu sur les stations Unix. Cest un interprte offrant de trs nombreuses fonctionnalits (wildcarding en particulier). Voici quelques commandes : exit interrompre linteraction dir liste des chiers du rpertoire courant . le rpertoire courant .. la racine du rpertoire cd changement de rpertoire c:> cd ..\durand\cours mkdir nom crer un nouveau rpertoire copy nom1 nom2 recopier un chier c:> copy prog2.c a:\durand rem nom supprimer un chier rename nom1 nom2 renommer nom1 c:> rename toto.c.txt toto.c toto excuter le programme toto.exe ou toto.com c:> notepad a : passer sur la disquette c : passer sur le disque c :
102
B.2.3 Le compilateur
Le programme permettant de fabriquer des programmes excutables partir de vos chiers sources est gcc (GNU C compiler). Cest en fait un enchaneur de passes, qui va successivement lancer : le prprocesseur (travaille uniquement au niveau du texte, aucune connaissance de la syntaxe du langage) ; le compilateur (traduit le code source en langage dassemblage) ; lassembleur (traduit le langage dassemblage en code objet) ;
103
lditeur de liens (ajoute au code objet les routines de dmarrage et darrt ainsi que les fonctions pr compiles ncessaires). B.2.3.1 Compilation dun programme indpendant Si votre chier source sappelle prog.c, la fabrication du programme se fait par la commande : gcc prog.c qui construit (si tout se passe bien !) le programme nomm a.exe. Si lon veut donner un autre nom au programme, on peut utiliser une option de gcc : gcc -o prog.exe prog.c loption -o tant suivie de largument prog.exe qui est le nom donner au programme rsultat (encore une fois, le sufxe .exe permet de reprer le type du contenu du chier). Si vous souhaitez avoir des messages davertissement (qui sont des aides vritables la mise au point lorsque lon sait les interprter), vous pouvez utiliser loption -W du compilateur : gcc -o prog.exe -Wall prog.c Pour lancer le programme, il suft alors de taper : prog.exe ou encore plus simple : prog B.2.3.2 Gnration de chiers intermdiaires La fabrication dun programme est une succession de passes. Il est possible de demander lexcution des passes une par une, an de gnrer les chiers intermdiaires. Par exemple, le prprocesseur peut tre appel par : gcc -o prog.i -E prog.c puis le compilateur par gcc -o prog.s -S -Wall prog.i puis lassembleur par gcc -o prog.o -c prog.s et enn lditeur de liens par gcc -o prog.exe prog.o Les chiers prog.i (pr-trait), prog.s (compil) sont des chiers texte que vous pouvez lire avec votre diteur. Les chiers prog.o (objet) et prog.exe (excutable) sont des chiers binaires.
104
105
Le lancement de ce programme sous le metteur au point permet davoir une ide prcise de la nature et de lemplacement de lerreur (les commandes tapes par lutilisateur sont indiques en gras) : [CTEST]$ gdb a.out GNU gdb 5.2.1-2mdk (Mandrake Linux) Copyright 2002 Free Software Foundation, Inc. (gdb) run Starting program : /home/girardot/CTEST/a.out Program received signal SIGFPE, Arithmetic exception. 0x08048372 in main (argc=1, argv=0xbffff6a4) at bug1.c :8 8 tab[i] = (i+3)/(i-5) ; (gdb) quit The program is running. Exit anyway ? (y or n) y [CTEST]$ Notes : La commande gdb a.out est le lancement du dbogueur sous le systme dexploitation utilis (fentre de commande Windows ou Linux). Le dbogueur est ds lors en attente de commande (son prompt est (gdb) ). Ici, lexcution du programme tester est dclenche immdiatement par la commande run . Lerreur (une division par 0) est dtecte la ligne 8 du programme bug1.c, et le texte source de cette ligne est imprime. La commande quit permet, aprs conrmation, darrt lexcution du dbogueur. B.2.4.6 Un deuxime exemple Voici un programme dont le temps dexcution est trs long, et donne limpression quil a entam une boucle sans n :
106
Lexcution du programme ne donne aucun rsultat visible, et au bout de quelques minutes, grande est la tentation de faire contrle-C pour linterrompre, ce qui, bien entendu, ne donne que peu dindications sur son fonctionnement. Voici le programme excut sous le contrle du dbogueur : [CTEST]$ gdb bug2 GNU gdb 5.2.1-2mdk (Mandrake Linux) Copyright 2002 Free Software Foundation, Inc. (gdb) run Starting program : /home/girardot/CTEST/bug2 Program received signal SIGINT, Interrupt. 0x08048396 in main (argc=1, argv=0xbffff6a4) at bug2.c :17 17 toto(&k,j) ; (gdb) Aprs avoir lanc le dbogueur, puis lexcution du programme tester, nous avons au bout de quelques minutes tap contrle-C pour linterrompre. gdb nous signale que cette interruption a eu lieu la ligne 17 du programme source, avant lappel de la procdure toto. Nous pouvons consulter les valeurs des variables : (gdb) print i $1 = 1 (gdb) print j $2 = 1832284597 (gdb) print k $3 = -1345098718 (gdb) Nous pouvons continuer lexcution du programme interrompu :
107
Aprs une nouvelle interruption (par contrle-C ), nous pouvons consulter nouveau les variables : (gdb) print i $4 = 6 Lvolution de la valeur de la variable i montre que le programme semble sexcuter normalement. Nous pouvons excuter la procdure courante ligne par ligne : (gdb) next 16 for (j=i ; j>=0 ; j++) (gdb) next 17 toto(&k,j) ; (gdb) next 16 for (j=i ; j>=0 ; j++) (gdb) next 17 toto(&k,j) ; (gdb) print j $7 = 1804878402 (gdb) next 16 for (j=i ; j>=0 ; j++) (gdb) next 17 toto(&k,j) ; (gdb) print j $8 = 1804878403 (gdb) Lexcution en mode ligne par ligne montre lenchanement des appels de la fonction toto au sein de la boucle dans laquelle on fait varier la valeur de j . Il est possible de suivre plus nement lexcution au moyen de la commande step . Celleci va non seulement excuter ligne par ligne, mais va de plus permettre de tracer les procdure appeles : (gdb) step toto (d=0xbffff63c, s=1804878403) at bug2.c :5 5 * d = s + * d; (gdb) step 7 } (gdb) step main (argc=1, argv=0xbffff6a4) at bug2.c :16 16 for (j=i ; j>=0 ; j++) (gdb) step 17 toto(&k,j) ; (gdb) step
108
Notons enn lutilisation de la commande backtrace qui permet de connatre le contexte de la ligne en cours : nous sommes ici dans la procdure toto (ligne 5 du texte source bug2.c ), appele depuis la procdure main ( la ligne 17 du texte source bug2.c ), elle-mme appele par la procdure gnrique de lancement dun programme C, qui porte le nom barbare de __libc_start_main . B.2.4.7 Rcapitulatif de gdb Commandes de base (gdb) run Si votre programme ncessite des arguments, on tape la commande : (gdb) run arg1 arg2 ... Il est possible dutiliser pour cette commande run labrviation r . Pour quitter le dbogueur, on tape la commande : (gdb) quit Il est possible dutiliser pour cette commande quit labrviation q . On peut interrompre le programme courant en cours dexcution par un contrle-C . tout moment, un programme interrompu peut tre continu par la commande continue , dont labrviation est c . B.2.4.8 Points darrt Un point darrt permet de stopper lexcution du programme un endroit prcis du code. Pour mettre un point darrt lentre de la fonction fonc, il suft de taper la commande : Pour lancer lexcution du programme, il suft de taper la commande :
109
Ainsi, break main permet dinterrompre le programme immdiatement avant le dbut de lexcution de la procdure principale. Pour stopper une ligne dtermine du programme source (la ligne 14, par exemple), on utilise la commande (gdb) break 14 Il est possible dutiliser pour la commande break labrviation b . On peut continuer lexcution par la commande continue (abrviation c ), ou bien excuter en mode pas pas par les commandes step ou next (abrviations s et n respectivement). On notera quil existe des instructions stepi et nexti , similaires step et next , mais qui nexcutent quune instruction de la machine la fois. Enn, la commande finish permet de poursuivre lexcution dune procdure jusqu son retour la procdure appelante, et dinterrompre lexcution ce moment. B.2.4.9 Visualisation de donnes Pour visualiser le contenu dune variable, il suft de taper (gdb) print x $1 = 4 (gdb) print tab[2] $2 = 6.13 Largument de la commande print est en fait toute expression C qui a un sens dans le contexte dexcution courant. B.2.4.10 Visualisation de code Lors dune excution de programme, la commande list (abrviation l ) permet dafcher lcran les 10 lignes qui entourent la ligne courante. B.2.4.11 La documentation en ligne Le dbogueur fournit la commande help , qui permet de disposer dindications prcises sur la syntaxe dune commande ou dun groupe de commandes. Sous Linux, la commande man fournit une indication plus ou moins dtaille sur le fonctionnement et les options des outils du systme. Essayez par exemple man gcc ou man gdb . DJGPP, ainsi que Linux, possdent un systme de documentation en ligne : on peut visualiser cette documentation par la commande info. On peut ainsi trouver de la documentation sur : le compilateur : info gcc le dbogueur : info gdb
110
111
F IG . B.5 Fentre Eterm [tp] cp prog2.c /home/eleves/durand rm nom supprimer un chier mv nom1 nom2 renommer nom1 [tp] mv toto.c truc.c toto excuter le programme toto [tp] toto [tp] gcc -o titi titi.c cd revenir dans son rpertoire daccueil cd coursc/tp passer dans le rpertoire dsign
112
113
Nous ne conseillons pas, dans un premier temps, lutilisation des outils plus complexes, tels vi ou emacs, qui ont, bien sr, leur raison dtre dans le cadre de dveloppements de grande envergure.
B.3.3 Compilateur
Pour mmoire, rappelons que le compilateur le plus appropri reste gcc. Mentionnons ici quelques-unes des options les plus utiles : -o : cette option doit tre suivie dun identicateur, qui indique sous quel nom le module excutable cr par le compilateur sera conserv. Cest une bonne pratique que dutiliser comme nom le mme que celui du programme source, dpourvu de lextension .c , comme dans : gcc -o prog3 prog3.c -Wall : signaler lors de la compilation toutes les constructions pouvant tre considres comme douteuses , mme si elles sont lgales, car est peuvent correspondre une erreur de programmation. Un exemple typique est : if (a=b) ... qui est lgale, mais peut aussi rsulter dune erreur de lcriture : if (a==b) ... -ansi : demander au compilateur le strict respect de la norme ANSI C. -pedantic : demander au compilateur de signaler toutes les constructions ne respectant pas la norme ISO C. -g : demander la gnration dans le programme dinformation spciques, destines au metteur au point gdb.
114
115
116
118
Recommandation : calculez formellement les rsultats demands, puis vriez que vos calculs informatiques concordent.
B (x0 , . . . , xn ) =
i=0
yi
j =i
X xj xi xj
Le programme principal pourra utiliser ces procdures sur les exemples suivants : le calcul du polynme U dont les racines sont -1, 0, 2 et 3, et son afchage ; V (1) = 2 V (0) = 1 et son afchage graphique. le calcul du polynme V tel que : V (1) = 2 V (2) = 1
119
120
122
lecture attentive du code source an dvaluer la qualit de rdaction du programme ; un programme ne comportant pas de dcoupage opportun en fonctions serait fort mal ressenti, de mme quun programme sans aucun commentaire !
E.3 Le problme
On veut raliser une implmentation du clbre jeu de la vie 1 . Dans ce jeu, on dispose dun tableau rectangulaire de cellules : chaque cellule, qui a 8 voisines, peut tre vivante ou morte. partir dun motif initial (qui indique les cellules vivantes et mortes initiales), on applique les rgles de transformation suivantes : si linstant t, une cellule morte a exactement trois voisines vivantes, alors linstant t + 1, cette cellule est vivante (rgle de naissance) ; si linstant t, une cellule vivante a deux ou trois voisines vivantes, alors linstant t + 1, cette cellule est vivante (rgle de survie) ; si linstant t, une cellule ne vrie aucune des deux conditions prcdentes, alors linstant t + 1, cette cellule est morte. Le but du projet est, partir dun motif initial, de construire et dafcher le tableau de cellules chaque tape.
E.4.2 Afchage
Pour raliser lafchage, on pourra, au choix : soit utiliser la bibliothque curses qui permet de grer un terminal de type alphanumrique en mode plein cran ; soit utiliser la bibliothque vogle qui permet de crer simplement une fentre de taille donne, et dafcher pixel par pixel dans cette fentre.
1
E.5. CORRECTION
123
Vous trouverez sur le site du ple http://kiwi.emse.fr/POLE/ des exemples simples (chiers curs_ex.c et vogle_ex.c) dutilisation de ces bibliothques partir desquels vous pouvez construire votre propre programme.
E.5 Correction
Cette section propose une correction, rdige par Marc Roelens. Cette solution ne doit pas tre considre comme lunique ou la meilleure, mais constitue simplement une description dune dmarche dattaque du problme.
124
E.7 Traitements
Le principe gnral du programme est trs simple : initialiser le jeu afcher le jeu tant que le jeu nest pas ni faire voluer le jeu afcher le jeu n Nous allons dtailler chacune de ces procdures.
E.7. TRAITEMENTS
125
Dans le calcul de la nouvelle conguration, la procdure essentielle est celle qui calcule le nombre de voisins vivants dune cellule donne : il est donc souhaitable den faire une procdure spare. Cette procdure devra dailleurs prendre en compte le cas particulier des bords : soit on considre que le tableau est entour dune bordure de cellules toujours mortes ; soit on considre que le tableau est priodique (ou, ce qui revient au mme, repli sur lui-mme), la premire et la dernire ligne tant voisines, de mme que la premire et la dernire colonne. En fonction de ce nombre, et de la nature de la cellule, on dtermine la nature de la cellule linstant suivant. Il est pratiquement indispensable dans ce cas deffectuer le travail sur deux structures de donnes distinctes ; on peut, au choix : copier la structure de linstant t dans un tableau temporaire, et calculer la structure linstant t + 1 partir du tableau temporaire dans le tableau initial ; calculer la structure linstant t + 1 dans un tableau temporaire, puis recopier ce tableau dans le tableau initial. Les deux solutions sont identiques en termes dencombrement mmoire et de temps de calcul. Dans les deux cas, une procdure de copie de tableau dans un autre tableau est utile ! Nous choisissons pour la suite la premire solution.
E.7.5 Codage
Voici une solution possible (ce nest bien sr pas la seule) pour les procdures gnriques (indpendantes de linterface).
/* fonction qui copie le tableau "src" dans le tableau "dst" */ /* seul le coin nord-ouest de taille nbl*nbc est copie */ void copie_jeu (unsigned char src[LMAX][CMAX], int nbl,int nbc, unsigned char dst[LMAX][CMAX]) { int i,j; for (i=0;i<nbl;i++) for (j=0;j<nbc;j++) dst[i][j]=src[i][j]; } E.7.5.2 Nombre de voisins vivants An dallger lcriture, cette procdure est implmente par une double boucle permettant de dcrire la cellule et ses 8 voisins. Cas de la bordure morte Voici le code correspondant cette option possible du jeu :
/* fonction qui calcule le nombre de cellules */ /* vivantes voisines de la cellule (i,j) dans */ /* le cas du jeu a "bordure morte " */ int nb_voisins_vivants (unsigned char jeu[LMAX][CMAX], int nbl,int nbc,int i,int j) { int di,dimin,dimax,dj,djmin,djmax,nbvv; dimin=(i==0 ? 0 : i-1); dimax=(i==(nbl-1) ? i : i+1); djmjn=(j==0 ? 0 : j-1); djmax=(j==(nbl-1) ? j : j+1); nbvv=(jeu[i][j] ? -1 : 0); for (di=dimin;di <= dimax;di++) for (dj=djmin;dj <= djmax;dj++) if (jeu[di][dj]) nbvv++; return nbvv; } Noter lutilisation dexpressions conditionnelles, de la forme : exp ? v1 : v2
E.7. TRAITEMENTS
127
interprter comme suit : lexpression exp est value ; si exp est vraie (non nulle), lexpression v1 est value et son rsultat est le rsultat de lexpression globale ; si exp est fausse (nulle), lexpression v2 est value et son rsultat est le rsultat de lexpression globale. Dans ce code, di et dj sont les indices des lignes et colonnes voisines de la cellule (i, j ) ; di (resp. dj) varie entre dimin et dimax (resp. djmin et djmax), ces valeurs tant calcules en fonction de i (dans le cas gnral, ce sont i-1 et i+1, mais on tient compte des bords). La valeur initiale de nbvv est -1 si la cellule (i, j ) est vivante : en effet, dans la double boucle, on va dcompter la cellule comme voisine delle-mme, cette initialisation corrige la valeur.
/* fonction qui calcule le nombre de cellules */ /* vivantes voisines de la cellule (i,j) dans */ /* le cas du jeu "periodique"*/ int nb_voisins_vivants (unsigned char jeu[LMAX][CMAX], int nbl,int nbc,int i,int j) { int di,direel,dj,djreel,nbvv; nbvv=(jeu[i][j] ? -1 : 0); for (di=(i-1);di <= (i+1);di++) { direel=(di+nbl)%nbl; for (dj=(j-1);dj <= (j+1);dj++) { djreel=(dj+nbc)%nbc; if (jeu[direel][djreel]) nbvv++; } } return nbvv; } La variable direel calcule lindice rel prendre en compte pour accder la ligne dindice di (qui peut prendre des valeurs allant de 1 nbl. Noter que lon neffectue pas simplement une opration modulo (on ajoute au pralable nbl), car en C, le rsultat de -1 % N est 1 et non pas N 1 !
On peut alors coder, en fonction des procdures prcdentes, la procdure globale dvolution : void evolue_jeu (unsigned char jeu[LMAX][CMAX], int nbl,int nbc) { unsigned char jeutmp[LMAX][CMAX]; int nbv,i,j; /* on effectue la copie du jeu initial */ copie_jeu(jeu,nbl,nbc,jeutmp); for (i=0;i < nbl;i++) for (j=0;j < nbc;j++) { nbv=nb_voisins_vivants(jeutmp,nbl,nbc,i,j); if (jeutmp[i][j]) jeu[i][j]=((nbv==2) || (nbv==3)); else jeu[i][j]=(nbv==3); } } Note : on notera lutilisation directe du rsultat dun test comme valeur affecte jeu[i][j] ; on rappelle ce propos quen C, le rsultat dun test vrai est la valeur (entire) 1, et que le rsultat dun test faux est la valeur (entire) 0. E.7.5.4 Procdure principale Voici le code de la procdure main : int main (int argc,char *argv[]) { int nbl,nbc; unsigned char jeu[LMAX][CMAX]; if (init_jeu(argc,argv,&nbl,&nbc,jeu)==-1) return 1; affiche_jeu(jeu,nbl,nbc); for (;!test_jeu_fini();) { evolue_jeu(jeu,nbl,nbc); affiche_jeu(jeu,nbl,nbc); }
129
130
} E.8.1.2 Afchage La procdure dafchage afche un caractre par cellule (@ si la cellule est vivante, un blanc si la cellule est morte), ligne par ligne. Voici un codage possible : void affiche_jeu (unsigned char jeu[LMAX][CMAX], int nbl,int nbc) { int i,j; for (i=0;i < nbl;i++) { for (j=0;j < nbc;j++) putchar(jeu[i][j] ? @ : ); putchar(\n);
131
Cette procdure lit une chane de caractres au clavier : si le premier caractre de la chane lue est q, la procdure retourne 1, sinon elle retourne la valeur 0. int test_jeu_fini (void) { char ligne[80]; return fgets(ligne,80,stdin) && ligne[0]==q; } Noter que cette procdure est bloquante : lutilisateur doit appuyer sur Entre pour passer la gnration suivante du jeu.
132
} E.8.2.2 Afchage Pour afcher, on commence par effacer lcran, puis on afche un caractre @ pour chaque cellule vivante : void affiche_jeu (unsigned char jeu[LMAX][CMAX], int nbl,int nbc) { int i,j; clear(); for (i=0;i < nbl;i++) for (j=0;j < nbc;j++) if (jeu[i][j]) mvaddch(i,j,*); refresh(); } E.8.2.3 Test darrt On teste simplement si lutilisateur a frapp la touche q au clavier : si oui, on termine proprement (effaage de lcran, restauration de lcran) et on retourne la valeur 1, sinon, on retourne 0. On rappelle que le timeout sur la fonction getch a t positionn dans la procdure dinitialisation 500 millisecondes. int test_jeu_fini (void) {
133
134
/* des parametres, on saisit le tableau initial */ int init_jeu (int argc,char *argv[], int *nbl,int *nbc, unsigned char jeu[LMAX][CMAX]) { int v; int wid,hei; int i,j,nb,wpix,hpix; float x,y,lastx,lasty; ..... code identique a linterface terminal ..... ..... pour recuperer *nbl et *nbc ..... /* initialisation du package VOGLE */ /* taille de la grille (en pixels) */ wpix=(*nbc)*ZOOM; hpix=(*nbl)*ZOOM; /* taille de la fenetre (en pixels) : VOGLE */ /* utilise une "bordure" de 16 pixels */ wid=wpix+16; hei=hpix+16; prefsize(wid,hei); vinit("X11") ; /* pour utiliser tout lecran (VOGLE utilise */ /* un ecran "carre" par defaut) */ ortho2(0.,*nbc,0.,*nbl); expandviewport(); viewport(-1.,1.,-1.,1.); /* on veut des cercles "pleins" */ polyfill(1); /* on met a zero le jeu initial */ raz_jeu(jeu,*nbl,*nbc); /* on efface lecran en blanc */ color(WHITE); clear(); /* on trace en bleu */ color(BLUE); lastx=2.;lasty=2; do { if (checkkey()==q) { return 0; } /* on teste si un bouton de la souris a ete */
135
Pas de problme particulier : on efface, et on trace un cercle pour chaque cellule vivante. void affiche_jeu (unsigned char jeu[LMAX][CMAX], int nbl,int nbc) { int i,j; backbuffer(); color(WHITE);clear();color(BLUE); for (i=0;i < nbl;i++) for (j=0;j < nbc;j++) if (jeu[i][j]) circle(j+0.5,i+0.5,0.5); swapbuffers(); } Note : on a ajout des appels aux procdures backbuffer et swapbuffers pour viter certains problmes de temporisation dafchage. On gre alors deux fentres : une afche et une cache . Lappel backbuffer indique que lon dessine dans la fentre cache, lappel swapbuffers change les deux fentres. E.8.3.3 Test darrt On teste simplement si lutilisateur a appuy sur la touche q : si oui, on retourne la valeur 1, sinon, on attend un peu, et retourne la valeur 0. int test_jeu_fini (void) { if (checkkey()==q) { vexit(); return 1; } usleep(500000); return 0; }
137
138
Bibliographie
[1] Jean-Jacques Girardot et Marc Roelens. Structures de donnes et Algorithmes en C. Accessible par la page http://kiwi.emse.fr/POLE/SDA/, septembre 2005.
A TEX 2 , guide pratique. Addison-Wesley, France, Juin 1995. [2] Christian Rolland. L
139
140
BIBLIOGRAPHIE
PGCD de deux nombres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . PGCD de deux nombres (version 1) . . . . . . . . . . Procdure PGCD (premire version) . . . . . . . . . . Procdure PGCD de deux nombres (deuxime version) Quelques procdures mathmatiques du Langage C . . Quelques utilitaires de C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
A.1 Pentium Pro : processeur, 2 caches de 512 Ko . . . . . . . . . . . . . . . . . . A.2 Pentium 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.1 B.2 B.3 B.4 B.5 B.6 B.7 Commande DOS . . . . . . . . . . . . . . . . . . . . . . Fentre bash sous DOS . . . . . . . . . . . . . . . . . . Le notepad Windows et sa fentre de slection de chier Fentre Xterm . . . . . . . . . . . . . . . . . . . . . . . Fentre Eterm . . . . . . . . . . . . . . . . . . . . . . . Lditeur Gedit sous Linux . . . . . . . . . . . . . . . . . Lditeur KWrite sous Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
141
142
Index
(void), 36 != (opration), 30 # (caractre), 18 % (caractre), 22 %f (format), 22 && (opration), 32 * (opration), 22 ++ (oprateur), 33 -Wall (option), 113, 121 -ansi (option), 113 -g (option), 104, 113 -lm (option), 51 -o (option), 113 -pedantic (option), 113 -- (oprateur), 33 . (rpertoire), 101, 110 .c (sufxe), 17 .exe (sufxe), 20 .h (sufxe), 19 .o (sufxe), 17, 113 .. (rpertoire), 101, 110 / (opration), 22 / (sparateur, chiers), 13 < (opration), 30 <= (opration), 30 == (opration), 30 > (opration), 30 >= (opration), 30 \ (sparateur, chiers), 13 \n (format), 22 { (caractre), 30 || (opration), 32 } (caractre), 30 abs (procdure), 53 accolades, 30 acos (procdure), 52 143 ADA, 6 adresse mmoire, 12 affectation, 21 Algol 60, 6 algorithme, 17 algorithme dEuclide, 29 alphanumrique, 16 APL, 6 apostrophe, 13 arborescence, 13 argc, 20 argv, 20 ASCII, 16 asin (procdure), 52 atan (procdure), 52 atan2 (procdure), 52 atof (procdure), 53 atoi (procdure), 53 atol (procdure), 53 backslash, 13 backtrace (gdb), 108 bash, 14, 101 bloc, 30 boolen, 30 branchement, 15 break (gdb), 109 bus, 11, 96 byte, 12, 95 C, 6 C++, 6 cd (commande), 101, 110 ceil (procdure), 52 Celsius, 27 chane de caractres, 17, 19 char, 12
144 char (dclaration), 23 cheminom, 13 code ASCII, 16 code de retour, 19, 20 commandes cd, 101, 110 copy, 101 cp, 110 dir, 101 exit, 101, 110 gcc, 109 gdb, 109 ls, 110 man, 109 mkdir, 101, 110 mv, 111 rem, 101 rename, 101 rm, 111 compilateur, 14 compilation, 14, 18 phases, 18 compteur programme, 15 continue (gdb), 107 copy (commande), 101 cos (procdure), 52 cosh (procdure), 52 cp (commande), 110 dclaration, 21 dclarations char, 23 double, 16, 23, 39 extern, 49 oat, 16, 23, 39 int, 16, 23 long, 23 long double, 23, 39 long long, 23 short, 23 unsigned, 23 void, 48 dcrmentation (oprateur), 33 dise, 18 diffrent (oprateur), 30 dir (commande), 101 DJGPP, 99, 104 do (instruction), 31 Dos, 13 double (dclaration), 16, 23, 39
INDEX
e (valeur M_E), 39 editeur de texte, 17 edition des liens, 17 egal (oprateur), 30 emacs, 17 entier, 95 entiers, 16 Euclide (algorithme), 29 exemples Calcul dun sinus, 35 Dates, 73 Hello World, 18 MasterMind, 82 PGCD, 31, 46 Reines, 79 Somme des N premiers entiers, 32 Surface dun disque, 21 exit (commande), 101, 110 exit (procdure), 19, 53 exp (procdure), 52 extension, 13 extern (dclaration), 49 fabs (procdure), 36, 52 Fahrenheit, 27 faux (boolen), 30 chier, 13 extension, 13 nom sparateur, 13 nish (gdb), 109 oat (dclaration), 16, 23, 39 oor (procdure), 52 fmod (procdure), 37, 52 for (instruction), 31 Fortran, 6 gcc, 109, 113
INDEX
gdb, 104, 113 backtrace, 108 break, 109 continue, 107 nish, 109 help, 109 list, 109 next, 107, 109 nexti, 109 print, 106, 109 quit, 105, 108 run, 105, 108 step, 107, 109 stepi, 109 Gedit, 17 Girardot (Jean-Jacques), 9 Gnome, 14 GNU, 99 header, 19 help (gdb), 109 identicateur, 15 if (instruction), 30 include (prprocesseur), 18 inclusion, 19 inclusions math.h, 36 stdio.h, 18 stdlib.h, 53 string.h, 64 index, 12 infrieur (oprateur), 30 infrieur ou gal (oprateur), 30 info (Linux), 109 instruction compose, 30 instructions affectation, 21, 26 impression, 26 return, 19 int (dclaration), 16, 23 KWrite, 17 labs (procdure), 53 langage machine, 14 LF, 22 line-feed, 22 Linux, 6, 13, 14, 17, 20, 99, 104, 110 list (gdb), 109 log (procdure), 52 log10 (procdure), 52 long (dclaration), 23 long double (dclaration), 23, 39 long long (dclaration), 23 ls (commande), 110 mmoire, 11, 95 mmoire secondaire, 95 Mac OS, 13 Machine de von Neumann, 11 main (procdure), 48 man (Linux), 109 MasterMind (exemple), 82 math.h (inclusion), 36 Matlab, 6 MIPS, 95 mkdir (commande), 101, 110 mv (commande), 111 M_E (valeur e), 39 M_PI (valeur pi), 36 next (gdb), 107, 109 nexti (gdb), 109 norme C, 113 NotePad, 17 octet, 12, 95 oprateurs ++, 33 --, 33 de comparaison, 30 logiques, 32 sizeof, 23, 62 options -Wall, 113, 121 -ansi, 113 -g, 104, 113 -lm, 51 -o, 113
145
146 -pedantic, 113 priphriques, 11 paramtre, 48 Pascal, 6 passage la ligne, 20, 22 PATH, 20 Pentium 4, 96 Pentium Pro, 96 PGCD, 29 pi (valeur M_PI), 36 pow (procdure), 52 prprocesseur, 18 include, 18 print (gdb), 106, 109 printf (procdure), 22, 24 procdure, 19, 42 procdures abs, 53 acos, 52 asin, 52 atan, 52 atan2, 52 atof, 53 atoi, 53 atol, 53 ceil, 52 cos, 52 cosh, 52 exit, 19, 53 exp, 52 fabs, 36, 52 oor, 52 fmod, 37, 52 labs, 53 log, 52 log10, 52 main, 19, 48 mathmatiques, 51 pow, 52 printf, 22, 24 puts, 19 rand, 53 sin, 52
INDEX
sinh, 52 sqrt, 52 srand, 53 strcat, 65 strcmp, 65 strcpy, 65 strlen, 65 strncat, 65 strncmp, 65 strncpy, 65 tan, 52 tanh, 52 processeur, 11, 95 processus, 11 programmation, 17 programme, 11 programme excutable, 17 programme objet, 17 programme principal, 20 programme source, 17 programmes Calcul dun sinus, 35 Dates, 73 Hello World, 18 MasterMind, 82 PGCD, 31, 46 Reines, 79 Somme des N premiers entiers, 32 Surface dun disque, 21 prototype, 48 puts (procdure), 19, 20 quit (gdb), 105, 108 quote, 13 rpertoire, 13 rpertoire courant, 20 racine, 13 rand (procdure), 53 registre, 12, 95 rem (commande), 101 rename (commande), 101 ressources, 14 return (instruction), 19
INDEX
rm (commande), 111 Roelens (Marc), 9, 123 run (gdb), 105, 108 saut, 15 Scheme, 6 Scilab, 6 shell, 14 short (dclaration), 23 signed char, 12 sin (procdure), 52 sinh (procdure), 52 sinus, 35 sizeof (oprateur), 23, 62 slash, 13 sqrt (procdure), 52 srand (procdure), 53 stdio.h (inclusion), 18 stdlib.h (inclusion), 53 step (gdb), 107, 109 stepi (gdb), 109 strcat (procdure), 65 strcmp (procdure), 65 strcpy (procdure), 65 string.h (inclusion), 64 strlen (procdure), 65 strncat (procdure), 65 strncmp (procdure), 65 strncpy (procdure), 65 sufxe, 13 sufxes .c, 17 .exe, 20 .h, 19 .o, 17, 113 suprieur (oprateur), 30 suprieur ou gal (oprateur), 30 tan (procdure), 52 tanh (procdure), 52 traducteur, 14 unit arithmtique et logique, 95 unit centrale, 11, 95 UNIX, 6, 13 unsigned (dclaration), 23 unsigned char, 12 valeur, 16 valeurs entires, 16 valeurs relles, 16 variables, 16 vi, 17 void, 36 void (dclaration), 48 von Neumann, 11 vrai (boolen), 30 while (instruction), 31 Windows, 13, 14, 20, 99
147
148
INDEX
3
5 5 5 6 6 7 7 7 8 8 8 8 8 9 11 11 11 11 12 12 12 12 13 14 14 14 15 15 16 17 17 17
1 Introduction 1.1 Cours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1.1 Modle de lordinateur . . . . . . . . . . . . . . . . . . . . 1.1.1.1 Architecture dun ordinateur . . . . . . . . . . . 1.1.1.2 La mmoire . . . . . . . . . . . . . . . . . . . . 1.1.1.3 Le processeur . . . . . . . . . . . . . . . . . . . 1.1.1.4 Communication entre processeur et mmoire . . . 1.1.1.5 Les disques durs . . . . . . . . . . . . . . . . . . 1.1.1.6 Les systmes de chiers . . . . . . . . . . . . . . 1.1.1.7 La programmation des ordinateurs . . . . . . . . 1.1.1.8 Le systme dexploitation . . . . . . . . . . . . . 1.1.1.9 Lenvironnement de travail . . . . . . . . . . . . 1.1.1.10 Fonctionnement gnral et processus dexcution 1.1.1.11 Notion de variable . . . . . . . . . . . . . . . . . 1.1.2 Reprsentation des informations . . . . . . . . . . . . . . . 1.1.3 Mise en uvre dun programme . . . . . . . . . . . . . . . 1.1.3.1 Quest-ce quun langage de programmation ? . . . 1.1.3.2 Les tapes de la vie dun programme . . . . . . . 149
150 Un premier programme C . . Un second exemple . . . . . . Types et variables en C . . . . 1.1.6.1 Types scalaires . . . 1.1.6.2 Constantes . . . . . 1.1.6.3 Arithmtique mixte 1.1.6.4 Variables et adresses retenir . . . . . . . . . . . . . . . . Travaux pratiques . . . . . . . . . . . 1.3.1 Exercice 1 . . . . . . . . . . . 1.3.2 Exercice 2 . . . . . . . . . . . 1.3.3 Exercice 3 . . . . . . . . . . . 1.1.4 1.1.5 1.1.6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 1.3
2 Quelques problmes lmentaires 2.1 Cours . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1.1 Un premier problme : lalgorithme dEuclide . 2.1.1.1 Lalgorithme . . . . . . . . . . . . . 2.1.1.2 Passage au programme . . . . . . . 2.1.1.3 Le programme nal . . . . . . . . . 2.1.1.4 Un cas non prvu ! . . . . . . . . . . 2.1.2 Somme des premiers entiers . . . . . . . . . . 2.1.3 Le calcul dun sinus . . . . . . . . . . . . . . 2.1.3.1 Lalgorithme . . . . . . . . . . . . . 2.1.3.2 Dtail de lalgorithme . . . . . . . . 2.1.3.3 Petit problme ! . . . . . . . . . . . 2.2 retenir . . . . . . . . . . . . . . . . . . . . . . . . . 2.3 Travaux pratiques . . . . . . . . . . . . . . . . . . . . 2.3.1 Exercice 1 . . . . . . . . . . . . . . . . . . . . 2.3.2 Exercice 2 . . . . . . . . . . . . . . . . . . . . 2.3.3 Exercice 3 . . . . . . . . . . . . . . . . . . . . 2.3.4 Exercice 4 . . . . . . . . . . . . . . . . . . . . 2.3.5 Exercice 5 . . . . . . . . . . . . . . . . . . . . 3 Procdures 3.1 Cours 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 . . . . . . . . . . . . . . . . . . . . . . . . . . Retour au PGCD . . . . . . . . . . . . . . . . La notion de procdure . . . . . . . . . . . . . La procdure PGCD, premire version . . . . La procdure PGCD, deuxime version . . . . Quelques aspects des procdures du langage C 3.1.5.1 Variables locales et globales . . . . . 3.1.5.2 Paramtres dune procdure . . . . . 3.1.5.3 Notion de prototype . . . . . . . . . 3.1.5.4 Compilation spare . . . . . . . . .
151 50 51 51 53 54 54 55 55 55 55 55 55 57 57 57 58 58 59 59 60 61 61 62 63 63 64 65 67 68 68 68 68 69 69 69 69 70 70 70 71
3.2 3.3
4 Tableaux 4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.1 Un premier exemple . . . . . . . . . . . . . . . . . . . . . . 4.1.2 Les tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.2.1 Caractristique de base . . . . . . . . . . . . . . . 4.1.2.2 Dclaration de tableau . . . . . . . . . . . . . . . . 4.1.2.3 Accs aux lments des tableaux . . . . . . . . . . 4.1.2.4 Tableaux et adresses . . . . . . . . . . . . . . . . . 4.1.2.5 Tableaux de caractres . . . . . . . . . . . . . . . . 4.1.2.6 Tableaux et procdures . . . . . . . . . . . . . . . 4.1.2.7 Exemples . . . . . . . . . . . . . . . . . . . . . . 4.1.3 Chanes de caractres . . . . . . . . . . . . . . . . . . . . . . 4.1.3.1 Introduction . . . . . . . . . . . . . . . . . . . . . 4.1.3.2 Oprations sur chanes de caractres . . . . . . . . 4.1.3.3 Exemples de manipulation de chanes de caractres 4.1.4 Retour sur la procdure main . . . . . . . . . . . . . . . . . 4.1.4.1 Utilisation des arguments de la fonction main . . . 4.2 retenir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3 Travaux pratiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.1 Exercice 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.2 Exercice 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.3 Exercice 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.4 Exercice 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.5 Exercice 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.6 Exercice 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.7 Exercice 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.8 Exercice 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.9 Exercice 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
152 5 Du problme au programme 5.1 Cours . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1.1 Calcul dintervalle de temps . . . . . . . . . . . . 5.1.1.1 Une premire dcomposition . . . . . . 5.1.1.2 Analyse de la fonction N . . . . . . . . 5.1.1.3 Calcul de N4 (h, m, s) . . . . . . . . . . 5.1.1.4 Calcul de Nj3 (j ) . . . . . . . . . . . . 5.1.1.5 Calcul de Nj1 . . . . . . . . . . . . . . 5.1.1.6 Calcul de Nj2 . . . . . . . . . . . . . . 5.1.1.7 Correction dune inexactitude . . . . . . 5.1.1.8 Le bug du 19 janvier 2038 . . . . . . . . 5.1.2 Le problme des reines sur un chiquier . . . . . . 5.1.2.1 Un peu danalyse . . . . . . . . . . . . 5.1.2.2 Gnration des positions . . . . . . . . . 5.1.2.3 Test dune position . . . . . . . . . . . 5.1.2.4 Dessin de lchiquier . . . . . . . . . . 5.1.2.5 Quelques rsultats . . . . . . . . . . . . 5.1.3 Toujours plus complexe . . . . . . . . . . . . . . 5.1.3.1 Description prcise du jeu . . . . . . . . 5.1.3.2 Analyse, modlisation des donnes . . . 5.1.3.3 Les traitements . . . . . . . . . . . . . . 5.1.3.4 Tirage au sort de la combinaison . . . . 5.1.3.5 Lecture de la proposition de lutilisateur 5.1.3.6 Manipulation des couleurs . . . . . . . . 5.1.3.7 Calcul du score dune proposition . . . . 5.1.3.8 Gestion de laide . . . . . . . . . . . . . 5.1.3.9 Le jeu complet . . . . . . . . . . . . . . 5.2 retenir . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Travaux pratiques . . . . . . . . . . . . . . . . . . . . . . 5.3.1 Exercice 1 . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
II Annexes
A Quelques aspects du matriel A.1 Processeur, ou unit centrale A.2 Mmoire . . . . . . . . . . . A.3 Bus . . . . . . . . . . . . . A.4 Exemple de Processeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
93
95 95 95 96 96
B Environnement de dveloppement 99 B.1 Lenvironnement de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 B.2 Utilisation sous Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 B.2.1 Linterprteur de commandes . . . . . . . . . . . . . . . . . . . . . . . 100
153 102 102 103 103 104 104 104 104 104 105 105 108 108 109 109 109 110 110 111 113
C Projet 2006 115 C.1 Prsentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 D Pour information : projet 2005 D.1 Prsentation . . . . . . . . . D.2 Manipulation de polynmes D.3 Sil vous reste du temps . . . D.3.1 Note : graphiques . . D.4 Critres dvaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 117 117 118 118 119 121 121 121 122 122 122 122 123 123 123 124 124 124
E Pour information : projet 2004 E.1 Prsentation . . . . . . . . . . . . . E.2 Les rgles du jeu . . . . . . . . . . E.3 Le problme . . . . . . . . . . . . . E.4 Quelques prcisions . . . . . . . . . E.4.1 Taille du tableau . . . . . . E.4.2 Afchage . . . . . . . . . . E.4.3 Saisie du motif initial . . . . E.5 Correction . . . . . . . . . . . . . . E.6 Donnes du problme . . . . . . . . E.7 Traitements . . . . . . . . . . . . . E.7.1 Initialisation du jeu . . . . . E.7.2 Gestion de lvolution du jeu
154 Afchage du jeu . . . . . . . . . . . Fin du jeu . . . . . . . . . . . . . . . Codage . . . . . . . . . . . . . . . . E.7.5.1 Recopie dun jeu . . . . . . E.7.5.2 Nombre de voisins vivants . E.7.5.3 Procdure dvolution . . . E.7.5.4 Procdure principale . . . . E.8 Interfaces utilisateur . . . . . . . . . . . . . . E.8.1 Interface terminal . . . . . . . . . . . E.8.1.1 Initialisation . . . . . . . . E.8.1.2 Afchage . . . . . . . . . E.8.1.3 Test darrt . . . . . . . . . E.8.2 Interface curses . . . . . . . . . . . . E.8.2.1 Initialisation . . . . . . . . E.8.2.2 Afchage . . . . . . . . . E.8.2.3 Test darrt . . . . . . . . . E.8.3 Interface vogle . . . . . . . . . . . . E.8.3.1 Initialisation . . . . . . . . E.8.3.2 Afchage . . . . . . . . . E.8.3.3 Test darrt . . . . . . . . . Bibliographie Figures Index Table des matires E.7.3 E.7.4 E.7.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .