Introduction à la programmation C++
Introduction à la programmation C++
le langage C++
Cours : Gritli Malek
Version : V 1.0
Gritli malek 1
Bibliographie
Gritli malek 2
Pourquoi le langage C++ ?
● C++ est un ''C amélioré''
Gritli malek 3
Les notions du C utilisées en C++
● Le C++ est une surcouche au langage C qui intègre les concepts de la POO
● L’objectif de B. Stroustrup en créant le C++ était de faciliter aux programmeurs C la
transition vers la programmation orientée objet.
● Le C++ est un langage typé. Le compilateur C++ vérifie donc la compatibilité des
types lors des opérations (affectations et des passages de paramètres aux
fonctions...)
● Types de base : (char, short, int, long, float,double), et les variables non signées
(unsigned char, unsigned short, unsigned int)
● Le nombre d’octets assigné à chacun de ces types peut varier d’un compilateur à un
autre.
int var;
printf("taille de var=%d, taille d’un char= %d",sizeof(var),sizeof(char));
Gritli malek 4
Les pointeurs
● Une variable de type T* est destinée à recevoir l’adresse d’une variable de type T .
● Lorsque ptr est une variable pointeur de type T* , l’opérateur * permet de
dé-référencer ce pointeur : *ptr est la variable de type T dont l’adresse est stockée
dans la variable ptr.
● & permet de fournir l’adresse d’une variable
Gritli malek 5
Les pointeurs
● Les pointeurs sont typés, le compilateur réalise aussi des vérifications de type impliquant
les pointeurs
int varI=12;
int * ptrI=&varI;
char varC=12;
char* ptrC=&varC;
ptrC=&varI ; // erreur de compilation : char * non compatible avec int *
ptrI=&varC ; // idem : int * pas compatible avec char *
● Le nom d’un tableau correspond à l’adresse de sa première case, une variable pointeur
représente l’adresse de la première case d’un tableau
Gritli malek 6
Les structures de contrôle
● Le C++ utilise la même syntaxe que le langage C pour les différentes structures de
contrôle. Les structures de contrôle sont if/else, for et while (ou do ... while).
● La structure if(...){...}else{...}
if((entier%2)==0)
{
printf("Entier pair");
}
else
{
printf("Entier impair");
}
Gritli malek 7
Les structures de contrôle
● La structure while(...){...} ou do{...}while(...);
do
{
printf("Saisir un entier impair :\n");
scanf("%d",&entier);
}
while((entier%2)==0); //boucle tant que entier pair
for(indice=1;indice<10;indice++)
{
tab[indice]=tab[indice-1]+indice;
}
Gritli malek 8
Les conversions de type (cast)
● Le compilateur C++ utilise et vérifie les types (opérations réalisées cohérentes, les
paramètres passés à une fonction correctes …)
● La conversion implicite de types (conversions que le compilateur peut déduire grâce au
contexte) : certaines conversions implicites génèrent tout de même des mises en garde
(warnings) (lorsque l’on compare un entier signé avec un entier non signé), aussi
quand on affecte la valeur d’une variable de type float à une variable de type int (ou
l’inverse) (Cette conversion est dégradante (perte d’information) dans le sens float->
int)
● Quelques conversions de types :
int varI=513; // variable entière sur 32 bits signé
char varC; // variable entière sur 8 bits signé
float varF=2.5 ; // variable utilisant la norme flottant IEEE 32 bits
varC=varI; // conversion dégradante
varI=varF; // ici, un warning signale « possible loss of data »
printf("%d %d",varC,varI); // affiche 1 (513mod256) et 2(partie entière de 2.5)
Gritli malek 9
Les conversions de type (cast)
int varI=513;
char * ptrC=(char *) &varI; // conversion int*-> char* demandée
printf("%d %d",ptrC[0],ptrC[1]); // affiche les deux octets de poids faible de varI
int varI=513;
char * ptrC=(char *) &varI; //cast int * -> char *
int adresse=(int) ptrC; // cast char * -> int (ça c’est vraiment douteux
int * pI=(int *) adresse; // cast int -> int * (aussi douteux)
Gritli malek 10
Les conversions de type (cast)
● Quelques cast utiles :
➢ T * -> V* : conversion de pointeurs. Les adresses restent intactes car elles ont la
même taille. Mais ces adresses ne désignent pas des variables de même type ! A
utiliser avec précaution.
➢ int -> char : dégradant : les 32 bits sont tronqués aux 8 bits de poids faible.
Gritli malek 11
Passage par valeur
● Les paramètres sont passés à une fonction par copie , c’est-à-dire via la pile
● Le programme qui utilise une fonction recopie dans la pile les paramètres effectifs de
la fonction, la fonction appelée n’a accès (dans la pile) qu’à une copie des
paramètres effectifs, et non à l’original
● Les paramètres effectifs et les variables locales sont des variables temporaires qui
n’existent que le temps d’exécution de la fonction
int main()
{
int pAppel=23; // paramètre d'appel
MaFonction(pAppel); // pAppel est recopié dans param
printf("d",pAppel); // pAppel vaut toujours 23
}
Gritli malek 12
Passage par adresse
● Lorsque l’on souhaite qu’une fonction modifie une variable, on doit lui fournir l’adresse
de la variable à modifier
Gritli malek 13
Transmission des paramètres : Performances
struct personne
{
char nom[30];
char prenom[30];
unsigned age;
};
● Pour les pointeurs sur des variables structurées, la notation ptr->nom est équivalente
à(*ptr).nom
Gritli malek 14
Transmission des paramètres : Performances
● L'utilisation du modificateur const pour les paramètres d’entrée (paramètres qui ne sont
pas censés évoluer dans la fonction).
● Le compilateur indique qu’une variable (ou un objet) constante est à gauche d’une
affectation (l-value)
Gritli malek 16
Les références en C++
T var;
T & ref = var; // ref est une référence à var
● Ci-dessus, ref est une référence à la variable var de type T
● Au contraire des pointeurs, les identificateurs var et ref désignent la même variable et
manipuler l'un, revient à manipuler l'autre
● L'initialisation d'une référence lors de sa déclaration est obligatoire
#include <iostream>
int main( )
{int I=3;
int &refI = I;
printf("I = %d \n",I);
printf("refI = %d \n",refI);
printf("adresse de I = %x \n",&I);
printf("adresse de refI = %x \n",&refI);
printf("Incrément de I (I++)");
I++;
printf("refI = %d \n",refI);}
Gritli malek 17
Transmission de paramètres par références
● L’utilisation de références en C++ concerne l’échange d’informations avec une fonction
#include <stdio.h>
void f(int &); // noter que le pramètre est une référence
Int main( )
{
int pAppel=5;
f(pAppel);
printf("pAppel = %d \n",pAppel); // Résultats pAppel = 6
}
void f(int & pFormel) // pFormel est un alias de pAppel
{Pformel++; // ici on modifie réellement le paramètre d’appel, grâce à l’alias}
● Ici le paramètre formel pFormel de la fonction f(int & ) est une référence au paramètre
d’[Link] ce cas, manipuler le paramètre formel pFormel revient à manipuler le paramètre
d’appel. Par la suite,on appellera ce mode de transmission passage par référence → convient
aux paramètres de sortie et aux paramètres d’entrée/sortie
● Lors de l’appel de la fonction, seule l’adresse du paramètre passé par référence est transmise
à la fonction
Gritli malek 18
Les paramètres passés par référence
constante : efficacité
Gritli malek 19
Bilan sur le passage de paramètres en C++
● Paramètre d’entrée d’une fonction : paramètre dont la valeur est utilisée par la fonction
pour réaliser un traitement mais dont la valeur n’est pas modifiée par la fonction
● Paramètre de sortie un paramètre dont la valeur en entrant dans la fonction est sans
importance mais dont la valeur va être modifiée par la fonction
● Paramètres d’entrée/ sortie
Paramètre d’entrée
Volumineux Passage par référence constante
Gritli malek 20
Bilan sur le passage de paramètres en C++ :
exemples
#include <stdio.h>
float Addition(float, float =3); //2nd paramètre avec valeur par défaut figure dans le
prototype
int main( )
{
printf("%f \n",Addition(10,5)); // 15
printf("%f \n",Addition(7)); //10
}
float Addition(float a, float b)
{return a+b;}
● Les arguments ayant des valeurs par défaut doivent tous figurer en fin de liste
desarguments
● Signature d'une fonction : la combinaison de sa classe (si elle est membre d’une
classe) de son identificateur et de la suite des types de ses paramètres. Les 3
fonctions suivantes ont des signatures différentes :
float Addition(float);
float Addition(int,float);
float Addition(float,float );
● En C++, il est possible de définir plusieurs fonctions avec le même identificateur mais
de signature différentes = la surcharge de fonction
Gritli malek 23
Introduction à la POO
●Dans chacune des classes, les objets sont caractérisés par des attributs et des
comportements
Le comportement d’un objet peut avoir des conséquences sur son état et inversement
●
●Une classe d’objets va décrire quels sont les attributs et les comportements retenus pour
décrire un objet au sein d’un programme
Gritli malek 24
Notion d'encapsulation des données
● Un objet contient des données et des méthodes prévues pour accéder à ces données
● La POO autorise la dialogue avec un objet qu'à travers l'interface constituée par
l'ensemble de ses méthodes
● Les méthodes jouent donc le rôle d'interface entre les données et l'utilisateur de l'objet.
Elles filtrent les accès aux données
● Pour assurer cette encapsulation, des mot-clés définissent la visibilité des membres
d'une classe.
Gritli malek 25
Les relations entre les objets
(par extension, entre les classes)
● Différentes relations peuvent apparaître entre des objets ou entre des classes.
● Deux objets peuvent être en relation du type composant /composite (UML introduit un
lien avec un losange du côté du composite)
Gritli malek 26
Les classes en C++
● Le mot clé class pour définir une classe d’objets avec des champs de données typés
(attributs) et des fonctions membres (méthodes)
● La classe ci-après est une classe de points à coordonnées entières, La définition de
la classe est située dans un fichier d’entête (point.h )
Gritli malek 28
Définition d'une classe
Gritli malek 29
L'implémentation d'une classe
● Le fichier d’implémentation des méthodes (ici [Link] ) contient le code des
méthodes de la classe.
Gritli malek 31
Utilisation de la classe
Gritli malek 32
A retenir sur les classes
● Les attributs privés sont néanmoins accessibles aux objets de la classe, c’est-à-dire
dans la définition des méthodes (regarder le code de la méthode distance)
● Deux objets de la même classe ont les mêmes attributs mais pas nécessairement le
même état
● L’état d’un objet de la classe Point est indéfini avant l’utilisation de la méthode
Point::SetXY()
● Faut il réaliser une initialisation correcte des attributs dès la création d’un objet ?
(oui ) → donc on doit ajouter des méthodes appelées constructeurs.
Gritli malek 33
A retenir sur les classes
● Le rôle d’un constructeur est de donner une valeur initiale (état initial) aux attributs.
● Un constructeur est une fonction membre qui porte comme nom le nom de la classe
et qui ne retourne rien
Gritli malek 34
Les constructeurs
● De multiples constructeurs au sein d'un classe d'objets Point
Gritli malek 35
Les constructeurs
Gritli malek 36
Les constructeurs
Gritli malek 37
Sélecteurs/accesseurs et modificateurs
● Pour la classe Point ,les modificateurs sont SetX(), SetY(), SetXY(), les sélecteurs sont
GetX(), GetY() ,les accesseurs sont Display(), Distance().
Celles
Celles qui utilisent l’état en lecture
qui retournent l’état de l’objet
Sélecteurs Accesseurs
Gritli malek 38
Sélecteurs/accesseurs et modificateurs
● Nous devons suivi les sélecteurs/accesseurs par un mot clé : const
class Point
{
public:
// modificateurs
void SetX(const unsigned x);
void SetY(const unsigned y);
void SetXY(const unsigned x,const unsigned y);
void Translate(const unsigned x,const unsigned y);
// sélecteurs
unsigned GetX() const;
unsigned GetY() const;
// accesseurs
float Distance(const Point & p) const;
void Display(std::ostream & flot) const;
};
Gritli malek 39
Constructeur de copie, opérateur
d’affectation
●
Un constructeur de copie sert à créer un nouvel objet ayant le même état qu’un objet
de la même classe.
Gritli malek 40
Constructeur de copie, opérateur
d’affectation
Gritli malek 42
Le pointeur this
int main(){
Point p1(10,20), p2,p3;
p3=p2=p1;
/* équivaut à [Link]=([Link]=(p1)); */
}
● Il est possible de surcharger la plupart des opérateurs
● Il n’est pas possible de redéfinir les opérateurs agissant sur les types élémentaires tels
que int , float , etc.
Gritli malek 43
Les opérateurs en C++
● L’opérateur sera qualifié d’opérateur unaire (1 seul opérande) ou d’opérateur binaire (2
opérandes). l’opérateur « = » est un opérateur binaire, la syntaxe d’utilisation de cet
opérateur étant Op1 = Op2 . L’ opérateur « ++ » en revanche est un opérateur unaire.
En effet, il s’applique à un seul opérande : Op1++ .
● Certains opérateurs peuvent avoir une version unaire et une version binaire. C’est le
cas par exemple de l’opérateur « - ». La syntaxe a-b utilise l’opérateur – binaire
(soustraction) tandis que –a utilise l’opérateur unaire -(opposé)
● Certain opérateurs ont un traitement par défaut (affectation sur des variables
structurés,l’opérateur & (opérateur unaire) retourne l’adresse de l’objet auquel il
s’applique.
Gritli malek 44
Surcharger des opérateurs en C++
● Les différents opérateurs que l’on peut surcharger en C++ sont les suivants :
Gritli malek 45
Surcharger des opérateurs en C++
● La définition des opérateurs passe simplement par l’écriture de fonctions ayant un nom
particulier comportant le mot clé operator suivi du signe de l’opérateur
● Le programmeur aura le choix entre la possibilité de définir ces opérateurs comme
fonctions membres d’une classe ou comme fonctions non membres
● Certains opérateurs ne peuvent être définis que s’ils appartiennent à une classe
comme les suivants :
= (opérateur d'affectation)
( ) (opérateur fonction)
[ ] (opérateur d’indexation)
->
Gritli malek 46
Surcharger des opérateurs en C++
● Un opérateur est utilisé sur un type défini par le programmeur (une classe ou un type
structuré)
● L’emploi de cet opérateur est équivalent à l’appel d’une fonction particulière qui peut
être hors classe ou membre d’une classe (ou d'une structure)
● On définit une nouvelle classe CLS , et deux objets x et y de la classe CLS , alors la
syntaxe (1) peut être équivalente à (2) ou (3) pour le compilateur C++
(1) x+y;
(2) operator+(x,y); // opérateur non membre de la classe CLS
(3) [Link]+(y); // opérateur membre de la classe CLS
Gritli malek 47
Exemple : classe de nombres rationnels
● On fournit ici une ébauche d’une classe de nombre rationnels mettant en évidence l'intérêt
des opérateurs.
Gritli malek 49
Exemple : classe de nombres rationnels
// Sélecteurs
int Rationnel::GetNum() const { return _num; }
int Rationnel::GetDen() const { return _den; }
// opérateur pour l’affichage d’un rationnel (non membre)
std::ostream & operator<<(std::ostream & flot, const Rationnel & r)
{
flot << [Link]() << "/" << [Link]();
return flot;
}
// opérateur d’affectation (obligatoirement membre)
Rationnel & Rationnel::operator=(const Rationnel & r)
{
_num=r._num;
_den = r._den;
return *this;
}
Gritli malek 50
Exemple : classe de nombres rationnels
Gritli malek 51
Exemple : classe de nombres rationnels
Gritli malek 52
classe de nombres rationnels : remarques
● Remarquer que la somme ne modifie pas les lobjets argument (référence constante)
Gritli malek 53
Classes paramétrées en type (la bibliothèque STL)
● En C++ au sein d’une classe ou d’une fonction, certains types peuvent être passés en
paramètre.
● Le mot clé template est introduit en C++ pour pouvoir paramétrer des types
● Une bibliothèque standard de classes paramétrées en type, libre d’utilisation, est fournie
avec la plupart des compilateurs C++ ((Standard Template Library STL)
Gritli malek 54
Notion de conteneur
Gritli malek 55
Notion de conteneur
● Tous les conteneurs STL sont paramétrés en type. Cela signifie qu’on peut choisir le type
des données stockées dans le conteneur
● On note vector<T> la classe obtenue à partir du conteneur vector pour le type T
#include<iostream> // entête pour les classe istream et ostream
#include<vector> // entête pour le conteneur vector
using namespace std; // la bibliothèque STL utilise le namespace std
int main ( )
{vector<int> tab1(4); // objet de la classe vector<int>
vector<float> tab2(2); // objet de la classe vector<float>
vector<vector<int> > tab3(2); //objet de la classe vector<vector<int> >
Tab1[1=3; // les éléments stockés dans tab1 sont des entiers
Tab2[0]=3.7; // les éléments stockés dans tab2 sont des float
tab3[0]=tab1; // tab3 stocke des tableaux d’entiers (vector<int>)
for(unsigned i=0;i<[Link]();i++) // affichage des éléments de tab1
{
cout << tab1[i] << " ";
}}
Gritli malek 56
Notion de conteneur
● L’accès à une case d’un objet vector<T> se fait simplement avec l’opérateur [
Gritli malek 57
Namespace
● Avant d’aller plus loin dans la présentation de la STL, il semble temps d’expliquer ce
que représente un namespace (espace de noms)
● Différentes classes ou fonctions peuvent être regroupées dans un espace de noms
particulier pour éviter certains conflits d’identifiant
● Pour utiliser une classe d’un espace de noms particulier, on doit faire précéder le nom
de la classe du nom de l’espace de noms
#include <iostream>
namespace NA{
void MaFonction()
{
std::cout << "Fonction du namespace NA \n";
}}
namespace NB{
void MaFonction()
{
std::cout << "Fonction du namespace NB \n";
}}
Gritli malek 58
Namespace
● Il n’y a pas de conflit de nom puisque les deux fonctions sont définies dans deux
espaces de noms distincts (les namespaces NA et NB ). On voit aussi que le flot cout
dépend du namespace std .
● Toutes les classes de la STL appartiennent au namespace std . Il convient donc de
préciser ce namespace pour l’utilisation de ces classes
● On ne peut pas utiliser la directive using namespace dans les fichiers d’entête
Gritli malek 59
Classe string
● N’est pas une classe paramétrée. Elle sert uniquement à gérer des chaînes de
caractère de façon un peu plus souple qu’en langage C
Gritli malek 60
Classe string
➢ un constructeur de copie pour créer une chaîne identique à une chaîne existante
➢ un opérateur d’affectation : =
➢ les opérateurs de comparaison selon l’ordre lexicographique (l’ordre des mots dans un
Dictionnaire) : <= < >= >
Gritli malek 61
Classe string
int main( )
{
string s1("abcdefg");
cout << "Longueur = " << [Link]() << endl;
cout << "Capacite = " << [Link]() << endl;
cout << s1 << endl; // s1 = abcdefg
[Link](1,"ABC"); // insère la chaîne "ABC" à l'indice 1
cout << s1 << endl; // s1 = aABCbcdefg
[Link](2,4,'a'); // insère 4 fois la lettre 'a' à l'indice 2
cout << s1 << endl; // s1 = aAaaaaBCbcdefg
[Link](0,1); // supprime 1 caractère à l'indice 0
cout << s1 << endl; // s1 = AaaaaBCbcdefg
[Link](2,5); // supprime 5 caractères à l'indice 2
cout << s1 << endl; // s1 = Aabcdefg
const char * ptr=s1.c_str(); // possibilité d'obtenir un pointeur compatible const char *
}
Gritli malek 62
Conteneurs vector<T>
● La classe paramétrée vector<T> permet de générer des objets tableaux pouvant
stocker des objets de n’importe quel type
#include<iostream>
#include<vector>
using namespace std;
void main(void)
{
vector<int> t1(4,-2),t2(5); // t1=-2,–2,–2,-2 t2=0,0,0,0,0
vector<float> t3(2),t4(4,1.2); // t3=0,0 t4=1.2,1.2,1.2
T1[0]=5; //t1=5,–2,–2,-2
T4[2]=-2.3; //t4=1.2,1.2,-2.3
t2=t1; // possible car t1 et t2 de la même classe
t3=t4; // possible car t3 et t4 de la même classe
// t1=t4; impossible car t1 et t4 de types différents
vector<int> t5(t1); // t5=5,–2,–2,-2
vector<float> t6(t3); // t6=1.2,1.2,-2.3
Gritli malek 64
Les itérateurs
● Les classes conteneur implémentent la notion d’itérateur qui généralise la notion de
pointeur
● Les opérateurs ++ et –sont surchargés sur les itérateurs pour passer à l’emplacement
suivant ou précédent du conteneur
● L’opérateur * permet l’indirection sur un itérateur (comme sur un pointeur)
● Grâce aux itérateurs, les algorithmes s’appliquant aux conteneurs sont moins
dépendants de leur structure interne.
● Les classes conteneurs contiennent des classes membres publiques pour instancier des
itérateurs adaptés. Il y a deux classes d’itérateurs par conteneur : les itérateurs
constants et les itérateurs non constants. Les premiers permettent de parcourir un
conteneur sans pouvoir en modifier le contenu.
int main( )
{vector<int> t1(4);
vector<int>::iterator it; // it est un iterateur sur vector<int>
for(it=[Link]();it!=[Link]();it++) (*it)=2;
for(unsigned i=0;i<[Link]();i++) t1[i]=2;
}
Gritli malek 65
Les itérateurs
Gritli malek 67
Conteneurs list<T>
Gritli malek 68
Conteneurs list<T>
l4=l2; //l4=2.3,2.7,-1.2
[Link]([Link](),2,-3); //l1=-3,-3,7,-3,12,13
cout << [Link]() << [Link]() << endl; // -3 13
list<int>::iterator it=[Link]();
while(it!=[Link]()){
if((*it)==-3) // suppression des noeuds valant -3
{
it=[Link](it);
}
else it++;
}
// affichage de la liste l1
for(it=[Link]();it!=[Link]();it++) cout << (*it) << " ";
}
Gritli malek 69
La composition
● Certains objets vont être composés d’objets d’autres classes
● On peut décrire la composition de deux manières :
→ soit les objets composants apparaissent comme attributs des objets composites
→ soit une relation de composition (lien avec losange côté composite) lie les classes
composant et composite.
Gritli malek 70
Objets composants comme attributs d’une
classe
Gritli malek 71
Objets composants comme attributs d’une
classe
Gritli malek 72
Attributs de type tableau d'objets
● Pour la classe Point , on peut voir les coordonnées comme un tableau d'objets
Rationnel à deux cases. Cette autre réalisation est mise en œuvre dans la classe
PointTab ci-dessous.
// pointtab.h
#ifndef _POINTTAB__
#define _POINTTAB__
#include "rationnel.h"
class PointTab
{
public:
PointTab(const Rationnel & x, const Rationnel & y);
Rationnel GetX() const;
Rationnel GetY() const;
private:
Rationnel _XY[2];
// tableau de deux objets Rationnel
};
#endif
Gritli malek 73
Attributs de type tableau d'objets
//[Link]
#include "PointTab.h"
Gritli malek 74
Objet membre de type vector<T>
#ifndef _POINTTAB__
#define _POINTTAB__
#include "rationnel.h"
#include<vector>
class PointTab
{
public:
PointTab(const Rationnel & x, const Rationnel & y);
Rationnel GetX() const;
Rationnel GetY() const;
private:
std::vector<Rationnel> _XY;
};
#endif
Gritli malek 75
Objet membre de type vector<T>
//[Link]
#include "PointTab.h"
// Attention ! Utiliser la liste d'initialisation pour initialiser le vecteur à la taille 2
PointTab::PointTab(const Rationnel & x, const Rationnel & y):_XY(2)
{
_XY[0]=x;
_XY[1]=y;
}
Gritli malek 76
La spécialisation en C++
● Une classe de dés à 6 faces peut être vue comme une spécialisation d’une classe dedés à n
faces : il suffit de fixer le nombre de faces
Gritli malek 77
La spécialisation en C++
Gritli malek 78
La spécialisation en C++
Gritli malek 79
La spécialisation en C++
Gritli malek 80
La spécialisation en C++
Gritli malek 81
La spécialisation en C++
#ifndef __CLS_DE6COUL__
#define __CLS_DE6COUL__
#include "De6.h"
#endif
Gritli malek 82
La spécialisation en C++
#include "De6Couleur.h"
De6Couleur::De6Couleur(unsigned c):De6(),_couleur(c)
{
}
Gritli malek 83
Les conversions de type entre sous-classe
et super-classe.
● En C++, les conversions de type entre sous-classe et super classe sont légales. Par
exemple, on peut convertir un objet De6 en objet DeNFaces .On peut de la même
manière convertir un objet de la classe De6Couleur en un objet de la classe
DeNFaces .
● Lorsque l’on convertit un dé coloré en dé (sans couleur) on perd l’information de
couleur (transformation dégradante ,perte de la la partie spécifique d’une classe
dérivée)
#include "De6Couleur.h"
#define rouge 5
int main(int)
{
De6Couleur de6r(rouge);
De6 de6;
DeNFaces denf(9);
((DeNFaces)de6r).GetValeur(); // cast De6Couleur -> DeNFaces
((DeNFaces)de6).GetValeur(); // cast De6 -> DeNFaces
}
Gritli malek 84
La conversions de type entre sous-classe et
super-classe.
● Conversion sous-classe * -> super-classe * : les conversions de pointeurs entre
sous-classe et super-classe sont également légales en C++
#include "De6Couleur.h"
#define rouge 5
int main( )
{
De6Couleur de6r(rouge);
De6 *ptrDe6;
DeNFaces *ptrDeNF;
ptrDe6=&de6r; // De6Couleur * -> De6 *
ptrDe6->Lancer();
ptrDeNF=ptrDe6; // De6 * -> DeNFaces *
ptrDeNF->Lancer();
}
Gritli malek 85
Le polymorphisme et les méthodes virtuelles
● En C++, le polymorphisme représente aussi la possibilité pour des objets d'une
descendance à répondre différemment lors de l'appel d'une méthode de même nom.
Le polymorphisme est donc lié dans ce cas aux hiérarchies de classes.
#include "Rectangle.h"
#include "Cercle.h"
#include<iostream>
using namespace std;
int main( )
{
Fenetre f; // objet fenêtre graphique
ObjetGraphique * tab[3];
tab[0]=new Cercle;
tab[1]=new Rectangle;
tab[2]=new Polygone ;
// méthode de la classe ObjetGraphique
// Invoquée car la méthode n’est pas
//polymorphe par défaut
tab[0]->DessinerDans(f);
tab[1]->DessinerDans(f);
tab[2]->DessinerDans(f);
for(int i=0;i<3;i++) delete tab[i];
Gritli malek 86
}
Le polymorphisme et les méthodes virtuelles
● Si la méthode DessinerDans() n'est pas déclarée virtual dans la classe de base
ObjetGraphique , c'est nécessairement la méthode de la classe ObjetGraphique qui va
être invoquée, même si l'objet pointé est de la classe Rectangle ou Cercle
● Le C++ laisse la possibilité de mettre en place ou non le polymorphisme (via les
méthodes virtuelles) pour des raisons de performance. Car la définition de méthodes
virtuelles (voir ci-dessous) implémente une table d’indirection supplémentaire qui
augmente la taille du code et ralentit l’exécution
#ifndef __RECTANGLE__
#ifndef __OBJETGRAPHIQUE__ #define __RECTANGLE__
#define __OBJETGRAPHIQUE__ #include "ObjetGraphique.h"
#include "Fenetre.h" class Rectangle: public ObjetGraphique
class ObjetGraphique {
{ public:
public: Rectangle();
ObjetGraphique(); virtual void DessinerDans(Fenetre &)
virtual void DessinerDans(Fenetre &) const; const;
}; };
#endif #endif
Gritli malek 87
Les classes abstraites
● Toutes les formes géométriques devront pouvoir réagir à des mêmes demandes (mêmes
méthodes (dessiner , supprimer ...)).
● la classe ObjetGraphique va être une classe dite [Link] contiendra des fonctions
virtuelles pures, c’est-à-dire des fonctions membres qui ne seront pas définies
● Les fonctions virtuelles pures n’ont pas de code et ont un prototype qui se termine par =0
● Une classe qui contient au moins une fonction virtuelle pure est dite abstraite. On ne peut
alors pas créer d’objet d’une telle classe (mais on pourra créer des objets d’une classe
dérivée).
Gritli malek 88
Les classes abstraites
#ifndef __OBJETGRAPHIQUE__
#define __OBJETGRAPHIQUE__
#include "Fenetre.h"
#include "Point.h"
class ObjetGraphique // classe abstraite
{
public:
ObjetGraphique();
virtual ~ObjetGraphique(); // destructeur virtuel
};
#endif
Gritli malek 89
Les classes abstraites
● On ne peut pas créer d’objet de la classe ObjetGraphique , mais en revanche, on
peut faire un conteneur d’objets graphiques (objets hétérogènes).
● En résumé, une classe abstraite décrit ce qu’on attend d’une classe (comportement)
sans savoir comment cela va être réalisé. En fait le comportement final est
programmé dans la ou les classes descendant de la classe abstraite.
int main()
{
vector<ObjetGraphique *> FormesGraphiques;
Fenetre F ;
FormesGraphiques.push_back(new Rectangle(10,10,40,55));
FormesGraphiques.push_back(new Ellipse(60,60,140,155));
FormesGraphiques.push_back(new Rectangle(30,30,40,40));
for(int i=0;i<[Link]();i++)
FormesGraphiques[i]->DessinerDans(F);
for(int i=0;i<[Link]();i++) delete FormesGraphiques[i];
}
Gritli malek 90
Visibilité des membres d’une classe
● Les membres public d’une classe sont accessibles à la fois aux objets de la classe
ainsi qu’à l’utilisateur de la classe
● L’encapsulation des données d’une classe est possible grâce au mot-clé private,
l’utilisateur ne peut pas agir directement sur les données membres d’un objet
● Comment des fonctions non membres d’une classe, ou comment d’autres classes,
peuvent avoir également accès à la partie encapsulée d’une classe donnée ?
Gritli malek 91
Mot-clé friend : fonctions amies
● On illustre ceci en reprenant la classe Rationnel vue dans la section sur les
opérateurs en définissant les opérateurs + et * comme des fonctions non membres
de la classe Rationnel mais amis de cette classe. Les membres _den et _num
deviennent accessibles à ces fonctions non membres.
class Rationnel
{
// fonctions non membres mais amies de la classe
friend Rationnel operator+(const Rationnel & r1,const Rationnel & r2);
friend Rationnel operator*(const Rationnel & r1,const Rationnel & r2);
...
};
#include "Rational.h"
#include <iostream>
Rationnel operator+(const Rationnel & r1,const Rationnel & r2){
Rationnel local(r1._num*r2._den+r2._num*r1._den,r1._den*r2._den);
return local;
}
Rationnel operator*(const Rationnel & r1,const Rationnel & r2){
Rationnel local(r1._num*r2._num,r1._den*r2._den);
return local;
} Gritli malek 92
Mot-clé friend :classes amies
● La classe B est déclarée amie de la classe A . Cela signifie que toutes les méthodes
de la classe B peuvent avoir accès à la partie privée de la classe A
● Il est préférable de chercher à éviter autant que possible d’utiliser l’amitié (pas
sécurisé du tout )
#include "B.h"
class A
{
public:
friend B; // la classe B est amie de la classe A
...
};
Gritli malek 93
Membres protégés (mot-clé protected )
● Un membre (donnée ou méthode) déclaré protected est accessible par les objets de
la classe mais pas par l’utilisateur de la classe (comme pour un membre privé)
#ifndef ___CLS_DERIVEE__
#ifndef ___CLS_BASE__ #define ___CLS_DERIVEE__
#define ___CLS_BASE__ #include "Base.h"
class Base class Derivee : public Base
{ {
public: public:
Base(); Derivee();
MethodeB(); MethodeD();
protected: protected:
bool _indPProtegeB; bool _indProtegeD;
void FonctionProtegeeB(); void FonctionProtegeeD();
private: private:
int _dPriveeB; int _dPriveeD;
void FPriveeB(); void FPriveeD();
}; };
#endif #endif
Gritli malek 94
Membres protégés (mot-clé protected )
Gritli malek 95
Formes de dérivation possibles
Gritli malek 96
Phénomènes de masquage
● La classe dérivée comporte, comme la classe de base, une méthode Affiche()
● Sur un objet Derivee , la méthode Affiche() appelée est nécessairement celle de la classe
dérivée. On dit qu’elle masque celle de la classe de base
● Pour éviter le masquage ,il faut préciser le nom de la classe lors d’un appel de méthode
Gritli malek 97
Phénomènes de masquage
#include "derivee.h"
int main()
{
Derivee D;
Base B;
[Link]();
[Link]();
[Link]();
[Link](); // Derivee::Affiche() masque ici
[Link]::Affiche(); // appel explicite de la méthode de la classe de base
[Link]::Affiche() ; // équivalent à [Link]();
Gritli malek 98
Classes avec données en profondeur
T * ptr;
ptr = new T; // allocation d'un objet (ou une variable) de type T
ptr = new T[n]; // allocation d'un tableau de n objets de type T
delete ptr;
delete [] ptr;
● Cette seconde syntaxe assure que tous les destructeurs des objets du tableau sont
appelés avant que le tableau soit libéré. Ceci est important si les objets du tableau
ont des données en profondeur
Gritli malek 99
Classes avec données en profondeur
● Un objet composant n'est pas réellement dans la classe mais est connu via un
pointeur
● Les données de l'objet composant n'étant plus dans l'objet composite, on dit que les
données sont en profondeur (obtenues indirectement par un pointeur)
#ifndef _POINT__
#define _POINT__
#include "rationnel.h"
class Point
{public:
Point(const Rationnel & x, const Rationnel & y);
Point(const Point & p);
~Point(); //destructeur
Point & operator=(const Point & p);
Rationnel GetX() const;
Rationnel GetY() const;
private:
Rationnel * _XY; // pointeur sur le composant
};
#endif
#ifndef __CLS_TABLEAU__
#define __CLS_TABLEAU__
class Tableau
{
public:
Tableau(unsigned taille=5);
Tableau(const Tableau & tab);
Tableau & operator=(const Tableau & tab);
virtual ~Tableau();
double & operator[](unsigned idx);
double operator[](unsigned idx) const;
void NouvelleTaille(unsigned taille);
unsigned GetTaille() const;
unsigned GetCapacite() const;
private:
double * _tab;
unsigned _capacite;
unsigned _taille;
};
#endif
Gritli malek 103
Classe d'objets tableaux
#include "Tableau.h"
#include<process.h>
#include<iostream>
using namespace std;
Tableau::Tableau(unsigned taille):_taille(taille),
_capacite(2*taille), _tab(new double[2*taille])
{
if(_tab==NULL){
cerr << "echec d'allocation";
exit(2);
}
// les éléments du tableau sont initialisés à 0
for(unsigned i=0;i<_taille;i++) _tab[i]=0;
}
Tableau::~Tableau(){
delete [] _tab;
}
// constructeur de copie
Tableau::Tableau(const Tableau & t):_taille(t._taille),
_capacite(t._capacite),_tab(new double[t._capacite])
{
if(_tab==NULL){
cerr << "echec d'allocation";
exit(2);
}
for(unsigned i=0;i<_taille;i++) _tab[i]=t._tab[i];
}
double & Tableau::operator [](unsigned idx)
{
if(idx>=GetTaille()){
// verification d’indice
cerr << "indice incorrect!";
exit(2);
}
return _tab[idx];
}
● Il faut noter que l’opérateur d’indexation [] retourne une référence. Ainsi on peut
modifier un élément du tableau via cet opérateur
● Il faut également remarquer que la classe dispose de deux opérateurs d’indexation.
Un opérateur constant qui peut être appelé sur des objets constants (c’est-à-dire des
tableaux constants) (renvoie donc le contenu de la case par valeur) et un opérateur
d'indexation (non constant) quant à lui retourne une référence
● On ne peut pas appeler sur un objet constant que les méthodes constantes : ici il
s’agit de GetTaille(), GetCapacite() et un opérateur []
● Il reste néanmoins préférable d’utiliser la bibliothèque STL plutôt que de redéfinir des
conteneurs.
Gritli malek 108
Les fonctions paramétrées en type
● On peut paramétrer les types, on n’a qu’une seule définition à donner. C’est ce que
permet la notation template
● La notation template<class T> indique au compilateur que T est un paramètre de
type (type primitif,structuré ou classe). La fonction permute peut alors être appelée
avec deux arguments d'un type quelconque,dans la mesure où les deux arguments
sont du même type
● Dans un patron de fonctions, on peut aussi faire apparaître des arguments typés
normalement. Ces paramètres sont parfois appelés paramètres expression
#include<iostream>
#include<string.h>
using namespace std;
//patron de fonctions
template <class T> T min(T a,T b){ return a<b ? a : b; }
//spécialisation du patron pour le type char *
char * min(char * s1,char * s2)
{
if(strcmp(s1,s2)<0)
return s1;
else
return s2;
}
int main()
{
int a=1,b=3;
char chaine1[]="coucou",chaine2[]="salut";
cout << min(a,b); //générée à partir du patron
cout << min(chaine1,chaine2); //spécialisation
}
Gritli malek 111
Classes paramétrées en type
● Jour est un type et {lundi,mardi …} sont des valeurs pour une variable de type jour
enum jour{lundi,mardi,mercredi,jeudi,vendredi,samedi,dimanche};
class Calendrier
{
public:
enum jour{lundi,mardi,mercredi,jeudi,vendredi,samedi,dimanche};
Calendrier();
//...
};
Calendrier::jour j=Calendrier::mardi;
● Le type jour, ainsi que ses valeurs possibles, appartiennent à la classe Calendrier
vecteur v1(3);
vecteur::iterateur i=[Link]();
● Ces modes peuvent être combinés par l'opérateur |, par exemple ios::out|ios::app .
#include<fstream>
#include<iostream>
using namespace std;
int main()
{
[Link]('a');
[Link]('\n');
char str[]={'a','b','c',0,'f','g','h'};
cout << str << endl;
[Link](str,7);
}
int main()
{
float f1=1.3698;
cout << f1 << endl; // sortie formatée -> 1.3698
[Link](3); // changement de la précision
cout << f1 << endl; // sortie formatée -> 1.37
}
● La classe fstream hérite des méthodes des classes istream et ostream , donc de
celles présentées précédemment
➢ fstream::seekp(déplacement, position)
➢ fstream::tellp()
#include "complexe.h"
void main()
{
complexe c1,c2(1,3); //1)-2)
c1=2.4; //3)
cout << c1 << endl; //4)
c2=c1+c2; //5)-6)
cout << c2 << endl; //7)
c1=c1+(complexe)3.5; //8)-9)-10)
cout << c1 << endl; //11)
c1=(double)c1+3.5 //12)-13);
cout << c1 << endl; //14)
double d;
d=c1; //15)
cout << d << endl; //16)
// c1=c1+3.5; // << error c2666 :
2 overloads have similar conversions
}