Professional Documents
Culture Documents
Notes de cours
Version 3.7
SOMMAIRE
Partie I :
Les bases du langage (rgles d'criture, types, variables, oprateurs, structures de contrle, ) Les entres /sorties en C++. Les tableaux. Les pointeurs et les rfrences. La gestion dynamique de la mmoire. Les fonctions. Les chanes de caractres Les structures et les numrations.
Partie II :
Introduction la programmation oriente objet. Les classes (attributs, mthodes, droits d'accs, instanciation,...) Constructeurs et destructeur. Espaces de noms. Membres statiques et membres constants. Fonctions amies. Hritage et polymorphisme. La surcharge des oprateurs. Les modles. La gestion des exceptions.
Annexe :
- Les fichiers.
Ce programme affiche le mot Bonjour l'cran. Cet affichage est ralis travers l'oprateur d'extraction appliqu l'objet cout. cout est un objet dfini dans la bibliothque iostream.h. Cette bibliothque doit tre incluse dans le programme. En gnral les dclarations des fonctions standards du langage C++ qui sont susceptibles d'apparatre dans le programme se trouvent dans des fichiers d'entte (header) ayant l'extension .h. Ces fichiers doivent tre inclus au dbut de chaque programme avec la directive du prprocesseur include. La fonction main est la fonction principale du programme. C'est elle qui contient le corps du programme. Les accolades jouent le rle de "begin" et "end" du Pascal. Elles indiquent le dbut et la fin d'un bloc de code. Remarque : Ce petit programme donne une premire ide sur la structure d'un programme C++. D'autres lments du langage peuvent encore prendre place. Leurs dfinitions ainsi que leur emplacements seront dcrits dans les parties suivantes du cours.
Version 3.7
Karim Kalti
Remarque : Certains compilateurs peuvent ajouter d'autres mots cls qui lui sont propres.
Les identificateurs
Les identificateurs servent dsigner les diffrents "objets" manipuls par le programme (variables, fonctions, etc.). Ils se prsentent sous la forme de chanes de caractres alphanumriques (combinaison de caractres alphabtiques et de chiffres), de taille quelconque, dans les limites acceptes par le compilateur. En C++, les identificateurs doivent respecter les rgles suivantes : Un identificateur doit toujours commencer par une lettre ou par le caractre underscore _. Les caractres accentus (, , , , , ,) ne sont pas accepts par le compilateur. Un identificateur doit tre diffrent des mots cls du langage. Les majuscules et les minuscules sont considres comme des lettres distinctes. En gnrale la taille d'un identificateur ne doit pas dpasser les 32 caractres. Ce nombre varie suivant les compilateurs. Par exemple Borland C++ Version 5.0 utilise 250 caractres comme caractres significatifs. Les caractres situs au del du nombre significatif ne sont pas pris en considration. Les identificateurs servent donner des noms divers lments du langage : les variables, les fonctions, les constantes, les numrations, les structures Exemples d'identificateurs valides :
Nabs n1234_ abc table chaise
Exemple d'identificateurs non valides : 2abc n'est pas un identificateur valide. Exemples d'identificateurs diffrents :
Somme somme sOmme
Les commentaires
Les commentaires sont fondamentaux pour la comprhension des programmes et donc pour leur maintenance et rutilisation. C'est pourquoi, il est trs conseill de les utiliser autant que c'est possible. Ils se prsentent comme des portions de texte qui ne sont pas pris en compte lors de la compilation. Le C++ supporte deux types de commentaires : Le commentaire multi-lignes : Il est introduit par /* et se termine par */ Le commentaire ligne : le commentaire peut s'tendre sur une ligne seulement. Il est introduit par //. Sa fin est indique par un retour la ligne. Exemple :
/* Ceci est un commentaire sur plusieurs lignes */ // Ceci est un commentaire sur une seule ligne.
Le format libre
Le C++ autorise une mise en page libre. Ainsi une instruction peut s'tendre sur un nombre quelconque de lignes pourvu qu'elle se termine par un point virgule. De mme une ligne peut comprendre autant d'instructions que voulues.
Version 3.7
Karim Kalti
Types de base et dclaration des variables Programmation oriente objet (C++) _________________________________________________________________________________________________________________
Les types drivs A ces types de base s'ajoutent d'autres variantes obtenues en plaant une spcification supplmentaire devant le type de base. Les mots cls permettant de construire les types drivs sont : Mot-cl
long short unsigned
Signification Pour dfinir des entiers ou des rels de grande taille. long s'applique aux types de base int et double. Lorsque ce mot est utilis tout seul il dsigne alors par dfaut un entier long. Permet de manipuler les entiers de petite taille. Il s'utilise avec int ou tout seul (mme signification). Il se place devant les types entiers ou caractres qui doivent tre considrs comme tant non signs. Il peut s'employer tout seul. Il dsigne alors un entier non sign (unsigned int).
Taille
1 1 2 2 4 4 2 ou 4 2 ou 4 4 8 10 1
Plage de valeurs
-128 127 0 255 -32768 32768 0 65535 -2 147 483 648 2 147 483 647 0 4 294 967 295 Comme short ou long Comme unsigned short ou unsigned long 1.175 10-38 3.402 10+38 2.225 10-308 1.797 10+308 3.4 10-4932 1.1 10+4932 true, false
Entiers
Rels Boolen
Remarque 1 : Le type int possde toujours la taille d'un mot machine. Par consquent l'espace qu'il occupe en mmoire dpend de la machine sur laquelle s'excute le programme. Cette taille est par exemple de 2 octets (identique celle du type short) pour les p d'intel 8086 et 80286. Elle est de 4 octets (identique celle du type long) pour les p PENTIUM. Pour cela, et pour assurer la portabilit et le bon fonctionnement des programmes sur n'importe quelle machine, il est prfrable d'utiliser pour les entiers les types short et long.
Version 3.7
Karim Kalti
Types de base et dclaration des variables Programmation oriente objet (C++) _________________________________________________________________________________________________________________
Tous les autres types correspondent des tailles fixes et ne dpendent pas des machines. Remarque 2 : La reprsentation des rels utilise le format suivant : Signe Mantisse 10Exposant. Remarque 3 : Le type char peut servir en C/C++ pour le stockage des entiers qui sont compris entre 128 et 127. Les caractres sont d'ailleurs reprsents sous forme d'entiers. Ces entiers correspondent aux codes ASCII.
Les variables
Dclaration TYPE NomVariable;
O : Type : dsigne le type des donnes que peut stocker la variable. NomVariable : dsigne le nom de la variable. La nomination d'une variable doit respecter les rgles qui rgissent les identificateurs en C++. Exemple :
float tension; int resistance; float Moyenne;
Plusieurs variables peuvent tre dclares simultanment si elles ont le mme type. Elles sont alors spares les unes des autres par des virgules. Exemple :
int a,b,c; float note, moyenne;
Une variable est caractrise par son adresse et par la taille qu'elle occupe en mmoire. L'adresse est automatiquement attribue par le systme alors que la taille dpend du type de la variable. Lieu de dclaration d'une variable Une variable doit tre dclare avant d'tre utilise. En C, les variables qui sont utilises l'intrieur d'un bloc doivent tre dclares au dbut de ce dernier. Cette restriction a t limine en C++. Il est ainsi possible de dclarer une variable n'importe o dans le bloc pourvu que cette dclaration soit faite avant la premire utilisation. Exemple :
// Code correct en C/C++ { double i,j, somme, moyenne; i=5.5; j=6.2; somme = i+j; moyenne = somme/2; } // Code correct en C++ // mais incorrect en C { double i,j; i=5.5; j=6.2; double somme; somme = i+j; double moyenne; moyenne = somme/2; }
Version 3.7
Karim Kalti
Types de base et dclaration des variables Programmation oriente objet (C++) _________________________________________________________________________________________________________________
Initialisation des variables Une valeur initiale peut tre affecte une variable ds sa dclaration.
int i=4; char C ='a'; bool b1 = true; bool b2 = false;
La valeur d'initialisation d'une variable peut tre le rsultat d'un calcul portant sur des constantes.
int i = 4+5; double j=2.5*3.2;
Le contenu d'une variable non initialise est indtermin, sauf pour les variables globales et statiques qui sont automatiquement initialises 0. Affectation d'un contenu une variable L'oprateur qui permet de faire l'affectation d'un contenu une variable est (=). Il est appel oprateur d'affectation ou d'assignation. Dans une affectation (exemple : i=5), la constante ou le rsultat d'une opration arithmtique ou logique se trouvant droite de l'oprateur est copi dans la variable se trouvant sa gauche. Il est possible de faire plusieurs affectations en mme temps et ce en enchanant plusieurs fois l'oprateur (=) de la manire suivante :
int i,j; i = j = 2;
Conversions lors d'assignation Les assignations entre variables de types diffrents sont autorises. Elles engendrent alors des conversions implicites (automatiques) des types de donnes. Ces conversions sont rgies par les rgles prsentes dans le tableau suivant : Commentaire Aucune modification de valeur. - Pour le type char on a expansion du bit de signe. char int - Pour le type unsigned char il n'y a pas d'expansion. int char Perte des octets les plus significatifs de l'entier. short int (4 octets) Pas de modification de valeur int (4 octets) short Rsultat tronqu : perte des octets les plus significatifs. unsigned short Perte des octets les plus significatifs de l'entier. long unsigned Les deux octets les plus significatifs du long prennent la valeur 0. int float Ajout d'une partie dcimale nulle (exemple : 15 15.0f) float int Perte de la partie dcimale (exemple :2.5f2) float double Pas de problme double float Perte de prcision Les conversions prsentes dans le tableau ci-dessus peuvent engendrer des pertes de donnes. C'est pourquoi les assignations entre variables de types diffrents doivent tre effectues avec prcaution. La notion de bloc Un bloc est une suite d'instructions dlimite par une accolade ouvrante et une accolade fermante. Exemple :
{ instruction_1; instruction_2; instruction_n; }
Conversion
Porte des variables : La porte d'une variable est dfinie par les zones du programme o la variable peut tre utilise ou en d'autres mots les zones o la variable est visible. Une variable n'est visible qu' l'intrieur du bloc dans lequel elle est dclare.
Version 3.7
Karim Kalti
Types de base et dclaration des variables Programmation oriente objet (C++) _________________________________________________________________________________________________________________
Exemple 1 :
#include <stdio.h> void main() { int i=5; { int i=1, j=3; printf("i du bloc :%d\n", i); printf("j du bloc :%d\n", j); } printf("i du main : %d\n", i); }
Rsultat :
i du bloc : 1 j du bloc : 3 i du main : 5
Exemple 2 :
#include <stdio.h> void main() { int i=5; { int i=1, j=3; printf("i du bloc :%d\n", i); } printf("j du bloc :%d\n", j); // ERROR car j n'est pas visible ce niveau printf("i du main : %d\n", i); }
Les variables i et j du bloc ne sont vues qu' l'intrieur du bloc et perdent par consquent leur signification la sortie de ce dernier. Elles sont dites locales au bloc. La variable i du bloc cache l'intrieur de ce dernier le i du main. Variable globale Des variables peuvent tre dclares en dehors de tous les blocs et fonctions. Elles sont dites globales et peuvent tre utiliss dans tout le programme. Exemple :
#include <stdio.h> int i; void main() { printf("i vaut : %d\n", i); // i vaut 0 }
La variable i dans ce cas de figure est considre comme une variable globale puisqu'elle ne fait partie d'aucun bloc. Elle est automatiquement initialise 0.
Les constantes
Une constante est une donne qui ne peut pas tre modifie. Cette donne peut tre un entier, un rel, un caractre ou une chane de caractres. C distingue les constantes entires, les constantes virgules flottantes, les constantes de type caractre et les constantes de type chane. C++ introduit en plus les constantes boolennes. Les constantes boolennes Elles peuvent avoir deux valeurs true ou false.
Version 3.7
Karim Kalti
Types de base et dclaration des variables Programmation oriente objet (C++) _________________________________________________________________________________________________________________
Les constantes entires Elles peuvent tre dfinies dans les diffrentes bases du codage. Format dcimal : (0,1,2, , 9). Format octal : (1,2,7) : Ce format se distingue par l'adjonction du chiffre 0 devant la valeur ou par le prfixe \. Format hexadcimal : (0,1,2, ,9,A,BC,D,E,F) : ce format est caractris par l'ajout du prfixe 0x ou 0X ou \x devant la valeur. De plus quand il s'agit : D'une constante reprsentant une valeur ngative, il faut faire prcder la valeur de l'oprateur de signe -. D'une valeur affecte un entier long, il faut adjoindre la fin de la valeur la lettre l ou L. Par rapport au C, le C++ introduit les suffixes u et U pour spcifier qu'une constante est un entier non sign. Exemples : Constante 12 12L 12U 12LU 014 0xC 0xCLU Les constantes flottantes Deux notations sont possibles pour les constantes flottantes : Notation littrale avec virgule flottante seule. Cette notation doit comporter obligatoirement un point (qui correspond la virgule). La partie entire ou la partie dcimale peut tre omise (seule une des deux mais pas les deux en mme temps). Exemples :
12.75, -0.58, -.58 , 4. , 0.27, 4.00
Signification Constante entire dcimale Constante entire de type long Constante entire non signe Constante entire non signe de type long Constante entire octale Constante entire hexadcimale (12 dans la base dcimale) Constante entire hexadcimale (12) affecte un entier long non sign
La notation scientifique avec virgule flottante et exposant not e ou E reprsentant l'exposant en base 10. (le point peut tre absent dans cette notation). Exemples : 5.69E4
5.69E+4
56.9e3
48e13
48.e13
48.E13
57.3e-5
Remarque : Par dfaut, toutes les constantes flottantes sont codes par le compilateur comme tant de type double. Il est toutefois possible de leur imposer d'tre de type : float : en les faisant suivre de la lettre f ou F. long double : en les faisant suivre de la lettre l ou L. Exemple :
12.34 12.34f 12.34L
Nombre flottant de double prcision (double) Nombre flottant de simple prcision (float) Nombre flottant de trs grande prcision (long double).
Version 3.7
Karim Kalti
Types de base et dclaration des variables Programmation oriente objet (C++) _________________________________________________________________________________________________________________
Les constantes caractres Les constantes de type caractres peuvent tre reprsentes de deux manires : Pour les caractres imprimables : par la valeur du caractre place entre deux apostrophes [simples quottes]. Pour les caractres imprimables ou non (d'une manire gnrale) : une constante caractre peut tre dfinie par son code ASCII octal ou hexadcimal prcd d'un antislash le tout plac entre deux quottes. Il est noter que la reprsentation l'aide du code hexadcimal doit tre prfix en plus d'un x. Exemple : Le caractre (a) peut tre reprsent de plusieurs manires :
Base dcimale : 'a' Base octale : '\101' Base hexadcimale : '\x41'
Les caractres spciaux Par ailleurs, certains caractres non imprimables possdent une reprsentation spciale utilisant l'antislash. Le tableau suivant prsente ces caractres et leur signification. Notation \n \t \v \b \r \f \a \' \" \? \\ Code ASCII 0A 09 0B 08 0D 0C 07 2C 22 3F 34 Signification Gnre une nouvelle ligne (saut de ligne). Tabulation horizontale Tabulation verticale Retour d'un caractre en arrire (backspace) Retour chariot Saut de page Bip sonore ' " ? \
Les constantes chanes de caractres Une chane est une squence de plusieurs caractres. Une constante de ce type peut tre dfinie en dlimitant cette squence par des guillemets.
Exemple : "Ceci est une constante chane de caractres"
Remarque : En C++, une constante symbolique est value au moment de la compilation. Ce n'est pas le cas pour les constantes symboliques en langage C. De ce fait, les constantes symboliques peuvent tre utilises en C++ dans la dclaration des tableaux. Exemple : Le code suivant est correct en C++ alors qu'il ne l'est pas en C :
const int MAX = 1000; char tab[MAX]; /
Version 3.7
Karim Kalti
Les oprateurs
Les oprateurs arithmtiques
Oprateur + * / % Signification Addition Soustraction Multiplication Division entire ou relle. Reste de la division entire
Remarques : Les oprateurs "+, - , *, / " peuvent effectuer des oprations entre entiers ou entre rels. L'oprateur "/" effectue en fonction du type des oprandes soit une division entire soit une division relle. L'oprateur " % " ne s'applique pas aux rels (aussi bien float que double). Il n'est dfini que pour des oprandes de type entier. Exemples :
7/3=2 7/-3= -2 7/-3= 2 7%3=1 7%-3=1 -7%-3= -1 car (7=-3*-2+1) car (-7= -3*2-1)
Dpassement de capacit des entiers : Soit l'instruction suivante : short n = 10000*100; La valeur place dans n se situe en dehors de la capacit du type short. Le contenu de n sera alors erron mais il n'y aura pas d'indication d'erreur ni au moment de la compilation ni au moment de l'excution. Dpassement de capacit des rels : Pour les rels ou pour la division par zro, le dpassement est immdiatement signal par le message "floating point error : overflow". Combinaison d'oprandes de types diffrents Une opration arithmtique peut faire intervenir des oprandes de types diffrents. Dans ce cas le compilateur effectue des conversions temporaires et automatiques de types. Ces conversions obissent aux rgles suivantes : Rgles
R0 R1 R2 R3 R4 R5 R6 long double double float unsigned long long unsigned int char int
Ces rgles possdent une priorit descendante. (R0 est prioritaire par rapport R1, R1 est prioritaire par rapport R2 et ainsi de suite).
Version 3.7
Karim Kalti
Exemple :
int i=5,j=2; double x; x=i/j; x=(double)i/j; // x= 2.5
Oprateur
= += -= *= /= i=5 ; permet d'affecter i+=5 i=i+5 i-=5 i=i-5 i*=5 i=i*5 i/=5 i=i/5
Description Le test infrieur entre deux expressions arithmtiques (entires ou flottantes). Cet oprateur renvoie true si la valeur de l'oprande gauche est infrieur celle de l'oprande de droite et false si non. Le test suprieur. Cet oprateur renvoie true si la valeur de l'oprande gauche est suprieur celle de l'oprande de droite et false si non. Le test infrieur ou gal. Cet oprateur renvoie true si la valeur de l'oprande gauche est infrieur ou gale celle de l'oprande de droite et false si non. Le test suprieur ou gal. Cet oprateur renvoie true si la valeur de l'oprande gauche est suprieur ou gale celle de l'oprande de droite et false si non. Le test d'galit. Renvoie true si l'oprande de gauche est gale l'oprande de droite et false sinon. ET logique : renvoie true si les deux oprandes sont valus true. OU logique : renvoie true si au moins un des deux oprandes est valu true. NON logique : renvoie true si l'oprande est valu false et false dans le cas contraire.
Remarque : Les valeurs (entires, flottantes, caractres, ) non nulles sont values true. Les valeurs (entires, flottantes, caractres, ) nulles sont values false. Exemple :
int i=0, j=5; bool b1=i<j; // b1 vaut true bool b2= i==j && i<j // b2 vaut false
Version 3.7
10
Karim Kalti
Remarque : Les oprateurs prsents dans le tableau ci-dessus possdent une priorit descendante : les oprateurs de la premire ligne sont prioritaires par rapport ceux de la deuxime ligne et ainsi de suite. Les oprateurs d'une mme ligne possdent la mme priorit. Si une expression fait intervenir en mme temps plusieurs oprateurs qui ont la mme priorit alors l'oprateur situ le plus gauche dans l'expression sera le premier valu. Exemple : Expression
8/4*6 8*6/4 28/(3*4) 3/4*6 3*6/4 (float)2/4 (float)(2/4) -3+4%5/2
Oprations
rsultat
Oprateur conditionnel
Cet oprateur permet de tester une expression et de retourner une valeur suivant le rsultat du test. Sa syntaxe est donne comme suit :
Expression ? Valeur renvoye si Expression vaut vrai : Valeur renvoye sinon
Exemple 2 : Cet exemple montre l'utilisation de l'oprateur conditionnel dans le calcul du maximum de deux entiers :
int FMAX(int a, int b) { return (a>b ? a : b); }
Version 3.7
11
Karim Kalti
Oprateur sizeof( )
L'oprateur sizeof renvoie la taille en octets d'un type ou d'une variable. Le type ou la variable sont passs en argument. Exemple :
unsigned i; float j; i = sizeof(short); // i vaut 2 i = sizeof(j); // i vaut 4 i = sizeof(long[12]); // i vaut 48
Version 3.7
12
Karim Kalti
Version 3.7
13
Karim Kalti
Manipulateurs d'affichage en C++ Il existe un ensemble de manipulateurs en C++ qui offrent diffrentes possibilits concernant le formatage de l'affichage autres que celles proposes par dfaut.
Manipulateur de saut de ligne : Le manipulateur endl (end line) permet d'insrer un saut de ligne dans le flux de texte envoy l'cran.
cout<<"Bonjour"<<endl<<"Au revoir"<<endl; // Rsultat de l'excution Bonjour Au revoir
Manipulateurs d'affichage des entiers : Il est possible de modifier la base dans laquelle est affich un entier l'aide des manipulateurs suivants : Manipulateur
dec hex oct
Signification Affichage dans la base dcimale pour les entiers (affichage par dfaut). Affichage dans la base hexadcimale pour les entiers. Affichage dans la base octale pour les entiers.
int i=75; cout<<"Affichage par dfaut : "<<i<<endl; cout<<"Affichage hexa : "<<hex<<i<<endl; cout<<"Sans manipulateur : "<<i<<endl; cout<<"Affichage dcimal :"<<dec<<i<<endl; cout<<"Sans manipulateur : "<<i<<endl;
// Rsultat de l'excution Affichage par dfaut : 75 Affichage hexa : 4b Sans manipulateur : 4b Affichage dcimal : 75 Sans manipulateur : 75
L'exemple prcdent montre qu'un manipulateur de conversion de la base d'affichage d'un entier reste actif depuis l'endroit de son application et jusqu' l'application d'un autre manipulateur.
setfill(caractre)
Signification Dfinit le gabarit de la variable afficher avec une justification droite par dfaut. Si la valeur afficher est plus importante que le gabarit, alors ce dernier ne sera pas respect et la variable sera affiche de faon conventionnelle. Le manipulateur setw doit tre utilis pour chacune des informations afficher. Dfinit le caractre de remplissage lorsqu'on utilise un affichage avec la gestion de gabarit. Par dfaut, le caractre de remplissage est l'espace.
Version 3.7
14
Karim Kalti
Il est possible d'enchaner plusieurs oprateurs de redirection avec cin afin de faire la saisie de plusieurs variables en mme temps. Dans ce cas les valeurs faire entrer doivent tre spares au moment de la saisie par un blanc (tabulation, espace ou retour chariot). Exemple :
int v1,v2; cout<<"Veuillez saisir deux entiers"; cin>>v1>>v2;
Version 3.7
15
Karim Kalti
Si la condition teste est vraie alors le programme excute le bloc d'instructions. Si cette condition est fausse alors le programme saute ce bloc et continue son excution normalement. Il est possible de spcifier un autre bloc d'instructions excuter dans le cas o la condition est fausse par l'adjonction de l'instruction else au branchement if. Cette structure de contrle devient alors : if(Condition est vraie) { Bloc 1 d'instructions excuter } else { Bloc 2 d'instructions excuter }
Remarque : Si le bloc (bloc 1 ou bloc 2) se rduit une seule instruction alors les accolades deviennent facultatives. Application 1 : crire un programme qui permet partir de la saisie d'un nombre d'afficher un message pour indiquer la possibilit ou non de l'utiliser comme diviseur.
#include<iostream.h> void main() { int i; cout<<"Donner un entier : "; cin>>i; if(i==0) cout<<"Impossible d'utiliser cet entier comme diviseur"<<endl; else cout<<"Il est possible d'utiliser cet entier comme diviseur"<<endl; }
Version 3.7
16
Karim Kalti
Application 2 : crire un programme qui permet de dterminer si un nombre entier saisi au clavier est pair ou impair.
#include<iostream.h> void main() { int i; cout<<"Donner un entier : "; cin>>i; if(i%2) cout<<"L'entier saisi est impair"<<endl; else cout<<"L'entier saisi est pair"<<endl; }
Les branchements conditionnels imbriqus En pratique, il est souvent utile de pouvoir enchaner un ensemble de tests pour examiner plusieurs valeurs possibles. Ceci peut tre ralis en imbriquant les if et les else de la manire suivante :
if(test_1 est vrai) { Bloc_1 } else if(test_2 est vrai) { Bloc_2 } else if(test_3 est vrai) { Bloc_3 } else if(test_n est vrai) { Bloc_n } else { Bloc_n+1 }
if(test_1 est vrai) { Bloc_1 } else { if(test_2 est vrai) { Bloc_2 } else { if(test_3 est vrai) { Bloc_3 } else else{ if(test_n est vrai) { Bloc_n } else { Bloc_n+1 } } } }
Remarque : Lors de l'imbrication de plusieurs instructions de branchement conditionnel simple, un else se rapporte toujours au dernier if rencontr auquel aucun else n'a t encore attribu. Exercice crire un programme qui demande l'utilisateur son age et lui indique le niveau du cours qu'il doit suivre en se basant sur les critres suivants : Si l'age est entre 7 et 15 il lui affiche "vous avez besoin du cours du premier niveau". Si l'age est entre 16 et 20 il lui affiche "vous avez besoin du cours du deuxime niveau". Si l'age est entre 20 et 25 il lui affiche "vous avez besoin du cours du troisime niveau". Si l'ge est infrieur 7 il lui affiche " vous tes encore jeune". Si l'ge est suprieur 25 il lui affiche " vous tes trop g".
Version 3.7
17
Karim Kalti
Branchement conditionnel multiple Les branchements imbriqus utilisant le if et le else donnent un programme un aspect peu lisible, en plus des risques d'erreurs qu'ils peuvent engendrer surtout lors du placement des accolades. C'est pourquoi, lorsqu'il s'agit de traiter plusieurs alternatives on leur prfre la structure switch dfinie de la manire suivante : switch(variable ou expression) { case constante_1: Bloc 1 d'instructions break; case constante_2: Bloc 2 d'instructions break; case constante_n: Bloc n d'instructions break; default: bloc d'instructions par dfaut }
La structure de contrle switch compare gnralement la valeur d'une variable de type entier ou assimil (char, int, unsigned, long, ) un ensemble de constantes. Lorsque cette valeur correspond l'une des constantes alors le bloc d'instructions associ cette dernire est excut. Le bloc dfini par le mot-cl default est un bloc facultatif (non obligatoire) qui dsigne un ensemble d'instructions qui seront excuts par dfaut. Le mot-cl break permet une sortie immdiate de la structure swtich et vite alors au programme de tester toutes les autres alternatives aprs avoir excut un bloc i donn. Cette instruction n'est pas obligatoire. Exercice: En utilisant la structure de contrle switch, donner un programme qui demande l'utilisateur de saisir un nombre. Si ce nombre est gale 0, il lui affiche "vous avez saisi une valeur nulle". S'il est gale un, il lui affiche "vous avez saisi un". Si ce nombre est diffrent de 1 et de 0 il lui affiche un message d'erreur "valeur incorrecte".
#include <stdio.h> void main() { int i; printf("Donner une valeur entire 0 ou 1:"); scanf("%d",&i); switch( i ) { case 0: printf("\n vous avez saisi une valeur nulle \n"); break; case 1: printf("\n vous avez saisi un "); break; default: printf("\n valeur incorrecte "); } }
Version 3.7
18
Karim Kalti
Rsultat de l'excution : Si le nombre saisi est 6: le message de sortie sera "valeur incorrecte". Si le nombre saisi est 1: le message de sortie sera "vous avez saisi un". Si on limine le break du case : 1. o Si le nombre saisi est 6: le message de sortie sera "valeur incorrecte". o Si le nombre saisi est 1: le message de sortie sera "vous avez saisi un " suivi du message "valeur incorrecte ". Si le nombre saisi est 0 le message de sortie sera "vous avez saisi une valeur nulle". Si on limine le break du case 0 et on prserve celui de case 1: o Si le nombre saisi est 6, le message de sortie sera "valeur incorrecte". o Si le nombre saisi est 1 le message de sortie sera "vous avez saisi un ". o Si le nombre saisi est 0 le message de sortie sera "vous avez saisi une valeur nulle", "vous avez saisi un". Si on limine le break du case 0 et celui de case 1: o Si le nombre saisi est 6, le message de sortie sera " valeur incorrecte ". o Si le nombre saisi est 1 le message de sortie sera " vous avez saisi un ". o Si le nombre saisi est 0 le message de sortie sera "vous avez saisi une valeur nulle", "vous avez saisi un", "valeur incorrecte". o Si on supprime les instructions break, le programme traitera tous les cas qui suivent la premire correspondance entre la valeur et une des constantes de test. Pour l'exemple prcdent, si on supprime les break et on saisit 0, le programme affichera les messages suivants : "vous avez saisi une valeur nulle", "vous avez saisi un", "valeur incorrecte".
Remarque : L'instruction switch possde l'inconvnient de limiter les comparaisons des valeurs constantes et ne peut pas traiter des intervalles.
La boucle while excute un bloc d'instructions tant que le test qui lui est associ est vrai. Si ce bloc se rduit une seule instruction alors les accolades deviennent non obligatoires. Exercice : crire un programme qui affiche tous les multiples d'un entier de rfrence qui sont infrieurs une valeur maximale. L'entier de rfrence et la valeur maximale seront donns par l'utilisateur.
Version 3.7
19
Karim Kalti
Les structures de contrle Programmation oriente objet (C++) _________________________________________________________________________________________________________________ #include <stdio.h> void main( ) { unsigned long ValeurMax,NbRef; printf("Donnez l'entier de rfrence : "); scanf("%lu", &NbRef); printf("Donnez la valeur maximale : "); scanf("%lu", &ValeurMax); while(ValeurMax >= NbRef) { if(ValeurMax % NbRef == 0) printf("%d\t", ValeurMax); ValeurMax--; } printf("C'est fini\n"); }
La boucle do while Cette structure est similaire la boucle while mais avec une petite diffrence qui rside dans le fait qu'elle assure l'excution au moins une fois des instructions du bloc. do { Bloc d'instructions excuter }while(Test est vrai);
En pratique, l'utilisation de la structure do - while n'est pas aussi frquente que while; mais dans certains cas, elle fournit une solution plus lgante. Une application typique de do - while est la saisie contrle de donnes. Exemple 1 : Donner un programme qui permet de contrler la saisie d'un entier compris entre 1 et 10.
int N; do { printf("Introduisez un nombre entre 1 et 10 :"); scanf("%d", &N); } while (N<1 || N>10);
Exemple 2 : On veut crire un programme qui demande l'utilisateur s'il veut continuer ou arrter une tche donne. Si l'utilisateur tape le caractre o alors le programme quitte et termine la tche. S'il tape le caractre n alors le programme continue l'excution de la tche. S'il tape tout autre caractre le programme repose la mme question l'utilisateur.
#include <stdio.h> void main( ) { char c; do { printf("voulez vous terminer et quitter ?"); scanf("%c",&c); fflush(stdin); }while (c!='o' && c!='n'); if(c=='o') printf("la tche se termine tout de suite\n"); else printf("OK on continue\n"); }
Version 3.7
20
Karim Kalti
La boucle for La structure de la boucle for se prsente comme suit : for (<expr1>;<expr2>;<expr3>) { <bloc d'instructions> }
<expr1> est value une seule fois et ce avant la premire itration de la boucle. Elle est utilise pour initialiser les donnes de la boucle. <expr2> est value avant chaque itration de la boucle. Elle reprsente gnralement une condition qui est utilise pour dcider si la boucle fera une itration supplmentaire ou non. <expr3> est value la fin de chaque itration de la boucle. Elle est utilise pour rinitialiser les donnes de la boucle. Remarque : La boucle for constitue une alternative syntaxique la boucle while dans laquelle tous les lments relatifs au contrle de la boucle sont regroups ensemble d'une manire lisible dans l'entte. En effet, les trois expressions de contrle prsentes ci-dessus peuvent tre places dans la boucle while de la manire suivante : <expr1>; while ( <expr2> ) { <bloc d'instructions> <expr3>; } Le plus souvent, la boucle for est utilise comme boucle de comptage : for(initialisation ; condition de continuit ; compteur) { <bloc d'instructions> } Exercice : crire un programme qui affiche les nombres entiers de 0 jusqu' 100.
#include <stdio.h> void main( ) { int i; for(i=0;i<101;i++) printf("%d\t",i); }
Remarques : Chacune des trois instructions de la boucle for est facultative. Ainsi la boucle de l'exercice prcdent peut tre crite de la manire suivante:
i=0; for( ;i<101;i++) printf("%d\t",i);
ou galement:
Lorsque <expr2> est absente, alors la condition de continuation sera considre comme tant toujours vraie et la boucle for sera une boucle infinie.
Version 3.7
21
Karim Kalti
Le branchement inconditionnel
Le langage C++ dispose de trois instructions pour le branchement inconditionnel : break, continue et goto. L'instruction break En plus de son utilisation en association avec switch, l'instruction break peut tre utilise dans n'importe quelle boucle. Cette instruction provoque alors une interruption et une sortie immdiate de la boucle. L'excution du programme continue alors au niveau de l'instruction situe tout juste aprs la boucle. Exemple:
void main() { int i; for(i=1;i<=10;i++) { printf("Dbut de l'itration %d\n",i); printf("Bonjour\n"); if(i==3) break; printf("Fin de l'itration %d\n",i); } printf("Aprs la boucle"); }
Excution:
Dbut de l'itration 1 Bonjour Fin de l'itration 1 Dbut de l'itration 2 Bonjour Fin de l'itration 2 Dbut de l'itration 3 Bonjour Aprs la boucle
Remarque : En cas de boucles imbriques, l'instruction break fait sortir seulement de la boucle la plus interne. L'instruction continue L'instruction continue intervient galement pour interrompre l'excution des boucles. Mais contrairement break, elle ne provoque pas la sortie complte de la boucle mais plutt l'interruption de l'itration courante et le passage l'itration suivante de cette boucle. Exemple 1:
#include <stdio.h> void main( ) { int i; for(i=1;i<=4;i++) { printf("Dbut itration %d\n",i); if(i < 3) continue; printf("bonjour\n"); } }
Excution:
Dbut itration Dbut itration Dbut itration bonjour Dbut itration bonjour 1 2 3 4
Exemple 2 : Le programme ci-dessous demande l'utilisateur de saisir un entier positif et lui affiche son carr. Si l'entier est ngatif alors le programme redemande l'utilisateur de saisir un autre entier. L'excution s'arrte lorsque'un entier nul est saisi.
#include <stdio.h> void main( ) {int n; do { printf("\n donnez un entier n > 0: "); scanf("%d",&n); if(n<0) { printf("\n donnez un n positif\n"); continue ; } printf(" le carr de n est : %d\n",n*n); }while(n) ; }
Remarque : En cas de boucles imbriques l'instruction continue ne concerne que la boucle la plus interne.
Version 3.7
22
Karim Kalti
L'instruction goto L'instruction goto provoque un branchement immdiat du programme un endroit prdfini. Les boucles, les tests sont interrompues. L'endroit o reprend le programme est dfini par une tiquette suivie du symbole : La syntaxe globale de cette instruction est : goto NomEtiquette; NomEtiquette: Exemple:
#include <stdio.h> void main( ) { int i; for(i=1;i<=10;i++) { printf("Dbut de l'itration %d\n",i); printf("Bonjour\n"); if(i==3) goto Sortie; printf("Fin de l'itration %d\n",i); } Sortie: printf("Aprs la boucle"); }
Excution:
Dbut de l'itration 1 Bonjour Fin de l'itration 1 Dbut de l'itration 2 Bonjour Fin de l'itration 2 Dbut de l'itration 3 Bonjour Aprs la boucle
Version 3.7
23
Karim Kalti
Les tableaux
Introduction
Un tableau est un type particulier de variables qui permet d'associer sous le mme nom, un ensemble fini de valeurs de mme nature (type). Tous les types de base et tous les types personaliss construits en C/C++ peuvent servir dfinir des tableaux.
O : Type dsigne le type des lments du tableau, NomTableau dsigne son nom, Taille dsigne le nombre de ses lments. Exemples: Dclaration:
int TableauEntiers[15]; double TableauFlottants[40];
La taille maximale d'un tableau dpend de la configuration de l'environnement de travail (compilateur). La taille d'un tableau doit tre une constante ou une expression constante, cependant elle ne peut pas tre une variable.
#define N 10 int M =10; int Tab1[10]; // Dclaration correcte int Tab2[N]; // Dclaration correcte int Tab3[M]; // Dclaration incorrecte float Tab4[2*N+1]; // Dclaration correcte
La dimension peut galement tre une constante symbolique (Ceci est possible en C++ mais pas en C).
const int N=10; int Tab[N]; // Dclaration correcte en C++ mais incorrecte en C
Version 3.7
24
Karim Kalti
Le nombre des valeurs d'initialisation ne peut pas dpasser la taille du tableau mais il peut tre infrieur. Dans ce cas les lments non explicitement initialiss seront automatiquement initialiss 0. Exemple 2:
int Tableau[5] = { , ,5, ,3};
Lors de l'initialisation d'un tableau, la spcification de sa taille est facultative. Dans le cas o elle est absente, la taille est automatiquement dtermine d'aprs le nombre des valeurs d'initialisation. Exemple 3 :
int Tableau[ ] = {1,7,4} // La taille est du tableau est gale 3
Il est galement possible d'omettre quelques valeurs qui seront automatiquement initialises 0.
int Tab[3][4] = { {1,2, ,4}, , {9, ,11,12}};
Version 3.7
25
Karim Kalti
Les indices peuvent tre des expressions arithmtiques: T[2*i-1] ou Tab[i-3][j+k] Il n'est pas possible d'affecter d'une manire globale un tableau un autre :
int T1[5]; int T2[5]; T1 = T2; // instruction incorrecte.
Exercice : Donner un programme qui fait la copie des lments d'un tableau d'entiers initialis, dans un deuxime tableau de mme taille.
#include<stdio.h> void main() { int i; int T1[3] = {5,6,23}; int T2[3]; for(i=0;i<3;i++) T2[i]=T1[i]; }
Version 3.7
26
Karim Kalti
Exercice : crire un programme qui fait la saisie des lments d'un tableau d'entiers deux dimensions 3x4 et qui les affiches sur 3 lignes et 4 colonnes.
#include<stdio.h> void main() { int i,j; int T1[3][4], T2[3][4]; for(i=0;i<3;i++) for(j=0;j<4;j++) scanf("%d",&tab[i][j]); /* Affichage */ for(i=0;i<3;i++) { for(j=0;j<4;j++) printf("%d\t", tab[i][j]); printf("\n"); } }
Version 3.7
27
Karim Kalti
2 - Dclaration
Formellement la syntaxe de dclaration d'un pointeur est la suivante : Type * NomPointeur ; NomPointeur dsigne le nom d'une variable de type pointeur. Exemples :
char *pc; // dfinit un pointeur vers une donne de type caractre. int * pi; // dfinit un pointeur vers une donne de type int double * pdr; // dfinit un pointeur vers une donne de type rel double unsigned long * pli; // dfinit un pointeur vers une donne de type unsigned long
Remarque : Les pointeurs occupent tous la mme taille en mmoire indpendamment de la taille du type de l'objet point. Cela signifie par exemple qu'une variable de type pointeur sur un long double possde la mme taille en mmoire qu'une variable de type pointeur sur un caractre. Dclaration multiple
int *p1, *p2; // p1 et p2 sont deux pointeurs sur des entiers. int *p1, p2; // p1 est un pointeur sur un entier alors que p2 est une variable entire. int p1, *p2 // p1 est une variable entire alors que p2 est un pointeur sur un entier.
Version 3.7
28
Karim Kalti
Remarque : Si l'on indique comme valeur d'initialisation pour un pointeur l'adresse d'une variable ayant un autre type de donnes que celui vers lequel le pointeur peut renvoyer, le compilateur affiche alors une erreur lors de la compilation. Exemple :
long l, *pl=&l; unsigned long *pul=&l; // erreur: pul n'est un pointeur vers un long
Conversion Il n'existe aucune conversion implicite d'un type pointeur vers un autre type pointeur. Le seul moyen pour faire des conversions entre types pointeurs est le casting.
int i,*pi=&i; unsigned int *pui; pui = pi; // erreur pui=&i; // erreur pui=(unsigned int *)pi; // OK
Variable
* NomPointeur
L'oprateur * est appel dans ce cas oprateur d'indirection car il permet d'accder d'une manire indirecte au contenu de la variable ( travers le pointeur). Exemple 1 :
int i; int *pi; i =1234; pi= &i // pi contient l'adresse de i // *pi dsigne d'une manire indirecte le contenu de i cout<<i; cout<<*pi;
Exemple 2 : Cet exemple montre la saisie et l'affichage de la valeur d'une variable travers un pointeur :
int i,*pi=&i; scanf("%d",pi); printf("%d",*pi);
Version 3.7
29
Karim Kalti
Exemple 3 :
#include<stdio.h> void main() { int i=125; int *pi=&i; printf("la valeur de i est: %d",i); printf("la valeur du *pi est %d",*pi); i++; printf("la valeur de i incrmente est: %d",i); printf("la valeur du *pi est %d",*pi); (*pi)++; printf("la valeur de i est: %d",i); printf("la valeur du *pi est %d",*pi); *pi=2*(*pi); printf("la valeur de i est: %d",i); printf("la valeur du *pi est %d",*pi); }
Exemple 2:
int * i,* j; int k; i=&k; j=i+10; j++; /* Si i contient par exemple 1600 alors j vaut 1600+10*sizeof(int)=1640. j++ donne 1644 */
Remarque : Pour les pointeurs, l'addition n'est dfinie qu'entre un pointeur et un entier. Elle n'est pas dfinie entre deux pointeurs ni entre un pointeur et un nombre en virgule flottante.
Version 3.7
30
Karim Kalti
Exemple 2 :
if(pa < pb) cout<<"le pointeur pb contient une adresse plus grande que le pointeur pa";
9 - Pointeur nul
Il existe en C/C++ un pointeur particulier qui pointe vers l'adresse 0, appel le pointeur nul. En C/C++ cette adresse ne contient aucune donne, par consquent le pointeur nul ne pourra en aucun cas pointer vers une donne. Chaque pointeur vers n'importe quel type peut tre compar au pointeur nul. Exemple :
int *pi; if(pi==0) cout<<"error";
La constante symbolique NULL peut tre utilise la place de la constante numrique 0. Elle est prdfinie dans le fichier stdio.h. NULL peut tre galement dfinie par la directive define comme suit : #define NULL 0 ou galement #define NULL 0L selon que les adresses sur la machine sont reprsentes par des int ou des long.
10 - Pointeurs gnriques
Il existe en C/C++ des pointeurs particuliers appels pointeurs gnriques qui peuvent pointer vers des donnes de nimporte quel type. Les pointeurs gnriques s'avrent utiles si par exemple l'adresse d'une zone mmoire doit tre enregistre, mais qu'il n'est pas encore tabli quel type de donnes cette zone doit accueillir ou si le programmeur veut se rserver la possibilit d'enregistrer (successivement) des types de donnes diffrents ou bien encore s'il souhaite pour d'autres raisons ne pas se fixer dans l'immdiat sur un quelconque type de donnes. Dclaration Les pointeurs gnriques sont dclars l'aide du type : void* La dclaration se fait comme suit : void* PointeurGenerique; Exemple:
int a; double d; void * vp; // pointeur gnrique vp=&a; // vp stocke l'adresse d'un int vp=&d; // vp stocke l'adresse d'un double
Version 3.7
31
Karim Kalti
Restrictions dans l'utilisation des pointeurs gnriques Un pointeur gnrique ne peut pas servir pour faire des accs indirects aux contenus des variables. Exemple :
double d; void* pg=&d; Ainsi *vp=1.234
gnre une erreur. En effet, mme si vp stocke l'adresse d'un double, le type void* ne donne aucune infromation sur la taille mmoire qu'occupe la variable pointe. Pour rsoudre ce problme, il faut explicitement convertir vp comme suit :
*(double * )vp=1.234;
Par ailleurs, vp++ ou toute autre opration arithmtique sur les pointeurs gnriques gnre une erreur car le compilateur ne peut pas savoir de combien d'octets se dplacer. Affectation d'un pointeur gnrique un pointeur d'un autre type (type* void*) En C++, l'affectation du contenu d'un pointeur gnrique (void*) un pointeur (type*) doit obligatoirement passer par le casting. En C, le casting n'est pas obligatoire pour faire ce genre d'oprations mme s'il reste conseill. Exemple :
int i=5; int *pi1=&i, *pi2; void* vp; vp=pi1; // Ok C et C++ pi2=vp; // Ok en C erreur en C++ pi2=(int*)vp // Ok C et C++ cout<<*pi2; // opration possible car le type de pi2 est connu int* pi2++; // opration possible car le type de pi2 est connu int*
Remarque La conversion implicite entre pointeur gnrique et un pointeur type se fait se fait donc dans les cas suivants : T* vers void* // lgale en C et C++ void* vers T* // lgale en C seulement en C++ le casting est obligatoire
11 - Pointeurs et tableaux
En C/C++, le nom d'un tableau est considr comme une constante d'adresse dfinissant l'emplacement de dbut partir duquel vont se succder les lments du tableau. Ainsi, lorsqu'il est employ seul, l'identificateur d'un tableau, est considr comme un pointeur constant. Notation d'accs aux lments d'un tableau Si on considre la dclaration suivante :
int T[10];
Exemple 1 : Cet exemple montre un programme de remplissage des lments d'un tableau d'entiers avec des 1.
int T[10],i; for(i=0;i<10;i++) T[i]=1; int T[10],i; for(i=0;i<10;i++) *(T+i) = 1; int T[10],i, *p; for(p=T,i=0; i<10 ; i++,p++) *p=1;
Version 3.7
32
Karim Kalti
Le nom du tableau ne peut pas tre utilis pour faire le parcours des lments (l'incrmentation T++ est incorrecte) car T est dans ce cas considr comme un pointeur constant. Ceci explique l'utilisation d'une variable de type pointeur dans l'exemple 3 pour faire ce parcours. Exemple 2 : L'accs aux lments peut se faire l'aide d'indices ngatifs si ces lments sont situs une adresse prcdant l'adresse contenue dans le pointeur permettant de faire l'accs.
int T[5]; int *p=&T[4]; P[-1] T[3], P[-2] T[2], , P[-4] T[0].
Cas d'un tableau deux dimensions: Toute dclaration d'un tableau N dimensions est considre comme une dclaration d'un pointeur constant. Cependant, la rfrence des lments l'aide des pointeurs diffre de celle des tableaux une dimension. En effet si on considre le cas particulier o N=2, la dclaration int T[3][4] est interprte comme tant un tableau de 3 lments, chaque lment de ce tableau tant lui-mme un tableau de 4 entiers. Exemple :
typedef int QuatreEntiers[4]; QuatreEntier Tab[3];
Le tableau tab est quivalent au tableau T dclar d'une manire classique comme suit : int T[3][4] Les lments de T tant des int[4]. De ce qui prcde T+1 correspond l'adresse de T+4*sizeof(int) octets (car l'lment du tableau correspond 4 entiers et non pas un seul). De mme les critures suivantes sont quivalentes :
T &T[0][0] T[0] T+1 &T[1][0] T[1]
Pointeurs et constantes
Pointeur en lecture seule Un pointeur en lecture seule peut pointer sur n'importe quelle variable. Il ne permet toutefois que l'accs en lecture la variable pointe. Toute tentative de modification (accs en criture) est signale comme erreur. Dclaration const Type* NomPointeur; Exemple :
int i=5,j=10; const int* p = &i; // pointeur sur un entier cout<<"Valeur pointe :"<<*p<<endl; p = &j; // ok cout<<"Valeur pointe :"<<*p<<endl; *p = 8; // erreur (tentative de modification) cout<<"Valeur pointe :"<<*p<<endl;
Remarque : On ne peut pas affecter l'adresse d'un objet constant un pointeur sur un type non constant, car une telle affectation pourrait permettre de modifier cet objet par l'intermdiaire du pointeur. Exemple :
const char espace=' ' ; const char *p = &espace ; // ok ; char *q = & espace ;; //erreur
Version 3.7
33
Karim Kalti
Pointeur constant Un pointeur constant est un pointeur qui ne peut pointer que sur une seule variable. Le contenu de la variable pointe n'est pas ncessairement constant et peut par consquent tre modifi. Dclaration Type* const NomPointeur; Exemple :
int i=5,j=10; int* const p = &i; cout<<"Valeur pointe :"<<*p<<endl; i=8; cout<<"Valeur pointe :"<<*p<<endl; p = &j; // erreur cout<<"Valeur pointe :"<<*p<<endl;
Les rfrences
Le C++ introduit un nouvel outil de construction de types drivs appel rfrence. Une valeur de type rfrence est une adresse unique (qui ne change pas) et qui dsigne une variable bien dtermine. Une rfrence doit obligatoirement tre initialise par une variable. Elle joue ds lors le rle d'alias de cette variable (cette variable peut tre manipule travers sa rfrence). Les rfrences sont dfinies selon la syntaxe suivante : Type &NomReference = VariableInitialisation; Les rfrences sont la fois similaires aux pointeurs du fait qu'ils stockent des adresses en mmoire et diffrents de ces derniers du fait que le contenu d'un pointeur peut tre variable alors que celui d'une rfrence est constant. Les rfrences peuvent tre considres comme des "pointeurs constants" qui sont manipuls, d'un point de vue syntaxique comme des variables ordinaires. Exemple :
int i; int &r1 = i; int &r2; // r1 est une rfrence de i. Elle le restera pour tout le programme. r1 peut // dsormais tre utilise en tant qu'autre nom de i. // Erreur, une rfrence doit tre initialise
Remarques : Il n'est pas possible de crer une rfrence gnrique. Ainsi void& n'est pas valide. Il n'est pas possible de crer des pointeurs vers des rfrences, ni des rfrences de rfrences.
int a=1; int &r =a; int & *ptr; // Erreur int &&rr; // Erreur
Il n'est pas possible de dclarer des tableaux de rfrences. Il est possible de dclarer des rfrences de pointeurs.
int i,*p=&i; int* &r=p; // r est une rfrence d'un pointeur sur entier i=5; cout<<*r; // Affiche le contenu de i savoir 5
Les rfrences de constantes En plus des rfrences de variables, il est galement possible de crer des rfrences de constantes en ajoutant const la dclaration.
Version 3.7
34
Karim Kalti
Exemple :
const int c=2; const int &rc=c; //rc est une rfrence de c. Elle peut la remplacer
Oprations sur les rfrences Une fois qu'une rfrence a t dclare et initialise, toutes les oprations effectues par la suite sur cette rfrence se rapporteront exclusivement "l'objet" rfrenc et non la rfrence elle-mme. (En effet, le contenu d'une rfrence ne peut pas tre modifi aprs son initialisation vu qu'il est constant). Exemples :
int x; int *p; int &r=x; r=1; // x reoit la valeur 1 et r reste inchang et contient toujours l'adresse de x. r++; // incrmente x de 1.
L'exemple ci-dessus montre que les rfrences sont bien manipules comme des variables sans utilisation de l'oprateur d'indirection). Dans les expressions d'adressage x peut galement tre remplace par r, ainsi :
p=&r; // affecte l'adresse de x p. *p=r; // copie le contenu de x dans l'emplacement point par p; cout<<&x<<'\t'<<p<<'\t'<<&r; //Affiche 3 fois la mme valeur qui est l'adresse de x
Initialisation des rfrences Une rfrence non constante ne peut tre initialise qu'avec une lvalue de mme type.
unsigned char uc; double d1,d2; int &ri =1024; // error : not lvalue char &rc =uc // error inexact types double &dr=d1+d2; // error not an lvalue
Une rfrence sur un objet constant peut tre initialise aussi bien avec une rvalue qu'avec une lvalue.
const const const const const const unsigned char uc; double d1; double d2; int &ri=1024; // OK : 1024 c'est l'adresse unsigned char &rc = uc; // OK double &rd = d1+d2; // OK
Version 3.7
35
Karim Kalti
La spcification de la taille d'une chane lors de sa dclaration n'est pas ncessaire si cette dclaration est suivie d'une initialisation.
char msg []= "Bonjour" ; char phrase[]= "ceci est une phrase" ;
La taille de msg nest pas fixe mais dpend de la taille de la chane dinitialisation. Dans le cas prsent, ce tableau occupe 8 octets, le premier contenant B et le dernier est le zro de fin de chane. Initialisation dun tableau de caractres deux dimensions.
char buf[3][10]={" Ali ", " Mohamed ","Salah"};
Version 3.7
36
Karim Kalti
La fonction gets retourne la chane saisie en cas de succs et NULL en cas d'chec. La fonction gets arrte la saisie des caractres lorsque l'utilisateur tape le caractre \n indiquant un retour la ligne. Elle remplace alors ce caractre par \0. L'affichage L'affichage d'une chane peut se faire laide de la fonction printf en utilisant le caractre de formatage %s ou galement laide de la fonction puts dont la syntaxe est la suivante : int puts(const char* NomChane); (stdio.h)
Cette fonction retourne une valeur non ngative en cas de succs. En cas d'chec elle retourne EOF. Elle remplace le caractre \0 de la chane par \n. Exemple 1
# include<stdio.h> void main( ) {
char ligne[81] ;
printf(" donnez une chane ") ; scanf(" %s ", ligne) ; printf(" la ligne saisie est : %s\n ",ligne) ; }
Exemple 2
#include <stdio.h> void main( ) { char tab[]= "Bonjour"; printf("Affichage du contenu dun tableau de caractres : \n " ) ; printf(" %s ",tab) ; }
Exemple 3
#include <stdio.h> void main( ) { char tab[]= "Bonjour ; /* puts ralise un retour automatique la ligne */ puts(" Affichage du contenu dun tableau de caractres : " puts(tab) ; }
) ;
Version 3.7
37
Karim Kalti
Exemple :
# include <string.h> # include <stdio.h> void main( ) { char string1[10], string2[10]=abcdef; strcpy(string1,string2); printf(" %s\n,string1); }
A B si et seulement si Long(A) Long(B) et quel que soit i Long(A), A[ i ] = B[ i ] . Ou s'il existe: i, 1 i min{ Long(A), Long(B) }, tel que : A[ i ] < B[ i ] et que quel que soit j, 1 j i A[ j ] = B[ j ]
Exemples : "ARBRE" < "ARBRES" "ARBRE" < "ARBRE " "ARBRE" > "ARBORESENCE" "ARBRE" < "ARBUSTE" Remarque : En C/C++, les oprateurs = =, < ,> ,<= et >= ne permettent pas de comparer le contenu de deux chanes. Il faut dans ce cas passer par la fonction strcmp qui effectue une comparaison sur la base de l'ordre lexicographique susmentionn. Prototype : int strcmp( const char *Chane1, const char * Chane2 );
Version 3.7
38
Karim Kalti
Bibliothque : string.h Valeur renvoye : une valeur entire qui est : < 0 si Chane1 est infrieure Chane2 = = 0 si Chane1 es gale Chane2 0 si Chane1 est suprieur Chane2. Exemple 1 :
# include <string.h> # include <stdio.h> void main( ) { int resultat; char ch1[]=ARBUSTE ; char ch2[]=ARBRE ; char ch3[]=ARBORESENCE ; resultat = strcmp(ch1,ch2); if(resultat>0) printf("ch1 est suprieure ch2\n ") ; else printf("ch1 est infrieure ch2\n ") ; resultat = strcmp (ch2,ch3) ; if( resultat > 0 ) printf("ch2 est suprieure ch3\n ") ; else printf("ch2 est infrieure ch3\n ") ; }
Exemple 2 :
# include <string.h> # include <stdio.h> void main( ) { char ch1[10], ch2[10]; int resultat; strcpy(ch1,"Bonjour"); printf("contenu de ch : %s\n",ch1); printf(" deuxime chane ? ") ; scanf(" %s ",ch2) ; resultat = strcmp(ch1,ch2); if(resultat ==0) printf("les deux chanes sont identiques ") ; else printf("les deux chanes sont diffrentes ") ; }
Version 3.7
39
Karim Kalti
Version 3.7
40
Karim Kalti
La fonction malloc
Prototype : void* malloc(size_t
size);
Cette fonction alloue une zone mmoire de la taille de size. Elle retourne un pointeur gnrique donnant l'adresse de dbut du bloc allou ou un pointeur NULL si l'allocation a chou pour une insuffisance d'espace. Bibliothques : malloc est dfinie dans les bibliothques stdlib.h et alloc.h.
La fonction free
Prototype : void free(void* PtrZone) La fonction free permet de librer un espace pralablement allou. Le paramtre PtrZone dsigne un pointeur qui pointe sur la zone librer. Bibliothques : free est dfinie dans les bibliothques stdlib.h et alloc.h. Exemple :
#include <stdio.h> #include <alloc.h> void main() { int * adr, i; adr= (int *) malloc(10*sizeof(int)); // adr= malloc(10*sizeof(int)); for(i=0;i<10;i++) *(adr+i)=1; for(i=0;i<10;i++) printf("%d\t", *(adr+i)); free(adr); } // Utilisation de la valeur de retour // de malloc #include <stdio.h> #include <alloc.h> void main() { int * adr, i; adr= (int *) malloc(10*sizeof(int)); // adr= malloc(10*sizeof(int)); if(!adr) printf("Echec de l'allocation"); else { for(i=0;i<10;i++) *(adr+i)=1; for(i=0;i<10;i++) printf("%d\t", *(adr+i)); free(adr); } }
Version 3.7
41
Karim Kalti
La fonction calloc
Prototype : (void*) calloc(size_t nb_blocs, size_t taille_bloc); Cette fonction joue le mme rle que celui de malloc. Ainsi, elle alloue l'emplacement ncessaire nb_blocs conscutifs, occupant chacun en mmoire taille_bloc octets. Contrairement ce qui se passe avec malloc, o le contenu de l'espace mmoire allou est alatoire, calloc remet zro chacun des octets de la zone alloue. calloc retourne un pointeur sur l'espace allou si l'allocation s'est bien droule, sinon elle retourne NULL. Bibliothques : stdlib.h et alloc.h Exemple :
#include<stdio.h> #include<alloc.h> void main ( ) { long* buffer; buffer = (long*) calloc(40, sizeof(long)); if(buffer!=NULL) printf("Espace allou pour 40 longs"); else printf("Impossible d'allouer de l'espace"); free (buffer); }
La fonction realloc
Prototype : void* realloc(void* PtrBloc, size_t taille) Bibliothques : stdlib.h et alloc.h Cette fonction permet de modifier la taille d'une zone pralablement alloue (par malloc, calloc ou realloc). Le paramtre PtrBloc dsigne l'adresse de dbut de la zone dont on veut modifier la taille, quant au paramtre taille, il dsigne la nouvelle taille souhaite. En cas de succs, cette fonction retourne un pointeur sur la zone rallou. Elle retourne NULL si la taille rallouer est 0 et PtrBloc n'est pas NULL ou s'il n'y a pas assez d'espace contigu pour tendre la mmoire la taille demande. Dans le premier cas, le bloc d'origine est libr, dans le second cas, le bloc d'origine reste inchang. Dans le cas o le paramtre PtrBloc vaut NULL alors la fonction realloc se comporte comme malloc. Lorsque la nouvelle taille demande est suprieure l'ancienne, le contenu de l'ancienne zone est conserv. Dans le cas o la nouvelle taille est infrieure l'ancienne, le dbut de l'ancienne zone verra son contenu inchang.
La fonction _msize
Prototype : size_t _msize( void * PtrBloc ); Bibliothques : alloc.h Cette fonction retourne la taille en octets d'une zone mmoire alloue par malloc, calloc ou realloc. Le type size_t est assimil au type unsigned int. Il est utilis pour les variables qui sont senses stocker la taille en octets des blocs mmoires.
Version 3.7
42
Karim Kalti
// 400
if((buffer= realloc(buffer, size + (200 * sizeof(long))))==NULL) exit(1); // ERROR size= _msize(buffer); printf("la taille du bloc aprs reallocation de 200 autres long est %u \n", size); // 1200 free(buffer); exit(0); // sortie normale }
L'adresse du buffer peut rester la mme comme elle peut changer. Cela dpend de l'espace mmoire contigu disponible au moment de l'excution.
L'oprateur New
L'oprateur new alloue de l'espace mmoire pour des objets de type lmentaire ou tendu. Il fournit l'adresse mmoire de la zone rserve un pointeur qui servira pour faire les oprations d'accs cette zone. Si l'allocation choue, l'oprateur new retourne NULL. Le contenu de la zone alloue n'est pas initialis (Il est alatoire). Allocation dynamique d'objets isols Pointeur = new type_de_donnees; Exemple 1 :
int *p; // dfinit un pointeur vers int p = new int; //alloue de l'espace pour un entier et affecte l'adresse de cet espace // p. int *x = new int; *x=1; // Accs la zone alloue (seulement travers x car cette zone ne possde // pas de nom).
Version 3.7
43
Karim Kalti
Allocation des tableaux Tableau une dimension Pointeur = new type_de_donnees[Nb d'lements]; Exemple :
int* p = new[10]; ou galement int* p; p = new int[10];
L'accs aux lments du tableau peut se faire par p[i] ou par *(p+i). L'initialisation d'un tableau dynamique lors de l'allocation n'est pas permise.
int* p = new int[5] (1,2,3,4,5); // ERREUR
Tableau plusieurs dimensions Pointeur = new type_de_donnees[n][Cst1][Cst2] Seule la premire dimension peut tre variable, les dimensions restantes doivent tre constantes, donc connues. Exemple :
int (*Z)[4] = new int [3][4] int Z[3][4];
L'oprateur delete
L'oprateur delete libre un espace mmoire dj allou par new. Il possde une syntaxe double qui diffrencie les objets isols des tableaux. Libration d'un objet isol : delete pointeur; Exemple :
#include <iostream.h> void main() { long* L = new long; cout<<"Donnez un nombre entier"; cin>>*L; cout<<"Le nombre saisi est:"<<*L; delete L; }
Libration des tableaux dynamiques : delete[] pointeur; Remarque : Pour les tableaux de type de base les crochets peuvent tre omis. Ce n'est pas le cas par contre pour les tableaux d'objets. Exemple :
#include <iostream.h> void main() { int i, *p; p = new int [10]; for(i=0;i<10;i++) *(p+i) = i; for(i=0;i<10;i++) cout<<*(p+i); delete[] p; }
Version 3.7
44
Karim Kalti
T* T* T* T* L lignes
Allocation de la matrice Dans le code suivant T dsigne un type quelconque, prdfini ou personnalis :
// Version C++ M= new(T*)[L]; for(int i=0; i<L;i++) *(M+i)=new T[C]; /* Version C */ M= (T**)malloc(L*sizeof(T*)); for(int i=0; i<L;i++) *(M+i)= (T*)malloc(L*sizeof(T));
Accs un lment de la matrice L'accs un lment d'indice (i,j) se fait comme suit : *(*(M+i)+j) Libration de la matrice Le code suivant permet de librer la matrice :
// Version C++ for(int i=0; i<L;i++) delete[] *(M+i); delete[] M;
M[i][j]
Il faut toujours commencer par librer les lignes puis on libre la table qui stocke l'adresse de ces lignes.
Version 3.7
45
Karim Kalti
Utilit de l'allocation dynamique de la mmoire Les espaces mmoire dynamiques sont utiles : o pour crer des variables dont la taille est inconnue au moment de la compilation. C'est le cas par exemple lorsqu'il s'agit de crer un tableau dont le nombre d'lments sera dtermin au moment de l'excution par l'utilisateur. o Pour crer des variables de trs grande taille qui dpasse ce que peut accepter la pile d'excution (c'est le cas des tableaux de grande taille par exemple). L'allocation des espaces dynamiques s'effectue sur le tas. Ceci fait que la seule contrainte concernant leur taille soit la disponibilit de la mmoire vive physique (taille de la RAM disponible). La taille de la pile d'excution quant elle reste limite et dpend gnralement des compilateurs. Elle peut tre paramtre dans les options de ces derniers.
Ce programme se compile avec succs. Il s'excute galement d'une manire normale. Toutefois sa sortie il engendre une fuite de mmoire. Pour dtecter cette fuite nous allons analyser l'tat de la mmoire qu'il utilise diffrents niveaux d'excution. Etat de la mmoire suite l'excution de l'instruction de la ligne 4
p i Pile
i et p sont deux variables de type "auto". Elles sont alloues sur la pile d'excution. Etat de la mmoire suite l'excution de l'instruction de la ligne 5
p i
new int va engendrer la cration d'une variable dynamique dont l'adresse est stocke dans p. Etat de la mmoire suite l'excution de l'instruction de la ligne 7
Pile
p i
FF0EA5C 5
Pile
Version 3.7
46
Karim Kalti
La ligne 10 dsigne la fin du programme. A ce niveau toutes les variables de type auto seront automatiquement libres (i et p dans ce cas car leur porte est limite au bloc du main) mais pas la zone dynamique pointe par p. Pour remdier ce problme il faut librer la zone pointe avant de quitter le programme.
01- void main() 02- { 03int i; 04int* p; 05p= new int; 06i=5; 07*p=6; 08cout<<"i :"<<i<<" et *p :"<<*p; 10delete p; 11- }
Il est noter que l'instruction delete p ne libre pas p (p tant de type auto) mais libre la zone mmoire dont l'adresse est stocke dans p. Remarque : La fuite de mmoire mme si elle ne cause aucune perturbation directe du fonctionnement intrinsque d'un programme peut tre trs nocive pour l'environnement dans lequel tourne ce dernier. En effet, l'espace mmoire non libre va occuper inutilement une partie des ressources (RAM) de l'environnement. Ce problme est encore plus grave si le programme en question est sens tourner sur un serveur en mode client/serveur. Dans ce cas, chaque client qui lance une excution du programme va engendrer aprs sa sortie une occupation inutile d'une portion de la RAM du serveur. Au bout d'un certain nombre de connexions de clients, une grande partie de cette RAM se trouvera occupe sans tre rellement utilise. Ceci va conduire vers une chute des performances du serveur voire mme son plantage.
Version 3.7
47
Karim Kalti
Les Fonctions
Introduction
Une fonction est une portion de code regroupant un ensemble d'instructions dlimit par deux accolades et laquelle est associ un nom qui permet de l'appeler. Une fonction peut tre dclare dans un programme ou dans une autre fonction, et peut tre excute suite son appel par le programme englobant. Une fonction est dite paramtre si elle demande des donnes en entre pour pouvoir s'excuter. Ces donnes sont appeles arguments ou paramtres. Gnralement, une fonction retourne une valeur qui est le rsultat de l'excution de son code. Cependant, il est possible en C/C++ de dfinir des fonctions qui ne retournent aucune valeur. De telles fonctions sont quivalentes aux procdures dfinies en algorithmique ou dans d'autres langages de programmation tel que le PASCAL.
Dclaration du prototype d'une fonction La dclaration d'une fonction peut se rduire seulement son prototype. Ce dernier donne une ide sur le modle de la fonction en termes du nombre et des types des paramtres qu'elle prend, ainsi que du type de sa valeur de retour. Il est dclar de la manire suivante: Type NomFonction(TypeArg1, TypeArg2,, TypeArg_n); Exemple:
int f(int,int);
Version 3.7
48
Karim Kalti
En C, les premires instructions du corps de la fonction doivent correspondre aux dclarations des variables qui seront utilises l'intrieur de la fonction. Ce n'est pas le cas en C++ o ces dclarations peuvent tre faites n'importe o dans le bloc dfinissant le corps de la fonction. En C/C++, Ces variables sont dites des variables locales la fonction. Elles ne peuvent par suite tre utilises qu' l'intrieur de cette dernire. Elles ne sont pas visibles l'extrieur. Les dernires instructions d'une fonction servent gnralement renvoyer une valeur, appele valeur de retour, l'extrieur de la fonction. Ce renvoi est effectu l'aide du mot-cl return. En C, lorsqu'une fonction ne prend aucun argument, le mot-cl void doit tre plac entre les parenthses la place de la liste des paramtres. Ceci n'est pas ncessaire en C++ o les parenthses peuvent rester vide.
Suite une excution une fonction ne peut retourner q'une seule valeur et pas plus. Exemple 1 : Le programme suivant permet de calculer le montant de l'achat d'un nombre donn d'articles possdant chacun un prix unitaires PU.
/* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* 1 */ 2 */ 3 */ 4 */ 5 */ 6 */ 7 */ 8 */ 9 */ 10*/ 11*/ 12*/ 13*/ 14*/ 15*/ 16*/ 17*/ #include <stdio.h> double Montant(int NbArticles, double PU); void main( ) { int nb; double val; printf("\n donnez le nombre d'articles puis leur prix unitaire: "); scanf("%f %lf", &nb, & val); resultat = Montant(nb,val); printf("\n Le Montant est %f", resultat); } double Montant(int NbArticles, double PU) { double total; total = NbArticles * PU; return total; }
Version 3.7
49
Karim Kalti
Commentaires La ligne 2 reprsente la dclaration de la fonction Montant. On aurait pu se limiter au prototype en omettant les noms des variables. La ligne 9 est un appel la fonction Montant. La dfinition de la fonction Montant s'tend de la ligne 12 jusqu' la ligne 17. La variable total (14) est une variable locale la fonction Montant, elle ne peut tre utilise qu' l'intrieur de cette dernire. Dans cet exemple on peut vrifier que la dclaration de la fonction s'est faite avant sa premire utilisation. La dfinition peut suivre par la suite. Une fonction doit tre dclare avant son utilisation. Exemple 2 : Il est possible dans un programme de se limiter la dfinition d'une fonction sans faire une dclaration du prototype. Dans ce cas, cette dfinition doit prcder le premier appel de la fonction.
#include <stdio.h> double Montant(int NbArticles, double PU) { double total; total = NbArticles * PU; return total; } void main() { int nb; double val; printf("\n donnez le nombre d'articles puis leur prix unitaire: "); scanf("%f %lf", &nb, & val); resultat = Montant(nb,val); printf("\n Le Montant est %f", resultat); }
Exemple 3 : Il s'agit d'une autre version de l'exemple 2 mais avec moins de variables.
#include <stdio.h> double Montant(int NbArticles, double PU) { return NbArticles * PU;; } void main() { int nb; double val; printf("\n donnez le nombre d'articles puis leur prix unitaire: "); scanf("%f %lf", &nb, & val); printf("\n Le Montant est %f", Montant(nb,val)); }
Exemple 4 : (fonction sans valeur de retour: procdure) Il s'agit de la dfinition d'une fonction qui prend comme paramtre un nombre et qui affiche un message indiquant si ce nombre est strictement positif, strictement ngatif ou nul.
#include <stdio.h> void Signe(int Nb); { if(Nb < 0) printf("le nombre est else if (Nb >0) printf("le nombre est else printf("le nombre est // return; (on aurait pu }
ngatif \n"); positif \n"); nul \n"); ajouter cette ligne pour indiquer que la fonction ne retourne rien)
Version 3.7
50
Karim Kalti
Les fonctions Programmation oriente objet (C++) _________________________________________________________________________________________________________________ void main( ) { int Nombre; printf("Donnez le nombre tester"); scanf("%d",&Nombre); Signe(Nombre); }
Exemple 5 :
// Fonction 1 int f1(int i, int j) { int res1, res2; res1 =i+j; res2 = i-j; return res1; return res2; //unreachable code } // Fonction 2 int f2(int i, int j) { if(i<j) return i; return j; }
La dernire instruction de la fonction 1 (return res2) ne peut jamais tre atteinte et par suite excute parce que le premier return (return res1) va engendrer une sortie immdiate de la fonction avec comme valeur de retour res1. Ce n'est pas le cas pour la fonction 2 o chacune des instructions return peut tre atteinte selon que le i est infrieur j ou non. Il est rappeler que pour une excution donne de la fonction 2 c'est un seul return parmi les deux qui sera excut.
Dans cet exemple la fonction Montant a t dfinie l'intrieur du main. Par consquent, elle ne peut tre appele que de l'intrieur de la fonction main. Exemple 2 : On veut crire un programme qui demande la matricule et les notes d'un tudiant et qui l'invite la fte de fin d'anne s'il possde une moyenne suprieure ou gale 10.
# include <stdio.h> void main( ) { int Mat; float n1,n2; float Moyenne(float, float); // dclaration locale void Invitation(int, float, float ); // dclaration locale
Version 3.7
51
Karim Kalti
Les fonctions Programmation oriente objet (C++) _________________________________________________________________________________________________________________ printf("Donnez la matricule de l'tudiant"); scanf("%d",&Mat); printf("Donnez ses deux notes "); scanf("%f %f",&n1,&n2); Invitation(Mat, n1, n2); } void Invitation(int Matricule, float note1, float note2) { float MoyenneLoc; MoyenneLoc = Moyenne(note1,note2); if( MoyenneLoc <10 ) printf(" L'tudiant possdant la matricule %d n'est pas invit \n", Matricule); else printf(" L'tudiant possdant la matricule %d est invit \n", Matricule); } float Moyenne ( float note1, float note2) { return (note1+note2)/2; }
Conformment la norme ANSI1, et puisque la fonction Moyenne a t dclare l'intrieur du main son utilisation dans le bloc de la fonction Invitation devient incorrecte. En effet, dans ce cas cette fonction est considre comme locale la fonction main et elle ne pourra pas tre appele par suite en dehors du bloc de cette dernire. Il est possible pour viter ce problme de dclarer la fonction Moyenne l'intrieure de la fonction Invitation. Ainsi, elle sera reconnue par cette dernire. Une autre alternative consiste faire une dclaration globale de la fonction Moyenne en dehors de tout bloc. Remarque : La norme ANSI concernant la dclaration locale des fonctions est tendue par certains compilateurs qui considrent qu'une fonction est reconnue dans tout le reste du fichier source depuis l'endroit o elle a t dclare peu importe que la dclaration soit interne un bloc ou globale. De ce fait, l'criture prcdente devient correcte avec certains compilateurs. Cependant et pour des raisons de portabilit elle est viter.
ANSI : Acronyme de American National Standards Institute, institution qui dfinit les normes d'un ensemble de systmes et de langages de programmation.
Version 3.7
52
Karim Kalti
Exemple : Les paramtres NbArticles et PU dans la fonction Montant sont des paramtres de type donnes. C'est le galement le cas des paramtres note1 et note2 dans la fonction Moyenne. Un paramtre "rsultat" est une variable qui est destine contenir le rsultat de l'action de la fonction. Sa valeur n'est pas significative avant le dbut. Exemple:
int CalculerTriple( int x, int triple) { triple = 3 *x; return triple; }
Dans cet exemple x est un paramtre "donne" alors que triple est un paramtre "rsultat". Gnralement un paramtre "rsultat" n'a pas besoin d'tre dclar comme argument de la fonction mais plutt comme une variable locale. Un paramtre "donne-rsultat" est la fois une donne et un rsultat. c'est dire qu'il sert passer une donne la fonction et que cette dernire modifie sa valeur. Exemple : Dans une fonction qui prend deux paramtres entiers et qui permute leurs valeurs. Ces entiers sont des paramtres "donnes-resultats" puisqu'ils fournissent les donnes en entre (les valeurs permuter) et stockent normalement le rsultat de la fonction (les valeurs permutes).
Le mode de passage par valeur est adapt pour le paramtre x (paramtre "donne") mais ne l'est pas pour le paramtre triple (paramtre "rsultat"). En effet la modification effectue sur ce paramtre l'intrieur de la
Version 3.7
53
Karim Kalti
fonction CalculerTriple n'est pas rcupre l'extrieur de cette dernire (dans le main dans ce cas de cet exemple). Exemple 3 :
#include <stdio.h> void Permuter (int val1,int val2) { int temp; temp =val1; val1= val2; val2= temp; } void main( ) { int x= 5, y=3; Permuter(x,y); printf("x contient %d et y contient %d \n", x,y); // }
5 et 3
Les paramtres val1 et val2 sont de type "donne-rsultat". En les faisant passer par valeur comme cest le cas dans cet exemple, le rsultat de la permutation n'est pas rcupr l'extrieur de la fonction et le rsultat affich est par consquent incorrect. Une des solutions permettant de remdier au problme du passage par valeur des paramtres "rsultat" et "donne-rsultat" consiste liminer ces derniers de la liste des arguments de la fonction et les dclarer comme des variables globales. Toutefois cette solution prsente l'inconvnient de rendre la fonction difficilement rutilisable. Une deuxime solution plus intressante consiste utiliser un autre mode de passage de paramtres qui permet de rcuprer ces modifications : c'est le cas du passage par adresse (C/C++) et du passage par rfrence (C++ seulement). Passage par adresse Dans un passage par adresse d'un argument on impose la fonction de travailler non plus sur une copie locale du paramtre effectif mais plutt directement sur ce dernier. (Pas de copie du paramtre effectif dans le paramtre formel, le travail se fait directement sur le paramtre effectif). De cette faon, toute modification effectue l'intrieur de la fonction sera en ralit ralise sur le paramtre effectif et par consquent visible l'extrieur de la fonction. Ce mode de passage de paramtre est effectu en faisant pass la fonction l'adresse du paramtre effectif. Toute rfrence ce dernier de l'intrieur de la fonction doit se faire l'aide de l'oprateur d'indirection (*). Dclaration d'un passage par adresse TypeFonction NomFonction(, Type* ParamAdresse,); ParamAdresse tant le paramtre pass par adresse et Type tant son type.
Rfrence l'intrieur de la fonction d'un paramtre pass par adresse TypeFonction NomFonction(, Type* ParamAdresse,) { *ParamAdresse; } Passage du paramtre par adresse au moment de l'appel de la fonction NomFonction(, &ParamAdresse,) Les paramtres concerns par le mode de passage par adresse sont ceux de type "rsultat" ou "donnersultat". Les paramtres "donne" peuvent toujours tre passs par valeur.
Version 3.7
54
Karim Kalti
Exemple 1 :
#include <stdio.h> void CalculerTriple(int x, int* triple) { *triple = 3*x; } void main(void) { int ent = 5; int res; CalculerTriple(ent, &res); printf("le rsultat est %d", res); // donne un rsultat correct 15 }
Exemple 2 :
#include <stdio.h> void Permuter (int* val1,int* val2) { int temp; temp =*val1; *val1= *val2; *val2= temp; } void main( ) { int x= 5, y=3; Permuter(&x,&y); printf("x contient %d et y contient %d \n", x,y); // }
3 et 5
Passage par rfrence (C++ seulement) Par rapport au C, le C++ introduit un nouveau mode de passage de paramtres appel le passage par rfrence. Il est quivalent de point de vue effet au passage par variable du pascal ou au passage par adresse du C. Dans le passage par rfrence, toute modification effectue sur le paramtre formel de la fonction est rpercute directement sur le paramtre effectif. La dclaration d'un passage par rfrence s'effectue en adjoignant au type du paramtre passer le symbole & de la manire suivante : Dclaration d'un passage par rfrence TypeFonction NomFonction(, Type& ParamRfrence,); ParamRfrence tant le paramtre pass par rfrence et Type tant son type.
Rfrence l'intrieur de la fonction d'un paramtre pass par rfrence TypeFonction NomFonction(, Type& ParamRfrence,) { ParamAdresse; } Passage du paramtre par adresse au moment de l'appel de la fonction NomFonction(,ParamRfrence,)
Exemple
#include <iostream.h> void Permuter (int& val1,int& val2) { int temp; temp =val1; val1= val2; val2= temp; }
Version 3.7
55
Karim Kalti
void main( ) { int x= 5, y=3; Permuter(x,y); cout<<" x contient : "<<x<<"\n y contient : "<<y<<endl; }
Remarque : Les deux prototypes suivants sont galement valables pour faire passer un tableau comme paramtre de la fonction ValAbsTab.
void ValAbsTab( float* TabVal, int Taille) void ValAbsTab( float TabVal[Taille], int Taille)
Version 3.7
56
Karim Kalti
Remarque : Les deux prototypes suivants sont galement valables pour faire passer un tableau comme paramtre de la fonction SaisirMatrice.
void SaisieMatrice( int Matrice[ ][10], int L, int C) void SaisieMatrice( int** Matrice, int L, int C)
, TypeN argN);
Version 3.7
57
Karim Kalti
Les fonctions Programmation oriente objet (C++) _________________________________________________________________________________________________________________ ///////////////////////////////////////////// char MaxTab(char* T) { char max = T[0]; while(*T++) if(*T> max) max = *T; return max; }
Exemple 2:
// Surcharge correcte int f(int, char); int f(char, int); // Surcharge incorrecte int f(char, int); char f(char, int);
Remarque 1: Seuls les derniers arguments, de la droite vers la gauche peuvent avoir des valeurs par dfaut. Exemple :
void g(int a = 3, int b, int c=5); // Error void g(int a, int b=6, int c =8); // OK
Remarque 2: Si la valeur d'un argument par dfaut a t spcifie dans la dclaration de la fonction, alors elle ne doit pas tre mentionne nouveau dans la dfinition. Si une fonction est directement dfinie, alors la valeur par dfaut doit tre spcifie dans la dfinition. Exemple :
void f(int a=0); // ou void f(int=0); void f(int a=0) // Error { Corps de la fonction } void f(int a = 0) // OK {Corps de la fonction}
Il est galement possible de donner une nouvelle valeur pour l'argument par dfaut.
Print(31,16) Print(31,2) 1f 11111
Version 3.7
58
Karim Kalti
Exemple :
int(*pf)(double,int);
Cette dclaration spcifie que pf est un pointeur qui peut pointer sur des fonctions prenant comme argument un double et un int et retournant un int. Soit la fonction int f(double, int) Si pf=f alors int k=f(5.4,6); int k=(*pf)(5.4,6); Exemple : On veut crire un programme qui affiche, suivant le choix de l'utilisateur le sinus, le cosinus ou la tangente d'un angle fourni par l'utilisateur.
#include <iostream.h> #include<math.h> void main( ) { double Deg, Rad; int NumF; double(*Tpf[3])(double)={sin,cos,tan}; cout<<"Donnez un angle en degree : "; cin>>Deg; cout<<"Tapez le numro de la fonction que vous voulez utiliser\n"; cout<<" 1- sinus \n 2- cosinus\n 3- Tangente\n"; cin>>NumF; Rad=3.1416*Deg/180; cout<<"Le rsultat est : "<<(*Tpf[NumF-1])(Rad)<<endl; }
Remarque : Diffrence entre le C et le C++ concernant les pointeurs sur des fonctions sans paramtres En langage C, la liste des paramtres spcifie lors de la dclaration d'un pointeur sur une fonction n'est pas obligatoire. Son absence ne signifie pas forcment que la fonction rfrence par le pointeur ne possde pas de paramtres. En effet, si rien n'est indiqu concernant les paramtres, le compilateur n'effectue aucune vrification en ce qui concerne la correspondance entre paramtres effectifs et paramtres formels (cette proprit existe dj pour les fonctions). Ainsi la dfinition d'un pointeur de fonctions ayant une liste de paramtres vide peut prsenter un avantage puisqu'un tel pointeur peut servir mmoriser les adresses des fonctions admettant toutes sortes de paramtres. Exemple : avec f1 et f2 deux fonctions. La seule contrainte ici pour que ces deux affectations soient correctes est que f1 et f2 retournent une valeur de type int. Les deux fonctions suivantes sont dans ce cas valables.
int (*pf)(); pf=f1; ou pf = f2;
Version 3.7
59
Karim Kalti
Contrairement au C, le compilateur C++ effectue toujours une vrification concernant la correspondance entre paramtres effectifs et paramtres formels mme si ces derniers ne sont pas mentionns. Par consquent un pointeur de fonction dclare sans paramtres ne peut pointer que sur une fonction n'ayant pas de paramtres. Le pointeur pf de l'exemple prcdent ne peut pointer alors que sur f1. Exemple : L'exemple suivant est correct en C mais incorrect en C++.
int(*pf)() = printf; printf("Test des pointeurs de fonctions");(*pf)("Test des pointeurs de fonctions");
Version 3.7
60
Karim Kalti
void AfficherFloat(float* Tab, int l) { int i; for(i=0;i<l;i++) printf("%f, ",Tab[i]); } void AfficherTableau( void* Tab, int l, void(*pf)(void*, int)) { (*pf)(Tab,l); } void SaisirTableau( void* Tab, int l, void(*pf)(void*, int)) { (*pf)(Tab,l); } void main() { char C[5]; int I[5], l =5,n; float F[5]; printf("Tapez le numro 1 du type des lments que vous voulez saisir : \n 1 - caractre\n 2 - entier\n 3 - rel\n"); scanf("%d", &n); fflush(stdin); printf("****** Dbut de la saisie : *******\n"); switch(n) { case 1: SaisirTableau(C,l,SaisirChar); break; case 2: SaisirTableau(I,l,SaisirInt); break; case 3: SaisirTableau(F,l,SaisirFloat); break; } printf("****** Affichage du rsultat de la saisie: *******\n"); switch(n) { case 1: AfficherTableau(C,l,AfficherChar); break; case 2: AfficherTableau(I,l, AfficherInt); break; case 3: AfficherTableau(F,l, AfficherFloat); break; } }
Version 3.7
61
Karim Kalti
Evaluation des paramtres optionnels L'valuation des paramtres effectifs optionnels doit passer par l'utilisation des trois macros suivantes va_start, va_arg et va_end du header stdarg.h ainsi que par un pointeur de type va_list (dfini comme void* ou char*) et qui va permettre de pointer vers ces arguments optionnels (Un argument est un paramtre effectif). La macro va_start void va_start( va_list arg_ptr, prev_param ); (ANSI version) void va_start( va_list arg_ptr ); (UNIX version) va_start prend deux paramtres, le premier est un pointeur de type va_liste vers les paramtres optionnels de la fonction alors que le second est le nom du dernier paramtre fixe. va_start initialise arg_ptr au premier argument optionnel de la liste des arguments passe la fonction. La macro va_arg TypeArg va_arg( va_list arg_ptr, TypeArg ); La macro va_arg rcupre une valeur de type TypeArg partir de l'endroit donn par arg_ptr. En d'autres mots, elle permet de rcuprer la valeur de l'argument courant. Elle incrmente aussi arg_ptr pour pointer sur l'argument suivant de la liste en utilisant la taille du type pour dterminer l'adresse de dbut du prochain argument. La macro va_end void va_end( va_list arg_ptr ); Aprs valuation de la liste des paramtres, le pointeur d'arguments va_end rinitialise arg_ptr NULL. Remarque : Dans la majorit des cas, Il est ncessaire de faire communiquer la fonction acceptant un nombre variable de paramtres le nombre effectif de paramtres optionnels traiter. Ce nombre est gnralement plac dans un des paramtres fixes. Exemple : L'exemple suivant montre la dfinition d'une fonction Somme qui peut calculer la somme d'un nombre variable d'entiers.
#include "iostream.h" #include "stdarg.h" long Somme(int L,...) { int i; va_list arg_ptr; long resultat; va_start(arg_ptr,L); i=1; resultat=0; while (i<=L) { resultat+=(long)va_arg(arg_ptr,int); i++; } va_end(arg_ptr); return resultat; } void main() { cout<<"le rsultat de la somme est :"<<f(3,3,5,10)<<endl; }
Version 3.7
62
Karim Kalti
Supposons que ce programme soit sauvegard dans un fichier nomm Somme.cpp alors la compilation et l'dition des liens de ce fichier va conduire vers la cration d'un fichier excutable nomm Somme.exe. Pour lancer cet excutable il suffit de taper son nom aprs le prompt du systme d'exploitation puis de valider avec <entre>. Le programme va demander alors l'utilisateur de saisir trois nombres et lui affiche comme rsultat leur somme. Il est galement possible de faire fonctionner le programme Somme la manire d'une commande qui prend trois arguments. Pour ce faire, ces arguments doivent tre passs la fonction principale main. Passage d'arguments la fonction main La fonction main peut prendre normalement deux paramtres formels optionnels communment appels argc et argv. main(int argc, char* argv[]) argc est un argument de type int et dsigne le nombre de paramtres spcifis sur la ligne de commande y compris le nom du programme qui est considr aussi comme paramtre. argv est un tableau de chanes de caractres qui stocke les arguments passs en ligne de commande. La taille de ce tableau dpend du nombre d'arguments passs. Par convention arg[0] renvoie sur la commande avec laquelle le programme est invoqu, arg[1] reprsente le premier argument pass rellement au programme, arg[2] reprsente l'argument suivant, etc. Le dernier lment de argv est toujours argv[argc] et contient le pointeur nul. Le premier argument pass est argv[1] et le dernier est argv[argc-1]. La reconnaissance du nombre de paramtres est automatique. Ces derniers doivent cependant tre spars par des espaces. Si un des paramtres lui mme est un espace alors il doit tre plac entre deux guillemets.
Exemple :
#include <stdio.h> #include <stdlib.h> void main(int argc, char* argv[]) { int a,b,c; a=atoi(argv[1]); b=atoi(argv[2]); c=atoi(argv[3]); printf("%d",a+b+c); }
Si on enregistre ce code dans un fichier portant le nom Somme.cpp alors l'excution en ligne de commande devra se faire de la manire suivante :
C:\ Somme 4 5 2
C:\ 11 Remarque : La fonction main peut prendre un troisime paramtre communment appel envp. Ce paramtre pointe sur les rubriques de l'environnement du programme (les chemins implicites, l'allure du prompt,) il est utilis sous dos et unix. void main(int argc, char* argv[],char* envp[])
Version 3.7
63
Karim Kalti
Structures et numrations
Les types de base du langage C (int, float, double, char, ) ne permettent pas de couvrir tous les besoins de programmation en types de donnes. Les programmeurs ont souvent recours la dfinition de types personnaliss qui rpondent leurs besoins spcifiques. Le langage C met la disposition de ces derniers un ensemble d'outils de construction de types personnaliss parmi lesquels figurent les structures et les numrations.
Il est galement possible (bien que peu recommand) de regrouper la dfinition du modle structure et la dclaration des variables dans une seule instruction. Exemple
struct FicheSignaletique { int age; float taille; float poids; char nom[20]; }P1, P2;
Version 3.7
64
Karim Kalti
Dclaration en langage C++ En langage C++, il n'est pas ncessaire d'utiliser struct pour la dclaration des variables et le nom de la structure peut tre par consquent utilis seul pour dsigner le type. Cette dclaration se fait alors comme suit : NomStructure Var; Exemple
struct FicheSignaletique { int age; float taille; float poids; char nom[20]; } FicheSignaletique F;
Pour des raisons de compatibilit la syntaxe de dclaration du C est galement accepte par les compilateurs C++ et peut encore tre utilise.
Remarque : Les types des membres d'une structure peuvent tre aussi bien des types prdfinis que personnaliss y compris les structures.
Remarques: L'affectation globale n'est pas possible entre tableaux. Elle l'est par contre entre structures. Paradoxalement, il devient de ce fait possible en crant une structure contenant un seul champ de type tableau de raliser une affectation globale entre tableaux. Deux structures ne peuvent pas tre compares d'une manire globale. Ainsi:
FicheSignaletique P1,P2; P1==P2 et P1>P2 sont deux instructions
incorrectes.
Version 3.7
65
Karim Kalti
S'il y a moins de valeurs d'initialisation que de champs, les variables qui manquent seront automatiquement initialises 0. S'il y a plus de valeurs d'initialisation que de champs, le compilateur signale une erreur. Il est strictement interdit de dfinir une initialisation systmatique d'un membre d'une structure (il faut sparer dclaration et initialisation). Exemple :
struct FicheEtudiant {float taille; float poids =75; //error char Nom[20]; };
Une constante symbolique peut avoir un type qui est dfini l'aide du mot rserv struct. Exemple :
const FicheSignaletique P1={23,1.78,75,"Ali"};
Imbrication de structures
Un membre d'une structure peut lui mme tre de type structure. Dans ce cas on parle de structures imbriques. Exemple:
struct Date { int jour; int mois; int annee; }; struct personne { char Nom[20]; char Prenom[20]; Date DateNaissance; };
ou galement :
struct FicheEtudiant { float taille, poids; Date DateNaissance; char Nom[20]; };
Initialisation d'une structure comportant une autre structure L'initialisation de la structure interne se fait en spcifiant les valeurs de ses membres sous forme d'un sous ensemble de la liste des valeurs des membres de la structure externe.
Version 3.7
66
Karim Kalti
Exemple :
FicheEtudiant P1 = {1.70, 70,{1,4,1985},"Ammar"};
Il est possible d'omettre des valeurs pour certains membres. Ces derniers seront automatiquement initialiss 0. Exemple :
FicheEtudiant P2 = {1.85, 90,{18,6, }, };
Tableau de structures
Dclaration Les lments d'un tableau peuvent tre de type structure. La dclaration de ce tableau se fait comme suit : NomStructure NomTableau[Taille]; Accs au tableau L'accs au ime lment du tableau se fait comme suit : NomTableau[i]; L'accs un membre du ime lment du tableau se fait comme suit : NomTableau[i].NomMembre; Exemple :
struct Point { int x; int y; }; Point Courbe[50];
L'accs l'abscisse du ime point de la courbe: Courbe[i].x L'accs l'ordonn du ime point de la courbe: Courbe[i].y
La dclaration de variables de ce nouveau type s'effectue comme les dclarations usuelles. Exemple 1 :
typedef int Chaise; Chaise Numero; // la variable numro a pour type chaise. Son type de base est int. typedef int vecteur[3]; Les deux dclarations suivantes sont quivalentes : int v1[3]; vecteur v2; // v1 et v2 sont deux tableaux de trois entiers
Exemple 2 :
typedef struct FicheEtudiant { float taille, poids; Date DateNaissance; }; struct FicheEtudiant P; typedef struct { float taille, poids; Date DateNaissance; } FicheEtudiant; FicheEtudiant P;
ou galement
Version 3.7
67
Karim Kalti
Exemple 3 :
struct Date{int jour, mois, annee;}; typedef Date MatriceDate[5][5]; MatriceDate M; // M est une matrice 5X5 de type Date. M[1][2].jour=5;
Accs aux membres L'accs aux membres de la structure partir d'un pointeur peut se faire de deux manires : (*NomPointeur).NomMembre Exemple :
Struct Point {int x; int y;}; Point P1; Point* PP; P1.x=10; P1.y=5; PP=&P1 printf(" l'abscisse de P1 est %d", (*pp).x); printf("l'ordonn de P1 est %d", pp->y);
ou galement
NomPointeur->NomMembre
Les numrations
Les numrations reprsentent un outil supplmentaire pour la construction de type. Une numration est constitue d'un ensemble de constantes symboliques associes chacune un numro entier. Dfinition d'une numration La dfinition d'une numration se fait l'aide du mot-cl enum de la manire suivante : enum NomEnumration {NomValeur1, NomValeur2,, NomValeurN}; Chaque valeur de l'numration est associe son numro d'ordre dans la liste. La premire ayant la valeur entire 0, la deuxime la valeur 1 et la nime la valeur N-1. Exemple 1 :
enum Couleur {blanc, noir, gris};
Exemple 2 :
enum Bool {false, true}; // construction du type boolen en C.
Version 3.7
68
Karim Kalti
Remarques : Il est possible de spcifier des valeurs affecter aux diffrents lments de l'numration. Ces valeurs doivent respecter l'ordre spcifi dans la dclaration et donc tre croissantes du premier lment de l'numration au dernier.
enum Couleur{blanc=3, noir= 255, gris=600}; enum jours{lundi,mardi=7,mercredi, jeudi, vendredi = 25, samedi = 30, dimanche}; Lundi vaut 0, mardi vaut 7, mercredi vaut 8, jeudi vaut 9, vendredi vaut 25, samedi vaut 30, et dimanche vaut 31.
Dclaration d'une variable de type numration en langage C La dclaration d'une variable de type numration se fait de la manire suivante : enum NomEnumration NomVariable; Exemple 1 :
enum Couleur {blanc, noir, gris}; enum Couleur cl=noir; //cl est une variable initialise noir
ou galement :
enum Couleur{blanc, noir,gris} cl; cl=noir;
Exemple 2 :
enum Couleur{rouge, orange, vert}; enum Couleur Feu; // Dclaration d'une variable Feu if(Feu==rouge) printf("il faut stopper"); else if (Feu==vert) printf("vous pouvez passer"); else if (Feu==orange) printf("il faut commencer freiner");
Dclaration d'une variable de type numration en langage C++ En C++, il n'est pas ncessaire d'utiliser le mot rserv enum lors de la spcification du type de la variable. La dclaration de cette dernire se fait alors comme suit : NomEnumration NomVariable; Exemple:
enum Couleur{rouge, orange, vert}; Couleur Feu; // Dclaration d'une variable Feu en C++.
Version 3.7
69
Karim Kalti
Les classes
Introduction
Les entits du monde rel sont gnralement assez complexes et ne peuvent tre convenablement reprsentes dans les programmes l'aide des types scalaires. Il faut leur construire de nouveaux types qui leurs sont adapts. C++ met la disposition des programmeurs un ensemble d'outils de construction de tels types. Comme exemple de ces outils, il est possible de citer : Les tableaux pour la reprsentation des ensembles d'lments de mme type. Les structures pour la reprsentation des entits complexes comportant plusieurs champs. Considrons l'exemple suivant :
#include <iostream.h> struct Date {int jour, mois, annee;}; Date Saisir() { Date D; cout<<"Donnez le jour :"; cin>>D.jour; cout<<"Donnez le mois :"; cin>>D.mois; cout<<"Donnez l'anne :"; cin>>D.annee; return D; } void Afficher(Date D) { cout<<endl; cout<<D.jour<<'/'<<D.mois<<'/'<<D.annee; cout<<endl; } Date DateRecente(Date D1, Date D2) { if( D1.annee<D2.annee) return D2; else if(D1.annee>D2.annee) return D1; else { if( D1.mois<D2.mois) return D2; else if(D1.mois>D2.mois) return D1; else { if(D1.jour<D2.jour) return D2; else return D1; } } }
Version 3.7
70
Karim Kalti
int main() { Date D1,D2; D1 = Saisir(); D2 = Saisir(); cout<<"la date la plus rcente est"; Afficher(DateRecente(D1,D2)); Return 0 ; }
CLASSE
Attribut_1 Attribut_2 Attribut_n Mthode_1() Mthode_2() Mthode_m()
Version 3.7
71
Karim Kalti
Remarque 1 : Il n'est pas possible d'instancier une classe dclare mais non encore dfinie. Il est possible par contre de dclarer un pointeur vers une classe dclare mais non encore dfinie. Remarque 2 : Dclaration des attributs d'une classe Les attributs d'une classe peuvent tre des variables ou des constantes. D'ailleurs leur dclaration se fait suivant la mme syntaxe que les variables et les constantes classiques. Toutefois par rapport ces dernires, les attributs se distinguent par le fait qu'au moment de leur dclaration dans la dfinition de la classe aucun espace mmoire ne leur est rserv. En effet la dfinition d'une classe constitue un processus de dfinition de type et non un processus de dclaration d'objet. De ce fait, il devient impossible d'initialiser un attribut au moment de la dfinition d'une classe. Exemple :
class A { int i = 5; // Erreur };
Version 3.7
72
Karim Kalti
Remarque 3 : Type des attributs Les attributs peuvent tre de n'importe quel type (scalaire ou personnalis y compris de type classe). Toutefois, un attribut ne peut pas tre du type de la classe laquelle il appartient ( cause de la rcursivit dans la rservation de l'espace mmoire que cela engendre lors de l'instanciation de la classe). Il est toutefois possible de dclarer un attribut de type pointeur sur la classe laquelle appartient cet attribut. Exemple :
class A { A pa; // Erreur }; class A { A *pa; // OK };
Classe anonyme
Une classe anonyme est une classe qui ne porte pas de nom (sans nom). Elle sert essentiellement faire des dclarations directes d'objets (dans la mme instruction qui dfinit la classe). Exemple :
class {} D; // D est un objet de type classe sans nom
Remarque : Une instance d'une classe anonyme ne peut pas figurer dans la liste d'arguments d'une fonction (Il est impossible en effet de spcifier le type du paramtre formel puisqu'il est sans nom).
Version 3.7
73
Karim Kalti
Deuxime faon : Elle consiste : dclarer la fonction lintrieur de la classe, la dfinir ensuite lextrieur de la classe. Il faut pour cela indiquer la classe dorigine de la fonction. Ceci se fait comme suit : Syntaxe : TypeRetour NomClasse::Methode(type1 arg1,, typeN argN); :: est loprateur de rsolution de porte. NomClasse::Methode est appel le nom qualifi de la mthode. Exemple :
class Date { int jour, mois, annee; void Saisir(); void Afficher(); }; void Date::Afficher(){} void Date::Saisir(){} // Dfinition de Afficher // Dfinition de Saisir
Remarque : Une mthode dfinie lintrieur dune classe est considre par dfaut comme tant inline. Une fonction dfinie lextrieur de la classe nest pas considre comme inline. Pour la rendre ainsi, il faut ajouter explicitement la spcification inline devant sa dfinition. Exemple :
inline void Date :: Afficher() { }
Encapsulation
L'encapsulation est le mcanisme qui permet de regrouper les donnes et les mthodes au sein d'une mme structure (classe). Ce mcanisme permet galement l'objet de dfinir le niveau de visibilit de ses membres. Un membre visible est un membre qui est accessible partir de l'objet : o Si le membre est un attribut alors l'accs concerne une opration de lecture ou d'criture de la valeur de cet attribut. o Si le membre est une mthode alors l'opration d'accs consiste en un appel de cette mthode.
Version 3.7
74
Karim Kalti
Gnralement un objet ne doit exposer (rendre visible) que les membres qu'il juge utiles pour ses utilisateurs. Il doit cacher tous les dtails de son implmentation (corps des mthodes ainsi que les attributs et mthodes qui sont utiliss en interne). Spcificateurs des droits d'accs aux membres En C++, la spcification des droits d'accs aux membres d'une classe se fait l'aide des trois mots-cls suivants : public, private et protected. public : ce spcificateur rend les membres dune classe X (attributs ou mthodes) accessibles en dehors de la classe, gnralement par les utilisateurs de cette dernire. private : ce spcificateur restreint laccs aux membres de la classe (attributs et mthodes) aux mthodes de la classe seulement, ainsi quaux fonctions dclares amies de la classe. protected : ce spcificateur a un effet similaire private mais qui est toutefois moins svre. En effet protected restreint laccs aux membres d'une classe X aux mthodes de X, aux fonctions amies de X et aux mthodes des classes bases sur X. Remarque : Il est possible dutiliser au sein dune mme classe diffrents spcificateurs daccs. Ainsi, il est possible de rendre une partie de la classe publique et de cacher une autre partie en la qualifiant de prive. Porte dun spcificateur daccs : La porte dun spcificateur daccs s'tend depuis l'endroit de sa dfinition jusqu' la rencontre de la dfinition d'un autre spcificateur.
class X { private : int a1; //priv char a2; //priv public : void f1(); //publique int f2(double k); //publique }; class X { public : int a1; //publique void f1(); //publique private : char a2; //priv public : int f2(double k); //publique };
Remarque : Spcificateur d'accs par dfaut Si aucun spcificateur daccs na t dfini dans une classe alors les membres de cette dernire sont qualifis par dfaut comme tant privs. Exemple :
class X { int a1; char a2; void f1(); int f2(double k); };
Tous les membres de la classe X sont considrs par dfaut comme tant privs.
Version 3.7
75
Karim Kalti
Le fait que les attributs jour, mois, annee soient directement accessibles par les mthodes Saisir et Afficher de la classe Date vite ces dernires de prendre un paramtre de type Date comme cest le cas lorsquil sagit de fonctions indpendantes (Cf. exemple avec les structures).
Version 3.7
76
Karim Kalti
Accs de l'extrieur de la classe L'utilisation d'un membre d'une classe en dehors de cette dernire (par exemple par une fonction non membre de la classe) se fait gnralement partir d'une instance (objet). Le membre doit tre associ l'objet auquel il appartient. Cette association se fait l'aide de l'oprateur dyadique (prend deux oprandes) point de la manire suivante : o Pour les attributs : Objet.Attribut; o Pour les mthodes : Objet.Methode(Paramtres effectifs); Seuls les membres publiques peuvent tre accds de l'extrieur d'une classe. Les autres membres (private et protected) ne sont pas accessibles de l'extrieur. Exemple :
class Date { int jour, mois; public: int annee ; void Saisir(); void Afficher(); }; void main() { Date D; D.jour = 5; // Erreur D.Mois = 10; // Erreur D.anne = 2004; // OK cout<<D.jour; // Erreur cout<<D.mois; // Erreur cout<<D.annee; // OK } void main() { Date D; D.Saisir(); // OK D.Afficher(); // OK }
Version 3.7
77
Karim Kalti
Imbrication de classes
Une classe X peut avoir un membre de type une autre classe Y. Les deux classes sont dites dans ce cas des classes imbriques. Exemple :
class Personne { char Nom[20]; char Prenom[20]; Date Dn; Public : void Saisir(); void Afficher(); };
Date et Personne sont dans ce cas deux classes imbriques. Manipulation des attributs et des mthodes des classes imbriques On considre les classes Personne et Date dfinies ci-dessus. Nous allons discuter dans ce qui suit de la possibilit d'accder aux champs de Date partir de la classe Personne et ce en utilisant diffrents spcificateurs d'accs. - Dn est un champ publique et ses champs sont publiques.
Personne P; P.Dn.Jour; // OK
Version 3.7
78
Karim Kalti
Le pointeur this
this est un pointeur particulier en C++ qui pointe toujours sur lobjet en cours dutilisation. Ce pointeur possde plusieurs champs d'applications. Utilisation implicite de this Chaque objet d'une classe possde sa propre copie des attributs (sa propre zone en mmoire). Cette copie dfinit l'identit mme de l'objet qui permet de le distinguer des autres. Toutefois, tous les objets se partagent la mme copie des mthodes (tous les objets ont le mme comportement). Cette organisation en mmoire des objets peut engendrer un problme lors de l'appel des mthodes. En effet, le problme qui peut se poser est la suivant : en cas de prsence de plusieurs objets d'une mme classe, comment une mthode peut-elle connatre la copie des attributs sur laquelle elle doit travailler (car rappelons-le on peut manipuler l'intrieur d'une mthode les attributs directement par leurs noms sans aucune qualification). La rsolution de ce problme passe par l'utilisation du pointeur this. En effet, toute mthode d'une classe prend d'une manire implicite un argument cach de type pointeur sur la classe laquelle elle appartient. Ce pointeur est utilis implicitement pour rfrencer les attributs l'intrieur de la mthode. Il suffit alors d'affecter le pointeur this cet argument au moment de l'appel de la mthode. Le rfrencement implicite des attributs devient alors : this->Attribut. Cette syntaxe limine toute ambigut concernant la copie de l'attribut sur laquelle on doit travailler puisqu'elle dsigne bien l'attribut de l'objet en cours d'utilisation. Exemple : La mthode Saisir dfinie comme suit :
void Date::Saisir() { cout<<"Donnez dans l'ordre le jour, le mois et l'anne"; cin>>jour>> mois>>annee; }
Version 3.7
79
Karim Kalti
Remarque : Le pointeur this est en lecture seule. C'est le systme qui lui affecte sa valeur pour un objet donn. Dans un programme, il n'est donc possible que de lire cette valeur mais pas la modifier. D'ailleurs ceci explique le fait que le this est dclar comme un pointeur constant (Type const* this). Exemple d'utilisation explicite du pointeur this Un des problmes qui peuvent tre rsolus par l'utilisation du pointeur this est celui qui se pose lors de l'utilisation d'une mthode qui possde des paramtres portant les mmes noms que les attributs. Une ambigut de qualification se pose dans ce cas. Pour lever cette ambigut il suffit d'appeler les attributs de la classe travers le pointeur this.
Exemple :
class A { int x, y; public : void f(int x, int y) { this->x=x; this->y=y; } };
Version 3.7
80
Karim Kalti
Affectation dobjets Il est possible daffecter dune manire globale un objet O1 un autre objet O2 de la mme classe. La valeur de chaque attribut de O1 est alors copie dans lattribut qui lui correspond dans O2. Exemple :
void main() { CX x1,x2; x1.a1=5; x1.a2=8; x2=x1; x2.Affiche(); x1.a1=18; x1.Affiche(); x2.Affiche(); }
Affectation entre pointeurs sur des objets Il est possible de faire une affectation entre deux pointeurs sur des objets de mme type. Toutefois lutilisation de ces pointeurs doit se faire avec prcaution. Exemple :
void main() { CX *px1,*px2; px1=new CX(); // Objet sans nom px1->a1=5; px1->a2=8; px2=px1; // px1 et px2 pointent sur le mme objet px2->Affiche(); px1->a1=18; px1->Affiche(); px2->Affiche(); delete px1; delete px2; // Erreur car l'objet a t dj libr }
Version 3.7
81
Karim Kalti
Il nest pas possible dutiliser cin>> et cout<< pour faire la saisie et laffichage d'une manire globale du contenu dun objet. Exemple :
CX x ; cin>> x ; // Erreur cout<< x ; // Erreur
Version 3.7
82
Karim Kalti
Constructeurs et destructeur
Initialisation d'objets
Il est possible d'attribuer des valeurs initiales aux attributs d'un objet travers une liste de valeurs spcifies entre deux accolades et spares par des virgules (comme pour les tableaux). Exemple :
class Point2D {public: int x; int y; }; class Point3D {public: int x; int y; int z; }; Point2D Point3D Point3D Point2D Point2D P1={5,6}; P2={5,6,9}; P3={4,,9} // le deuxime attribut est initialis 0. T[3] = {1,5,6,8,9,10}; T[3] = {{1,5},{6,8},{9,10}};
Limites de l'initialisation avec les listes de valeurs Il faut que tous les membres de l'objet initialiser soient publiques. Il faut que l'objet soit d'une classe de base et qu'il n'ait pas de membres virtuels. Certaines oprations d'initialisation sont complexes et ncessitent plusieurs tapes comme le montre l'exemple suivant : Exemple :
class TabEntiers { public : int Nb; int* T; };
Pour pouvoir initialiser le membre T, ce dernier doit auparavant tre allou. Solution 1 : Passage par une mthode d'initialisation Avantages : Possibilit de raliser les oprations d'allocation pour les membres dynamiques. Possibilit d'accder aux membres privs et protgs.
Version 3.7
83
Karim Kalti
Exemple :
class TabEntiers { int Nb; int* T; public : void Init(int Ni) { Nb=Ni; T=new int [Nb]; } void Init(int* Ti,int Ni) { Nb=Ni; T=new int [Nb]; for(int k=0;k<Nb;k++) T[k]=Ti[k]; } } void main() { TabEntiers TE; TE.init(5); }
Inconvnients : Pour pouvoir utiliser l'objet, il est ncessaire d'appeler chaque fois, d'une manire explicite, la mthode Init. Cet appel explicite peut engendrer des erreurs surtout dans les programmes de grande taille o le risque d'oubli devient lev. L'utilisation de Init ne peut pas tre considre comme tant une initialisation au vrai sens technique du terme car une initialisation se fait gnralement dans la mme instruction que la dclaration. Solution 2 : Utilisation des constructeurs du C++ Le C++ offre une solution de cration et d'initialisation d'objets qui permet de remdier tous les problmes susmentionns (initialisation au vrai sens du terme, appel implicite, initialisation des membres dynamiques avec allocation de mmoire,).
Les constructeurs
Dfinition Un constructeur est une mthode particulire d'une classe qui s'excute automatiquement d'une manire implicite, lors de la cration d'un objet de cette classe. Utilisation des constructeurs Rle de base : (implicite, transparent par rapport aux programmeurs) Un constructeur d'une classe assure la rservation de l'espace mmoire ncessaire la cration de tout objet de cette classe. L'espace dont on parle ici dsigne l'espace de base ncessaire la cration de l'objet. Il ne comprend pas les espaces dynamiques associs aux membres dynamiques ventuels de la classe.
Version 3.7
84
Karim Kalti
Exemple : Pour la classe TabEntiers dfinie prcdemment, l'espace de base ncessaire la cration d'un objet est gale la somme des tailles de Nb et du pointeur T. Il ne comprend pas la taille de la zone mmoire pointe par T (sizeof(Nb)+sizeof(T)). Exploitation usuelle des constructeurs : (explicite, effectue par les programmeurs) Les constructeurs sont gnralement exploits par les programmeurs pour raliser les oprations suivantes : Initialisation des attributs de la classe (publiques, privs ou protgs). Allocation des espaces mmoires ncessaires aux membres dynamiques de la classe.
Version 3.7
85
Karim Kalti
Cas d'un objet dynamique Pour la cration dynamique d'objets, seul l'appel explicite du constructeur est possible. NomClasse* ptrObjet = new NomClasse(<paramtres effectifs>);
Time* T = new Time(16,30,25);
Exemple :
Time T1(16,30,25); Time* T2 = new Time(17,44,59); T1.Affiche(); T2->Affiche();
Version 3.7
86
Karim Kalti
La classe Time dispose de deux constructeurs : un paramtr et un autre qui ne l'ai pas. Ces deux constructeurs respectent la rgle de surcharge de mthodes (ils ont deux signatures diffrentes). Remarque 1 : Si une classe dispose de plusieurs constructeurs alors au moment de la cration d'un objet partir de cette classe il y aura appel un parmi ces constructeurs. Dans l'exemple ci-dessus, la cration de l'objet T1 est faite l'aide du constructeur sans arguments. Cet objet est non initialis. Son contenu sera par la suite saisi avec la mthode Saisir. La cration de l'objet T2 est faite l'aide du constructeur paramtr. Son contenu est initialis 16h15mn30s.
Version 3.7
87
Karim Kalti
Remarque 2 : Tout objet ne peut tre cr qu' travers un appel un constructeur. Par consquent, ce sont les constructeurs dfinis dans une classe qui dterminent la manire avec laquelle peuvent tre instancis les objets partir de cette classe. Dans l'exemple ci-dessus la classe Time dispose de deux constructeurs et offre donc deux faons pour instancier les objets (celles utilises respectivement par T1 et T2). A titre d'exemple, Si le constructeur sans arguments Time() n'tait pas dfini dans la classe Time alors l'instanciation Time T1; serait syntaxiquement incorrecte (erreur de compilation). Remarque 3 : Il est toujours recommand de dfinir plusieurs constructeurs dans une classe et ce pour offrir aux utilisateurs de cette dernire plus de scnarios possibles et une plus grande souplesse concernant l'instanciation des objets. Par exemple la classe Time, qui constitue un type personnalis, a t conue de faon offrir les mmes scnarii d'instanciation que ceux offerts par les types standards du langage (int, char, float, ) concernant la cration des variables. Ainsi pour le type int par exemple il est possible de crer une variable initialise ou une variable non initialise et laisser la main l'utilisateur du programme pour lui affecter une valeur. Ces deux scnarios sont galement possibles pour le type Time grce aux constructeurs dfinis dans cette classe comme le montre le tableau suivant : Cration sans initialisation puis saisie de valeur int (Type standard) Time (Type personnalis)
int i1; cin>>i1, Time T1; T1.Saisir();
Cration et initialisation
int i2 =5; Time T2(16,15,30);
Le destructeur
Un destructeur joue le rle symtrique du constructeur. Il est automatiquement appel pour librer lespace mmoire de base ncessaire lexistence mme de lobjet (espace occup par les attributs). Cette libration est implicite. Elle est effectue par tout destructeur. Il est gnralement recommand dexploiter le destructeur pour faire : La libration dventuels espaces mmoires dynamiques associs aux attributs de lobjet. Contrairement la libration de lespace de base, cette libration ne seffectue pas dune manire automatique. Elle doit tre faite dune manire explicite par le programmeur. La fermeture dventuels fichiers utiliss par lobjet. Lenregistrement des donnes, etc. Dclaration et dfinition dun destructeur : Un destructeur porte le mme nom que celui de la classe mais prcd dun tilde ~ (pour le distinguer du constructeur). Un destructeur ne retourne aucune valeur.
Version 3.7
88
Karim Kalti
Un destructeur ne prend jamais de paramtres. Un destructeur est une mthode de la classe. Toutes les rgles qui sappliquent aux mthodes (porte, qualification daccs, dclaration, dfinition) sappliquent donc galement au destructeur. Une classe ne peut disposer que dun seul destructeur. Il est toujours non paramtr. Exemple 1 :
class Time { int Hour; int Minute; int Second; public : Time () { .... } Time (int H, int M, int S) { .... } Time (const char* str) { .... } ~Time() {} // Destructeur vide Afficher() { .... } } ;
La classe Time possde des attributs de type auto. Donc la libration d'un objet de cette classe ne demande aucun traitement supplmentaire particulier. Par consquent son destructeur est dfini vide. Appel du destructeur : Un destructeur peut tre appel dune manire implicite ou explicite. Appel explicite : NomObjet.~NomClasse(); ou
PointeurObjet->.~NomClasse();
Cet appel reste toutefois rare dutilisation. Appel implicite : Un destructeur est implicitement appel lorsque la dure de vie de lobjet est coule. Ceci est le cas : Pour un objet local de la classe mmoire auto, lorsque le domaine de validit de lobjet (bloc dinstructions dans lequel il est dclar) est quitt. Pour un objet global ou local de la classe mmoire "static" lorsque le programme se termine. Pour un objet dynamique cr par new lorsque loprateur delete est appliqu au pointeur qui le dsigne.
Version 3.7
89
Karim Kalti
Exemple : L'exemple suivant montre les moments d'appel du constructeur et du destructeur pour les objets de type auto et dynamique.
class X { char NomObjet[20]; public : X(char* NomObj) { strcpy(this->NomObjet, NomObj); cout<<"Excution du constructeur de l'objet : "<<this>NomObjet<<endl; } ~X() { cout<<"Excution du destructeur de l'objet : "<<this>NomObjet<<endl;} }; void main() { X x1("x1"); X* x2 = new X("x2"); delete x2; }
Destructeur par dfaut Si aucun destructeur na t explicitement dfini pour une classe, alors le compilateur en gnre un par dfaut, public. Ce destructeur ralise par dfaut la libration de lespace mmoire de base (des attributs). Il est par consquent toujours dfini vide de la manire suivante : ~NomClasse(){} Remarque : Le destructeur gnr par dfaut convient gnralement aux classes simples (Exemple : la classe Time). Toutefois, les classes complexes qui demandent des traitements supplmentaires lors de leurs librations ncessitent une dfinition explicite et personnalise de leur destructeur comme le montre le paragraphe suivant.
Version 3.7
90
Karim Kalti
La classe TabEntiers ne dispose d'aucun constructeur ou destructeur explicitement dfinis. Ces derniers seront alors automatiquement gnrs par le compilateur. Ils ont respectivement les dfinitions suivantes :
TabEntiers(){} ~TabEntiers(){}
Avec de telles dfinitions, la fonction principale suivante engendrera une erreur au moment de l'excution.
void main() { TabEntiers TE; // Appel implicite du constructeur par dfaut TE.Saisir(); TE.Afficher(); }
L'erreur survient exactement lors de l'excution de la mthode Saisir et plus prcisment au niveau de l'instruction cin>>T[k]. Elle est due l'absence d'allocation dynamique de l'attribut T de l'objet TE. Puisque tout objet de TabEntiers ncessite obligatoirement une allocation dynamique de l'espace mmoire pour l'attribut T alors il est possible d'automatiser cette opration en plaant le code qui la ralise dans le constructeur de la classe. La deuxime version de la classe TabEntiers met en uvre cette recommandation.
Version 3.7
91
Karim Kalti
Cette version de la classe TabEntiers dispose de deux constructeurs explicitement dfinis : le premier permet de crer un objet convenablement allou en mmoire mais non initialis. Son contenu peut tre saisi avec la mthode Saisir. Le deuxime constructeur permet de crer un objet directement initialis avec des entiers stocks dans un tableau pass comme argument. Les deux fonctions principales suivantes illustrent l'utilisation de ces deux constructeurs.
void main() { TabEntiers TE(2); TE.Saisir(); TE.Afficher(); } void main() { int Ti[3]={21,3,45}; TabEntiers TE(Ti,3); TE.Afficher(); }
Problme de fuite de mmoire : L'excution de chacune des deux fonctions principales en utilisant la classe TabEntiers telle qu'elle est dfinie dans sa deuxime version engendre une fuite de mmoire qui concerne le tableau point par l'attributs T de l'objet TE. En effet cet espace tant dynamiquement cr par new ne sera pas libr travers l'appel (implicite dans ce cas) du destructeur de la classe (destructeur par dfaut en l'absence d'une dfinition explicite de ce dernier).
Version 3.7
92
Karim Kalti
TE
int Nb 3 21 3 45 21 3 45 int* T FFE4D8A
Etat de la mmoire suite l'appel du destructeur par dfaut aprs la fin du main
Cet espace ne peut en effet tre libr de la mmoire qu' travers un appel explicite delete de la manire suivante: delete TE.T; Toutefois un tel appel n'est possible que si T est un attribut publique ce qui n'est pas le cas ici. Mme si T tait publique, il serait dconseill de laisser la tche de libration de la mmoire l'utilisateur de la classe cause du risque d'oubli. Pour remdier ce problme, il serait plus astucieux de placer le code de libration dans le destructeur ce qui conduit vers la version finale de la classe TabEntiers. Version finale de la classe TabEntiers La classe TabEntiers utilise en plus de l'espace mmoire ncessaire pour les attributs un espace mmoire dynamique (tableau point par T) qui est allou chaque cration d'un objet. Pour automatiser la libration de cet espace il suffit de dfinir explicitement le destructeur et d'y placer les instructions qui ralisent cette opration comme suit :
~TabEntiers(){delete[] T;} //Destructeur
Version 3.7
93
Karim Kalti
void Afficher() { for(int k=0;k<Nb;k++) { cout<<T[k]<<' '; } } }; void main() { TabEntiers TE(2); TE.Saisir(); TE.Afficher(); }
L'instanciation de l'objet TE fait appel au premier constructeur de la classe TabEntiers. Ce dernier va allouer de l'espace mmoire pour les deux attributs ainsi que le tableau dynamique. TE tant un objet de type auto, son destructeur (explicitement dfini dans ce cas) sera automatiquement appel la fin du main. Ce dernier va commencer par librer l'espace mmoire dynamique puis les attributs. Intrt d'une bonne exploitation du constructeur et du destructeur Le fait de placer les instructions d'allocation et de libration dynamiques de la mmoire pour l'attribut T respectivement dans le constructeur et le destructeur de la classe TabEntiers permet d'automatiser le droulement de ces oprations pour tout objet de cette classe. Une telle conception de la classe permet donc d'obtenir un code d'utilisation simple (voir le main) dans lequel toutes les oprations complexes (relatives la gestion dynamique de la mmoire dans ce cas) sont dfinies une seule fois au moment de la construction de la classe et se droulent d'une manire transparente (automatique et cache) pour chaque objet instanci partir de cette classe.
Constructeurs particuliers
Il existe en C++ trois types de constructeurs qui sont un peu particuliers. Leur particularit provient du fait qu'ils peuvent tre appels automatiquement d'une manire parfois cache par le compilateur. Ces constructeurs sont : le constructeur par dfaut, le constructeur de recopie, le constructeur de transtypage.
Version 3.7
94
Karim Kalti
Exemple :
#include <iostream.h> #include <stdlib.h> #include <iomanip.h> class Time { int Hour; int Minute; int Second; public : Time() // Constructeur qui initialise l'heure par dfaut Midi {Hour = 12; Minute = 0; Second = 0; } Time(int H, int M, int S) {Hour = H; Minute = M; Second = S;} void Affiche() { cout<<"L'heure est : "; cout<<setw(2)<<setfill('0')<<Hour<<':' <<setw(2)<<setfill('0')<<Minute<<':' <<setw(2)<<setfill('0')<<Second<<endl; } }; Time T; // T contient Hour=12, Minute=0, Second=0;
Remarque : La dfinition donne au dbut de ce paragraphe permet de considrer comme tant un constructeur par dfaut : un constructeur qui ne possde aucun paramtre, un constructeur qui possde des paramtres ayant tous des valeurs par dfaut. En effet, un tel constructeur peut tre appel sans qu'il ncessite aucun passage explicite de paramtres (cf. chapitre les fonctions en C++). Ce type de constructeurs doit tre utilis avec prcaution car il peut poser dans certains cas une ambigut au moment de l'instanciation des objets comme le montre l'exemple suivant. Exemple : Considrons la dfinition suivante de la classe Time :
class Time { int Hour; int Minute; int Second; public : // Constructeur qui initialise l'heure par dfaut Midi Time() { Hour = 12; Minute = 0; Second = 0; }
Version 3.7
95
Karim Kalti
// Constructeur ayant des paramtres qui ont tous des valeurs par // dfaut Time(int H=12,int M=0, int S=0) { Hour = H; Minute = M; Second = S; } };
Au moment de la dfinition de la classe aucun problme d'ambigut ne se pose car les deux constructeurs ont des signatures diffrentes. Toutefois l'ambigut peut tre rencontre au moment de l'instanciation. Considrons par exemple l'instanciation suivante : Time T; Suite cette instruction, le compilateur signale une erreur car il ne peut pas dterminer quel constructeur appeler pour la cration et l'initialisation de T. En effet, tout comme le constructeur sans paramtres, le constructeur paramtr ayant des valeurs par dfaut peut tre appel sans aucun argument. Constructeur par dfaut implicite Si aucun constructeur n'a t explicitement dfini pour une classe, alors le compilateur gnre automatiquement un, publique. Ce constructeur est un constructeur par dfaut (sans paramtres). Ce constructeur assure seulement la cration d'objets. Il ne fait aucune initialisation. De ce fait si un objet cr l'aide de ce constructeur est : o Local : alors son contenu sera alatoire. o statique ou global : alors tous ses attributs seront initialiss 0. Exemple :
class Time { int Hour; int Minute; int Second; public : Affiche(){ .... } }; Time T1; // objet global void main() { static Time T2; // objet static Time T3; // objet local de type auto T1.Affiche(); // 00:00:00 T2.Affiche(); // 00:00:00 T3.Affiche(); // -2145455875:-1245652381:-2896574215 }
Remarque importante : Le compilateur ne gnre aucun constructeur par dfaut pour les classes qui possdent au moins un constructeur explicitement dfini, peu importe que ce constructeur soit de type par dfaut ou non.
Version 3.7
96
Karim Kalti
Exemple :
class Time { int Hour; int Minute; int Second; public : Time (int H, int M, int S){ .... } Affiche(){ .... } }; void main() { .... Time T; // Erreur pas de constructeur par dfaut .... }
Exemple :
class Time { ... public : Time(const Time& T){...} // Constructeur de recopie ... };
Remarque 1 : Pourquoi un premier paramtre de type rfrence ? La question laquelle rpond cette remarque est la suivante : pourquoi passe-t-on au constructeur de recopie une rfrence l'objet copier et non une copie de cet objet ? En d'autres mots, pourquoi utilise-t-on un passage par rfrence l o le passage par valeur semble suffisant? 97
Version 3.7
Karim Kalti
Justification du passage par rfrence : Considrons la classe Time dfinie prcdemment. On ajoute cette classe un constructeur de recopie qui prend un paramtre Tm de type Time.
class Time { ... public : Time(){...} Time(Time Tm){...} // On suppose que c'est possible ... };
Soient f une fonction qui prend un paramtre Tf de type Time et X une instance de Time :
void f(Time Tf){...} Time X; L'appel de f avec le paramtre effectif X engendre la copie de X dans le paramtre formel Tf. Cette copie est ralise avec le constructeur de recopie de Time. f(X) // Tf=X Tf(X) Appel au constructeur de recopie de Time et par suite : f(X) f(Time(X)) // instanciation du paramtre formel l'aide du // constructeur de recopie.
Le passage de X au constructeur Time se fait par valeur. Ce constructeur va travailler donc sur une copie de X. Cette copie sera cre par un autre appel au constructeur de recopie. Ce dernier travaillant lui-mme sur une copie de X, il va faire lui aussi un appel lui-mme. Cet appel rcursif du constructeur de recopie va se poursuivre l'infini. Time(X) // Tm=X Tm(X) et par suite Time(X) devient Time(Time(X)) et f(X) devient f(Time(Time(Time ))) Pour rsoudre ce problme il suffit d'viter l'appel implicite du constructeur de recopie qui est engendr par le passage par valeur et de remplacer ce dernier par un passage par rfrence. Ainsi avec Time(Time& Tm), f(X)se rduit seulement f(Time(X)) car l'affectation du paramtre effectif au paramtre formel (Time& Tm=Time(X) ) reprsente une copie de rfrences et non une copie d'objets. Remarque 2 : Pourquoi la recommandation const L'utilisation d'une rfrence constante comme premier paramtre dans les constructeurs de recopie est une recommandation. L'intrt de cette recommandation rside dans la protection de l'objet pass comme argument contre toute modification qui peut survenir par erreur dans le constructeur surtout que son passage se fait par rfrence (on ne travaille pas sur une copie de l'objet mais directement sur ce dernier). Constructeur de recopie par dfaut Considrons l'exemple suivant : Exemple :
class Time { int Hour, Minute, Second;
Version 3.7
98
Karim Kalti
public : Time(){...} Time(int H,int M, int S){...} void Afficher(){...} }; void main() { Time T1(10,12,15); Time T2(T1); T2.Afficher(); }
L'instruction Time T2(T1); reprsente un appel du constructeur de recopie de la classe Time. Cette instruction passe avec succs la phase de compilation malgr l'absence d'une dfinition explicite d'un tel constructeur dans la classe. Par ailleurs, lexcution de ce programme donne 10 :12 :15. Ce rsultat montre bien que lobjet T2 a t initialis avec le contenu de T1. Explication : En cas d'absence d'un constructeur de recopie explicitement dfini, le compilateur en gnre automatiquement un par dfaut. Ce constructeur effectue une copie champ par champ du contenu des deux objets. Limites du constructeur de recopie par dfaut Exemple 1 :
class TabEntiers {public : int Nb; int* T; TabEntiers(int Ni){...} TabEntiers(int* Ti,int Ni){...} void Saisir(){...} void Afficher(){...} }; void main() { int Ti[4]={5,2,8,1,3}; TabEntiers TE1(Ti,4); TabEntiers TE2(TE1); TE2.Afficher(); }
Commentaires : La classe TabEntiers ne dispose pas d'un constructeur de recopie explicitement dfini. Par suite c'est le constructeur de recopie par dfaut qui est utilis dans l'instruction :
TabEntiers TE2(TE1);
De par sa dfinition, ce constructeur effectue une copie champ par champ du contenu de TE1 dans TE2. De ce fait, pour le membre dynamique T, c'est une copie d'adresses qui est effectue entre TE1.T et TE2.T. Ces deux membres font alors rfrence la mme zone en mmoire et par consquent toute modification de la ime case du tableau TE1.T est effectue galement sur la ime case du tableau TE2.T ( cause de la copie incomplte d'objets). 99
Version 3.7
Karim Kalti
TE1
int Nb 5 int* T FFE4D8A copie
TE2
5 int Nb FFE4D8A int* T
Pour remdier cela, il faut que le constructeur de recopie soit dfini de faon ce que chaque objet ait sa propre copie du tableau. L'exemple prcdent montre bien que le constructeur de recopie par dfaut nest pas adapt aux objets possdant des membres dynamiques. Pour ce genre d'objets il faut dfinir explicitement un constructeur qui tient compte de cette particularit. Exemple 2 : Soit la fonction f suivante : void f(TabEntiers TabE); Tout appel de la fonction f avec un paramtre effectif TE va engendrer la cration d'un objet local TabE par recopie par dfaut partir de TE. La fin de l'excution de f va engendrer la suppression de l'objet local TabE par appel son destructeur. Cet appel va conduire vers la libration du tableau d'entiers point dans ce cas la fois par TabE.T et par TE.T. Par consquent, toute rfrence un lment du tableau T partir de TE aprs l'appel de f va engendrer un erreur d'excution.
TE
int Nb 5 int* T FFE4D8A copie
TabE
5 int Nb int Nb FFE4D8A int* T
TE
5 int* T FFE4D8A TE.T ne pointe plus sur aucune zone mmoire alloue. Etat de la mmoire aprs la fin de l'appel de f. Le paramtre formel est dtruit. Il libre galement le tableau.
Solution : Il faut que la copie se fasse d'une manire totale : copie d'attributs mais galement copie des ventuels espaces mmoires dynamiques points par les attributs de type pointeur. Pour remdier ce problme de copie dans la classe TabEntiers, il faut dfinir explicitement le constructeur de recopie de la manire suivante :
TabEntiers::TabEntiers(const TabEntiers& Tc) { Nb=Tc.Nb; T=new int [Nb]; for(int k=0;k<Nb;k++) T[k]=Tc.T[k]; }
TE1
int Nb 5 int* T FFE4D8A Copie
TE2
5 int Nb FFF5E33 int* T
Version 3.7
100
Karim Kalti
Lieux dappel dun constructeur de recopie dobjet Un constructeur de recopie est appel : Explicitement par un utilisateur de la classe lors de la cration dun objet laide dun autre objet de la mme classe. Implicitement par le compilateur : o lors de la transmission de la valeur dun paramtre effectif (de type objet) un paramtre formel suite lappel dune fonction. o lors du retour dune valeur de type objet par une fonction. En effet la fonction cre un objet temporaire sans nom laide du constructeur de recopie et ce, partir de lobjet pass return et cest cet objet temporaire qui est affect la variable recevant la valeur de retour de la fonction. Exemple :
class NoData { public : NoData(){cout<<"constructeur par dfaut";} NoData(const NoData& ND){cout<<"constructeur de recopie";} }; void f1(NoData D){} void f2(NoData& D){} NoData f3() { NoData D; ... return D; }; void main() { NoData X; // Constructeur par dfaut f1(X); // Constructeur de recopie (en raison du passage par //valeur) f2(X); // Rien n'est affich X=f3(); // Constructeur par dfaut (pour l'objet local D) // Constructeur de recopie (pour l'objet renvoy) }
Question : Le prototype suivant pour f3 : NoData& f3() permet-il daboutir un rsultat correct mme en l'absence d'un constructeur de recopie explicite ? Le constructeur de recopie n'est pas appel mais on obtient une rfrence sur un objet qui n'existe plus en mmoire puisqu'il est local la fonction.
Version 3.7
101
Karim Kalti
Exemple :
class Time { int Hour; int Minute; int Second; public : Time(){Hour = 12; Minute = 0; Second = 0;} Time(int H,int M, int S){Hour = H; Minute = M;Second = S;} Time(const char* str) {cout<<"Appel du constructeur de transtypage"; Hour = 10*(*str-'0') + (*(str+1)-'0'); Minute = 10*(*(str+3)-'0') + (*(str+4)-'0'); Second = 10*(*(str+6)-'0') + (*(str+7)-'0'); } void Afficher(){...} };
Le constructeur Time(const char* str) peut tre considr comme tant un constructeur de transtypage puisqu'il rpond aux deux caractristiques de ce type de constructeur savoir un appel l'aide d'un seul paramtre en plus le paramtre possde un type diffrent de Time(const char*). Champs d'utilisation des constructeurs de transtypage Utilisation explicite : Un constructeur de transtypage peut tre explicitement appel par le programmeur chaque fois que ce dernier a besoin de travailler avec un objet d'un certain type et qu'il dispose d'une donne d'un autre type. Exemple
Time X = Time("18:30:12"); ou galement Time X("18:30:12");
Version 3.7
102
Karim Kalti
Utilisation implicite : Un constructeur de transtypage d'un type T1 (objet ou non) vers un type objet T2 diffrent de T1 est implicitement appel par le compilateur : Lors de la transmission d'un paramtre effectif de type T1 une fonction ayant un paramtre formel de type T2. Dans une opration d'affectation ayant un membre de droite (objet, variable ou expression) de type T1 et un membre de gauche de type objet T2. Remarque : L'appel implicite du constructeur de transtypage par le compilateur engendre la cration d'un objet temporaire sans non ayant le type de la classe du constructeur. C'est cet objet qui est alors transmis s'il s'agit d'un appel de fonction ou affect s'il s'agit d'une opration d'affectation. Exemple 1 : Soit f une fonction ayant le prototype : void f(Time Tf); Considrons la portion de code suivante :
char T[9] ="18:30:00"; f(T);
A premire vue, cet appel peut sembler incorrect car f prend un argument de type Time et T est de type chane de caractres. Cependant et grce au constructeur de transtypage cet appel est tout fait correct. En effet, au moment de la transmission des paramtres le compilateur effectue un appel implicite au constructeur de transtypage qui va crer partir de T un objet temporaire sans non de type Time. Par la suite c'est cet objet qui sera transmis rellement f. Par ailleurs, et puisque le passage de l'argument de f se fait par valeur, alors le compilateur effectue un autre appel implicite mais cette fois au constructeur de recopie. Le but de cet appel est de faire la copie de l'objet temporaire prcdemment cr dans le paramtre formel de f. L'instanciation du paramtre formel Tf partir du paramtre effectif se traduit rellement comme suit :
Tf(Time(Time(T))); // instanciation du paramtre formel de f.
En considrant la dfinition de la classe Time donne au dbut de ce paragraphe l'affectation entre T et X est tout fait possible. En effet, au moment de l'affectation, le compilateur appelle implicitement le constructeur de transtypage pour crer partir de T un objet temporaire de type Time. C'est cet objet qui est ensuite copi dans X.
Version 3.7
103
Karim Kalti
Remarque : Une classe peut comporter plusieurs constructeurs de transtypage pourvu que ces derniers respectent les rgles de surcharge de mthodes.
Exemple 2 :
// Appel du constructeur de la classe TabEntiers TabEntiers(int N, int* Tab) : Nb(N),T(new int[N]) { for(int i = 0;i<Nb;i++) T[i] = Tab[i]; }
Remarques : Il n'est pas obligatoire de mentionner les attributs dans une liste d'initialisation dans le mme ordre que celui de leur dclaration dans la classe. L'ordre de l'initialisation effective des attributs suit toujours l'ordre de dclaration de ces derniers dans la dfinition de la classe mme si cet ordre n'est pas respect dans la liste d'initialisation. Une liste d'initialisation peut se contenter d'initialiser seulement une partie des attributs d'une classe. Initialisation des attributs constants et rfrences Pour les attributs constants et rfrences, les listes d'initialisation ne constituent pas une simple alternative d'criture de code mais reprsentent plutt le seul moyen permettant de leurs affecter leurs valeurs initiales. Considrons la classe ConstRef qui comporte les trois attributs suivants : x (entier), r (rfrence un entier) et y (constante entire).
Version 3.7
104
Karim Kalti
// ERROR // ERROR
Le fait que r et y soient respectivement une rfrence et une constante rend leurs initialisations obligatoires. Toutefois leurs initialisations telles que ralises dans la classe ConstRef sont interdites. En effet, la dfinition d'une classe constitue une dfinition de type sans aucune instanciation en mmoire. Par consquent au moment de la dfinition de ConstRef aucun attribut n'a une existence physique en mmoire. Par suite, initialiser r avec la rfrence d'une variable x qui n'existe pas ou galement affecter 5 une constante y qui n'existe pas en mmoire reprsentent des oprations interdites. L'utilisation d'un constructeur dfini comme suit est galement interdite :
ConstRef::ConstRef( ){ x =3; // OK r = x; // ERROR y = 5; // ERROR }
En effet, l'instruction r=x; reprsente une affectation de la valeur 3 la variable rfrence par r. Or r ne rfrence aucune variable car elle n'a pas t initialise. Par ailleurs, l'instruction y=5; reprsente une affectation de la valeur 5 la constante y et non une initialisation. Or les oprations d'affectations ne peuvent pas tre appliques aux constantes. Le seul moyen en C++ permettant d'initialiser de tels membres consiste dans l'utilisation des listes d'initialisation. Exemple :
class ConstRef { int x; int& r ; const int y ; ConstRef( ) : r(x),y(5) {x=3;} };
Initialisation d'objets membres Les listes d'initialisation sont galement utilises pour initialiser les membres qui sont de type classe (membres objet) essentiellement lorsque ces derniers possdent des constructeurs paramtrs. La liste d'initialisation constitue le seul moyen permettant de crer et d'initialiser un objet membre si la classe de ce dernier ne dispose pas de constructeur par dfaut (implicite ou explicite).
Version 3.7
105
Karim Kalti
Exemple :
class X { int a; public : X(int i) { a=i; cout<<"Constructeur de X\n"; } }; class Y { int b; X x; public : Y():b(5),x(55) {cout<<"Constructeur de Y\n";} };
L'initialisation du membre x par la liste comme suit : x(55) est possible parce qu'un constructeur ayant le prototype X(int i) est dfini pour la classe X. Remarque 1: En tenant compte de la dfinition de la classe X, la dfinition suivante du constructeur de Y n'est pas correcte :
Y() { b=5; x=X(55); cout<<"Constructeur de Y"; }
En effet, l'instruction x=X(55); ne constitue pas syntaxiquement une initialisation au vrai sens du terme. Elle est plutt considre comme tant une premire affectation x du contenu de l'objet temporaire X(55). Cela suppose que x a t cr auparavant l'aide d'un constructeur par dfaut chose qui n'est pas possible faute de ce type de constructeur dans la dfinition de la classe X.
Version 3.7
106
Karim Kalti
o Si la classe possde un constructeur qui demande des paramtres, alors ce constructeur doit tre explicitement appel dans la liste d'initialisation. o Si la classe dispose d'un constructeur qui prend un seul paramtre, alors ce paramtre peut tre directement spcifi dans la liste d'initialisation sans avoir besoin de faire un appel explicite du constructeur. o Si la classe possde plusieurs constructeurs, il n'est pas obligatoire d'appeler le mme constructeur pour initialiser tous les objets : diffrents constructeurs peuvent en effet tre appels pour les diffrents lments du tableau. o Si un constructeur par dfaut est dfini pour la classe, alors la liste d'initialisation peut tre omise. Dans ce cas le constructeur par dfaut sera automatiquement appel pour tous les lments du tableau. Exemple :
class Time {int Hour; int Minute; int Second; public : Time(){Hour = 12; Minute = 0; Second = 0;} Time(int H,int M, int S){Hour = H; Minute = M; Second = S;} Time(const char* str) { Hour = 10*(*str-'0') + (*(str+1)-'0'); Minute = 10*(*(str+3)-'0') + (*(str+4)-'0'); Second = 10*(*(str+6)-'0') + (*(str+7)-'0'); } void Afficher(){...} }; void AfficherTab(Time* Tab, int N) { for(int i=0;i<N;i++) { (*(Tab+i)).Afficher(); cout<<'\n'; } } void main() { Time Tab1[3]; AfficherTab(Tab1,3); Time Tab2[3]={Time(), Time(), Time()}; AfficherTab(Tab2,3); Time Tab3[4]={"10:15:30", Time("15:30:45"), Time(14,20,30), Time()}; AfficherTab(Tab3,4); Time Tab4[4]={"10:15:30", Time(14,20,30)}; AfficherTab(Tab4,4); }
Remarque : L'appel explicite du constructeur peut tre omis seulement pour les derniers lments du tableau. Dans ce cas c'est le constructeur par dfaut qui est implicitement appel (Exemple Tab4). Un lment du tableau, pour lequel l'appel du constructeur se fait d'une manire implicite ne peut pas avoir sa droite un lment pour lequel l'appel du constructeur se fait d'une manire explicite.
Version 3.7
107
Karim Kalti
Exemple :
Time Tab[4]={"10:15:30", , Time(14,20,30), Time()}; //ERREUR
Cas des tableaux dynamiques Il nest pas possible d'utiliser une liste d'initialisation pour crer dynamiquement un tableau dobjets. Exemple :
Time* Tab=new Time[2] (Time(10,04,2000),Time(05,04,1999));// ERREUR
Pour crer dune manire dynamique un tableau dobjets, la classe des objets doit avoir obligatoirement un constructeur par dfaut (implicite ou explicite). Seul ce constructeur peut tre utilis dans ce cas. Exemple :
Time* Tab=new Time[2]//OK:Appel implicite du constructeur par dfaut
Version 3.7
108
Karim Kalti
109
Karim Kalti
Exemple :
namespace MesClasses { X(){... ...}; } int main() { MesClasses::X x1(); //OK X x2(); //Erreur return 0; }
Dans cet exemple x1 reprsente un objet de la classe X de MesClasses et x2 est un objet de la classe X de MesAutresClasses.
La dclaration using
Afin damliorer la lisibilit du code, il est possible dindiquer ds le dpart en une seule fois la provenance des classes qui seront utilises. Cette indication se fait pour chaque classe laide du mot rserv using de la manire suivante : Syntaxe : using EspaceDeNoms::NomClasse; La classe NomClasse peut alors tre dsigne directement par son nom dans lespace de porte dans lequel a t faite la dclaration using. Exemple :
namespace MesClasses { X(){... ...}; Y(){... ...}; }
Version 3.7
110
Karim Kalti
using MesClasses::X; int main() { X x(); //OK la classe X est connue dans tout le fichier Y y1(); //Erreur MesClasses::Y y2(); //OK return 0; }
La directive using
En labsence de conflits, il est possible de rendre tous les lments dun espace de noms accessibles et viter de les spcifier ainsi un par un. Cette spcification globale se fait laide de la directive using de la manire suivante : Syntaxe : using namespace EspaceDeNoms; Exemple :
namespace MesClasses { X(){... ...}; Y(){... ...}; } using namespace MesClasses; int main() { X x(); // OK Y y(); // OK return 0; }
ou galement
#include <iostream> using namespace std; cout<<Ceci est un message;
Version 3.7
111
Karim Kalti
Membres statiques et membres constants d'une classe Programmation oriente objet (C++) _________________________________________________________________________________________________________________
Version 3.7
112
Karim Kalti
Membres statiques et membres constants d'une classe Programmation oriente objet (C++) _________________________________________________________________________________________________________________
public : Cercle(int xi, int yi){x=xi; y=yi;} Cercle(){x=0; y=0;} void Deplacer(int dx, int dy){x+=dx; y+=dy;} void SetRayon(int R){Rayon = R;} void Dessiner(){...} };
Avec cette spcification chaque cercle possde sa propre copie de l'attribut Rayon. Or puisque tous les cercles possdent la mme taille, il devient plus intressent de faire partager l'attribut Rayon par toutes les instances de cette classe en le dclarant comme statique.
class Cercle { int x; int y; static int Rayon; public : Cercle(int xi, int yi){x=xi; y=yi;} Cercle(){x=0; y=0;} void Deplacer(int dx, int dy){x+=dx; y+=dy;} void SetRayon(int R){Rayon = R;} void Dessiner(){...} }; // Dfinition de l'attribut statique int Cercle::Rayon = 2;
Un tel partage possde deux avantages : Il permet un gain en espace mmoire. En effet, une seule zone mmoire sera utilise par toutes les instances. Il permet de diminuer la quantit de code ncessaire la modification d'une caractristique commune toutes les instances. Par exemple, pour modifier la taille de la neige, il suffit de modifier la valeur de l'attribut Rayon une seule fois. Cette modification affectera toutes les instances. Caractristiques d'un attribut statique : Un attribut statique est partag par toutes les instances d'une classe. Sa dure de vie ne dpend pas de celles des objets. Elle s'tend depuis l'endroit de dfinition de l'attribut jusqu' la fin du programme. De ce point de vue, un tel attribut se comporte comme une variable globale. Toutefois, et la diffrence de cette dernire, un attribut statique n'est visible qu' l'intrieur de sa classe (sa visibilit est lie la classe) alors q'une variable globale est visible dans tout le programme. Un attribut statique existe en mmoire mme si aucun objet de la classe na encore t cr. Un attribut statique est souvent appel attribut de classe puisque son existence dpend de l'existence de la classe et non de l'existence des objets. Un attribut non statique est appel attribut d'instance puisque son existence est lie l'instanciation de la classe. Un attribut statique peut tre manipul par les mthodes de sa classe comme n'importe quel autre attribut non statique. Un attribut statique peut tre qualifi de priv, de publique ou de protg.
Version 3.7
113
Karim Kalti
Membres statiques et membres constants d'une classe Programmation oriente objet (C++) _________________________________________________________________________________________________________________
Un attribut statique publique peut tre appel de l'extrieur de la classe travers son nom qualifi. Cette qualification peut tre faite l'aide : o Du nom de la classe : NomClasse::NomAttributStatic; o Du nom d'un objet : NomObjet.NomAttributStatic; o Du nom d'un pointeur sur un objet : NomPtrObjet->NomAttributStatic; Exercice d'application : Supposons que l'on souhaite calculer l'espace mmoire occup par toutes les instances d'une classe dans un programme en cours d'excution. Ce calcul passe par la dtermination du nombre d'objets instancis. Une solution possible ce problme consiste utiliser un attribut statique qui s'incrmente chaque instanciation d'un nouvel objet et qui se dcrmente chaque destruction d'un objet. Proposer une implmentation de cette solution avec la classe Point qui reprsente un point dans un repre deux dimensions. Solution
class Point { int x; int y; public : static int Memory; Point():x(0),y(0) { Memory+=sizeof(Point); } Point(int a, int b):x(a),y(b) { Memory+=sizeof(Point); } ~Point() { Memory-=sizeof(Point); } }; int Point::Memory =0; void main() { cout<<" Mmoire occupe :"<<Point::Memory<<endl; Point P1(10,10); cout<<" Mmoire occupe :"<<Point::Memory<<endl; Point *P2= new Point(5,5); cout<<" Mmoire occupe :"<<Point::Memory<<endl; delete P2; cout<<" Mmoire occupe :"<<Point::Memory<<endl; }
Mthodes statiques
Tout comme les attributs, il est possible de dclarer les mthodes comme tant statiques. Une mthode statique est gnralement une mthode qui ralise une tche qui est indpendante des instances et qui est plutt lie au modle dfini par la classe.
Version 3.7
114
Karim Kalti
Membres statiques et membres constants d'une classe Programmation oriente objet (C++) _________________________________________________________________________________________________________________
Une mthode statique est appele une mthode de classe alors qu'une mthode non statique est appele une mthode d'instance. Dclaration d'une mthode statique Une mthode statique est dclare dans la dfinition d'une classe l'aide du mot-cl static comme suit : class NomClasse { ... ... ... static Type NomMethodeStatique(<Liste des paramtres>); ... ... ... }; Dfinition d'une mthode statique Comme les mthodes classiques, une mthode statique peut tre dfinie l'intrieur ou l'extrieur de la classe. Dfinition l'intrieur de la classe class NomClasse { ... ... ... static Type NomMethodeStatique(<Liste des paramtres>) { ... } ... ... ... }; Dfinition l'extrieur de la classe La dfinition d'une mthode statique l'extrieur de la classe n'utilise pas le mot-cl static. Elle doit tre prcde par la dclaration de la mthode l'intrieur de la classe : Type NomClasse::NomMethodeStatique(<Liste des paramtres>) { ... } Appel d'une mthode statique Appel de l'intrieur de la classe Une mthode statique peut tre appele de l'intrieur de sa classe comme une mthode classique et ce directement par son nom (sans considration des droits d'accs et sans qualification). Appel de l'extrieur de la classe Une mthode statique publique peut tre appele de l'extrieur de la classe partir : Du nom de la classe : NomClasse::NomMethodeStatique(); D'un objet de la classe : NomObjet.NomMethodeStatique(); D'un pointeur sur un objet : NomPtrObjet->NomMethodeStatique(); Remarques : Une mthode statique : 115
Version 3.7
Karim Kalti
Membres statiques et membres constants d'une classe Programmation oriente objet (C++) _________________________________________________________________________________________________________________
Peut appeler les mthodes statiques de sa classe. Ne peut pas appeler les mthodes non statiques de sa classe. Peut appeler les mthodes statiques publiques des autres classes. Peut appeler les mthodes d'instances publiques des objets qui sont dclars en local l'intrieur de cette mthode. Une mthode non statique (mthode d'instance) peut appeler : o Les mthodes statiques et non statiques de sa classe. o Les mthodes statiques publiques des autres classes. o Les mthodes d'instances publiques des objets qui lui sont locaux. o o o o Accs aux attributs par une mthode statique Une mthode statique ne peut accder qu'aux attributs statiques de sa classe. Elle ne peut pas accder aux attributs non statiques. En effet, une mthode statique peut tre appele partir du nom de la classe en dehors de toute cration d'objets. Dans ce genre d'appel, aucun attribut non statique ne peut exister en mmoire car de tels attributs sont lis aux instances. Une mthode statique ne possde pas de paramtre cach this pour rfrencer les attributs vu que les seuls attributs qu'elle peut utiliser sont statiques et sont par consquent uniques et non lis aux instances. Une mthode non statique peut accder la fois aux champs statiques et non statiques de sa classe. Exemple :
class X { int a; static int b; static int c; int f(){return a+b;} static int g(){return a-b;} static int h()({return a*b;} };
Etant donns la classe X dfinie ci-dessus et obj un objet convenablement instanci de X. Indiquer si les appels suivants sont possibles ou non.
int res; res=obj.f(); res=obj.g(); res=obj.h(); X.f(); X.g(); X.h();
Remarque : De par sa dfinition un constructeur doit tre capable d'accder tous les attributs de sa classe (pour faire la cration et l'initialisation de l'objet). Or cette capacit n'est pas la porte des mthodes statiques. C'est pourquoi un constructeur ne peut pas tre dclar comme tant statique. De mme un destructeur ne peut pas tre dclar comme tant statique.
Version 3.7
116
Karim Kalti
Membres statiques et membres constants d'une classe Programmation oriente objet (C++) _________________________________________________________________________________________________________________
Exemple :
class Cercle { int x; int y; static int Rayon; static int Nb; public : Cercle(int xi, int yi){x=xi; y=yi; Nb++;} Cercle(){x=0; y=0; Nb++;} ~Cercle(){Nb--;} void Deplacer(int dx, int dy){x+=dx; y+=dy;} static void SetRayon(int R){Rayon = R;} static int GetNb(){ return Nb;} void Dessiner(){...} }; int Cercle::Rayon =5; int Cercle::Nb=0; void main() { ... Cercle c1(2,7);Cercle c2(2,17);Cercle c3(2,27); int Nombre = Cercle::GetNb(); ... }
Remarque : Une mthode statique peut tre appele la manire des fonctions du langage C (sans ncessiter une cration d'instance). C'est pourquoi, dans les nouveaux langages orients objet on tend regrouper les fonctions d'une mme bibliothque dans une mme classe sous forme de mthodes statiques pour faciliter leur appel. A titre dexemple, la classe Math en C# reprsente la bibliothque mathmatique et ne possde que des mthodes statiques qui peuvent tre appels facilement sans ncessiter la cration d'une instance au pralable. Dclaration : public static double Cos(double d ) Appel direct : double i = Math.Cos(0) ; Exercice d'application On considre une classe possdant un attribut qui stocke une valeur entire. Complter la dfinition de cette classe pour que l'on puisse calculer tout moment la moyenne des valeurs stockes dans toutes les instances de la classe. Solution
class Entier { int Val; static int Nb; static float Somme; public : Entier() : Val(0) {Nb++;} Entier(int V):Val(V) { Nb++; Somme+=Val; }
Version 3.7
117
Karim Kalti
Membres statiques et membres constants d'une classe Programmation oriente objet (C++) _________________________________________________________________________________________________________________
~Entier() { Nb--; Somme-=Val; } static float Moyenne() {return Somme/Nb;} }; int Entier::Nb=0; float Entier::Somme=0.f; void main() { Entier E1(10); Entier E2(30); cout<<"La moyenne est :"<<Entier::Moyenne()<<endl; Entier *E3=new Entier(50); cout<<"La moyenne est :"<<Entier::Moyenne()<<endl; delete E3; cout<<"La moyenne est :"<<Entier::Moyenne()<<endl; }
Version 3.7
118
Karim Kalti
Membres statiques et membres constants d'une classe Programmation oriente objet (C++) _________________________________________________________________________________________________________________
Remarque : L'appel d'une mthode qui manipule les attributs de la classe est interdit partir d'un objet constant mme si cette mthode ne modifie pas le contenu de ces attributs comme c'est le cas pour la mthode Afficher. Ceci est d au fait que la compilation des appels des fonctions se base essentiellement sur la dclaration de ces dernires (analyse de la signature). Le corps de la fonction appele peut tre dj dfini et pr-compil dans un module externe au programme (c'est le cas des bibliothques de fonctions lies d'une manire statique ".lib" ou dynamique ".dll"). Le compilateur ne peut pas par consquent dterminer si des fonctions de ce genre effectuent en interne un accs en criture aux attributs puisqu'il ne va pas les recompiler mais tout simplement vrifier la justesse de leur appel.
Exemple :
class Time { public :
Version 3.7
119
Karim Kalti
Membres statiques et membres constants d'une classe Programmation oriente objet (C++) _________________________________________________________________________________________________________________
int Hour; int Minute; int Second; Time(int H,int M, int S) : Hour(H),Minute(M),Second(S) { } void Affiche() const { cout<<"L'heure est : "; cout<<setw(2)<<setfill('0')<<Hour<<':' <<setw(2)<<setfill('0')<<Second<<endl; } }; void main() { const Time T(12,10,30); T.Hour = 11; // ERREUR car T est un objet constant. T.Affiche(); // OK car Affiche est une mthode constante }
Remarque : Le spcificateur const dans ce cas constitue une garantie pour le compilateur que la mthode en question n'effectue aucune modification des attributs de la classe, mme si la classe est dj prcompile (sous forme de dll par exemple). Un utilisateur de cette classe peut dans ce cas appeler une telle mthode pour des instances constantes de cette classe. Toute instruction d'accs en criture un attribut dans le corps d'une mthode constante est signale comme une erreur au moment de la compilation. Tout passage par rfrence ou par adresse d'un attribut d'une instance constante une fonction appele en interne dans une mthode est signal comme erreur (risque de modification de l'attribut par la fonction).
Version 3.7
120
Karim Kalti
La notion d'amiti
L'amiti peut exister entre une fonction et une classe ou entre deux classes. Dans ce cadre : o Une fonction amie d'une classe est autorise accder aux membres privs de cette dernire comme n'importe quel autre membre de cette classe. o Les mthodes d'une classe A amie d'une deuxime classe B sont autorises accder tous les membres privs de cette classe B. La notion d'amiti est dclare l'aide du mot-cl friend.
Version 3.7
121
Karim Kalti
L'emplacement de la dclaration de l'amiti de F au sein de la classe A peut se faire n'importe o dans cette dernire. D'ailleurs cet emplacement peut figurer indiffremment dans la partie publique ou prive de la classe. Exemple : L'exemple suivant montre la dfinition d'une fonction indpendante appele Somme qui calcule la somme de deux complexes. Pour pouvoir accder aux membres privs cette fonction a t dclare comme amie de la classe Complexe.
class Complexe { double Reel; double Imaginaire; public : Complexe(double r, double i) : Reel(r), Imaginaire(i){} Complexe(){Reel = 0; Imaginaire = 0;} // Dclaration de la fonction Somme comme amie friend Complexe Somme(Complexe C1, Complexe C2); void Afficher() { cout<<"Partie relle : "<<Reel; cout<<"Partie imaginaire : "<<Imaginaire; } }; // Dfinition de la fonction Somme Complexe Somme(Complexe C1, Complexe C2) { Complexe res(C1.Reel+C2.Reel, C1.Imaginaire+C2.Imaginaire); return res; } int main() { Complexe C1(3,6), C2(4,2); Somme(C1,C2).Afficher(); return 0; }
Fonction membre amie d'une classe La fonction dfinir comme tant amie d'une classe peut ne pas tre indpendante mais plutt une fonction membre d'une autre classe. Dans ce cas la dclaration de l'amiti se fait de la manire suivante : class B { TypeRetour F(Liste des paramtres); }; class A { friend TypeRetour B::F(Liste des paramtres); };
Version 3.7
122
Karim Kalti
Si la classe A figure dans le prototype de F (comme paramtre ou comme type de retour) alors pour que la compilation russisse il faut que cette classe soit dclare avant la classe B mais pas ncessairement dfinie et ce pour qu'elle soit connue au niveau de la compilation de la dfinition de la classe B. Voici un schma complet de la dclaration d'amiti pour ce cas de figure : // Dclaration de A pour les besoins de compilation class A; // Dfinition de B class B { TypeRetour F(A); }; // Dfinition de A class A { friend TypeRetour B::F(Liste des paramtres); }; Fonction amie de plusieurs classes Une fonction peut avoir besoin d'accder aux membres privs de plusieurs classes et ncessite par consquent d'tre dclare amie de toutes ces classes en mme temps. La dclaration d'amiti doit se faire dans ce cas dans toutes ces classes. Exemple : On considre une fonction indpendante F qui doit accder aux membres privs de deux classes A et B. Le prototype de F est le suivant : void F(A, B); F doit tre dclare amie la fois A et B : Syntaxe : class B; // Dfinition de A class A { friend void F(A, B); }; // Dfinition de B class B { friend void F(A, B); };
Version 3.7
123
Karim Kalti
void F(A a, B b) { } La dclaration suivante class B; est ncessaire pour les besoins de compilation vu que la classe A utilise B (dans F) et que B est dfinie aprs A.
Version 3.7
124
Karim Kalti
Hritage et polymorphisme
Dfinition de l'hritage
L'hritage est le fait de dfinir une nouvelle classe en se basant sur la dfinition d'une classe dj existante. La nouvelle classe hrite alors les attributs et les mthodes de la classe existante et ce en plus de ses propres membres (ses attributs et/ou mthodes spcifiques). L'hritage est un concept propre la POO. Terminologie utilise par le concept d'hritage La nouvelle classe dfinie par hritage est appele classe drive ou classe fille. La classe partir de laquelle se fait l'hritage est appele classe de base, super classe ou galement classe mre. L'hritage est galement appel drivation de classes. La relation d'hritage est schmatise graphiquement par une flche qui part de la classe drive vers la classe de base. Cette relation peut s'exprimer par la phrase "est un". Exemple : ANIMAL
HERBIVORE
CARNIVORE
VACHE
CHEVAL
LION
Un herbivore est un animal, un cheval est un herbivore, etc. Remarque : La classe drive constitue une spcialisation de la classe de base. La classe de base constitue une gnralisation de la classe drive. Intrt de l'hritage L'intrt de l'hritage rside dans : La rutilisation des modules dj existants. La factorisation des proprits communes un certain nombre de classes programmes moins complexes et plus facilement maintenables.
Rendre les
Version 3.7
125
Karim Kalti
Exemple : Prenons par exemple le cas de la gestion d'une bibliothque de documents. Ces documents peuvent tre de deux catgories : des livres et des priodiques (magazines, journaux, revues scientifiques, etc.). Les spcifications des classes reprsentant ces deux catgories de documents sont donnes comme suit :
LIVRE
Reference Titre Auteur Editeur GetReference() GetTitre() GetAuteur() GetEditeur()
PERIODIQUE
Reference Titre DirecteurRedaction Numero GetReference() GetTitre() GetDirecteurRedaction() GetNumro()
D'aprs ces spcifications il est facile de remarquer que les deux classes possdent un certain nombre de caractristiques en commun qui se rptent. Afin d'viter cette rptition, il est possible de factoriser ces caractristiques communes et ce l'aide d'une classe gnraliste qui portera le nom Document. Les classes Livre et Periodique constitueront alors des spcialisations de cette classe. La nouvelle spcification des classes devient alors comme suit :
DOCUMENT
Reference Titre GetReference() GetTitre()
LIVRE
Auteur Editeur GetAuteur() GetEditeur()
PERIODIQUE
DirecteurRedaction Numero GetDirecteurRedaction() GetNumro()
Version 3.7
126
Karim Kalti
Remarque : Il ne faut pas confondre l'hritage avec l'agrgation des classes (appele galement composition) dj traite dans le chapitre prcdent. Il est rappeler que dans l'agrgation, la nouvelle classe est compose en partie par une classe dj existante. Cette dernire s'intgre gnralement comme un attribut de la nouvelle classe.
Version 3.7
127
Karim Kalti
Le tableau suivant dresse tous les cas de figures possibles concernant cette accessibilit :
Mode de drivation public Statut du membre dans la classe de base public protected private public protected private public protected private Statut du membre dans la classe drive public protected private protected protected private private private private Accessibilit du membre dans la classe drive Accessible Accessible Inaccessible Accessible Accessible Inaccessible Accessible Accessible Inaccessible Accessibilit du membre par les utilisateurs de la classe drive Accessible Inaccessible Inaccessible Inaccessible Inaccessible Inaccessible Inaccessible Inaccessible Inaccessible
protected
private
Exemple : L'exemple suivant donne une illustration de quelques situations de contrle d'accs pouvant tre engendres par une drivation prive.
class A { int a1; protected : int a2; public : int a3; }; class B : private A { private : int b1; int b2; protected : void f() { b1 = a1*2; //Erreur car a1 est inaccessible dans B } void g() { b2 = a2*a3; //OK car a2 et a3 sont accessibles dans B } }; void main() { A Obj1; B Obj2; Obj1.a2 = 6; // Erreur car a2 est protg dans A Obj2.a3 = 8; // Erreur car a3 est priv dans B }
Version 3.7
128
Karim Kalti
Remarques : Le mode de drivation par dfaut (si rien n'est mentionn) est le mode "private". Toutefois le mode le plus utilis est le mode "public". Ce mode permet aux membres hrits de prserver le mme statut que dans la classe de base. A travers les mots-cls : public, protected et private, le C++ fournit aux concepteurs des classes des outils de contrle de l'accs aux membres. Ce contrle est effectu par rapport aux utilisateurs des classes. Ces utilisateurs peuvent en effet tre : o des objets instancis directement partir de ces classes, o des classes drives partir de ces classes, o des instances de classes drives partir de ces classes. Manipulation des membres de la classe de base dans la classe drive Les membres d'une classe de base qui sont accessibles dans la classe drive sont manipuls dans cette dernire directement travers leurs noms la manire des membres de la classe drive.
Version 3.7
129
Karim Kalti
Le contenu du champ att_B n'est pas affich. Il est clair que cette mthode telle qu'elle est dfinie dans A ne convient pas aux objets de la classe B (afffichage partielle du contenu de Obj). Pour cette raison et pour que l'affichage puisse toucher tous les attributs de B il faut redfinir cette mthode dans la classe drive pour qu'elle tienne compte de l'attibut supplmentaire. Nouvelle dfinition de B :
class B : public A {public : int att_B; B(int p1, int p2) : A(p1) { att_B = p2;} void Afficher() { cout<<"La valeur de att_A est : "<<att_A<<endl; cout<<"La valeur de att_B est : "<<att_B<<endl; } };
Dans ce cas c'est la mthode Afficher de B qui est appele et non celle de A. Remarque: (masquage des mthodes de base) Lorsqu'une mthode d'une classe de base est redfinie dans une classe drive alors la redfinition masque la mthode de la classe de base pour les objets de la classe drive. Pour l'exemple prcdent, la mthode Afficher de A est masque par la mthode Afficher de B pour les instances de cette dernire. Appel des membres d'une classe de base redfinis dans la classe drive Les membres d'une classe de base qui sont accessibles dans la classe drive sont manipuls dans cette dernire directement travers leurs noms la manire des membres de la classe drive. Toutefois dans le cas o un membre (attribut ou mthode) est redfini, ce dernier sera cach dans la classe drive par la redfinition. Pour pouvoir l'appeler quand mme dans la classe drive, le nom de ce membre doit tre associ celui de la classe de base et ce l'aide de l'oprateur de rsolution de porte selon la syntaxe suivante : NomClasseBase::NomMembre; Si le membre en question est publique dans la classe drive alors il peut tre manipul partir des objets de cette dernire selon la syntaxe suivante : NomObjet.NomClasseBase::NomMembre;
Version 3.7
130
Karim Kalti
Remarque : Dans l'exemple prcdent, il est plus intressant d'appeler la mthode Afficher de la classe A dans la dfinition de Afficher de B pour assurer l'affichage de att_A (afin d'viter de rcrire un code dj crit). Toutefois la dfinition suivante de la mthode Afficher de B :
void Afficher() { Afficher(); cout<<"La valeur de att_B est : "<<att_B<<endl; }
sera interprte comme un appel rcursif de Afficher de B. Pour obtenir l'effet souhait la redfinition de Afficher doit se faire comme suit :
class B : public A {public : int att_B; B(int p1, int p2) : A(p1) { att_B = p2;} void Afficher() { A::Afficher(); cout<<"La valeur de att_B est : "<<att_B<<endl; } };
Gnralisation de la redfinition La redfinition peut s'appliquer aux membres d'une classe d'une manire gnrale. Elle peut donc concerner aussi bien les mthodes que les attributs. Toutefois la redfinition des attributs reste trs rare d'utilisation mais tout fait possible. Exemple :
class X { public : int att; }; class Y : public X { public : int att; }; Y obj; obj.att=5; // att dsigne l'attribut de Y obj.X::att = 8; // att dsigne ici l'attribut de X
Diffrence entre redfinition et surdfinition Il ne faut pas confondre surdfinition et redfinition. Dans la redfinition, la mthode de base et celle redfinie doivent avoir exactement la mme signature. Ce n'est pas le cas dans la surdfinition.
Version 3.7
131
Karim Kalti
Version 3.7
132
Karim Kalti
Cette instruction est correcte car tout objet de type B est galement de type A. Dans ce cas le contenu des attributs de b provenant de la classe A sera copi dans les attributs correspondants de l'objet a (att_A de b dans att_A de a).
b = a; // Erreur
Cette instruction est incorrecte car l'objet ne comporte pas tous les attributs de b (exemple : att_B ne peut pas avoir de valeur dans ce cas). // Compatibilit entre pointeurs
pa = &b; // OK
Un pointeur sur une classe de base peut pointer sans problme sur un objet d'une classe drive.
pa->att_A; // OK
Cette instruction va engendrer une erreur malgr le fait que l'attribut att_B existe en mmoire. En effet, syntaxiquement pa ne reconnat pas le nom du champ point parce qu'il est de type A* et non B*.
pa->Afficher();
Cette instruction va engendrer l'appel de la fonction Afficher dfinie dans A. En effet, dans ce cas, le compilateur se base sur le type du pointeur et non sur le type de l'objet point pour dcider de la version de la mthode appeler.
Remarque : Conversion explicite d'un pointeur (Base* vers Drive*) La conversion implicite d'un pointeur d'une classe de base vers un pointeur d'une classe drive n'est pas implicitement accepte par le compilateur.
pb = &a; // Erreur
pb est un pointeur sur un objet d'une classe drive. Il ne peut pas recevoir l'adresse d'un objet d'une classe de base. Toutefois le compilateur peut accepter cette conversion si elle est faite d'une manire explicite travers un casting. Cette conversion explicite mme sil elle est possible reste dangereuse si elle est mal utilise car elle peut mener vers : o des tentatives d'accs des attributs qui ne figurent pas dans la structure de l'objet point. o A la violation des protections pour la classe de base. Exemple 1 : tentative d'accs un attribut non existant physiquement
pb = (A*)&a; // OK car conversion explicite pb->att_B; // Erreur
Cette instruction va engendrer une erreur car l'objet point ne comporte pas un champ att_B.
Version 3.7
133
Karim Kalti
Exemple 2 : violation des protections de la classe de base On considre les deux classes suivantes :
class A {public : int att_A; void fa(); }; class C : private A {public : int att_C; };
Ces deux instructions sont syntaxiquement correctes et possibles. La deuxime instruction va engendrer donc un accs l'attribut att_A d'un objet d'une classe C. Ceci constitue une violation des droits d'accs car att_A est normalement priv pour les objets de la classe C et ne doit pas tre par consquent accessible.
A* pa
EF0A78
Version 3.7
134
Karim Kalti
Ordre d'excution des constructeurs D'une manire gnrale l'instanciation d'un objet d'une classe drive engendre une excution hirarchique de constructeurs qui se fait dans l'ordre suivant : o Constructeur de la (des) classe(s) de base. o Constructeurs des ventuels objets membres de la classe drivs. o Constructeur de la classe drive. Syntaxe d'appel du constructeur de la classe de base dans celui de la classe drive L'appel du constructeur de la classe de base peut se faire d'une manire explicite ou implicite selon que la classe de base ou la classe drive disposent de constructeurs explicitement dfinis ou non et que ces constructeurs soient paramtrs ou non. I - Cas o la classe drive dispose d'un constructeur paramtr : I.1 Cas o la classe de base possde un constructeur paramtr : Dans ce cas, le constructeur de la classe de base est appel par le constructeur de la classe drive dans la liste d'initialisation de ce dernier comme suit :
ConstructeurClasseDrive(Arguments):ConstructeurClasseBase(Arguments) { }
La liste d'initialisation du constructeur d'une classe drive peut comporter gnralement : o Un appel au constructeur de la classe de base. o Des valeurs d'initialisation des membres spcifiques de la classe drives. Exemple :
class A {public : int att_A; A(int p) : att_A(p) { cout<<"Constructeur de A"<<endl; } void Afficher() { cout<<" att_A :"<<att_A<<endl; } }; class B : public A {public : int att_B; B(int p1,int p2) : A(p1),att_B(p2) { cout<<"Constructeur de B"<<endl; } void Afficher() { cout<<" att_A :"<<att_A<<" et att_B :"<<att_B<<endl; } }; B b(5,7); // OK
Version 3.7
135
Karim Kalti
I.2- Cas o la classe de base dispose d'un constructeur par dfaut Ce constructeur peut tre implicite ou explicite. Dans ce cas, l'appel explicite du constructeur de la classe de base dans la classe drive n'est pas ncessaire. En effet cet appel peut se faire d'une manire implicite. Exemple :
class A {public : int att_A; }; class B : public A {public : int att_B; B(int p) : att_B(p) { cout<<"Constructeur de B"<<endl; } }; B b(7);
Ce constructeur de la classe B fait un appel implicite au constructeur par dfaut de la classe A. Avec ce constructeur de B, att_B sera cr et initialis mais att_A sera seulement cr. II - Cas o la classe drive ne dispose pas de constructeurs paramtrs II.1 - Si la classe de base ne dispose que de constructeurs paramtrs Dans ce cas l'instanciation de l'objet driv ne sera pas possible. En effet, l'instanciation de cet objet se fera avec le constructeur par dfaut gnr par le compilateur. Or ce dernier ne peut pas faire un appel automatique au constructeur paramtr de la classe de base car il ne saura pas quels arguments faut-il lui passer. Cette situation engendre une erreur de compilation. Exemple :
class A {public : int att_A; A(int p) : att_A(p) { cout<<"Constructeur de A"<<endl; } }; class B : public A {public : int att_B; }; B b; // Erreur
Il est clair qu'avec cette dfinition il n y a pas moyen d'appeler et de passer des arguments au constructeur de A lors de la cration d'un objet de type B.
Version 3.7
136
Karim Kalti
II.1- Si la classe de base dispose d'un constructeur par dfaut Dans ce cas, c'est ce dernier qui sera automatiquement appel par le constructeur par dfaut de la classe drive. Exemple :
class A {public : int att_A; }; class B : public A {public : int att_B; }; B b; // appel du constructeur par dfaut de B qui fait appel son // tour au constructeur par dfaut de A.
Version 3.7
137
Karim Kalti
Problme de l'appel explicite du constructeur de recopie de la classe de base par son homologue de la classe drive Lors de l'appel explicite du constructeur de recopie de la classe de base par le constructeur de recopie de la classe drive le problme suivant se pose : comment dduire l'objet de la classe de base passer comme argument au constructeur de recopie de cette dernire partir de l'objet de la classe drive. L'ide permettant de rsoudre ce problme consiste tout simplement faire passer l'objet de la classe drive au constructeur de recopie de la classe de base. La conversion "objet drive" vers "objet de base" sera dans ce cas automatiquement faite par le compilateur. Exemple 1 : L'exemple suivant montre comment appeler le constructeur de recopie d'une classe de base dans le constructeur de recopie d'une classe drive.
class A {public : int att_A; A(int p) : att_A(p) { cout<<"Constructeur de A"<<endl; void Afficher() { cout<<" att_A :"<<att_A<<endl; } };
class B : public A {public : int att_B; B(int p1,int p2) : A(p1),att_B(p2) {cout<<"Constructeur de B"<<endl;} B(const B& x):A(x) { att_B = x.att_B; } void Afficher() { cout<<" att_A :"<<att_A<<" et att_B :"<<att_B<<endl; } };
est de type B mais il sera automatiquement converti vers un objet de type A par le compilateur grce la compatibilit entre "objet drive" et "objet de base". Exemple 2 : L'exemple suivant propose une autre dfinition du constructeur de recopie d'une classe drive qui fait appel non pas au constructeur du recopie de la classe de base mais son constructeur paramtr.
B(const B& x):A(x.att_A) { att_B = x.att_B; }
Dans cette version le constructeur de recopie de B fait appel au constructeur paramtr de A dfini comme suit :
A(int p) : att_A(p){ }
Version 3.7
138
Karim Kalti
Remarque : Les membres issus de la classe de base ne peuvent pas tre initialiss directement par le constructeur de la classe drive. Leur initialisation doit ncessairement passer par l'appel du constructeur de la classe de base. Ainsi une dfinition comme la suivante du constructeur paramtr de B sera incorrecte.
B(int p1,int p2) : att_A(p1),att_B(p2) { cout<<"Constructeur de B"<<endl; }
ou galement :
B(int p1,int p2) { cout<<"Constructeur de B"<<endl; att_A=p1; att_B=p2; }
Dans ce cas le compilateur signalera que la classe B ne possde pas un attribut att_A. En effet lorsqu'il s'agit de construction et d'initialisation chaque classe (de base ou drive) doit s'occuper de la partie qui la concerne. Ce n'est pas le cas lorsqu'il s'agit d'utilisation de l'objet drive.
Version 3.7
139
Karim Kalti
Polymorphisme
Le mot polymorphisme dsigne le fait de pouvoir prendre plusieurs formes. En programmation oriente objet le polymorphisme est un concept qui se manifeste par la capacit d'une mthode s'excuter de diffrentes manires selon la nature de l'objet qui l'appelle (objet de base ou objet driv). Le polymorphisme concerne essentiellement des objets lis par une relation d'hritage. Limite de la liaison statique des mthodes Exemple : Rappel de la liaison statique
class A {public : int att_A; A(int p) : att_A(p) { cout<<"Constructeur de A"<<endl; } void Afficher() { cout<<" att_A :"<<att_A<<endl; } }; class B : public A {public : int att_B; B(int p1,int p2) : A(p1),att_B(p2) { cout<<"Constructeur de B"<<endl; } void Afficher() { cout<<" att_A :"<<att_A<<" et att_B :"<<att_B<<endl; } }; void main() { A* pa; B objB(3,5), *pb; pb=&objB; pb->Afficher(); pa=pb; pa->Afficher(); } pb->Afficher(); engendre l'affichage suivant : att_A : 3 et att_B : 5 pa->Afficher(); engendre l'affichage suivant : att_A : 3
Version 3.7
140
Karim Kalti
L'affichage incomplet partir du pointeur pa provient du fait que le compilateur se base sur le type de pa au moment de la compilation pour dcider de la version de la mthode appeler : pa tant de type A* alors c'est la mthode Afficher de la classe A qui sera appele dans ce cas. Le compilateur effectue dans ce cas une liaison statique. Cette dernire ne tient pas compte du vritable type de l'objet point et ne permet pas par consquent d'appeler les versions appropries des mthodes utiliser. Liaison dynamique et polymorphisme Pour remdier au problme de la liaison statique rencontr lors de la conversion d'un objet driv vers un objet de base il faut tre capable d'appeler la mthode qui correspond au type de l'objet point et non au type du pointeur. Pour ce faire il faut effectuer ce que l'on appelle une liaison dynamique entre l'appel et le corps de la mthode. Cette liaison doit tre faite au moment de l'excution et non au moment de la compilation. La liaison dynamique dote les mthodes de la capacit de se comporter de diffrentes manires selon l'objet appelant. Ces mthodes sont alors dites polymorphes. Mise en uvre du polymorphisme Pour rendre une mthode polymorphe, cette dernire doit tre dclare virtuelle dans la classe de base l'aide du mot-cl virtual selon la syntaxe suivante : virtual Type NomMethodePolymorphe(Paramtres) { } Exemple 1:
class A {public : int att_A; A(int p) : att_A(p){}; virtual void Afficher() { cout<<" att_A :"<<att_A<<endl } }; class B : public A {public : int att_B; B(int p1,int p2) : A(p1),att_B(p2){} void Afficher() { cout<<" att_A :"<<att_A<<" et att_B :"<<att_B<<endl; } }; void main() { A* pa; B objB(3,5), *pb; pb=&objB; pb->Afficher(); pa=pb; pa->Afficher(); }
Version 3.7
141
Karim Kalti
Remarque : La liaison dynamique d'une mthode n'est pas limite au cas o cette dernire est appele explicitement partir d'un objet mais s'applique galement au cas de l'appel de la mthode l'intrieur de la classe par d'autres mthodes. L'exemple suivant illustre cette situation. Exemple :
class A {public : int att_A; A(int p) : att_A(p){}; virtual void Contenu() { cout<<" att_A :"<<att_A<<endl; } void Afficher() { cout<<"Le contenu de l'objet est : "<<endl; Contenu(); } }; class B : public A {public : int att_B; B(int p1,int p2) : A(p1),att_B(p2){} void Contenu() { cout<<" att_A :"<<att_A<<" et att_B :"<<att_B<<endl; } };
Considrations relatives la dclaration et l'usage des fonctions virtuelles La spcification du mot-cl virtual n'est pas ncessaire pour les redfinitions de la mthode virtuelle. Cette spcification peut se limiter la classe de base. A partir du moment o une fonction a t dclare virtuelle dans une classe de base, alors elle sera soumise la liaison dynamique dans cette classe et dans toutes les classes drives. Il est noter que l'effet de virtual ne se limite pas aux classes obtenues par drivation directe mais s'tend toute la hirarchie des classes obtenues par drivation successives. La redfinition d'une fonction virtuelle n'est pas obligatoire dans toutes les classes drives. Toutefois une classe drive ne peut appeler une mthode virtuelle que si cette dernire possde au moins une dfinition dans sa hirarchie. Dans ce cas c'est la dernire redfinition qui sera utilise. Si une mthode a t dj dfinie dans une classe de base puis redfinie comme virtuelle mais dans une classe drive, alors la nouvelle redfinition sera soumise la liaison dynamique. Toute autre redfinition de cette mthode dans une classe drive sera galement soumise la liaison dynamique.
Version 3.7
142
Karim Kalti
La surdfinition (et non la redfinition) dans une classe drive d'une mthode dclare virtuelle dans une classe de base est interprte comme une nouvelle dfinition de la mthode (une dfinition possdant une signature et une syntaxe d'appel diffrentes). Elle ne sera pas par consquent soumise la liaison dynamique mais plutt la liaison statique. Objets auto, pointeurs, rfrences et possibilit de typage dynamique Le typage dynamique n'est possible qu' partir des pointeurs et des rfrences. Il ne l'est pas dans le cas des objets auto. Cas des objets de type auto : La copie d'un objet auto d'une classe drive dans un objet auto de la classe de base engendre la copie membre par membre des attributs correspondant la classe de base depuis l'objet driv vers l'objet de base. L'appel d'une mthode virtuelle depuis l'objet de base ainsi copi ne peut invoquer que la dfinition associe la classe de base. En effet, il n'est pas concevable d'appeler la version redfinie dans la classe drive car l'objet copi ne dispose dans sa copie d'aucune information sur les donnes drives. Exemple : Etant donnes les classes A et B drive de A.
A ObjA; B ObjB(5,7); ObjA = ObjB;
ObjA att_A = 5 ObjB
Copie
att_A = 5 att_B = 7
possible d'utiliser la version redfinie car ObjA n'a aucune information sur les attributs drivs (att_B dans ce cas). Cas des pointeurs : Lorsqu'il s'agit de pointeur la liaison dynamique est envisageable. En effet, dans le cas o ce pointeur pointe sur un objet driv, l'appel de la redfinition d'une mthode virtuelle depuis ce pointeur ne risque pas de poser de problme puisque toutes les informations ncessaires au bon fonctionnement de la redfinition sont ncessairement disponibles dans l'objet point. Exemple : Etant donnes les classes A et B drive de A.
B ObjB(5,7); A* pa = (A*)&ObjB; pa->Afficher();
A* pa
EF0A78
Il est envisageable en faisant une liaison dynamique de faire appel la redfinition de la mthode Afficher (version dfinie dans B). En effet, l'objet point dispose dans ce cas de toutes les informations ncessaires au bon fonctionnement de cette redfinition.
Version 3.7
143
Karim Kalti
Cas des rfrences : Il est possible d'envisager une liaison dynamique avec les rfrences pour les mmes raisons que celles voqus pour les pointeurs. Exemple : Etant donnes les classes A et B drive de A.
B ObjB(5,7); A& r = ObjB; r.Afficher(); A& r
EF0A78
Il est envisageable en faisant une liaison dynamique de faire appel la redfinition de la mthode Afficher (version dfinie dans B). En effet, l'objet rfrenc (ObjB) dispose dans ce cas de toutes les informations ncessaires au bon fonctionnement de cette redfinition. Restriction du polymorphisme Les constructeurs ne peuvent pas tre virtuels. Tout objet ne peut tre construit que par le constructeur qui a t dfini pour sa classe. Polymorphisme des destructeurs Les destructeurs peuvent tre dclars virtuels. Considrons le cas suivant :
class A {public : ~A(){cout<<"Destructeur de A";} }; class B : public A {public : ~B(){cout<<"Destructeur de B";} }; A* pa = new B(5,7); pa->Afficher(); delete pa;
A* p
EF0A78
att_A=5 att_B=7
Si le destructeur de A n'a pas t dclar comme virtuel dans A. delete pa; va engendrer dans ce cas un appel au destructeur de A (liaison statique car pa est un pointeur sur A et ~A n'est pas virtuel. Or ce destructeur n'est pas appropri pour dtruire l'objet point puisque ce dernier est de type B. Pour rsoudre ce problme il faut modifier la classe A comme suit :
class A {public : virtual ~A(){ } }; delete pa; va engendrer dans ce cas un appel du destructeur de B (liaison dynamique).
Version 3.7
144
Karim Kalti
Toute forme possde une surface. C'est pourquoi une mthode de calcul de surface a t intuitivement associe la classe Forme. Toutefois cette dernire est incapable de dfinir la manire avec laquelle sera calcule cette surface. Ce calcul ncessitant en effet la connaissance de la nature exacte de la forme. Classes abstraites Une classe qui possde au moins une mthode virtuelle pure est appele une classe abstraite. Une classe abstraite ne peut pas tre instancie.
Version 3.7
145
Karim Kalti
Une mthode virtuelle pure peut tre redfinie dans la classe drive ou laisse encore virtuelle pure (Il faut la dclarer explicitement virtuelle pure dans la classe drive pour les versions 2.0 et antrieures du C++. Ceci n'est plus ncessaire depuis la version 3.0). Une classe drive qui ne dfinit pas toutes les mthodes virtuelles pures de ses classes ascendantes est considre comme une classe abstraite. Elle ne peut pas par consquent tre instancie. Une classe drive (directement ou indirectement) d'une classe abstraite n'est instanciable que si elle dfinit toutes les mthodes virtuelles pures qu'elle hrite. Utilit des classes abstraites Les classes abstraites servent gnralement tablir une sorte de cahier des charges qui doit tre rempli par les classes qui en drivent. Elles permettent galement de factoriser les proprits communes ces classes.
Version 3.7
146
Karim Kalti
C1 et C2 sont deux objets de type Complexe. L'expression suivante C1 + C2 ne sera pas accepte par le compilateur car l'oprateur + n'est pas dfini par dfaut pour des oprandes de type Complexe. La surcharge des oprateurs Le C++ offre aux programmeurs un outil permettant de proposer des dfinitions supplmentaires un oprateur qui soient adaptes aux types personnaliss qu'ils construisent. Cet outil est appel la surcharge d'oprateurs ou galement la surdfinition des oprateurs. Remarque : La surcharge des oprateurs permet de manipuler d'une manire plus simple et plus intuitive des objets de types personnaliss. En effet si on considre l'exemple prcdent, il est tout fait possible de dfinir une mthode qui effectue la somme de deux complexes.
Complexe Complexe::Somme(Complexe C);
La somme sera effectue comme suit : C3 = C1.Somme(C2); Cette expression reste toutefois moins pratique que : C3=C1 + C2;
La fonction oprateur
Tout oprateur du C++ est en ralit dfini comme une fonction classique qui porte comme nom l'oprateur en question prcd du mot-cl operator comme suit : TypeRetour operator SymboleOprateur(paramtres);
Version 3.7
147
Karim Kalti
Exemple :
int a, b, c; L'expression a = b + c est en ralit interprte par le compilateur comme un appel de la
Pour que cette dfinition soit correcte, il faut que les deux attributs Reel et Imaginaire soient accessibles par la fonction globale operator+. Pour cela ces deux attributs doivent tre publiques. Pour prserver le principe d'encapsulation (la classe doit cacher son implmentation et la laisser prive), il est possible de procder autrement en laissant ces deux attributs privs et en dclarant la fonction operator+ comme une fonction amie de la classe Complexe comme suit :
Version 3.7
148
Karim Kalti
class Complexe { double Reel; double Imaginaire; public : Complexe(){Reel = 0; Imaginaire = 0;} Complexe(double r, double i) : Reel(r), Imaginaire(i){} friend Complexe operator+(const Complexe& C1, const Complexe& C2); };
Remarque : L'utilisation des rfrences pour les paramtres complexes permet d'viter de copier des objets qui peuvent tre de grande taille et d'optimiser par consquent le code. L'utilisation de const permet par ailleurs de prserver ces paramtres contre toute ventuelle modification par la fonction. Surcharge d'un oprateur sous forme d'une fonction membre La deuxime possibilit consiste raliser la surcharge sous forme d'une fonction membre. Cette forme de surcharge n'est possible que si le premier oprande est de type classe ce qui est le cas dans l'exemple du nombre complexe.
class Complexe {
public : Complexe operator+(const Complexe& C) { Complexe Resultat; Resultat.Reel = this->Reel + C.Reel; Resltat.Imaginaire = this->Imaginaire + C.Imaginaire; return Resultat; }
};
La fonction operator+ prend dans ce cas un seul paramtre qui reprsente le deuxime oprande. Le premier oprande tant l'objet qui va invoquer la mthode operator+. En effet : C3 = C1 + C2; est interprte par le compilateur dans ce cas comme suit :
C3=C1.operator+(C2);
Le problme d'accessibilit des attributs Reel et Imaginaire ne se pose pas dans ce cas car la fonction operator+ est membre de la classe Complexe.
Version 3.7
149
Karim Kalti
La surcharge par dfaut ralise la copie champ par champ de l'oprande de droite dans l'oprande de gauche et reste par consquent inapproprie pour l'affectation d'objets ayant des membres dynamiques allous sur le tas. Exemple : L'exemple suivant montre la limite de la surcharge par dfaut de l'oprateur d'affectation
class TabEntiers { int Nb; int* T; public : TabEntiers(int Ni) { Nb=Ni; T=new int [Nb]; } ~TabEntiers() { delete[]T; } void Saisir() { for(int k=0;k<Nb;k++) { cou<<"Donner l'lment d'indice "<<k; cin>>T[k]; } } void Afficher() { for(int k=0;k<Nb;k++) { cout<<T[k]<<' '; } } }; void main() { TabEntiers TE1(5), TE2(5); TE1.Saisir(); TE2 = TE1; }
int Nb 5
TE1
copie
TE2
5 int Nb FFE4D8A int* T
int* T FFE4D8A
La copie ralise par l'oprateur d'affectation reste donc partielle et incomplte. Dans ce cas il faut modifier le comportement par dfaut de cet oprateur pour raliser une copie complte d'objets. Cette modification doit procder selon les tapes suivantes : o Vrifier le cas de l'auto-affectation (Affectation d'un objet lui-mme) o Librer l'ancien contenu de l'oprande de gauche. o Allouer un nouvel espace pour l'oprande de gauche qui correspond aux donnes copier depuis l'oprande droit. o Copier les donnes de l'oprande droit vers l'oprande gauche.
Version 3.7
150
Karim Kalti
Remarques : Le C++ impose que la surcharge de l'oprateur d'affectation se fasse comme une fonction membre non statique. Une telle contrainte oblige l'oprande de gauche d'tre un objet de classe et garantit par consquent de pouvoir le manipuler en tant qu'une lvalue dans laquelle on peut placer un contenu. Le fait d'utiliser un paramtre de type const permet de traiter convenablement le cas de l'affectation d'un objet constant et ce en plus des objets non constants comme oprande de droite. La valeur de retour de la surcharge aurait pu tre void auquel cas l'affectation se limiterait la simple expression TE2 = TE1. Le fait de renvoyer un objet TabEntiers offre la possibilit de pouvoir effectuer des affectations multiples du genre : TE3 = TE2 = TE1; TE3 = TE2 = TE1; est traduite comme TE3.operator=(TE2.operator=(TE1)); Nota bene : Renvoi d'une rfrence Le renvoi d'une rfrence par une fonction doit tre employ avec prcaution. En effet, il faut viter de renvoyer la rfrence d'un objet locale la fonction parce que cet objet sera automatiquement dtruit aprs l'appel de la fonction. Ce problme ne se pose pas dans le cas de cette surcharge puisque la rfrence renvoye est celle de l'objet courant qui n'est pas local la fonction. Le fait de renvoyer une rfrence un objet TabEntiers permet par ailleurs d'viter l'appel implicite du constructeur de recopie. Surcharge de l'oprateur d'indexation [ ] Pour les classes qui possdent un attribut de type tableau, la surcharge de l'oprateur d'indexation offre une possibilit syntaxique qui permet d'accder aux lments de ce dernier directement partir de l'objet sans passer d'une manire explicite par le tableau. Exemple : Par exemple pour le cas de la classe TabEntiers, la rcupration de la valeur d'un lment du tableau doit passer par l'appel d'une fonction ddie :
Version 3.7
151
Karim Kalti
permet d'avoir le mme effet que GetElement mais en utilisant en plus une syntaxe plus souple. En effet l'accs un lment du tableau partir d'un objet TE se fait comme suit :
TabEntiers TE(5); TE[i]=8; // Accs l'lment d'indice i grce la surcharge de []
Remarques : Le fait d'utiliser une valeur de retour de type rfrence permet de laisser envisager d'utiliser TE[i] comme un oprande gauche dans une affectation. (Exemple : TE[i]=5). Une valeur de retour de type int est donc possible mais restreint l'utilisation de l'indexeur aux oprations de lecture de valeur seulement. Tout comme l'oprateur d'affectation, le C++ impose que la surcharge de l'oprateur d'indexation se fasse comme une fonction membre non statique. Surcharge de l'oprateur ++ L'oprateur ++ est un oprateur unaire. Il a la particularit de pouvoir tre utilis de deux manires : postfix et prfix. Surcharge de la version prfixe L'exemple suivant montre la surcharge sous forme d'une fonction membre de la version prfixe de l'oprateur ++ pour la classe Complexe :
// Surcharge sous forme d'une fonction membre Complexe Complexe::operator++() { Reel++; Imaginaire++; return *this; }
Il est tout fait possible de raliser cette surcharge sous forme d'une fonction globale et amie. Le prototype de cette dernire prendra alors un seul paramtre qui reprsente l'objet incrmenter. Cette surcharge se prsente comme suit :
// Surcharge sous forme d'une fonction amie Complexe operator++(const Complexe& C) { C.Reel++; C.Imaginaire++; return C; }
Version 3.7
152
Karim Kalti
Surcharge de la version postfixe Pour pouvoir la distinguer de la version prfixe, le C++ impose que la version postfixe utilise un paramtre supplmentaire et fictif de type int. Ce paramtre permet tout simplement au compilateur de choisir la version approprie utiliser mais aucune valeur ne lui sera rellement transmise lors de l'appel. La surcharge sous forme d'une fonction membre de cette version se fait comme suit :
Complexe operator++(int n) { Complexe C(*this); Reel++; Imaginaire++; return C; }
La surcharge sous forme d'une fonction globale et amie de cette version se fait comme suit :
Complexe operator++(const Complexe& C, int n) { Complexe R(C); C.Reel++; C.Imaginaire++; return R; // Renvoi de l'objet non incrment }
Version 3.7
153
Karim Kalti
Version 3.7
154
Karim Kalti
Grce cette surcharge de loprateur cast, il devient possible dutiliser lexpression suivante lors de la manipulation des objets de la classe Complexe :
Complexe C(2.5,3.8); double d = C ; // Conversion implicite de C vers le type double // grce lappel de loprateur cast.
Laddition est par dfaut dfinie entre deux rels mais elle ne lest pas entre un rel et un complexe. Pour raliser cette addition le compilateur convertit implicitement lobjet C vers le type double puis ralise une addition entre deux rels et le rsultat est par consquent un rel.
Version 3.7
155
Karim Kalti
Linterprtation de lexpression 5.2 + C; par le compilateur va conduire ce dernier vers une ambigut. En effet deux interprtations sont possibles dans ce cas : Premire interprtation : o Conversion de 5.2 vers complexe grce au constructeur de transtypage. o Faire une addition entre deux complexes vu que cette opration est surdfinie dans ce cas pour les complexes, chose qui va donner un rsultat complexe. Deuxime interprtation : o Conversion du C vers le type double grce loprateur cast. o Faire une addition entre deux rels, chose qui va donner un rsultat rel. Utilisation des conversions explicites Pour viter cette ambigut, il est ncessaire dutiliser des conversions explicites dans cette expression. Cette dernire pourra alors scrire de deux manires : Premire possibilit :
5.2 + (double)C ;
Le compilateur commence par faire un appel loprateur cast pour la conversion de lobjet C vers le type double. Le rsultat est ensuite ajout au premier double (5.2). Il sagit dans ce cas dune addition entre doubles et le rsultat final est par consquent un double. Deuxime possibilit :
Complexe(5.2) + C ;
Lappel explicite du constructeur de transtypage engendre la conversion du double 5.2 vers un objet Complexe. Ce dernier sera additionn par la suite lobjet C en utilisant la surcharge de loprateur + dfinie dans la classe Complexe. Le rsultat est donc dans ce cas un objet de type Complexe. Interdiction des conversions implicites Lappel implicite du constructeur de transtypage peut conduire parfois vers des situations gnantes. En effet le compilateur considre tout constructeur pouvant tre appel avec un seul paramtre (de type diffrent que celui de la classe en cours) comme un constructeur de transtypage alors quun tel constructeur peut figurer dans une classe sans tre destin lorigine par le concepteur de cette dernire faire des conversions. Cest le cas par exemple du constructeur suivant de la classe TabEntiers qui est sens tout simplement allouer lespace mmoire ncessaire lobjet.
Version 3.7
156
Karim Kalti
class TabEntiers { int Nb; int* T; public : TabEntiers(int Ni) { Nb=Ni; T=new int[Nb]; } } ;
Pour pallier ce problme, le C++ offre un outil permettant de contrler lappel implicite des constructeurs particuliers. Ce contrle est ralis avec le mot-cl explicit. Ce dernier utilis comme prfixe de la dclaration dun constructeur interdit ce dernier dtre appel implicitement par le compilateur et seul les appels explicites lui seront alors autoriss. La signature du constructeur de TabEntiers serait alors :
explicit TabEntiers(int Ni);
Version 3.7
157
Karim Kalti
Les modles
Les modles, appels galement patrons, permettent de crer des fonctions ou des classes gnriques qui peuvent tre paramtres de point de vue types des donnes qu'elles manipulent. Ces modles permettent ainsi d'avoir une criture plus concise du code puisqu'un modle particulier peut remplacer plusieurs dfinitions de fonctions ou de classes.
Version 3.7
158
Karim Kalti
Appel d'une fonction gnrique La syntaxe de lappel dune fonction gnrique se prsente comme suit : NomFonction<TypeEffectif>(arguments); Exemple : Lappel suivant renvoie le maximum de deux entiers :
int c ; c = Max<int>(5,19) ;
Toutefois, le compilateur est capable en analysant la signature de lappel de connatre implicitement le ou les types effectifs utiliss. De ce fait, l'appel de la fonction gnrique peut se faire comme pour les fonctions classiques en lui passant des paramtres effectifs sans spcifier explicitement largument du type. NomFonction(arguments); Exemple :
template <class T> T Max(T a, T b) { if(a>b) return a; return b; } int main() { int a=5, b=9, c=Max(a,b); double m=3.5, k=Max(m,n); cout<<"Le max cout<<"Le max return 0; }
Remarque : Au moment de lappel, le compilateur va gnrer une nouvelle instance de la fonction gnrique dans laquelle il remplace le type gnrique T par le type effectif qui lui correspond dans l'appel. Cette nouvelle instance reprsente la fonction qui sera vritablement excute au moment de l'appel. Dans lexemple prcdent le compilateur gnre deux instances de la fonction Max qui correspondent aux prototypes suivants : int Max(int; int) pour le premier appel. double Max(double, double) pour le second appel. 159
Version 3.7
Karim Kalti
Remarque : Le compilateur gnre autant d'instances partir d'un modle qu'il y a d'appels avec des types effectifs diffrents. La fonction gnrique va servir essentiellement pour la synthse des fonctions qui seront vritablement appeles. C'est le code binaire de ces dernires qui va figurer dans l'excutable gnr. Dans cet excutable le code gnrique n'a aucune existence. Utilisation de plusieurs paramtres de type Il est possible d'utiliser plus qu'un paramtre de type dans une fonction gnrique. Dans ce cas, ces paramtres doivent tre dclars chacun avec le mot rserv class et spars par des virgules. L'exemple suivant montre le prototype d'une fonction gnrique qui utilise deux paramtres de type distincts : Exemple :
template <class T1, class T2> T2 F(T1 a, T2 b);
La fonction F prend un premier paramtre de type T1 et un deuxime paramtre de type T2. T1 et T2 sont dans ce cas gnriques et seront instancis au moment de l'appel de F. Utilisation des paramtres classiques dans un modle de fonctions Il est tout fait possible de combiner dans le prototype d'un modle de fonctions des paramtres de type avec des paramtres classiques pour lesquels le type est connu. L'exemple suivant donne une illustration de cette situation. Exemple :
template <class T> int CompteZero(T* Tab, int N) { int NbZeros = 0; for(int i=0;i<N; i++) if(!Tab[i]) NbZeros++; return NbZeros; } int N est dans ce cas un paramtre classique.
Surdfinition d'une fonction gnrique Tout comme les fonctions classiques, il est tout fait possible de surdfinir une fonction gnrique. Cette surdfinition peut tre elle-mme une fonction gnrique comme elle peut comporter des paramtres classiques. Exemple : L'exemple suivant montre deux surdfinitions possibles de la fonction gnrique Max une gnrique et l'autre non :
// Surdfinition gnrique de la fonction Max template <class T> T Max(T a, T b, T c) { return Max(Max(a,b),c); }
Version 3.7
160
Karim Kalti
// Surdfinition comportant des paramtres classiques (int N) template <class T> T Max(T* Tab, int N) { T MaxVal = Tab[0]; for(int i=1;i<N;i++) if(Tab[i]>MaxVal) MaxVal = Tab[i]; return MaxVal; }
Spcialisation d'une fonction gnrique Il y a des situations o l'algorithme d'une fonction gnrique savre inadapt pour certains types d'arguments. Dans ce cas, il est possible de spcialiser la fonction gnrique en ajoutant des versions adaptes ces types tout en conservant le modle du prototype (il ne s'agit pas d'une surdfinition). Exemple : La fonction gnrique Max telle qu'elle est dfinie prcdemment n'est pas adapte pour la comparaison des chanes parce qu'une telle comparaison ne se fait pas l'aide de l'oprateur > mais avec la fonction strcmp. Il est dans ce cas possible d'ajouter une spcialisation de la fonction gnrique pour ce cas particulier.
template <class T> T Max(T a, T b) { if(a>b) return a; return b; } // Spcialisation de Max char* Max(char* a, char* b) { if(strcmp(a,b)>0) return a; return b; } char Ch1[20]="Programme"; char Ch2[20]="Algorithme"; cout<<Max(Ch1,Ch2); // Cest la spcialisation qui est utilise
Dans ce cas le compilateur va commencer sa recherche par la fonction qui correspond exactement la signature de l'appel et va par consquent directement utiliser la spcialisation sans avoir besoin d'analyser la fonction gnrique afin de synthtiser une version adapte. Remarque : Il n'y a pas de moyens de limitation des types qui peuvent tre utiliss avec une fonction gnrique. Par consquent, c'est celui qui fait l'appel de vrifier si le modle est adapt aux types des arguments qu'il utilise. Par exemple la fonction gnrique Max peut accepter n'importe quel type y compris les types personnaliss comme les classes. Si des classes sont passes Max alors, il faut vrifier que l'oprateur > est bien surcharg pour de telles classes.
Version 3.7
161
Karim Kalti
Dfinition des mthodes dun modle de classe Les mthodes dun modle de classe sont des modles de fonctions avec les mmes paramtres de type que la classe. Ces mthodes peuvent tre dfinies lintrieur ou lextrieur de la classe. Dfinition lintrieur de la classe (en ligne) La dfinition se fait dans ce cas dune manire classique comme pour les mthodes usuelles. Dfinition lextrieur de la classe La dfinition lextrieur de la classe utilise une syntaxe un peu diffrente que dans le cas classique. Cette syntaxe rappelle au compilateur quil sagit dun modle et mentionne les paramtres de types qui sont utiliss. Cette syntaxe se prsente comme suit :
Version 3.7
162
Karim Kalti
template<class T> NomClasse<T>::NomMethode(Paramtres) { // Dfinition de la mthode } Lexemple suivant donne une illustration de ces deux possibilits de dfinition. Exemple :
template <class T> class Point { T x; T y; // Exemple de dfinition lintrieur de la classe Point(T abs, T ord) { x = abs ; y = ord ; } void Afficher(); }; // Exemple de dfinition lextrieur de la classe template <class T> void Point<T>::Afficher() { cout<<"Abs : "<<x; cout<<"Ord : "<<y; }
Utilisation dun modle de classe Lors de lappel dun modle de fonctions, le compilateur se base sur la signature pour dterminer le type effectif des arguments utiliss. Cette identification automatique par le compilateur nest pas possible lorsquil sagit dutiliser un modle de classe pour instancier un objet. Cest pourquoi la spcification de la classe dans ce cas doit tre accompagne explicitement du (des) types effectif(s) utilis(s). La syntaxe dinstanciation se prsente de ce fait comme suit : NomModleClasse <TypeEffectif> NomObjet(args du constructeur); Exemple :
int main() { Point<int> P1(5,3); Point<double> P2(2.4, 3.7); P1.Afficher(); P2.Afficher(); return 0 ; }
Version 3.7
163
Karim Kalti
Remarque : Thoriquement, il est possible dinstancier la classe Point avec nimporte quel type. Toutefois, sur un plan pratique les types utiliser doivent tre compatibles avec la signification de cette classe (types numriques). Le bon usage du modle reste alors et toujours laffaire de lutilisateur de ce dernier. Classe gnrique avec des paramtres de type et des paramtres classiques En plus des paramtres de type, il est tout fait possible dutiliser des paramtres classiques ayant des types concrets et ce lors de la cration dun modle de classe. Lexemple suivant montre un exemple de ce type de situation. Exemple : La classe Tableau reprsente des tableaux dlments dune manire gnrale indpendamment du type de ces lments. Cette classe prend deux arguments, le premier reprsente le type des lments manipuler et le second reprsente la dimension du tableau. La dfinition de cette classe se prsente comme suit :
template <class T, int N> class Tableau { T Elements[N]; T& operator[](int Index) { return Elements[Index]; } };
Remarques : Cet exemple montre entre autres quil est tout fait possible de crer un tableau non dynamique avec une dimension paramtre. En effet au moment de la cration de la classe concrte partir du modle, le prprocesseur va dans le cas prsent remplacer linstruction : T Element[N]; par int Element[4]; qui est une instruction accepte par le compilateur.
Version 3.7
164
Karim Kalti
La dimension du tableau aurait pu tre passe comme argument du constructeur ce qui viterait ainsi lutilisation du paramtre int dans le modle de la classe. Mais une telle option ne permettrait pas de crer le tableau sur la pile dexcution. Seul serait possible dans ce cas lallocation dynamique. Valeur par dfaut des paramtres dun patron de classe Il est possible de spcifier des valeurs par dfaut aux paramtres dune classe gnrique. La rgle qui rgit cette spcification est semblable celle utilise avec les fonctions usuelles. Exemple :
template <class T=int, int N=10> class Tableau { T Elements[N] ; T& operator[](int Index) { return Elements[Index] ; } } ;
Les instructions suivantes montrent des exemples dutilisation du modle Tableau ainsi dclar.
Tableau<float, 4> ; // Tableau de 4 rels simples Tableau<Complexe> ; // Tableau de 10 objets de type Complexe. Tableau<> ; // Tableau de 10 entiers.
Remarque : La notion de paramtre par dfaut na pas de signification pour les modles de fonctions. Spcialisation dun modle de classe La possibilit de spcialisation existe galement avec les modles des classes comme cest le cas avec les fonctions. Elle peut tre utile afin dadapter le modle certaines situations particulires. La spcialisation peut concerner une mthode du modle de classe comme elle peut concerner la classe en entier. Spcialisation dune mthode La spcialisation dune mthode dun modle de classe se fait selon la syntaxe suivante : TypeRetour NomClasse<TypeEffectif>::NomMethode(Paramtres) { // Dfinition de la spcialisation de la mthode } Lexemple suivant montre une spcialisation de la mthode Afficher du modle Point pour quelle affiche convenablement les coordonnes dans le cas o le type effectif utilis est le char (codage des entiers sur un octet).
Version 3.7
165
Karim Kalti
// Dfinition de la mthode Afficher pour le modle de classe template <class T> class Point { T x; T y; Point(T abs, T ord) {x= abs ; y ord ;} void Afficher(); }; template <class T> void Point<T>::Afficher() { cout<<"Abs : "<<x<<endl; cout<<"Ord : "<<y<<endl; } // Spcialisation de la mthode Afficher pour les caractres void Point<char> ::Afficher() { cout<<"Abs : "<<(int)x<<endl; cout<<"Ord : "<<(int)y<<endl; } // Utilisation de la classe Point int main() { Point<int> P1(5,9); Point<char> P2('a','b'); P1.Afficher(); // Afficher gnre par le compilateur P2.Afficher(); // Afficher spcialise pour char return 0; }
Spcialisation dune classe Il est tout fait possible de spcialiser la classe dans sa totalit. Dans ce cas la nouvelle spcialisation doit suivre la dfinition du modle tout en indiquant le ou les types concrets quelle utilise. La syntaxe adopter est la suivante : class Nomclasse<TypeConcret> { // Nouvelle dfinition } Exemple : La spcialisation de la classe Point pour le type char doit tre dfinie de la manire suivante :
class Point<char> { // Nouvelle spcialisation }
Version 3.7
166
Karim Kalti
Remarque : Les modles constituent un outil puissant permettant de rduire considrablement le code. Toutefois cet outil doit tre utilis avec prcaution vu lexistence de situations pouvant mener vers des ambiguts dinterprtation au moment de la compilation. Par ailleurs, la gnration du code faite par le compilateur est ralise dune manire automatique et ne donne aucune garantie sur ladaptation des traitements aux donnes. Cest lutilisateur du modle qui doit toujours veiller sur cette adaptation et lassurer ventuellement par des spcialisations.
Version 3.7
167
Karim Kalti
Version 3.7
Karim Kalti
Remarque : Une exception peut tre de nimporte quel type (entier, chane, numration, etc.). Toutefois en POO, les exceptions sont le plus souvent codes sous forme dobjets (instances de classe). Un tel codage permet de faire accompagner lexception dautres informations qui peuvent savrer utile celui qui va la grer.
169
Karim Kalti
Remarque : Les accolades dans les blocs de try et de catch sont obligatoires mme si ces derniers comportent une seule instruction. Exemple :
#include <iostream.h> void f(int); int main() { int minute; try { cout<<" Donner une minute : "; cin>>minute; f(minute); } catch(const char* e) { cout<<e<<endl; } return 0; }
Remarque : La porte de l'identificateur e est limite au bloc catch. Il est gnralement utilis pour rcuprer les informations sur l'erreur. S'il n'est pas fait usage de e dans le bloc catch alors cet identificateur peut tre omis. L'entte du catch se prsente dans ce cas comme suit : catch(TypeException) { // Bloc de gestion des erreurs } Droulement de l'excution Le programme entre dans le bloc try. Il commence alors l'excution squentielle des instructions de ce bloc. Ds qu'une anomalie est dtecte, une exception correspondant au type de cette anomalie est cre. Le programme abandonne alors le reste des instructions du bloc, quitte ce dernier et rentre tout aussi automatiquement dans le bloc catch. Aprs le traitement de l'exception, le programme reprend son excution au niveau de l'instruction qui suit immdiatement le bloc catch. Si aucune anomalie n'a t dtecte dans le bloc try alors le gestionnaire catch sera ignor et l'excution continue au niveau de l'instruction qui suit immdiatement ce dernier. Exemple :
#include <iostream.h> float Div(float a, float b) { if(b==0) throw "Impossible de faire une division par zro"; return a/b; }
Version 3.7
170
Karim Kalti
int main() { float a,b; try { cout<<" Donner a :"; cin>>a; cout<<" Donner b :"; cin>>b; cout<<"\nLe rsultat de la division est :"<<Div(a,b); } catch(const char* e) { cout<<e<<endl; } cout<<"FIN DU PROGRAMME"<<endl; return 0; }
Exemple d'une division normale Donner a : 6 Donner b : 3 Le rsultat de la division est : 2 FIN DU PROGRAMME
Exemple d'une division par zro Donner a : 6 Donner b : 0 Impossible de faire une division par zro FIN DU PROGRAMME
Version 3.7
171
Karim Kalti
Droulement de l'excution En cas d'anomalie, le contrle passe au premier gestionnaire catch. Si cette anomalie correspond l'argument de ce dernier alors elle sera traite par le bloc qui lui est associ. Dans le cas contraire le catch suivant est inspect et ainsi de suite. Ds qu'une anomalie est traite par un catch les catchs suivants ne sont plus envisags. Dans le cas o l'anomalie ne correspond aucun gestionnaire catch du bloc try-catch courant, la recherche se poursuit dans le bloc try-catch de niveau suprieur (celui qui englobe le try-catch imbriqu). Ce processus de remonte de la recherche se poursuit jusqu' ce qu'un gestionnaire catch pour l'exception courante soit trouv. Ds qu'un gestionnaire catch pouvant grer l'exception est rencontr, on entre dans son bloc et l'excution du programme continue dans ce gestionnaire. Si aucun gestionnaire n'est trouv, le programme appelle la fonction terminate() dfinie dans la bibliothque standard du C++. Cette fonction propose un comportement par dfaut, qui appelle notamment la fonction abort() qui elle-mme indique que le programme se termine anormalement Abnormal program termination . Exemple 1:
class ErreurCreation{ }; class ErreurAcces{ }; class Tableau { int* Elements; int NbElements; public : Tableau(int Taille) { if(Taille<0) throw ErreurCreation(); Elements = new int[Taille]; NbElements = Taille; for(int i=0; i<NbElements; i++) Elements[i]=0; } ~Tableau() { delete[] Elements; } int Get(int Indice) { if(Indice<0 || Indice>=NbElements) throw ErreurAcces(); return Elements[Indice]; } void Set(int Indice, int Valeur) { if(Indice<0 || Indice>=NbElements) throw ErreurAcces(); Elements[Indice] = Valeur; } void Afficher() { for(int i=0; i<NbElements; i++) cout<<Elements[i]<<' '; } };
Version 3.7
172
Karim Kalti
int main() { int Taille, Indice; try { cout<<"Donner la taille du tableau : "; cin>>Taille; Tableau Tab(Taille); cout<<"Donner l'indice de la case mettre 1 : "; cin>>Indice; Tab.Set(Indice,1); Tab.Afficher(); } catch(ErreurAcces) { cout<<"Erreur d'accs"; } catch(ErreurCreation) { cout<<"Erreur de cration du tableau"; } cout<<"\nFIN DU PROGRAMME"; system("PAUSE"); return 0; }
Exemple 1 : Leve d'une exception de cration de tableau Donner la taille du tableau : -3 Erreur de cration du tableau FIN DU PROGRAMME Exemple 2 : Leve d'une exception d'accs au tableau Donner la taille du tableau : 3 Donner l'indice de la case mettre 1 : 5 Erreur d'accs FIN DU PROGRAMME
Version 3.7
173
Karim Kalti
Exemple :
class Tableau { int* Elements; int NbElements; public : Tableau(int Taille) throw (ErreurCreation); ~Tableau(); int Get(int Indice) throw (ErreurAcces); void Set(int Indice, int Valeur) throw (ErreurAcces); void Afficher() throw(); };
Remarque : L'instruction throw utilise sans paramtres dans la dclaration d'une fonction de la manire suivante throw( ) signifie que cette fonction ne lve aucune exception. L'absence du throw dans la dclaration d'une fonction signifie que cette dernire peut lever tout type d'exceptions.
Alors les deux derniers gestionnaires de cette structure ne pourront jamais tre excuts. En effet, toutes les exceptions leves de type B et C seront automatiquement interceptes par le premier gestionnaire car elles sont la base de type A. Le gestionnaire interceptant l'exception de type A devrait normalement tre plac en dernier lieu.
Version 3.7
174
Karim Kalti
Exemple :
#include <iostream.h> #include <sstream.h> class Erreur { public : string GetMessage() { ostringstream MessageErreur; MessageErreur<<"Erreur d'excution du programme\n"; return MessageErreur.str(); } }; class ErreurCreation : public Erreur { int TailleIncorrecte; public: ErreurCreation(int Valeur) { TailleIncorrecte = Valeur; } string GetMessage() { ostringstream MessageErreur; MessageErreur<<"Erreur de cration du tableau : "<<endl; MessageErreur<<"Taille incorrecte:"<<TailleIncorrecte; return MessageErreur.str(); } }; class ErreurAcces : public Erreur { int IndiceIncorrect; int TailleMax; public: ErreurAcces(int Valeur, int Taille) { IndiceIncorrect = Valeur; TailleMax = Taille; } string GetMessage() { ostringstream MessageErreur; MessageErreur<<"Erreur d'accs au tableau : "<<endl; MessageErreur<<"Indice incorrect:"<<IndiceIncorrect; return MessageErreur.str(); } };
Version 3.7
175
Karim Kalti
class Tableau { int* Elements; int NbElements; public : Tableau(int Taille) { if(Taille<0) throw ErreurCreation(Taille); Elements = new int[Taille]; NbElements = Taille; for(int i=0; i<NbElements; i++) Elements[i]=0; } ~Tableau() { delete[] Elements; } int Get(int Indice) { if(Indice<0 || Indice>=NbElements) throw ErreurAcces(Indice, NbElements); return Elements[Indice]; } void Set(int Indice, int Valeur) { if(Indice<0 || Indice>=NbElements) throw ErreurAcces(Indice, NbElements); Elements[Indice] = Valeur; } void Afficher() { for(int i=0; i<NbElements; i++) cout<<Elements[i]<<' '; } }; int main() { int Taille, Indice; try { cout<<"Donner la taille du tableau : "; cin>>Taille; Tableau Tab(Taille); cout<<"Donner l'indice de la case mettre 1 : "; cin>>Indice; Tab.Set(Indice,1); Tab.Afficher(); } catch(ErreurCreation e) { cout<<e.GetMessage(); } catch(ErreurAcces e) { cout<<e.GetMessage(); } catch(Erreur e) { cout<<e.GetMessage(); } cout<<"\nFIN DU PROGRAMME\n"; return 0; }
Version 3.7
176
Karim Kalti
Remarque : Exploitation du polymorphisme dans l'interception des exceptions En dfinissant les mthodes GetMessage() comme tant virtuelles, il devient possible de rduire les trois gestionnaires de l'exemple prcdent un seul tout en obtenant le mme effet. La dfinition de ce seul gestionnaire est comme suit :
try { } catch(Erreur& e) { cout<<e.GetMessage(); }
En effet, l'interception de la rfrence de l'exception permettra dans ce cas de bnficier du polymorphisme de la mthode GetMessage(). Interception de toutes les exceptions Il existe une dfinition de l'entte du catch qui permet d'intercepter toute exception quelque soit son type. Cette dfinition se prsente comme suit : catch() S'il figure dans une structure comportant plusieurs catch, ce gestionnaire doit tre alors le dernier de la liste. En effet, tous les gestionnaires catch qui le succdent ne peuvent jamais tre atteints puisqu'il intercepte tout type d'exception.
Version 3.7
177
Karim Kalti
Annexe
Version 3.7
Karim Kalti
Les Fichiers
Techniques d'accs un fichier
x L'accs squentiel : il consiste traiter les informations "squentiellement" c'est dire dans l'ordre dans lequel elles apparaissent dans le fichier. x L'accs direct : il consiste se placer immdiatement sur l'information souhaite sans avoir parcourir celles qui prcdent.
Fermeture d'un fichier (fclose) Prototype : int fclose( FILE *F ); Paramtres : F le fichier fermer. Valeur de retour : 0 si le fichier a t convenablement ferm. Autre fonction de fermeture de fichier int _fcloseall( void ) fcloseall ferme tous les fichiers ouverts.
179
Vidage des tampons (fflush) Prototype : int fflush(FILE *stream ); <stdio.h> Paramtres : F: le tampon vider. Valeur de retour : 0 si succs et EOF si erreur. Autre fonction : int flushall(void); <stdio.h> Vide tous les tampons associs aux fichiers ouverts.
Rle : fwrite permet de transfrer un bloc de donnes de taille (count x sizeU) octets, situ en mmoire l'adresse ptr dans le fichier point par F. Valeur de retour : Nombre d'units de donnes (les SizeU) rellement crites dans le fichier. Lecture partir d'un fichier (fread) Prototype : size_t fread(void* ptr, size_t Paramtres : x x x x ptr : Adresse de dbut du bloc de mmoire dans lequel seront stockes les informations lues partir du fichier. SizeU : taille en octets du bloc unitaire de donnes lire. count : nombre de blocs unitaires de donnes lire. F : pointeur sur le fichier partir duquel les informations sont lues.
Rle : fread lit (count x sizeU) octets d'informations partir du fichier F et les place dans le bloc mmoire point par ptr. Valeur de retour : Le nombre d'units de donnes (les SizeU) rellement lues partir du fichier.
180
Dtection de la fin d'un fichier (feof) Prototype : int feof(FILE* F); Paramtre : Fichier dont on veut dtecter la fin. Valeur de retour : Vrai si la fin du fichier a t atteinte et faux sinon. Remarque : Il est noter qu'il n'est pas suffisent d'avoir lu le dernier octet du fichier pour retourner vrai mais il faut lire au del du dernier octet. Exemple 1: Le programme suivant enregistre d'une manire squentielle une suite d'entiers dans un fichier.
#include <stdio.h> void main() { char NomFichier[21]; int n; FILE* Fichier; printf("Donnez le nom du fichier crer : "); scanf("%20s", NomFichier); Fichier=fopen(NomFichier,"w"); do { printf("Donnez un entier : "); scanf("%d",&n); if(n) fwrite (&n,sizeof(int),1,Fichier); } while(n); fclose(Fichier); }
Exemple 2 : Le programme suivant fait un parcours squentiel du fichier prcdemment cr et liste son contenu.
#include <stdio.h> void main() { char NomFichier[21]; FILE* Fichier; int n; printf("Donnez le nom de fichier lire : "); scanf("%20s",NomFichier); Fichier=fopen(NomFichier,"r"); while(!feof(Fichier)) { if(fread(&n, sizeof(int),1,Fichier)) printf("\n%d",n); } fclose(Fichier); }
181
Dtermination de la position courante d'un pointeur de fichier (ftell) Prototype : long ftell( FILE * F); Valeur de retour : ftell retourne la position courante du pointeur de fichier par rapport au dbut du fichier.
182