Vous êtes sur la page 1sur 26

Informations pratiques

Programmation orientée objet - C++

I Responsable : Julien MILLE, julien.mille@insa-cvl.fr


I Répartition :
GSI 3A
I CM : 1h20 × 6
INSA Centre Val de Loire I TD : 1h20 × 7 + 2h40 sur machine
I TP : 2h40 × 5
Laboratoire d’Informatique Fondamentale I Evaluation :
et Appliquée de Tours (LIFAT) I Interrogation courte en TD
I Contrôle continu d’1h
Julien Mille - version 1.0 I Dernier TP noté

Aperçu du cours Bibliographie

I Rappels de C
I Adresses, pointeurs
I Passage de paramètres
I Types de données, structures I B. Stroustrup. The C++ Programming Language. 4th edition, Addison
I C++ Wesley, 2013
I Entrées/sorties I C. Delannoy. Programmer en langage C++. 6ème édition, Eyrolles, 2004
I Références I S. Meyers. Effective C++. 3rd Edition, Addison Wesley, 2005
I Opérateurs new et delete
I Surcharge d’opérateurs I https://cplusplus.com, https://en.cppreference.com
I Programmation orientée objet en C++
I Classe, attribut, méthode
I Abstraction, encapsulation
I Héritage

3 4

Plan Fonctionnement de la mémoire


I La mémoire d’un ordinateur est composée d’un certain nombre de ”cases”
(1 case = 1 octet)
I Toute déclaration d’une variable entraı̂ne l’allocation d’une ou plusieurs
Rappels de C : pointeurs et tableaux case(s) mémoire pour cette variable.
I Le nombre de cases occupées dépend du type de la variable (ex : un int
occupe 4 octets, un double 8 octets, etc)
Rappels de C : types de données
I Ces emplacements mémoire sont identifiés par une adresse unique.
I Le compilateur traduit les noms de variables en adresses.
C++ : entrées/sorties, référence, surcharge d’opérateurs I Lorsqu’une variable est lue ou écrite, à l’exécution du programme, le
processeur va lire ou écrire à l’adresse correspondante.
I Dans l’ensemble du cours, cette représentation graphique sera utilisée :
C++ : programmation orientée objet avec les classes adresse
contenu

nom de variable
I Remarque : contenu et valeur sont synonymes
5 6
Fonctionnement de la mémoire Représentation des variables en mémoire
I Rigoureusement, l’adresse d’une variable est en fait l’adresse du premier
1 octet occupé par cette variable
I Une adresse est écrite par convention en hexadécimal I Souvent, le compilateur va positionner les variables locales d’une fonction à
I La base hexadécimale comporte 16 chiffres, de 0 à F des adresses contigües
I En C, le préfixe 0x indique que l’entier qui suit est en hexadécimal I Dans l’exemple suivant, les positions des variables sont contigües et suivent
l’ordre de leur déclaration (ce n’est pas toujours le cas)
Décimal Héxadécimal Décimal Héxadécimal Décimal Héxadécimal
int main()
0 0x0 11 0xB 22 0x16
{
1 0x1 12 0xC 23 0x17
2 0x2 13 0xD 24 0x18
int x1;
3 0x3 14 0xE 25 0x19 int x2;
4 0x4 15 0xF 26 0x1A int x3;
5 0x5 16 0x10 27 0x1B x1 = 10;
6 0x6 17 0x11 28 0x1C x2 = 15;
7 0x7 18 0x12 29 0x1D x3 = x1+x2;
8 0x8 19 0x13 30 0x1E ...
9 0x9 20 0x14 31 0x1F
10 0xA 21 0x15 32 0x20 A A+1 ... A+4 ... A+8 ...

10 15 25

1. La base héxadécimale est également abordée dans le cours de Microcontrôleurs 1 7 x1 x2 x3 8

Pointeurs Pointeurs

I Déclaration : pour déclarer un pointeur, on a besoin de connaı̂tre le type


I En C, les adresses mémoires sont manipulées via les pointeurs des données contenues dans l’emplacement mémoire pointé
I Il est possible d’accéder à un emplacement mémoire directement par son I Un pointeur contient l’adresse de la première case de l’emplacement
adresse, sans qu’une variable soit obligatoirement associée à cet mémoire pointé
emplacement I Syntaxe : type pointé *pointeur;
I Pour cela, on utilise un pointeur, qui est une variable dont le contenu (= I Exemple 1 : int *p1;
la valeur) est... une adresse ! I p1 est une variable prévue pour contenir l’adresse d’un emplacement
I On dit alors qu’elle ”pointe” sur cet emplacement mémoire mémoire contenant un entier. On dit que p1 pointe sur un entier
I Un pointeur est une variable comme une autre ! I Exemple 2 : float *p2;
I p2 est une variable prévue pour contenir l’adresse d’un emplacement
mémoire contenant un réel. On dit que p2 pointe sur un réel

9 10

Pointeurs Pointeurs

I Comment accéder à la valeur contenue dans l’emplacement pointé ?


I Un pointeur contenant l’adresse d’une variable, on a besoin d’un opérateur I On a besoin d’un opérateur permettant de passer d’une adresse mémoire à
permettant de passer d’une variable à son adresse. la valeur contenue à cette adresse.
I On utilise l’opérateur &, qui signifie ”adresse de ..” I On utilise l’opérateur * (dit opérateur de déréférencement ou opérateur
I Exemple : d’indirection), qui signifie ”valeur stockée à l’adresse...”
int x=5; I Exemple :
int *pointeur1; int x=5;
pointeur1=&x; /* affecte l’adresse de x int *pointeur1;
au pointeur pointeur1 */ pointeur1=&x;
*pointeur1 = 2; /* Modifie la valeur dans
I La valeur de pointeur1 est l’adresse de la première case mémoire occupée l’emplacement dont l’adresse
par la variable x. est contenue dans pointeur1 */

I En modifiant *pointeur1, on modifie donc également x

11 12
Pointeurs Pointeurs et tableaux
I Exercice : donnez la sortie à l’écran du programme suivant :
int main()
{ I Un tableau est en réalité un pointeur vers un nombre fini de cases mémoire
int var1 = 5, var2 = 10; allouées (= réservées pour stocker les élements du tableau).
int * p; I Les éléments du tableau sont obligatoirement contigus, et à des adresses
p = &var1; croissantes.
printf("var1 = %i, valeur pointée par p = %i\n", I Le compilateur traduit le nom du tableau en l’adresse de la première case
var1,*p); du tableau.
var1 = 20; I Soit la déclaration suivante :
printf("valeur pointée par p = %i\n",*p);
*p = 2; int tab[10];
printf("var1 = %i\n",var1); I tab est en fait le nom du pointeur contenant l’adresse de la première case
p = &var2; du tableau. On sait qu’à partir de l’adresse tab, 10 fois la taille d’un entier
printf("valeur pointée par p = %i\n",*p); a été allouée.
return 0;
}

13 14

Arithmétique des pointeurs Arithmétique des pointeurs

I Il est possible d’accéder aux valeurs ou aux adresses des différents éléments
du tableau en décalant le pointeur tab. I Exercice : soit la déclaration suivante
int tab[10]; int tab[]={12, 23, 34, 45, 56, 67, 78, 89, 90};
int i=0;
*(tab + 4) = 2; I Quelles valeurs ou adresses fournissent ces expressions ? (on considère que
for (i=0;i<10;i++) tab pointe sur l’adresse 0x500)
*(tab + i) = 0; tab+1
*tab+2
I Quand on ajoute une valeur entière x à un pointeur, il est décalé d’un *(tab+2)
nombre de cases égal à la taille du type multiplié par x &tab[4]-3
I Il est également possible d’accéder aux adresses des différents éléments du tab+2
tableau en décalant le pointeur : &tab[0]
tab+(*tab-10)
tab = adresse de la première case
*(tab+*(tab+8)-tab[7])
tab + i = adresse de la ième case du tableau
I Les expressions tab+i et &tab[i] sont donc équivalentes !

15 16

Passage de paramètres Passage de paramètres


I Exemple de passage par valeur :
void echangeRecopie(int x1, int x2)
{
I Rappel : le passage de paramètres est le mécanisme par lequel les int tmp = x1;
paramètres sont créés à partir des arguments fournis lors de l’appel de la x1 = x2;
fonction. x2 = tmp;
I En C, le passage de paramètres se fait toujours par copie : lors de l’appel }
d’une fonction, les paramètres sont définis et initialisés avec la valeur des
arguments associés. Dit autrement, les paramètres sont des copies int main()
indépendantes des arguments. {
I Peut-on faire en sorte qu’une fonction modifie les arguments qui lui sont int variable1 = 1;
int variable2 = 2;
passés ?
echangeRecopie(variable1,variable2);
...
}

I Que valent variable1 et variable2 après l’appel à echangeRecopie ?


17 18
Passage de paramètres Passage de paramètres
I Exemple de passage par pointeur :
void echangePointeur(int *x1, int *x2)
I Pour qu’une fonction puisse modifier une variable passée en argument, il {
faut qu’elle soit capable d’y accéder. int tmp = *x1;
I La solution est que l’échange paramètres/arguments se fasse via les *x1 = *x2;
adresses des emplacements mémoire, et non via les valeurs contenues dans *x2 = tmp;
ces emplacements. }
I Ainsi, la fonction a connaissance du ou des emplacement(s) mémoire
int main()
qu’elle doit modifier.
{
I Les paramètres et arguments de la fonction ne sont donc plus des valeurs, int variable1 = 1;
mais des adresses. int variable2 = 2;
I On parle alors de passage par adresse, ou passage par pointeur. echangePointeur(&variable1,&variable2);
I Une fonction bien connue prenant en paramètre un ou plusieurs ...
pointeur(s) : scanf }

I Penser à l’opérateur d’accès à l’adresse & !


I Que valent variable1 et variable2 après l’appel à echangePointeur ?
19 20

Tableaux dynamiques Tableaux dynamiques


I Les pointeurs permettent de manipuler des tableaux dynamiques
I Inconvénients des tableaux statiques :
I Utilisation de la fonction malloc (déclarée dans stdlib.h)
I Taille fixée dans le code du programme.
I Obligation d’utiliser un tableau de grande taille lors de la déclaration pour I Prototype : void *malloc(size t)
que le programme ne stocke pas le même nombre de valeurs à chaque I Rôle : alloue un certain nombre d’octets en mémoire (passé en paramètre)
exécution → espace inutilisé en mémoire. et retourne l’adresse de début de cette zone.
I Si le nombre de valeurs à stocker est supérieur à la taille déclarée, il est I Pour savoir le nombre d’octets à allouer, on utilise l’opérateur sizeof(),
nécessaire de modifier le code du programme. qui retourne le nombre d’octets d’un type
I Les tableaux dynamiques permettent de pallier ces inconvénients : I Exemples : sizeof(int) retourne 4, sizeof(double) retourne 8,
I La taille d’un tableau dynamique peut être différente à chaque exécution du sizeof(char) retourne 1
programme, I Une zone mémoire allouée avec malloc n’est pas désallouée à la sortie du
I Elle peut varier au cours d’une même exécution (un tableau dynamique peut
être réalloué autant de fois que nécessaire)
bloc où elle est allouée
I Pour créer un tableau dynamique, on commence par déclarer un pointeur, I Il est nécessaire de la désallouer (= libérer) lorsqu’elle n’est plus utilisée, en
qui contiendra (plus tard) l’adresse de la première case du tableau. appelant la fonction free, qui libère la zone mémoire pointée.
Exemple : int *tab; I Prototype : void free(void *)
I Le nom du tableau sera alors le nom donné au pointeur lors de sa
déclaration (tab dans notre exemple).
21 22

Tableaux dynamiques Tableaux dynamiques

I Syntaxe de l’appel à malloc : I Représentation en mémoire : dans l’exemple suivant, on considère une
pointeur = malloc(<nb_éléments>*sizeof(<type_élément>)); architecture 32 bits (les adresses sont stockées sur 4 octets)
int *tab1;
I La fonction retourne la constante NULL si l’allocation a échoué. double *tab2;
I Exemple 1 : allocation de mémoire pour 2 entiers (8 octets) tab1 = malloc(3*sizeof(int));
tab2 = malloc(2*sizeof(double));
int* tab1;
tab1 = malloc(2*sizeof(int));

I Récupération de l’adresse de début de cette zone dans tab1 tab1 tab2

I Exemple 2 : Allocation de mémoire pour 4 double (32 octets)


? ? ? ? ? ? ? ? ? ? ? ?
double* tab2;
tab2 = malloc(4*sizeof(double));
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
I Récupération de l’adresse de début de cette zone dans tab2

23 24
Tableaux dynamiques Tableaux dynamiques

I Une fois l’allocation effectuée, un tableau dynamique s’utilise comme un


I Autre représentation possible : tableau statique.
I Seule différence : pas de distinction taille effective / taille réelle → une
tab1 seule taille (effective ET réelle).
I Une fois que le tableau n’est plus utilisé, on désalloue la zone mémoire à
? ? ? ? ? ? ? ? ? ? ? ? l’aide de la fonction free
tab2 I Ex : free(tab); désalloue le bloc de mémoire désigné par tab
I Ne jamais tenter de libérer de l’espace mémoire qui n’a pas été alloué par
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? malloc (plantage).
I Un programme doit contenir autant d’appels à free que d’appels à
malloc !

25 26

I Exemple : I Exemple (suite)


#include <stdio.h> tab[0] = 2;
#include <stdlib.h> /* Nécessaire pour tab[1] = 3;
utiliser malloc et free */ /* etc */
int main()
{ printf("%d",tab[4]);
int * tab; /* contiendra l’adresse
de début du tableau en mémoire */ ...
int taille; /* contiendra la taille du tableau */
/* Désallocation de la mémoire */
printf("Saisissez le nombre d’élements : \n"); free(tab);
scanf("%i",&taille);
return 0;
/* Allocation mémoire du tableau */ }
tab = malloc(taille * sizeof (int));

27 28

Fonctions et tableaux Fonctions et tableaux


I Comme un tableau statique, un tableau dynamique peut être passé en I La fonction précédente peut aussi bien être appelée avec un tableau
paramètre d’une fonction. statique en paramètre...
I Le type d’un tableau dynamique correspond au type du pointeur sur sa int main()
première case. {
I Comme pour les tableaux statiques, il est souvent nécessaire de passer float tableau[4];
également en paramètre la taille du tableau.
I Exemple : tableau[0] = 1.2;
tableau[1] = 1;
float moyenne(float* tab, int nbVal)
tableau[2] = 2.5;
{
tableau[3] = 1.7;
float somme;
int i;
moy = moyenne(tableau, 4);
somme=0;
printf("Moyenne = %f\n", moy);
for (i=0; i<nbVal; i++)
somme += tab[i];
return 0;
return somme/nbVal;
}
}

29 30
Fonctions et tableaux Plan
I ... qu’avec un tableau dynamique
int main()
{
float *tableau;
int taille = 4; Rappels de C : pointeurs et tableaux
float moy;

tableau = malloc(taille * sizeof (float)); Rappels de C : types de données


tableau[0] = 1.2;
tableau[1] = 1;
tableau[2] = 2.5; C++ : entrées/sorties, référence, surcharge d’opérateurs
tableau[3] = 1.7;

moy = moyenne(tableau,taille);
C++ : programmation orientée objet avec les classes
printf("Moyenne = %f\n", moy);

free(tableau);
return 0;
}
31 32

Types de données Types de données


I Supposons qu’on a créé un nouveau type MonType. Il est possible de
déclarer des variables de type MonType...
MonType m;
I Pourquoi créer des types de données ? MonType *p;
I Pour donner du sens au code, pour faciliter son écriture et sa lecture MonType tab[4];
I Pour représenter des entités du monde réel
I Pour regrouper des données hétérogènes (int, char *, ...) → première I ... de faire des affectations...
approche de l’objet
MonType a, b;
I Pour définir un nouveau type, on utilise le mot-clé typedef ...
I En principe, les types sont déclarés entre les inclusions (#include ...) et a = b;
la première fonction
I ... et d’écrire des fonctions prenant en paramètre ce type et/ou le
retournant
void uneFonction(MonType *, int, char *);
MonType uneAutreFonction(MonType *, const MonType *);

33 34

Types synonymes Types synonymes


void affiche(vecteur3d v)
I Créer un nouveau type à partir d’un type existant T, ou d’un tableau dont {
printf("%f %f %f", v[0],v[1],v[2]);
les éléments sont de type T, ou d’un pointeur sur T. Exemple :
}
#include <stdio.h>
int main()
/*Définition d’un nouveau type "scalaire" {
synonyme de float */ scalaire x1, y1, z1;
typedef float scalaire; vecteur3d v1;
texte nom;
/* Définition d’un nouveau type "vecteur"
synonyme de tableau de 3 "scalaire" */ x1 = 10.5; y1 = -7.6; z1 = 12.0;
typedef scalaire vecteur3d[3]; v1[0] = x1; v1[1] = y1; v1[2] = z1;

/* Définition d’un nouveau type "texte" affiche(v1);


synonyme de tableau de 100 char */
typedef char texte[100]; scanf("%s", nom);
printf("Au revoir %s !\n", nom);
return 0;
35 } 36
Types synonymes Enumérations

I Intérêt d’utiliser le type scalaire de l’exemple précédent : si, à un moment I Les énumérations sont utiles pour représenter des variables prenant un
donné, on souhaite travailler avec des double à la place des float, il suffit nombre fini (et relativement petit) de valeurs (ex : mois, jour de la
de modifier la définition du type → le code est plus modulaire ! semaine, booléen, ...)
I Types ”synonymes” et opérateurs : si le type de base est doté d’un certain I Intérêt : rendre le code plus lisible
opérateur, le type synonyme l’est aussi. I Déclaration avec le mot-clé enum
I Exemple : tous les opérateurs valides sur float le sont aussi sur enum {VALEUR1, VALEUR2,...} maVar;
scalaire :
typedef float scalaire; I maVar est une variable pouvant prendre les valeurs VALEUR1, VALEUR2, ...
...
typedef enum {VALEUR1, VALEUR2, ...} monType;
scalaire a, b, c;
...
I monType est un type, dont les variables pourront prendre les valeurs
if (a*b==c || a<=b)
c += a; VALEUR1, VALEUR2, ...
I Par convention, les valeurs d’une énumération sont écrites en majuscules

37 38

Enumérations Enumérations
I Exemple avec définition de type
I Exemple sans définition de type : typedef enum {LUNDI, MARDI, MERCREDI, JEUDI,
int main() VENDREDI, SAMEDI, DIMANCHE} Jour;
{
enum {CONCIS, VERBEUX} mode; int main()
... {
mode = CONCIS; Jour j1, j2;
... ...
if (mode==VERBEUX) j1 = MARDI;
printf("Blablabla\n"); j2 = VENDREDI;
... ...
} if (j1==j2 || j1==DIMANCHE)
...
I Ici, mode est une variable }
I Si le programme ne comporte qu’une seule variable pouvant prendre ces
I Ici, Jour est un type (on parle de type énuméré)
valeurs, la définition d’un nouveau type n’est pas justifiée
I Si on est amené à utiliser plusieurs variables contenant les jours de la
semaine, cette définition de type est justifiée
39 40

Enumérations Enumérations

I Correspondance entre valeurs d’une énumération et entiers I Tous les opérateurs sur les variables de types numériques s’appliquent
I Les variables de types énumérés sont traitées par le compilateur comme des également sur les variable de types énumérés
entiers + - * / %
I Une valeur entière est associée à chaque élément de l’énumération. Par += -= *= /= %=
++ --
défaut, la première vaut 0 et les suivantes sont incrémentées.
== != < > <= >=
typedef enum {LUNDI, MARDI, MERCREDI,
JEUDI, VENDREDI, SAMEDI, DIMANCHE} Jour;
I Par exemple, l’expression logique (LUNDI < MERCREDI) est vraie.
I Exemple d’utilisation : une boucle for peut être contrôlée par une variable
I Dans l’exemple précédent, LUNDI vaut 0, MARDI vaut 1, MERCREDI vaut 2,
de type énuméré.
...
I Possibilité d’imposer les valeurs (quand aucune valeur n’est précisée, Jour j;
for (j=LUNDI; j<=DIMANCHE; j++)
l’incrémentation reprend). Exemple :
{
typedef enum {LUNDI=1, MARDI, MERCREDI, ...
JEUDI, VENDREDI, SAMEDI, DIMANCHE} Jour; }

41 42
Structures Structures
I Syntaxe de définition d’un nouveau type structure (que l’on utilisera
beaucoup !)
I Une structure regroupe des variables de types quelconques mais ayant un typedef struct {
lien au sein d’une même entité. typeChamp1 nomChamp1;
I Elle permet de représenter des objets ou concepts de la vie réelle. typeChamp2 nomChamp2;
...
I La structure traduit une relation de composition.
} monType;
I Les variables qui composent la structure sont appelées les champs
I Une structure est définie à l’aide du mot-clé struct I La syntaxe suivante est également possible :
I Syntaxe de déclaration d’une variable structure (que l’on utilisera peu !) struct maStructure {
struct { typeChamp1 nomChamp1;
typeChamp1 nomChamp1; typeChamp2 nomChamp2;
typeChamp2 nomChamp2; };
... typedef struct maStructure monType;
} maVar;
I Accès aux champs avec le point. Exemple :
monType maVar;
maVar.nomChamp1 = VALEUR_CHAMP1;
43 44

Structures Structures

I Exemple : on souhaite écrire en C une application de gestion d’une flotte de I Exemple 1 : définition et initialisation de deux vélos :
vélos. Dans notre application, un vélo est défini par un nom de marque, un int main()
nom de modèle, un diamètre de roue (en pouces) et un nombre de vitesses {
I Définition d’un nouveau type Velo : Velo unVelo, unAutreVelo;
typedef struct strcpy(unVelo.marque,"Motobecane");
{ strcpy(unVelo.modele,"Confort");
char marque[250]; unVelo.diametre = 26;
char modele[250]; unVelo.nbVitesses = 12;
int diametre;
int nbVitesses; strcpy(unAutreVelo.marque,"Gitane");
} Velo; strcpy(unAutreVelo.modele,"Racer");
unAutreVelo.diametre = 28;
unAutreVelo.nbVitesses = 18;
I Toute future variable de type Velo contiendra automatiquement une valeur
...
pour chacun de ses champs (marque, modèle, diamètre de roue et nombre
return 0;
de vitesses).
}
I En mémoire, les champs sont contigus

45 46

Structures Structures
I Exemple 2a : définition et initialisation d’un tableau statique de vélos I Exemple 2b : définition et initialisation d’un tableau dynamique de vélos

int main() int main()


{ {
Velo flotte[10]; Velo *flotte;
int i; int i, n=10;
flotte = malloc(n*sizeof(Velo));
for (i=0; i<10; i++) for (i=0; i<n; i++)
{ {
strcpy(flotte[i].marque, "INCONNUE"); strcpy(flotte[i].marque, "INCONNUE");
strcpy(flotte[i].modele, "INCONNU"); strcpy(flotte[i].modele, "INCONNU");
flotte[i].diametre = 0; flotte[i].diametre = 0;
flotte[i].nbVitesses = 0; flotte[i].nbVitesses = 0;
} }
... ...
return 0; free(flotte);
} return 0;
}

47 48
Structures et passage de paramètre Structures et passage de paramètre

I Fonction prenant en paramètre un vélo et ne le modifiant pas → passage I Fonction prenant en paramètre un vélo et le modifiant → passage de
de paramètre par valeur paramètre par pointeur (= passage par adresse)
void afficheVelo(Velo v) void initVelo(Velo *pv)
{ {
printf("%s %s, diamètre de roues : %d, nombre de vitesses : strcpy(pv->marque, "INCONNUE");
%d\n", v.marque, v.modele, v.diametre, strcpy(pv->modele, "INCONNUE");
v.nbVitesses); pv->diametre = 0;
} pv->nbVitesses = 0;
}
I Recopie de la variable structurée
I Lorsqu’on dispose d’un pointeur sur une variable structurée, on peut utiliser
I Nous verrons plus loin qu’un autre passage de paramètre est possible :
directement l’opérateur − >
passage par pointeur sur constante
I pv− >champ est équivalent à (*pv).champ

49 50

Qualificatif de type const Conversion de type (typecast)

I Une variable qualifiée const ne pourra pas être modifiée dans le bloc de I Syntaxe : (type cible)expression
code où elle est visible
I Les types numériques peuvent être convertis entre eux
I Lorsqu’il est utilisé dans la définition d’un paramètre, le qualificatif const
I Exemple : arrondir un réel positif à l’entier le plus proche
peut renseigner sur le rôle d’un paramètre dans la fonction : on sait déjà
que la fonction ne modifie pas ce paramètre (paramètre en lecture seule) float f = ...;
int i = (int)(f+0.5);
/* dst = destination, src = source */
void copy(char *dst, const char *src)
{ I Les pointeurs peuvent toujours être convertis entre eux
dst[0] = ’\n’; // Ok I Exemple : convertir un pointeur sur type énuméré en pointeur sur entier
src[0] = ’\n’; // Erreur de compilation typedef enum {...} MonEnum;
... ...
int *pi;
I A utiliser dès que possible ! MonEnum e = ...;
I Pour les structures passées en lecture seule : passage par pointeur sur pi = (int *)(&e);
constante : const MonType *

51 52

Organisation en modules, inclusion Organisation en modules, inclusion

I Fichier livre.h
#ifndef LIVRE_H
I Module = couple de fichiers monModule.h et monModule.c #define LIVRE_H
I Les définitions de type(s) et les déclarations de fonctions travaillant sur
typedef enum {JANVIER=1, FEVRIER, MARS, AVRIL, MAI,
ce(s) type(s) doivent être dans le fichier d’en-tête (header) monModule.h
JUIN, JUILLET, AOUT, SEPTEMBRE,
I Les définitions des fonctions (déclarées dans le .h) doivent être dans le OCTOBRE, NOVEMBRE, DECEMBRE } mois;
fichier source monModule.c
I Tout autre module utilisant les types et les fonctions de monModule doit typedef char texte[200];
l’inclure :
#include "monModule.h" typedef struct
{
mois m;
int annee;
} date;

53 54
Organisation en modules, inclusion Directives #ifndef ... #endif

I Fichier livre.h (suite)


typedef struct I Dans un .h, les définitions de types et déclarations de fonctions doivent
{ toujours se trouver entre
int code; #ifndef MONMODULE_H
texte titre; #define MONMODULE_H
texte auteur;
texte editeur;
et #endif
date dateParution;
} livre; I Evite les inclusions multiples = évite que le compilateur rencontre plusieurs
fois les mêmes définitions si le .h est inclus plusieurs fois lors de la
void saisirLivre(livre *); compilation d’un .c
void afficherLivre(const livre *); I Exemple : si A.c inclut monmodule.h et B.h, et que B.h inclut lui-même
monmodule.h
#endif

55 56

Organisation en modules (suite) Fonction assert


I Fichier livre.c
#include "livre.h"
#include <stdio.h>
I Comment quitter le programme en cas d’erreur, en informant l’utilisateur
void saisirLivre(livre *pl) { qu’une condition n’est pas vérifiée ?
... I Solution 1 : message d’erreur et appel à exit
} Mat2x2 divMatReal(const Mat2x2 m, float f)
{
void afficherLivre(const livre *pl) { if (f==0.0)
... {
} printf("Division par zéro !\n");
exit(-1);
I Fichier main.c }
#include "livre.h" ...
}
int main() {
Livre bibliotheque[100];
...
saisirLivre(&bibliotheque[0]);
... 57 58

Fonction assert Plan

I Solution 2 : utiliser assert


I Déclarée dans <assert.h> Rappels de C : pointeurs et tableaux
I La fonction assert interrompt le programme si la condition passée en
paramètre est évaluée à FAUX
I La condition évaluée à FAUX est affichée Rappels de C : types de données
I Permet de vérifier les préconditions des algorithmes
Mat2x2 divMatReal(const Mat2x2 m, float f) C++ : entrées/sorties, référence, surcharge d’opérateurs
{
assert(f!=0.0);
...
C++ : programmation orientée objet avec les classes
}

59 60
Quelques caractéristiques du C++ Divers

Commentaires
I Une surcouche au C
I Commentaires sur une seule ligne : débutent par //
I Proche du matériel (comme le C) : manipulation explicite des adresses,
I Il est toujours possible d’utiliser /* ... */
gestion manuelle de la mémoire
I Nouveaux opérateurs (<<, >>, new, delete, ...) Entrées/sorties
I Déclarations dans l’en-tête <iostream>
I Nouveaux mots-clés (class, public, private, ...)
I Objets cout (classe ostream) et cin (classe istream), déclarés dans
I Nouvelle bibliothèque standard (en-têtes cmath, cassert, ...)
l’espace de noms 2 std
I Nouveaux mécanismes : appels automatiques de fonctions !
I Sortie standard (console) via l’objet cout, entrée standard (saisie au
I Toutes les fonctions standards C (printf, scanf, malloc, free, ...) clavier) via l’objet cin
restent utilisables dans un programme compilé en C++
I Opérateurs << et >>
I Un langage qui évolue
I En remplacement de printf et scanf
I Première édition publiée en 1985
I Normalisé par l’ISO depuis 1998 I Plus besoin de formatage des données : adieu %i, %d, %f...
I Mise à jour majeure en 2020 (norme C++20), mise à jour en cours de I Possibilité d’afficher et de saisir des entiers, des réels, des caractères, des
finalisation pour 2023 (norme C++23) chaı̂nes de caractères

61 2. Un espace de noms est un regroupement d’identifiants, accessibles via le même préfixe 62

Entrées/sorties Entrées/sorties
I Exemple :
#include <iostream>
using namespace std; // Pour alléger l’écriture, évite
I Extrait, très simplifié, du fichier iostream : // d’avoir à spécifier l’espace de nommage std::
namespace std // avant chaque accès à cout, cin, ...
{ int main()
... {
ostream cout; char nom[100];
... int age;
istream cin; cout<<"Entrez votre nom :"<<endl;
... cin>>nom;
} cout<<"Entrez votre age :"<<endl;
cin>>age;
I cout et cin sont bien des variables (en fait, des objets), pas des fonctions ! cout<<"Bonjour "<<nom<<". Vous avez "<<age
<<" an(s)"<<endl;
...
}

63 Equivalent C ? (au tableau) 64

Référence Référence

I Le C++ ajoute la notion de référence I Toute opération effectuée sur la référence se répercute sur la variable
référencée
I Une référence peut être considérée comme un alias sur une variable
I On ne peut pas ré-affecter une référence (alors que l’on peut modifier un
I Peut être utilisée pour définir une variable locale, un paramètre de fonction
pointeur)
ou la valeur de retour d’une fonction
I Retour sur les opérateurs & et * : leur rôle est différent selon que ces
I Une référence est obligatoirement affectée lors de sa définition
opérateurs apparaissent dans une déclaration de variable ou dans une
I Une référence n’est rien d’autre qu’un pointeur déguisé ! expression
I A ne pas confondre avec l’opérateur d’accès à l’adresse & ! I Il est très important de savoir identifier la signification des symboles * et &
int n = 5; en tout lieu
int &r = n; // Définition d’une référence sur n I Si * ou & est précédé d’un nom de type, nous sommes en présence d’une
// Si r est modifié, n l’est aussi déclaration de variable (variable locale, paramètre ou retour d’une
fonction). Sinon, nous sommes dans le cas d’une expression

65 66
Opérateur * (rappel) et & Pointeur ou référence ?
I Pour un paramètre en lecture/écriture, nous avons le choix entre pointeur
et référence. Exemple avec une fonction qui échange deux entiers :
Opérateur *
I Dans le cas d’une déclaration de variable : définition d’un pointeur Solution 1 : passage par pointeur : Solution 2 : passage par référence :
I Dans le cas d’une expression : accès à la valeur pointée (opérateur de void swap1(int *pa, int *pb) void swap2(int &a, int &b)
déréférencement, également appelé opérateur d’indirection) { {
int temp; int temp;
I Si p est de type MonType *, (*p) est de type MonType
temp = *pa; temp = a;
Opérateur & *pa = *pb; a = b;
I Dans le cas d’une déclaration de variable : référence (en C++ seulement) *pb = temp; b = temp;
I Dans le cas d’une expression : accès à l’adresse } }
I Si p est de type MonType, &p est de type MonType *
Appel : Appel :
(exemples au tableau)
int i, j; int i, j;
... ...
swap1(&i, &j); swap2(i, j);

67 68

Passage par référence Structure et référence


I Considérons à nouveau le passage de paramètre par valeur
I Les arguments passés par valeur à une fonction ne sont pas modifiés par
I Dans le corps de la fonction, le programmeur peut considérer les références cette fonction
comme des valeurs I Le passage par valeur implique une recopie dans la pile
I Le passage par référence n’est rien d’autre qu’un passage par adresse typedef struct
déguisé {
I Avantages, par rapport au passage par adresse : double tab[10000];
I Manipulation de ”valeurs” dans le corps de la fonction (pas de } MaStructure;
déréférencement avec *)
I Pas de vérification de non-nullité des pointeurs void calcul(MaStructure s)
I Simplification du passage de paramètres au moment de l’appel (moins de &) {
I Pas d’ambigüité possible entre un unique élément et un tableau ... // Lourd ! 80ko recopiés dans la pile
(contrairement aux pointeurs)
I Si le paramètre est une référence constante, l’argument peut être une
I Inconvénient : peut être lourd si l’on passe des structures de grande taille !
expression (et pas seulement une variable)
I Problème : comment passer un paramètre sans recopie tout en indiquant
que la fonction ne modifie pas ce paramètre ? (autrement dit, comment
allier les avantages du passage par adresse/référence et ceux du passage
par valeur ?)
69 70

Passage par référence constante Retour par référence


I Le retour par référence permet de retourner “une variable elle-même” (et
non pas une copie de cette variable, comme le ferait le retour par valeur)
I Solution : utilisation du qualificatif de type const → passage par
référence constante. Avantages : velo.hpp
I Lors de l’appel à la fonction, seule l’adresse de l’argument est recopiée dans typedef struct
la pile (rappel : la référence est un pointeur déguisé) {
I En lisant le prototype, on sait que la fonction ne modifie pas le paramètre Velo *tab;
I Utilisation : dès que l’on écrira des fonctions ou méthodes prenant en int nbVelos;
paramètre un objet ou une variable structurée } FlotteVelos;
I Si la fonction ne modifie pas le paramètre → passage par référence ...
constante : const MonType & Velo &getVeloIndice(FlotteVelos &, int);
I Si la fonction modifie le paramètre → passage par référence : MonType &
I Pour le passage de tableaux (dont les chaı̂nes de caractères), le passage par velo.cpp
pointeur reste valable
...
I Si la fonction ne modifie pas les éléments du tableau : passage par pointeur
Velo &getVeloIndice(FlotteVelos &f, int i)
sur constante → const MonType *
I Si la fonction modifie les éléments du tableau : passage par pointeur → {
MonType * assert(i>=0 && i<f.nbVelos);
return f.tab[i];
}
71 72
Retour par référence Opérateurs new et delete
I Allocation et désallocation de mémoire dans le tas
I L’appel getVeloIndice(f,i) retourne le vélo f.tab[i] lui-même, pas I En remplacement des fonctions malloc et free
une copie de celui-ci I Allocation et désallocation d’un seul élément :
I Ainsi, un appel à getVeloIndice peut donc être utilisé comme membre MonType *p;
gauche d’une affectation (l-value) : ...
FlotteVelos fl; p = new MonType;
Velo v; ...
... delete p;
getVeloIndice(fl,2) = v;
getVeloIndice(fl,3).diametre = 26; I Allocation et désallocation d’un tableau :
I Dans l’exemple précédent, l’avant-dernière instruction est équivalente à MonType *p;
fl.tab[2] = v, avec vérification sur l’indice 2 ...
I Si la fonction getVeloIndice retournait un Velo par valeur, elle ne p = new MonType[25];
pourrait pas être utilisée pour modifier un Velo de la flotte ! ...
delete[] p;

73 74

Redéfinition de fonctions Redéfinition de fonctions

void maFonction()
I Le C++ autorise la définition de plusieurs fonctions portant le même nom, {
mais avec des paramètres différents int i=4, j=-2;
I Les appels ne doivent pas être ambigus : le compilateur doit toujours float f=4.8, g=-9.14;
pouvoir déterminer quelle fonction appeler
cout<<mul(i,j)<<" "<<mul(f,g)<<endl; // Ok
int mul(int a, int b)
cout<<mul(i,f)<<endl; // Erreur de compilation
{
cout<<mul(g,j)<<endl; // Erreur de compilation
return a*b;
}
}
I Les deux derniers appels à mul sont ambigus
float mul(float a, float b)
{ I Lorsque les types des arguments sont différents de ceux des paramètres, le
return a*b; compilateur tente des transtypages (conversion de type) pour correspondre
} à un prototype disponible
I Ici, le compilateur ne sait pas s’il doit faire la conversion (int → float),
pour appeler la version avec float, ou (float → int) pour appeler la
version avec int
75 76

Le type bool Surcharge d’opérateurs

I Lorsqu’on définit un nouveau type, on souhaite parfois le doter de certains


opérateurs. Tous les opérateurs suivants peuvent être surchargés. Le
I Prend les valeurs true ou false programmeur peut leur affecter le rôle qu’il souhaite :
I Stocké sur 1 octet (comme char) = + - * / % ^ & | ~ !
I Une expression logique peut être convertie implicitement en bool : == != < > <= >=
bool b; += -= *= /= %= ^= &= |=
int i = ...; << >> <<= >>= && || ++ --
b = (i>5 || i<10); -> , [] () new delete

I Doté des opérateurs &&, ||, !, ==, != I Cas particuliers : les opérateurs = [] () et − > ne peuvent être
surchargés qu’en méthodes de classe (abordées plus loin dans le cours)
I Pour attribuer un nouveau rôle à un opérateur, définition d’une fonction à
l’aide du mot-clé operator

77 78
Surcharge d’opérateurs Surcharge d’opérateurs

typedef struct
{
float x, y, z; I L’addition et la multiplication retournent un vector3D. La comparaison
} vector3D;
retourne un booléen
vector3D u, v; I Ces opérations ne modifient pas les vector3D passés en paramètres
I Déclarations :
vector3D operator+(const vector3D &, const vector3D &);
I On aimerait pouvoir additionner ou comparer des vector3D entre eux, et
multiplier un vector3D par un réel. De base, le compilateur ne sait pas vector3D operator*(const vector3D &, float);
traiter des expressions telles que u+v, u*2.0, ou u==v, car le type
vector3D n’est, pour l’instant, pas doté des opérateurs d’addition, de bool operator==(const vector3D &, const vector3D &);
multiplication et de comparaison !
I Les opérateurs peuvent être unaires (ex : - unaire, ++, --, !) ou binaires
(=, +, - binaire, *, /, %, ...)
I Un opérateur unaire (resp. binaire) sera défini comme une fonction prenant
un (resp. deux) paramètre(s)
79 80

Surcharge d’opérateurs Surcharge d’opérateurs


I Définitions :
vector3D operator+(const vector3D &u, const vector3D &v)
{ I Lors de l’appel, les opérandes deviennent les arguments : ils sont recopiés
vector3D s; dans les paramètres
s.x = u.x+v.x; s.y = u.y+v.y; s.z = u.z+v.z; I Cas d’un opérateur binaire : opérande gauche → 1er paramètre, opérande
return s; droite → 2ème paramètre
} I Exemple :

vector3D operator*(const vector3D &v, float f) vector3D u, v, w;


{ ...
vector3D s; w =u + v
| *{z2.0}
s.x = v.x*f; s.y = v.y*f; s.z = v.z*f;
Appel à operator*(const vector3D &,
return s;
float) → vector3D
} | {z }
Appel à operator+(const vector3D &,
bool operator==(const vector3D &u, const vector3D &v) const vector3D &) → vector3D
{
return (u.x==v.x && u.y==v.y && u.z==v.z);
}
81 82

Opérateurs d’affectation composée Opérateur d’insertion


I Surcharge de l’opérateur d’insertion << avec un objet de type ostream,
I Pour (presque) chaque opérateur arithmétique surchargé, on peut
pour permettre l’affichage sur la sortie standard.
également surcharger l’opérateur d’affectation composée correspondant.
I L’objet de type ostream est modifié par la fonction, et est donc passé par
I En principe, cet opérateur modifie le premier paramètre, et le retourne par
référence
référence (cf diapo 72)
I Déclaration :
I Exemple avec l’opérateur += :
std::ostream &operator<<(std::ostream &, const vector3D &);
vector3D &operator+=(vector3D &u, const vector3D &v)
{ I Définition :
u.x+=v.x; u.y+=v.y; u.z+=v.z; ostream &operator <<(ostream &os, const vector3D &v)
return u; {
} os<<"("<<v.x<<","<<v.y<<","<<v.z<<")";
return os;
I Possibilité d’enchaı̂ner les affectations composées (comme avec int, }
float, ...) :
I La fonction retournant l’objet de type ostream, il est possible d’enchaı̂ner
vector3D a, b, c; les affichages :
...
a+=b+=c; vector3D u, v;
cout<<"Les deux vecteurs sont "<<u<<" et "<<v<<endl;
83 84
Surcharge d’opérateurs Surcharge d’opérateurs
I Exemple complet : vector3d.hpp (suite)
vector3d.hpp vector3D operator*(float, const vector3D &);
vector3D operator*(const vector3D &, float);
#ifndef VECTOR3D_H
#define VECTOR3D_H vector3D operator/(const vector3D &, float);
#include <iostream> vector3D &operator+=(vector3D &, const vector3D &);
typedef struct bool operator==(const vector3D &, const vector3D &);
{ bool operator!=(const vector3D &, const vector3D &);
float x, y, z;
} vector3D; // Produits scalaire et vectoriel
float dotProduct(const vector3D &, const vector3D &);
vector3D operator +(const vector3D &, const vector3D &); vector3D crossProduct(const vector3D &, const vector3D &);
// Deux versions : - unaire et - binaire std::ostream &operator<<(std::ostream &, const vector3D &);
vector3D operator-(const vector3D &);
vector3D operator-(const vector3D &, const vector3D &); #endif
85 86

Surcharge d’opérateurs Paramètres par défaut


I Lors de la déclaration, il est possible de doter les paramètres de valeurs par
I Remarque 1 : il est impossible (et sans intérêt !) de surcharger un opérateur défaut. Si le paramètre n’est pas fourni lors de l’appel, il est initialisé avec
avec des types pour lesquels il est déjà défini cette valeur
float operator/(float, float); I Syntaxe : TypeParam NomParam=valDefaut
// Erreur de compilation I Exemple : un vecteur a plusieurs normes (norme 1, norme euclidienne,
I La division entre deux réels existe déjà ! norme infinie, ...)
vector3d.cpp (suite)
I Remarque 2 : il faut conserver, autant que possible, la sémantique classique float norme(const vector3D &u, float typeNorme)
des opérateurs : {
float n;
vector3D &operator+=(vector3D &u, const vector3D &v)
if (typeNorme==1.0)
{
n = fabs(u.x)+fabs(u.y)+fabs(u.z);
u.x *= v.x;
else if (typeNorme==2.0)
u.y *= v.y;
n = sqrt(u.x*u.x+u.y*u.y+u.z*u.z);
return u;
else
}
...
// Possible, mais incohérent !
return n;
}
87 88

Paramètres par défaut Plan

I Si l’on souhaite que la norme euclidienne soit celle calculée par défaut :
vector3d.hpp Rappels de C : pointeurs et tableaux
...
float norme(const vector3D &, float typeNorme=2.0);
... Rappels de C : types de données

I La fonction peut être appelée avec ou sans 2ème argument :


C++ : entrées/sorties, référence, surcharge d’opérateurs
vector3D u;
...
cout<<norme(u)<<" "<<norme(u,1.0)<<endl;
... C++ : programmation orientée objet avec les classes

89 90
L’approche objet ? Notion d’objet

I Différence entre langage orienté objet et approche objet


I C++ est un langage orienté objet (comme Java, PHP, Python, Ruby, etc) I Un objet est une entité ayant une existence propre, regroupant des
qui offre la possibilité de programmer avec une approche objet caractéristiques, sur laquelle on peut effectuer des opérations (qui vont
I L’approche objet est une philosophie de programmation, que l’on peut consulter ou modifier son état)
mettre en œuvre dans différents langages I Représentation informatique d’un objet de la vie réelle
I Il est possible de programmer en C++ en ne respectant aucune règle de I Peut correspondre à une entité ”concrète” (animal, véhicule, ...) ou
l’approche objet (et c’est mal) ... ”immatérielle”, ”conceptuelle” (compte utilisateur, unité d’enseignement,
I ... comme il est possible de programmer en C avec, dans la mesure du ...)
possible, une approche objet (et c’est mieux !) I Principe d’encapsulation : les caractéristiques des objets (= les données)
I L’objectif du cours : programmer en C++ en respectant une approche et les opérations sur ces données sont regroupées au sein des objets
objet (c’est encore mieux)

91 92

Classe et objet Classe et objet

I Attention à la confusion entre objet et classe !


I Membres d’un objet : I Exemple : si l’on modélisait l’INSA avec une approche objet, GSI et STPI
I Attributs (champs) : caractéristiques pouvant être consultées ou modifiées ne seraient pas des classes différentes, mais deux instances différentes d’une
I Méthodes (fonctions membre) : opérations sur l’objet
même classe Département
I Classe I Lien entre classe et structure :
I Une classe est un regroupement d’objets ayant le même comportement I La classe généralise la notion de structure
I Abstraction décrivant les propriétés communes des objets qui en sont des I Tout comme une structure, la classe a des champs (ses attributs)
instances I Lors de l’exécution, en mémoire, un objet est comme une variable
I Un objet est une instance d’une certaine classe
structurée : ses attributs sont contigus en mémoire, comme les champs
I Une classe peut décrire une infinité d’instances
d’une variable structurée
I Lien avec les notions de type et variable : I L’écriture d’une classe se fait dans un fichier d’en-tête (extension .hpp) et
I Une classe est utilisée comme un type
un fichier source (extension .cpp)
I Un objet est une variable
I Exemple : un étudiant est défini par son numéro, son nom de famille et son
prénom...

93 94

Classe : définition Classe : définition


I Définition de la classe Student...
student.hpp I Les méthodes sont déclarées dans la définition de classe, et sont définies
... dans un fichier source :
class Student student.cpp
{
private: ...
int number; void Student::setSurname(const char *sn)
char *surname; {
char *firstname; strcpy(surname, sn);
}
public:
Student(); I Prototype de définition d’une méthode :
Student(const Student &); TypeRetour NomClasse::NomMethode(parametres)
Student(int);
Student(int, const char *, const char *); I L’opérateur de résolution de portée 3 :: indique à quelle classe
~Student(); appartient la méthode
void copy(const Student &); I Il permet de lever les éventuelles ambigüités : deux méthodes de classes
void setSurname(const char *); différentes pourraient avoir le prototype (type de retour, nom, paramètres)
...
}; 95 96
3. Nous l’avons déjà utilisé pour les espaces de nommage (ex : std
Définition inline Objet courant, attributs

I Une méthode peut également être définie dans la définition de la classe


elle-même
I L’objet courant est celui sur lequel la méthode est appelée
I On parle alors de définition inline
I L’appel d’une méthode sur un objet est semblable à un accès à un champ
I Plutôt pour les petites fonctions (quelques lignes)
de structure :
class Student
Student st1;
{
Student *pSt2 = new Student;
...
...
void setSurname(const char *sn)
st1.setSurname("..."); // Objet courant = st1
{
pSt2->setSurname("..."); // Objet courant =
strcpy(surname, sn);
// objet pointé par pSt2
}
...
};

97 98

Objet courant, attributs Autoréférence : this

I Dans le corps d’une méthode, les attributs de l’objet courant sont


I Dans le corps de toute méthode, this est un pointeur sur l’objet courant
accessibles comme des variables locales
void Student::copy(const Student &st)
I Les attributs d’un autre objet (par exemple, passé en paramètre) sont
{
accessibles comme des champs de structure...
this->number = st.number;
void Student::copy(const Student &st) strcpy(this->surname, st.surname);
{ strcpy(this->firstname, st.firstname);
number = st.number; }
strcpy(surname, st.surname);
strcpy(firstname, st.firstname); I Il peut être omis la plupart du temps
}
I Il peut être utile pour lever une éventuelle ambigüité. Par exemple,
lorsqu’un paramètre porte le même nom qu’un attribut
I ... s’ils sont visibles ! (la notion de visibilité est abordée plus loin)

99 100

Instanciation Constructeur et destructeur

I Constructeur : méthode appelée AUTOMATIQUEMENT lors de la création


I Une variable de type Student allouée = une instance de la classe Student d’une nouvelle instance
I Dans cet exemple, 2 instances de la classe Student sont créées : I Rôle : donne une valeur initiale pertinente aux attributs
Student st1; // Création d’une nouvelle instance I Types de constructeurs
// (dans la pile) I Constructeur par défaut : pas de paramètre
Student *pSt1 = &st1; // Définition d’un pointeur I Constructeur de recopie : un paramètre (un objet de la classe passé par
// sur une instance existante référence constante)
Student &st1ref = st1; // Définition d’une référence I Autres constructeurs : paramètres libres (il est possible de définir autant de
// sur une instance existante constructeurs que l’on souhaite)
Student *pSt2 = new Student; // Création d’une nouvelle I Appels implicites
// instance (dans le tas), I Si les attributs de la classe sont des objets, leurs constructeurs sont appelés
// via un pointeur automatiquement
... I Lorsqu’il y a héritage, le(s) constructeur(s) par défaut de la ou des classe(s)
delete pSt2; // Désallocation mère(s) est/sont appelé(s) automatiquement (l’héritage est abordé plus loin)
I Remarque : le constructeur porte mal son nom. Lorsqu’il est appelé, l’objet
et les attributs existent déjà (son rôle est d’initialiser les attributs)

101 102
Constructeurs : définitions et appels Constructeurs : définitions et appels

// Constructeur par défaut // Autres constructeurs


Student::Student() { Student::Student(int n) {
number = 0; number = n;
surname = new char[NB_CHAR_MAX]; surname = new char[NB_CHAR_MAX];
surname[0] = ’\0’; surname[0] = ’\0’;
firstname = new char[NB_CHAR_MAX]; firstname = new char[NB_CHAR_MAX];
firstname[0] = ’\0’; firstname[0] = ’\0’;
} }

// Constructeur de recopie Student::Student(int n, const char *sn, const char *fn) {


Student::Student(const Student &st) { number = n;
surname = new char[NB_CHAR_MAX]; surname = new char[NB_CHAR_MAX];
firstname = new char[NB_CHAR_MAX]; firstname = new char[NB_CHAR_MAX];
copy(st); strcpy(surname, sn);
} strcpy(firstname, fn);
}

103 104

Constructeurs : définitions et appels Constructeurs : définitions et appels

I Exemples d’appels automatiques aux constructeurs : // Appel au constructeur


// Student(int, const char *, const char *)
Student st1; // Appel au constructeur par défaut
Student *pSt5 = new Student(20140401, "Fifi", "Pierrot");
// Définition de pointeur : pas d’appel !
// Appel au constructeur de recopie
Student *pSt2;
Student st6(st1);
pSt2 = new Student; // Appel au constructeur par défaut
// Appel au constructeur de recopie
Student st7 = st3;
// Appel au constructeur Student(int)
Student st3(20157840);
// Définition et affectation de pointeur : pas d’appel !
Student *pSt8 = &st6;
// Appel au constructeur
// Student(int, const char *, const char *)
// Appel au constructeur de recopie
Student st4(20140401, "Jeanjean", "Bruno");
Student *pSt9 = new Student(st4);

105 106

Constructeur de recopie Constructeur de recopie

I Le constructeur de recopie doit effectuer une copie complète des attributs


d’un objet vers un autre I Rappel : que se passe t-il en mémoire lorsqu’on exécute le code suivant ?
I Nécessaire pour les classes contenant des attributs dynamiques (pointeurs,
char ch1[NB_CHAR_MAX] = "Bonjour";
tableaux) ...
I S’il n’est pas défini, un constructeur de recopie implicite est appelé à la char *ch2;
place. Ce constructeur effectue une copie attribut à attribut ch2 = new char[NB_CHAR_MAX];
I Recopie des attributs : ch2 = ch1;
I pour les types simples (int, float, ...), l’opérateur = suffit
I pour les objets, appel au constructeur de recopie (ou opérateur = surchargé)
de la classe correspondante

107 108
Constructeur de recopie Destructeur
I ch2 et ch1 pointent au même endroit ! (schéma au tableau)
I Le destructeur est exécuté lors de la suppression de l’objet
→ mauvaise gestion de la mémoire, source de bugs
I Appelé automatiquement ...
I Pour avoir des tableaux bien distincts en mémoire, les éléments des
I ... en fin de bloc, pour les instances locales, ou dans le destructeur d’une
tableaux doivent être recopiés un à un
classe contenante
I Utiliser strcpy pour les chaı̂nes de caractères I ... lors de la désallocation avec delete, pour les instances précédemment
I Ecrire des boucles pour les autres tableaux allouées avec new
I Dans la classe Student, si le constructeur de recopie n’était pas défini, I Que met-on dedans ?
quel serait l’affichage donné par le code suivant ? (correction au tableau) I delete sur les attributs alloués avec new
I Fermeture des flux de données (fichiers, streams, ...) pouvant avoir été
Student st1(20156743, "Doudou", "Marcel"); ouverts auparavant
Student st2(st1);
I Nom du destructeur : ∼ suivi du nom de la classe
cout<<st1.getSurname()<<endl; Student::~Student() {
delete[] surname;
st2.setSurname("Jeanjean"); delete[] firstname;
}
cout<<st1.getSurname()<<endl;

109 110

Masquage des données Masquage des données : niveaux de visibilité

I Le concept d’encapsulation est lié au masquage des données : protection


de l’information contenue dans un objet I L’accès aux membres de la classe est différent selon que l’on se trouve à
I Le masquage garantit la cohérence éventuelle entre plusieurs attributs l’intérieur ou à l’extérieur de la classe
inter-dépendants (exemple : un tableau dynamique et sa taille) I A l’intérieur de la classe (c-à-d dans le corps d’une méthode de la classe),
I Garantit l’intégrité de l’objet (exemple : évite les affectations par erreur, = tout est permis
au lieu de == !) I A l’extérieur de la classe, le programmeur ne ”voit” que ce que la classe
I Respecte la notion de Type Abstrait de Donnée (facilite la prise en main ”expose” (”veut bien lui montrer”)
du code dans un premier temps) I Trois niveaux de visibilité pour les membres (attributs et méthodes) :
I Métaphore de la voiture : I public : les membres publics sont accessibles partout
I private : les membres privés sont accessibles seulement au sein de la classe
I Le fonctionnement interne du moteur est encapsulé : il est masqué au
elle-même
conducteur I protected : les membres protégés sont accessibles dans la classe elle-même,
I Pour faire avancer la voiture, le conducteur n’accède pas directement aux
et ses classes dérivées (voir héritage)
bougies ou aux pistons, il passe par une interface de plus haut niveau, que la
voiture met à sa disposition : le volant, les pédales, ...

111 112

Membres privés Membres privés


I Avantage : les accès interdits sont détectés dès la compilation 4
I Comportement similaire dans une autre classe contenant une ou plusieurs
int main()
instance(s) de la classe
{
Student st; // Appel automatique au class StudentPair
// constructeur par défaut {
char s[] = "Jean"; private:
st.firstname = s; // Erreur : accès à un membre privé Student st1, st2;
... ...
public:
I Si elle était autorisée, l’instruction précédente créerait une situation ...
malsaine en mémoire ! (pourquoi ?) void printSurnames() const
I Utilisation correcte de la classe : à l’extérieur de la classe, accès {
uniquement aux membres publics : cout<<st1.surname<<" "<<st2.surname<<endl;
int main() }
{ // Erreur : surname est privé dans Student
Student st; };
st.setFirstname("Jean"); I Attention : l’erreur n’a rien à voir avec le fait que st1 et st2 soient privés
... dans StudentPair
4. Cela illustre l’intérêt d’une classe (avec membres privés) par rapport à une structure 113 114
Programmation modulaire Méthode const
I Dans une méthode, placé après les paramètres, le const indique que l’objet
courant n’est pas modifié par cette méthode
I Le masquage des donnée est lié au principe de programmation modulaire I Exemples : méthode d’affichage, méthode qui retourne une copie d’un
I Intérêt de la programmation modulaire : couplage minimal entres modules. attribut (ou une référence constante, ou un pointeur sur constante)
L’implantation d’un module peut changer sans que les autres modules class Student
soient impactés, si l’interface ne change pas {
I Principe valable quel que soit le langage ! ...
public:
I Exemple : Un programmeur P1 code une classe A. Il déclare la classe dans ...
le fichier A.h et définit les méthodes (l’implantation) dans A.cpp. Un int getNumber() const;
programmeur P2 code une classe B, qui utilise la classe A. Si P1 a ...
correctement programmé et suffisamment documenté l’interface de A };
(importance des commentaires !), P2 peut se servir de A sans avoir à lire
A.cpp, en se basant uniquement sur A.h. P2 peut déclarer des objets de A int Studient::getNumber() const
et appeler les méthodes que P1 a mis à sa disposition. P2 n’accède jamais {
directement aux attributs de A. Si P1 modifie le corps des méthodes de A return number;
(il modifie l’implantation) sans modifier l’interface, P2 n’a pas à modifier B. }

I Si un objet est const, seules les méthodes const peuvent être appelées
115 dessus 116

Accesseurs et mutateurs Accesseurs et mutateurs


I Exemple avec un attribut chaı̂ne de caractères :
class Student
I Accesseurs (getters)
{
I Méthodes get...() : lecture d’un attribut (ou d’une propriété déterminée
...
en fonction d’un ou plusieurs attribut(s) )
I Un accesseur de modifie pas l’objet → méthode const
public:
I Retourne une valeur, un pointeur constant, ou une référence constante void setSurname(const char *sn)
{
I Mutateurs (setters)
strcpy(surname, sn);
I Méthodes set...() : modification d’un attribut (et éventuellement,
}
d’attributs dépendants)
I Un mutateur modifie l’objet
const char *getSurname() const
I Type de retour = void ou un type indiquant un statut (modification {
effectuée ou non, ...) return surname;
}
I A écrire seulement pour les attributs que l’on souhaite exposer ! (principe
...
de masquages des données) };
I Ici, surname est exposé en lecture (présence d’un accesseur) et en écriture
(présence d’un mutateur)
117 118

Complément sur les constructeurs : appel explicite Complément sur les constructeurs : appel explicite

I Objet temporaire / appel explicite de constructeur


I Considérons la classe Group : I ... mais il est également possible de passer en paramètre un objet
class Group temporaire en appelant explicitement le constructeur
{ Group g;
... g.addStudent(Student(2018, "Jean", "Bruno"));
public:
void addStudent(const Student &);
I L’objet temporaire n’a pas de variable associée. Il sera détruit
...
}; immédiatement après l’instruction dans laquelle il est utilisé
I Autre exemple :
I Il est possible de créer un objet Student de manière classique et de Student tabStudent[20];
l’ajouter au groupe... for (int i=0; i<20; i++)
Group g; tabStudent[i] = Student(i, "", "");
Student st(208, "Jeanjean", "Bruno");
g.addStudent(st);

119 120
Complément sur les constructeurs : listes d’initialisation Complément sur les constructeurs : listes d’initialisation

I Si l’attribut n’est pas un objet, son initialisation dans la liste est


I Possibilité d’initialiser les attributs en dehors du corps d’un constructeur équivalente à une affectation. Par exemple,
I Si les attributs sont des objets, cela permet de spécifier quel constructeur Student::Student(int n):number(n)
appeler en remplacement du constructeur par défaut) {
I Les initialisations de cette liste sont effectuées AVANT la première ...
instruction dans le corps du constructeur }
I Syntaxe :
NomClasse::NomConstructeur(params):NomAttribut1(params1), a le même effet que :
NomAttribut2(params2), ... Student::Student(int n)
{ {
... number = n;
} ...
}

121 122

Complément sur les constructeurs : listes d’initialisation Complément sur les constructeurs : composition
I Si l’attribut est un objet, l’initialisation dans la liste indique quel I Le constructeur par défaut d’une classe composante est automatiquement
constructeur (de l’attribut) est appelé appelé par tout constructeur de la classe composite
I Dans l’exemple suivant, B contient des attributs de type A (A est la classe
class StudentPair
composante, B la classe composite)
{
class A
private:
{
Student st1, st2;
private:
int a;
public:
public:
StudentPair();
A() {cout<<"ctor default A"<<endl;}
...
A(int i) {a=i; cout<<"ctor int A"<<endl;}
};
};
class B
StudentPair::StudentPair():st1(0), st2(1, "Loulou", "Dudu")
{
{
private:
}
A a1, a2;
public:
→ appel au constructeur Student(int) sur st1 et Student(int,const B() {cout<<"ctor default B"<<endl;}
char *,const char *) sur st2 B(int i) {cout<<"ctor int B"<<endl;}
I Si la liste était vide, le constructeur par défaut de Student serait appelé 123 }; 124

Complément sur les constructeurs : composition Appel automatique au constructeur de recopie


I Passage de paramètre par valeur : à l’exécution de la fonction, le paramètre
I Quel est l’affichage donné par le code suivant ? (correction au tableau) est initialisé par recopie de l’argument passé par la fonction appelante
int main() student.cpp
{ ...
B b; bool Student::hasSameSurname(Student st)
B bi(5); {
} return (strcmp(surname, st.surname)==0);
}
I Même question après avoir ajouté une liste d’initialisation au constructeur à ailleurs.cpp
un entier de la classe B (correction au tableau) : Student stA(20156743, "Doudou", "Marcel");
class B Student stB(20148411, "Doudou", "Bernard");
{ ...
... if (stA.hasSameSurname(stB)) // Appel du constructeur
B(int i):a1(i),a2(i) { // de recopie sur le param. st
{ ...
cout<<"ctor int B"<<endl; }
}
}; I Passage de paramètre par référence : pas d’appel à constructeur, la
fonction travaille avec l’argument lui-même, et pas une copie → technique
125 à privilégier ! (penser au const) 126
Appel automatique au constructeur : conversion Appel automatique au constructeur : conversion
I Une fonction prenant en paramètre un objet d’une classe B peut être
appelée avec un objet de type A en argument, à condition que le I Exemple : si le compilateur sait construire un complexe à partir d’un réel,
compilateur sache comment convertir un A en B (= s’il existe un et qu’il sait additionner deux complexes, alors il peut additionner un
constructeur adéquat dans B) complexe et un réel :
ab.hpp complexe.hpp

class A class Complexe


{ {
... ailleurs.cpp public: ailleurs.cpp
}; float re, im;
...
...
A a;
class B Complexe(); Complexe c, d;
{ Complexe(float); float f;
f1(a);
... ... ...
f2(a);
public: }; d = add(c,f);
B(const A &);
...
}; Complexe add(const Complexe &,
const Complexe &);
void f1(const B &); ...
void f2(B);
127 128

Surcharge d’opérateur (en méthodes) Surcharge d’opérateur (en méthodes)


I Cas particulier de l’opérateur d’affectation = (rôle proche du constructeur
de recopie) :
class Student
I Possibilité de redéfinir le rôle des opérateurs (=, ==, !=, [], +, -, etc) dans {
des méthodes de classe ...
I Les opérateurs binaires seront surchargés en méthodes prenant 1 Student &operator=(const Student &);
paramètre : lors de l’appel, l’opérande gauche est l’objet courant, ...
l’opérande droit est le paramètre };
I Les opérateurs unaires seront surchargés en méthodes ne prenant aucun
Student &Student::operator=(const Student &st)
paramètre : lors de l’appel, l’unique opérande est l’objet courant
{
I Attention à la confusion avec les opérateurs surchargés en fonction ! copy(st);
(non-membre d’une classe) return *this;
I Avantage de la surcharge en méthode plutôt qu’en fonction non-membre : }
accès aux membres privés I Si l’opérateur = n’est pas surchargé, le compilateur génère un opérateur
d’affectation implicite, qui appelle l’opérateur = sur les attributs (attribut
par attribut)
I En général, si l’on a écrit un constructeur de recopie et un destructeur (car
129 présence d’attributs dynamiques), on surcharge également l’opérateur = 130

Surcharge d’opérateur (en méthodes) Surcharge d’opérateur (en méthodes)


I Exemple : on transforme la structure vector3D (diapo 85) en classe. Les
opérateurs sont surchargés en méthode dès que possible (= lorsque
l’opérande gauche est un objet de notre classe) : vector3d.hpp (suite)
vector3d.hpp Vector3D &operator+=(const Vector3D &);
... Vector3D &operator-=(const Vector3D &);
class Vector3D Vector3D &operator*=(float);
{ Vector3D &operator/=(float);
public: ...
float x, y, z; };
Vector3D();
Vector3D operator*(float, const Vector3D &);
Vector3D operator+(const Vector3D &) const; ...
Vector3D operator-() const;
(correction au tableau)
Vector3D operator-(const Vector3D &) const;
Vector3D operator*(float) const; I Remarque : Vector3D n’a pas d’attribut dynamique → pas besoin de définir
Vector3D operator/(float) const; de constructeur de recopie, de destructeur ni de surcharger l’opérateur
d’affectation (dans le cas présent, les implicites font déjà le nécessaire)
bool operator==(const Vector3D &) const;
bool operator!=(const Vector3D &) const;
131 132
Opérateur [ ] Opérateur [ ]
I Utilisation courante de l’opérateur [ ] : accès à un élément, dans une
I La version en lecture seule est appelée si l’objet courant est const. Elle
séquence d’éléments
I L’accès se fait via un indice (le plus souvent, un entier) retourne une référence constante
I Typiquement, la méthode retourne une référence, afin de permettre l’accès I La version en lecture/écriture est appelée si l’objet n’est pas const. Elle
en écriture retourne une référence. Elle peut être utilisée comme membre gauche d’une
I Deux versions de la méthode : une en lecture seule et une en affectation (l-value).
lecture/écriture. Exemple sur une classe Group : I Dans les deux versions, possibilité de vérifier la validité de l’indice
class Group const Student &Group::operator[](int idx) const
{ {
private: assert(idx>=0 && idx<nbStudents);
Student *array; return array[idx];
int nbStudents, nbStudentsMax; }
public:
... Student &Group::operator[](int idx)
void addStudent(const Student &); {
const Student &operator[](int) const; assert(idx>=0 && idx<nbStudents);
Student &operator[](int); return array[idx];
... }
};
133 134

Opérateur [ ] Opérateur d’insertion


I Quelles versions de l’opérateur [ ] sont appelées dans cet exemple ?
void fonc1()
I Certains opérateurs ne peuvent être surchargés qu’en dehors de la classe,
{
Group g; Student st; car l’opérande gauche n’est pas un objet de notre classe
g.addStudent(Student(...)); I Exemple type : opérateur d’insertion << avec objet de type ostream en
g.addStudent(Student(...)); opérande gauche
g.addStudent(Student(...)); student.hpp
st = g[1]; // Version en lecture/écriture ...
g[1] = st; // Version en lecture/écriture #include <iostream>
...
} class Student
{
void fonc2(const Group &g) ...
{ };
...
for (i=0; i<g.getSize(); i++) std::ostream &operator<<(std::ostream &, const Student &);
cout<<g[i].getNumber(); // Version en lecture seule
...
}
135 136

Opérateur d’insertion La classe string


I Comme la fonction n’est pas membre de la classe, accès aux membres
publics seulement !
student.cpp
#include "student.hpp" I La bibliothèque standard C++ (Standard Template Library) fournit une
using namespace std; classe pour gérer les chaı̂nes de caractères : string
... I Pourquoi maintenant ? Parce qu’elle illustre bien l’intérêt du constructeur
ostream &operator<<(ostream &os, const Student &st) de recopie et de la surcharge d’opérateur
{ I Nous reviendrons plus tard sur d’autres classes de la STL
os<<"["<<st.getNumber()<<" , "<<st.getFirstname()
I Définie dans l’espace de noms std
<<" ,"<<st.getSurname()<<"]";
return os; I En remplacement des chaı̂nes allouées dynamiquement (char *, ...) et des
} tableaux statiques (char texte[...])
I Opérateurs surchargés
ailleurs.cpp I Pour l’utiliser : #include <string>
...
Student st;
...
cout<<st<<endl;
137 138
La classe string La classe string
I Déclaration (très résumée !) :
class string
{ I Déclaration (suite) :
... // Comparaison selon l’ordre alphabétique
public: bool operator<(const string &) const;
string(); bool operator>(const string &) const;
string(const char *); bool operator<=(const string &) const;
string(const string &); bool operator>=(const string &) const;
~string();
const char *c_str() const;
unsigned int size() const;
char operator[](int) const;
string &operator=(const string &); char &operator[](int);
string &operator+=(const string &);
...
string operator+(const string &) const; };

bool operator==(const string &) const;


bool operator!=(const string &) const;
139 140

La classe string Héritage


I L’héritage permet de réutiliser du code en faisant dériver une nouvelle
I Le constructeur de recopie et l’opérateur = assurent une gestion saine de la classe d’une classe existante, afin de récupérer ses attributs et méthodes,
mémoire : sans avoir à la réécrire complètement
I Il est utilisé pour représenter une spécialisation d’une entité
string nom1;
nom1 = "Bernard"; I Terminologie : si une classe Child hérite d’une classe Parent, on dit que :
I Child est une spécialisation, une sous-classe, une classe fille, une classe
string nom2(nom1); // Appel au constructeur de dérivée de Parent
I Parent est une généralisation, une super-classe, une classe mère de Child
// recopie
string nom3 = nom1; // Appel au constructeur de I L’entité dérivée (classe fille) possède toutes les caractéristiques et
// recopie opérations de l’entité initiale (classe mère) avec, éventuellement, des
string nom4; caractéristiques et opérations supplémentaires
nom1 = nom4; // Appel à operator=(const string &) I La classe fille est plus ”précise” que la classe mère. Exemples :
I Chataigner ou Pommier pourraient hériter de Arbre
I A la fin du programme, les chaı̂nes contenues dans les 4 objets string I Voiture ou Bicyclette pourraient hériter de Vehicule
sont bien distincts en mémoire I Possibilité de modifier les opérations dans la classe dérivée → redéfinition
I Grâce au destructeur, elles seront automatiquement désallouées de méthodes
I L’héritage est transitif : si B hérite de A et C hérite de B, alors
indirectement, C hérite de A
141 142

Héritage simple public Héritage simple public


I L’héritage public conserve la visibilité : si B hérite de A, et que le membre i ailleurs.hpp
est public (resp. privé, protégé) dans A, alors il est public (resp. #include "b.hpp"
inaccessible, protégé) dans B class Ailleurs
I Un membre privé sera inaccessible même dans les classes dérivées
{
a.hpp
private:
b.hpp
class A A a;
{ #include "a.hpp" B b;
private: class B: public A public:
int i; { ...
protected: public: void setAllToOne()
int j; B() {
public: { a.i = 1; // Erreur : i est privé dans A
int k; i = 0; // Erreur : i est privé a.j = 1; // Erreur : j est protégé dans A
A() // dans A a.k = 1; // Ok : k est public dans A
{ j = 0; // Ok : j est protégé b.i = 1; // Erreur : i est inaccessible dans B
i = 0; // dans A b.j = 1; // Erreur : j est protégé dans B
j = 0; k = 0; // Ok : k est public b.k = 1; // Ok : k est public dans B
k = 0; } }
} }; };
}; 143 144
Héritage et visibilité Héritage et constructeur
I Tout constructeur de la classe fille appelle automatiquement le
constructeur par défaut de la classe mère (sauf si un autre constructeur est
I L’héritage lui-même peut être protégé ou privé spécifié dans la liste d’initialisation)
I La visibilité d’un membre de la classe fille dépend donc : I L’appel est effectué au début
I de la visibilité de ce membre dans la classe mère I Considérons l’exemple suivant :
I de la visibilité indiquée dans l’héritage class A
I Règle : le moins visible l’emporte. Visibilité d’un membre de la classe fille {
en fonction de la visibilité de ce membre dans la mère, et de la visibilité de protected:
l’héritage : int a;
private protected public public:
dans Mère dans Mère dans Mère A() {cout<<"ctor default A"<<endl;}
A(int i) {a=i; cout<<"ctor int A"<<endl;}
héritage private protected public
};
public et inaccessible
héritage private protected protected
class B : public A
protected et inaccessible
{
héritage private private private
public:
private et inaccessible
B() {cout<<"ctor default B"<<endl;}
B(int i) {cout<<"ctor int B"<<endl;}
145 }; 146

Héritage et constructeur Héritage et destructeur


I Quel est l’affichage donné par le code suivant ? (correction au tableau) I Le destructeur de la classe fille appelle automatiquement le destructeur de
la classe mère
int main()
{ I L’appel est effectué à la fin
B b; I A l’exemple précédent, on ajoute les destructeurs :
B bi(5); class A
} {
I Même question après avoir ajouté une liste d’initialisation au constructeur à ...
un entier de la classe B (correction au tableau) ~A() {cout<<"dtor A"<<endl;}
};
class B
{
class B : public A
...
{
B(int i):A(i)
...
{
~B() {cout<<"dtor B"<<endl;}
cout<<"ctor int B"<<endl;
};
}
}; I Quel est l’affichage donné par le main() de l’exemple précédent ?
(correction au tableau)
147 148

Héritage : appels automatiques Héritage : appels automatiques


I Attention ! Contrairement aux autres méthodes, les constructeurs et
destructeurs ne sont pas hérités
I Si on supprime les constructeurs et le destructeur dans B...
int main()
class A {
{ B b; // Ok : un constructeur par défaut implicite
protected: // est généré automatiquement dans B
int a;
public: B bi(5); // Erreur de compilation, le constructeur
A() {cout<<"ctor default A"<<endl;} // A::A(int) n’est pas hérité !
A(int i) {a=i; cout<<"ctor int A"<<endl;}
~A() {cout<<"dtor A"<<endl;} } // Appel du destructeur implicite de B
};
I Même comportement pour le constructeur de recopie : s’il n’est pas défini
class B : public A
dans B, le compilateur génère automatiquement un constructeur de recopie
{
implicite qui appelle celui de A
};
I ... le compilateur génère automatiquement un constructeur par défaut
implicite (resp. un destructeur) dans B, qui appelle automatiquement le
constructeur par défaut (resp. le destructeur) de A 149 150
Conversion implicite fille → mère Redéfinition de méthodes
I Toute fonction ou méthode (dont l’opérateur d’affectation, le constructeur
de recopie, ...) prenant en paramètre un objet d’une classe mère peut être
appelée avec un objet de la classe fille en argument
I Si B hérite de A, le compilateur sait toujours comment convertir une I Par défaut, la classe fille a le même ”comportement” que la classe mère :
instance de B en une instance de A tous les attributs et méthodes sont hérités
ab.hpp I Intérêt de la redéfinition : pouvoir changer partiellement ce comportement
en redéfinissant certaines méthodes (en conservant le type de retour et les
class A ailleurs.cpp paramètres)
{
... ... I Une méthode redéfinie peut être complètement différente de la méthode de
void m(const A &) A a; base, ou bien réutiliser celle-ci en effectuant des opérations supplémentaires
... B b; I Si un membre de la classe fille porte le même nom qu’un membre de la
}; classe mère, ce dernier est accessible dans la classe fille via l’opérateur de
class B : public A a.m(b); résolution de portée
{
... f1(b);
}; f2(b);
void f1(const A &);
void f2(A);
151 152

Redéfinition de méthodes Redéfinition de méthodes

I Illustration de l’intérêt de la redéfinition de méthode : L’employé travaille. lazyworker.hpp


En plus de l’employé standard, on distingue deux sortes d’employé #include "worker.hpp"
supplémentaires : le fainéant qui, lorsqu’il travaille, fait en réalité autre class LazyWorker : public Worker
chose et le bourreau de travail qui, en plus de son travail obligatoire, réalise {
des tâches supplémentaires. protected:
I Modélisons ces employés... ... // Attributs supplémentaires propres au fainéant
worker.hpp
public:
class Worker LazyWorker() {...}
{ void work()
protected: {
... // Attributs de l’employé ... // Rien... ou autre chose que le travail
public: }
Worker() {...} ...
void work() {...} };
};

153 154

Redéfinition de méthodes
I Accès aux membres de la classe mère (s’ils sont protégés ou publics) avec
l’opérateur de résolution de portée précédé du nom de la classe mère
workaholic.hpp
#include "worker.hpp"
class Workaholic : public Worker
{
protected:
... // Attributs supplémentaires propres
// au bourreau de travail
public:
Workaholic() {...}

void work()
{
Worker::work(); // Appel à work() de Worker
... // Travail supplémentaire !
}
...
};
155

Vous aimerez peut-être aussi