Académique Documents
Professionnel Documents
Culture Documents
Cours CPP
Cours CPP
1 Un peu dhistoire
Le langage C++ a deux grands anctres : Simula, dont la premire version a t conue en 1967. Cest le premier langage qui introduit les principaux concepts de la programmation objet. Probablement parce quil tait en avance sur son temps, il na pas connu lpoque le succs quil aurait mrit, mais il a eu cependant une inuence considrable sur lvolution de la programmation objet. Dvelopp par une quipe de chercheurs norvgiens, Simula-67 est le successeur de Simula I, luimme inspir dAlgol 60. Conu dabord des ns de modlisation de systmes physiques, en recherche nuclaire notamment, Simula I est devenu un langage spcialis pour traiter des problmes de simulation. Ses concepteurs faisaient aussi partie du groupe de travail I FIP 1 qui poursuivait les travaux ayant donn naissance Algol 60. Simula-67 est avec Pascal et Algol 68 un des trois langages issus des diffrentes voies explores au sein de ce groupe. Son nom fut chang en Simula en 1986. Comme son prdcesseur Simula I, Simula permet de traiter les problmes de simulation. En particulier, un objet est considr comme un programme actif autonome, pouvant communiquer et se synchroniser avec dautres objets. Cest aussi un langage de programmation gnral, reprenant les constructions de la programmation modulaire introduites par Algol 60. Il y ajoute les notions de classe, dhritage et autorise le masquage des mthodes, ce qui en fait un vritable langage objets. Le langage C a t conu en 1972 aux laboratoires Bell Labs. Cest un langage structur et modulaire, dans la philosophie gnrale de la famille Algol. Mais cest aussi un langage proche du systme, qui a notamment permis lcriture et le portage du systme Unix. Par consquent, la programmation oriente systme seffectue de manire particulirement aise en C, et on peut en particulier accder directement aux fonctionnalits du noyau Unix. C possde un jeu trs riche doprateurs, ce qui permet laccs la quasi-totalit des ressources de la machine. On peut par exemple faire de ladressage indirect ou utiliser des oprateurs dincrmentation ou de dcalage. On peut aussi prciser quon souhaite implanter une variable dans un registre. En consquence, on peut crire des programmes presque aussi efcaces quen langage dassemblage, tout en programmant de manire structure. Le concepteur de C++, Bjarne Stroustrup, qui travaillait galement aux Bell Labs, dsirait ajouter au langage C les classes de Simula. Aprs plusieurs versions prliminaires, le langage a trouv une premire forme stable en 1983, et a trs rapidement connu un vif succs dans le monde industriel. Mais ce nest quassez rcemment que le langage a trouv sa forme dnitive, conrme par une norme. C++ peut tre considr comme un successeur de C. Tout en gardant les points forts de ce langage, il corrige certains points faibles et permet labstraction de donnes. De plus, il permet la programmation objet. Dautres langages, et en particulier Java, se sont fortement inspirs de la syntaxe de C++. Celle-ci est de ce fait devenue une rfrence. Nous supposons en particulier que les lves qui ont dj appris Java ne seront pas dpayss par ce langage. Cependant, nous voulons mettre en garde contre plusieurs fausses ressemblances : si la syntaxe est la mme ou trs proche, plusieurs concepts sous-jacents sont diffrents. Nous nous efforcerons de signaler ces piges potentiels.
double : rel en double prcision (2 mots machines), et sa variante long double (3 ou 4 mots machine), void qui spcie un ensemble vide de valeurs. Les constantes caractres scrivent entre quotes simples : a G 3 * [ Certains caractres de contrle scrivent par des squences prdnies ou par leur code octal ou hexadcimal, comme par exemple : \n \t \r \135 \ \x0FF Les constantes entires peuvent scrire en notations dcimale, hexadcimale (prcdes de 0x 3 ) ou octale (prcdes de 0 4 ). Pour forcer la constante tre de type entier long, il faut ajouter un L la n, de mme le sufxe u indique une constante non signe : 12 -43 85 18642 54L 255u 38ul 0xabfb 0x25D3a 0x3a 0321 07215 01526 Les constantes relles scrivent avec point dcimal et ventuellement en notation exponentielle : 532.652 -286.34 52e+4 42.63E-12 12.73 -28.15e4
Les constantes de type chanes de caractres (voir plus loin) scrivent entre double-quotes : "Home sweet home" "Franais, je vous ai compris."
3 Oprateurs et expressions
C++ offre un jeu trs tendu doprateurs, ce qui permet lcriture dune grande varit dexpressions. Un principe gnral est que toute expression retourne une valeur. On peut donc utiliser le rsultat de lvaluation dune expression comme partie dune autre expression. De plus, le parenthsage permet de forcer lordre dvaluation. Les oprateurs disponibles sont les suivants :
3.3 Laffectation
= affectation Il faut bien noter que le signe = est loprateur daffectation, et non de comparaison ; cela prte parfois confusion, et entrane des erreurs difciles discerner. noter aussi que laffectation est une expression comme une autre, cest--dire quelle retourne une valeur. Il est donc possible dcrire : a = b = c+2; ceci revenant affecter b le rsultat de lvaluation de c+2, puis a le rsultat de laffectation b = c+2, cest--dire la valeur quon a donne b. Remarquez lordre dvaluation de la droite vers la gauche.
5. Dans ce polycopi, nous expliquons lancien systme de conversion de types, qui est encore largement en vigueur. Il faut nanmoins savoir quun nouveau mcanisme de cast, utilisant les oprateurs static_cast, dynamic_cast, const_cast et reinterpret_cast, a ofciellement remplac lancien systme dans la norme dnitive de C++. Cet ancien systme reste nanmoins utilisable, et la plupart des programmes existants lemploient encore largement.
qui permet deffectuer linstruction (ou le bloc) une premire fois avant le premier test sur la condition darrt. Nous avons dj vu lemploi de break dans les structures conditionnelles. En fait, break permet plus gnralement de sortir prmaturment et proprement dune structure de contrle. Ainsi, on peut lutiliser galement dans une itration pour sortir sans passer par la condition darrt. Donnons en exemple une boucle qui lit un caractre en entre (par une fonction getchar()) et qui sarrte sur la lecture du caractre & : for ( ; ; ) if ((c = getchar()) == &) break; Cette fonction peut bien sr scrire plus simplement : while ((c = getchar()) != &) ; // le point-virgule ici est // linstruction vide ! Une autre instruction particulire qui peut tre utile dans les itrations est continue, qui permet de se rebrancher prmaturment en dbut ditration. Enn, signalons que C++ permet aussi de faire goto ; mais comme nous sommes des informaticiens bien levs qui ne disent jamais de gros mots, nous nen parlerons pas...
5 Fonctions et variables
Thoriquement, toute fonction retourne une valeur, qui peut tre utilise ou non. Toutefois, un type particulier, void, permet dindiquer quune fonction ne retourne pas de valeur, ou plutt que la valeur retourne ne doit pas tre prise en compte. Le passage de paramtres peut se faire par valeur ou par rfrence. Le passage dune rfrence se note par le caractre &. En voici un exemple avec une fonction qui change les valeurs de deux variables : void swap(int& a, int& b) { int tmp = a; a = b; b = tmp; } ... int x, y; ... swap(x, y); Une rfrence peut galement tre dclare constante, par exemple pour passer la rfrence dun objet de grande taille, tout en interdisant laccs en criture dans la fonction. Avec un passage par valeur, lobjet serait dupliqu dans la pile dexcution. En supposant lexistence dun type Matrice dcrivant une matrice, on peut par exemple crire : void print(const Matrice& m) { // le compilateur interdit toute tentative // de modification de la variable m dans // le corps de la fonction print } Une fonction peut tre dclare en ligne, comme dans lexemple suivant : inline int max(int x, int y) { return (x > y ? x : y); } La qualication inline indique au compilateur quil est prfrable de remplacer chaque appel la fonction par le code correspondant. Cette qualication nest quindicative, et nest en particulier pas prise en compte si elle est irralisable, cest--dire si le compilateur a besoin de connatre ladresse de la fonction. Une fonction peut galement tre surcharge ; la discrimination est alors faite sur le nombre et le type des paramtres effectifs. 7
Les variables dun programme C++ peuvent avoir plusieurs classes de stockage : automatiques : cest loption par dfaut pour toute variable interne dune fonction. Lallocation se fait dans la pile dexcution. externes ou globaux : ce sont les variables dnies lextrieur de toute fonction, et qui sont donc globales. Si on fait rfrence dans une fonction une variable dnie dans un autre module (en compilation spare), on prcisera quelle est externe par le mot-cl extern. NB : Nous dconseillons fortement lutilisation de variables externes. statiques : une variable globale statique (mot-cl static) est une variable dont le nom nest pas export ldition de liens, et qui reste donc invisible hors du module o elle est dnie. Une variable interne une fonction qui est dclare statique est une variable rmanente : sa porte de visibilit est rduite la fonction, mais elle nest initialise que la premire fois o la fonction qui la dclare est appele ; ensuite, sa valeur persiste dun appel de la fonction lautre. Le mot-cl static permet galement de dnir les variables et mthodes de classe (cf. 7.6). registres : on peut demander quune variable de type entier, caractre ou pointeur soit implante dans un registre, ce qui est souvent utile quand on veut aller vite. Les indices dans les tableaux et les pointeurs en mmoire sont souvent de bons candidats pour tre dclars comme registres. Attention : seule une variable automatique peut tre de type registre. De plus, le mot-cl register, employer dans ce cas, ne donne quune indication au compilateur ; on ne garantit pas que la variable sera bien en registre, le compilateur nayant sa disposition quun nombre limit de registres. vous de donner les indications les plus intelligentes... Les dclarations de variables peuvent en plus tre agrmentes de lun des deux mots cls suivants : const : la variable dsigne en fait une constante ; aucune modication nest autorise dans le programme. volatile : un objet dclar volatile peut tre modi par un vnement extrieur ce qui est contrl par le compilateur (exemple : variable mise jour par lhorloge systme). Cette indication donne au compilateur lui signale que toute optimisation sur lemploi de cette variable serait hasardeuse.
6 Pointeurs
Les pointeurs sont des variables contenant des adresses. Ils permettent donc de faire de ladressage indirect. Ainsi : int* px; dclare une variable px qui est un pointeur sur un entier. La variable pointe par px est note *px. Inversement, pour une variable int x; on peut accder ladresse de x par la notation &x. Ainsi, je peux crire : px = &x; ou x = *px; Voici une autre manire dcrire la fonction swap() qui change deux entiers, cette fois-ci en passant par des pointeurs : swap(int* px, int* py) { int temp; // variable temporaire temp = *px; *px = *py; *py = temp; } 8
et pour changer deux paramtres on appellera : int a,b; swap(&a,&b); Attention : un des piges les plus classiques en C++ est celui du pointeur non initialis. Le fait davoir dclar une variable de type pointeur ne suft pas pour pouvoir drfrencer ce pointeur. Encore faut-il quil pointe sur une ( case ) mmoire valide. Pour reprendre lexemple prcdent, si jcris ( ) int* px; *px = 3; jai de trs fortes chances davoir une erreur lexcution, puisque px ne dsigne pas une adresse mmoire dans laquelle jai le droit dcrire. Ce nest quaprs avoir crit par exemple px = &x; comme dans lexemple ci-dessus que linstruction *px = 3; devient valide.
Remarque : on peut crire cela de manire encore plus efcace en protant du fait quon utilise p pour lincrmenter en mme temps. Par ailleurs, une seule boucle suft, et il est inutile dutiliser des compteurs : int tab[200][200]; long int moyenne=0; register int* p = tab; register int* stop = p + 200 * 200; for ( ; p < stop ; ) /*on ne fait plus p++ ici*/ moyenne += *p++; /*on accde la valeur pointe par p, puis on lincrmente*/ moyenne /= 40000; Mais attention : le programme devient ainsi peu prs illisible, et je dconseille dabuser de telles pratiques, qui ne sont justies que dans des cas extrmes, o loptimisation du code est un impratif. Notez aussi quil est exclu de raliser des ( affectations globales ) sur les tableaux, autrement que par le ( ) mcanisme des pointeurs (pas de recopie globale).
Mais comme vous risquez dtre parfois confronts des chanes de caractres ( lancienne ) (cest( ) -dire la mode C), sachez que ce sont des tableaux de caractres termins par le caractre nul (de code 0, et not comme lentier 0 ou le caractre \0). On peut bien sr utiliser des tableaux de pointeurs, des pointeurs de pointeurs, des pointeurs de pointeurs de pointeurs, etc. Bref, vous voyez ce que je veux dire... On peut mme manipuler des tableaux de fonctions, des pointeurs de fonctions, ce qui permet dappeler plusieurs fonctions diffrentes en se servant du mme pointeur.
7 Classes et instances
De manire classique, la classe regroupe des variables dinstance et des mthodes, ainsi que dventuelles variables et mthodes de classe. Contrairement Java, on distingue en C++ la dnition de la classe de sa mise en uvre. La premire regroupe la dclaration des variables et les signatures des mthodes ; elle se met dans un chier header, qui est inclus quand on veut faire rfrence linterface de cette classe dans une autre classe ou dans un programme. Dans ce chier header, on ne met a priori pas les corps des mthodes, sauf celles qui sont inline. Illustrons cela en dclarant une classe dobjets postaux, ayant quatre variables dinstance : poids, valeur, recommande et tarif : class ObjetPostal { protected: int poids; int valeur; bool recommande; public: // Variable dinstance publique -- je sais, ce nest pas bien ! int tarif; // Constructeur ObjetPostal(int p = 20); // Mthodes inline bool aValeurDeclaree() { return (valeur > 0); } int poidsObjet() { return poids; } void recommander() { recommande = true; } }; Comme en Java, les variables dinstance et les mthodes peuvent tre prives, protges ou publiques. La diffrence entre donnes protges et donnes prives est que seules les premires restent accessibles dans les sous-classes de la classe. Les trois variables poids, valeur et recommande sont protges : elles ne sont accessibles que par les mthodes dnies dans la classe ObjetPostal et dans celles de ses sousclasses ventuelles. La variable tarif est publique : elle est accessible par nimporte quelle instance de nimporte quelle classe 6 . Les mthodes dont la dnition est incluse dans la dclaration de la classe, comme aValeurDeclaree, recommander et poidsObjet, sont implantes par des fonctions inline pour un gain defcacit lexcution. Le fait quelles soient dnies lintrieur de la dclaration de classe suft les rendre inline, sans ncessit de mot cl particulier. La fonction ObjetPostal(int), de mme nom que la classe, est un constructeur de la classe. Elle est simplement dclare ici, et sera dnie ailleurs. Nous y reviendrons au 7.1. La classe ObjetPostal peut tre utilise comme un nouveau type dans le programme : ObjetPostal* z = new ObjetPostal(200); ... delete z;
6. Le fait que jai choisi de rendre cette variable dinstance publique pour les besoins de la dmonstration ne signie pas que cette pratique est recommander, loin de l. Une rgle gnrale, qui souffre trs peu dexceptions, est de toujours cacher les dtails dimplantation, donc de rendre les variables dinstance prives ou protges.
11
Attention : la variable z est ici un pointeur sur linstance, et non linstance elle-mme. Dans le corps dune mthode, les variables dinstance de la classe sont dsignes simplement par leur nom. Laccs aux variables et mthodes dautres objets se fait classiquement par la notation pointe, ou par la notation ( che ) dans le cas dun pointeur : ( ) ObjetPostal op; ... op.recommander(); ... ObjetPostal* z = new ObjetPostal(200); ... if (z->aValeurDeclaree()) { ... }
ObjetPostal::ObjetPostal(int p) { poids = p; valeur = 0; recommande = false; } noter que dans la dclaration de la classe, le paramtre p a la valeur par dfaut 20 ; lappel du constructeur sans paramtre est donc quivalent son appel avec la valeur 20. noter aussi lutilisation de loprateur de rsolution de porte::, ncessaire ds quon nest plus ( dans ) la dnition de la classe, pour rattacher ( ) la fonction sa classe dappartenance. En fait, il nest jamais ncessaire dappeler explicitement un constructeur pour crer une instance. Cest le compilateur qui se charge de choisir le constructeur utiliser, en fonction des paramtres dinstanciation. Si aucun constructeur ne sapplique, un constructeur par dfaut est appel, qui initialise les variables des valeurs nulles. Il est cependant fortement recommand de toujours prvoir un constructeur, en tout cas ds que la classe nest pas triviale. Conformment ce qui vient dtre dit, la dclaration : ObjetPostal x; dans une mthode ou un programme, cre un objet postal dont le poids est de 20 (valeur par dfaut). En revanche, la dclaration ObjetPostal x(140); cre une instance de la classe ObjetPostal de poids 140 grammes. En fait, un constructeur comme ce dernier, avec un seul paramtre, tient lieu de fonction de conversion implicite de type. Par exemple, la dclaration suivante est valide : ObjetPostal x = 30; Elle est traduite par lapplication de la fonction de conversion dun entier en objet postal, quivalente la dclaration suivante : ObjetPostal x(30); Ce mcanisme de conversion implicite reste nanmoins limit aux constructeurs ayant un seul argument, ou pour lesquels les autres arguments ont tous des valeurs par dfaut. La place mmoire occupe par une instance locale est automatiquement restitue quand la variable qui la dsigne cesse dexister, cest--dire la sortie du bloc de programme dans lequel la variable est dnie. 12
Cependant, il arrive quun constructeur effectue une allocation dynamique de mmoire, typiquement pour une des variables dinstance. Pour restituer la place ainsi alloue quand lobjet doit disparatre, il faut dnir un destructeur, dclar comme une fonction portant le nom de la classe prcd du caractre ~. Ce destructeur est appel automatiquement quand lobjet cesse dexister. Supposons par exemple quun sac postal est caractris par une capacit maximale, un nombre dobjets contenus et un tableau dobjets postaux dont la taille est xe dynamiquement. La place ncessaire pour ce tableau tant alloue par le constructeur, elle doit tre restitue par un destructeur : // Dans le fichier SacPostal.h class SacPostal { private: int nbelts; // nombre dobjets dans le sac int capacite; // capacit du sac ObjetPostal* sac; // le tableau reprsentant le sac public: SacPostal(int); // le constructeur ~SacPostal(); // le destructeur // et les autres methodes... }; // Dans le fichier SacPostal.cpp SacPostal::SacPostal(int cap) { capacite = cap; nbelts = 0; // sac vide sac = new ObjetPostal[cap]; // allocation du tableau } SacPostal::~SacPostal() { delete [] sac; // restitution de la place // occupe par le tableau sac } La dclaration dune variable courrierDeLyon de type SacPostal peut se faire comme suit : SacPostal courrierDeLyon(250); Le constructeur SacPostal::SacPostal(int) est automatiquement appel et un tableau de 250 objets postaux est allou dynamiquement. Le compilateur engendre aussi un appel automatique au destructeur SacPostal::~SacPostal() quand la variable courrierDeLyon cesse dexister, cest--dire pour lexemple donn la sortie du bloc dans lequel elle est dnie. Les constructeurs et destructeurs peuvent aussi tre appels explicitement, lorsquon fait de lallocation dynamique de mmoire, comme dans lexemple suivant : SacPostal* ps = new SacPostal(55); ... delete ps; // constructeur appel // destructeur appel
}; Cette ( amiti ) peut tre plus slective et se limiter une ou plusieurs fonctions prcises. Supposons quen ( ) fait seule la mthode affranchir de la classe SacPostal ait besoin daccder aux champs privs de ObjetPostal. Seule cette mthode est alors dclare amie, la place de la classe : class ObjetPostal { friend void SacPostal::affranchir(); ... }; ... // Et dans la dfinition de la classe SacPostal SacPostal::affranchir() { ObjetPostal* x; ... if (x->poids < 20) // Laccs poids est autoris car la x->tarif = 2; ... // mthode est amie de la classe ObjetPostal } Associes aux notions de donnes publiques, protges et prives, les classes et les fonctions amies permettent de contrler de manire ne les protections et les accs aux variables dinstance.
7.3 Lhritage
C++ permet de raliser de lhritage multiple entre classes ; nous nous limiterons cependant dans ce polycopi lexpos de lhritage simple. Une sous-classe, appele classe drive, hrite classiquement des attributs de sa superclasse : class Colis : public ObjetPostal { protected: int volume; }; class Lettre : public ObjetPostal { protected: bool urgent; }; class CourrierInterne : public Lettre { public: CourrierInterne(int p) : (p) { tarif = 0; } }; Lors de la cration dune instance dune classe donne, tous les constructeurs de la hirarchie dhritage de la classe sont automatiquement activs, du plus gnral au plus particulier. Ainsi, la dnition du constructeur de CourrierInterne indique les valeurs donner aux paramtres du constructeur de la superclasse, savoir celui dObjetPostal. Grce lexpression : (p) qui suit la dnition du constructeur CourrierInterne(int), le constructeur ObjetPostal(int) est donc appel avec la valeur de p avant la mise zro du champ tarif. Ce mcanisme est similaire lemploi de super en Java. Dans une classe, les attributs hrits deviennent privs par dfaut, mme sils taient publics dans la superclasse. Toutes les autres classes, y compris ses sous-classes, ne peuvent y accder directement. Cependant les attributs publics hrits restent publics si lhritage est dit public grce au mot cl public, comme dans les exemples prcdents. En pratique, lhritage est public dans la grande majorit des cas, et ce nest que lorsquon souhaite hriter de limplantation tout en masquant linterface de la classe quon fait de lhritage ( priv ) Pensez donc mettre le mot cl public ! ( ). 14
dune lettre, augment de quelques oprations spciques, on peut crire : class Lettre : public ObjetPostal { protected: bool urgent; public: void affranchir() { tarif = 2 + (urgent ? 1 : 0); } }; class ParAvion : public Lettre { public: void affranchir(); }; void ParAvion::affranchir() { // affranchissement ordinaire Lettre::affranchir(); // 7F de supplement pour courrier aerien tarif += 7; }
8 La surcharge doprateurs
C++ autorise la surcharge des oprateurs. Par exemple, dnissons un oprateur + permettant dajouter le contenu de deux sacs postaux dans un nouveau sac. Comme cet oprateur doit accder aux champs privs de ses oprandes, il est dclar ami de la classe SacPostal 7 : class SacPostal { private: int nbelts; int capacite; ObjetPostal* sac; public: SacPostal(int); ~SacPostal(); friend SacPostal operator+(SacPostal&, SacPostal&); };
7. On aurait aussi pu dnir loprateur + dans la classe SacPostal.
16
... SacPostal operator+(SacPostal& sac1, SacPostal& sac2) { // cration dun gros sac de capacit ad hoc SacPostal grosSac(sac1.capacite + sac2.capacite); // nombre dlments de ce gros sac grosSac.nbelts = sac1.nbelts + sac2.nbelts; // mettre les lments de sac1 dans grosSac int i = 0; for ( ; i < sac1.nbelts ; i++) grosSac.sac[i] = sac1.sac[i]; // puis mettre les lments de sac2 dans grosSac for (register int j = 0 ; j < sac2.nbelts ; i++, j++) grosSac.sac[i] = sac2.sac[j]; return grosSac; } Le nouvel oprateur semploie ensuite de manire transparente sur les instances de la classe SacPostal : SacPostal sacSeichamps = 200, sacVandoeuvre = 1500; ... SacPostal sacNancy = sacSeichamps + sacVandoeuvre; ...
9 La bibliothque dentres/sorties
Nous ne prtendons pas couvrir dans ce polycopi les trs nombreuses fonctionnalits couvertes par la bibliothque standard C++. Cependant, il nous semble utile de donner quelques indications sur les entres/sorties. Pour utiliser la bibliothque, il faut inclure son chier de dclarations : #include <iostream> Les oprations standards dentre et de sortie sont fournies par trois ots (streams), dsigns par les variables suivantes : cin dsigne le ot dentre standard (typiquement, votre clavier), cout dsigne le ot de sortie standard (typiquement, la fentre dexcution sur votre cran), cerr dsigne le ot standard des messages derreur. Les oprateurs << et >> sont rednis pour permettre des critures et lectures aises : #include <iostream> #include <string> cout << "Bonjour, comment vous appelez-vous ? "; string nom; cin >> nom; if (nom.string_empty()) { cerr << "erreur : nom vide" << endl; } else { cout << nom << ", donnez-moi maintenant votre ge : "; int age; cin >> age; cout << "Ouah, vous ntes plus tout jeune !" << endl; } 17
On notera au passage lemploi de string, la bibliothque de manipulation de chanes de caractres C++, et de la constante endl, qui indique le passage la ligne. Bien entendu, la bibliothque iostream fournit de nombreuses autres fonctionnalits dentre/sortie, et la bibliothque fstream fournit les fonctionnalits de manipulation de chiers. Nous nous sommes contents ici de donner quelques rudiments vous permettant dcrire vos tout premiers programmes...
18