Upload
others
View
7
Download
1
Embed Size (px)
Citation preview
D. DUBOIS Mars 2016
en résumé…
Prérequis : - Langage C
- Java / POO
D. DUBOIS Version 3 – Mars 2016 Page 2 / 33
I. Introduction ................................................................................................................... 3
1. Références ................................................................................................................................................. 3 2. Présentation .............................................................................................................................................. 3 3. Premier programme « Hello world » en C++ ............................................................................................ 4
II. Variables ........................................................................................................................ 5
1. Règles de base ........................................................................................................................................... 5 2. Conventions d’usage ................................................................................................................................. 5 3. Type de variable ........................................................................................................................................ 5 4. Déclaration d’une variable ........................................................................................................................ 6 5. Déclarer une constante ............................................................................................................................. 6 6. Déclarer une chaine de caractères ........................................................................................................... 6
III. Les références (&) ............................................................................................................ 7
IV. Lecture depuis la console .................................................................................................. 7
V. Arithmétique ................................................................................................................. 8
VI. Les structures de contrôle.................................................................................................. 8
VII. Les fonctions ................................................................................................................... 8
VIII. Les tableaux ................................................................................................................... 9
1. Les tableaux statiques .............................................................................................................................. 9 2. Les tableaux dynamiques (vector) ............................................................................................................ 9
IX. Les surcharges & les modèles.............................................................................................. 11 1. La surcharge (overloading functions) ..................................................................................................... 11 2. Le modèle (Function Templates) ............................................................................................................ 11
X. C++ et la POO ............................................................................................................... 13
1. Les classes ............................................................................................................................................... 13 2. La surcharge d’opérateurs ...................................................................................................................... 19 3. L’héritage ................................................................................................................................................ 21 4. Le polymorphisme ................................................................................................................................... 24
XI. Exercices ....................................................................................................................... 26
1. Exercice 1 : trier/échanger ...................................................................................................................... 26 2. Exercice 2 : Référence ............................................................................................................................. 26 3. Exercice 3 : Recopie ................................................................................................................................. 26 4. Exercice 4 : String ................................................................................................................................... 26 5. Exercice 5 : Classe ................................................................................................................................... 26 6. Exercice 6 : classe .................................................................................................................................... 26 7. Exercice 7 : surcharge d’opérateurs ....................................................................................................... 26
XII. Corrigés des exercices ....................................................................................................... 27
1. Exercice 1 ................................................................................................................................................ 27 2. Exercice 2 ................................................................................................................................................ 28 3. Exercice 3 ................................................................................................................................................ 29 4. Exercice 4 ................................................................................................................................................ 30 5. Exercice 5 ................................................................................................................................................ 31 6. Exercice 6 ................................................................................................................................................ 32 7. Exercice 7 ................................................................................................................................................ 33
D. DUBOIS Version 3 – Mars 2016 Page 3 / 33
I. Introduction
1. Références
Prérequis : Langage C & POO
Référence :
Références Web :
o http://www.cplusplus.com
o http://fr.cppreference.com
Références bibliographiques :
2. Présentation
Langage développé par Bjarne STROUSTRUP (années 80)
Evolution du langage C avec l’apport de la POO
Langage de programmation les plus populaires avec le C et le JAVA
Langage normalisé - Standard actuel : ISO/IEC 14882:2011 (Maj : ISO/IEC 14882:2014)
Multiplateforme / multi systèmes d’exploitation
Langage de programmation compilé
Permet différents types de programmation
o Programmation procédurale (ou modulaire) : découpe d’un programme en série de
fonction (ou procédures). une procédure (routine ou fonction) contient une série d'étapes à
réaliser. Elle peut être appelée à n’importe quelle étape de l’exécution du programme ou à
partir d’autres procédures ou d’elle-même (récursivité)
o Programmation Orientée Objet : Représenter des objets et leurs relations entre eux. Un
objet = concept possédant une structure et un comportement et interagit avec d’autres objets
o Programmation générique : définir des algorithmes identiques qui traitent des données
de types différents (équivalent au polymorphisme)
La syntaxe du C++ est proche du langage C : découpage du code en module, utilisation de
fichiers inclus,….
Les principaux ajouts par rapport au langage C :
o le type de données bool (booléen) ;
o les références
o les paramètres par défaut dans les fonctions
o les référentiels lexicaux (namespace) et l'opérateur de résolution « :: »
o les classes - POO
o la surcharge des opérateurs
o les templates
o la gestion d'exceptions
o les string
o …
D. DUBOIS Version 3 – Mars 2016 Page 4 / 33
3. Premier programme « Hello world » en C++
Variante 1
#include<iostream>
int main()
{
// affichage d’un message
std::cout << "Hello world!\n";
return 0 ;
}
Variante 2
#include<iostream>
using namespace std ;
int main()
{
// affichage d’un message
cout << "Hello world!\n";
return 0 ;
}
#include<iostream> C'est une directive de préprocesseur.
Rôle : charger les fonctionnalités du C++ pour effectuer certaines actions ; ce sont
des extensions du langage appelées bibliothèques.
Ici le fichier iostream (Input Output Stream) permet d'utiliser une bibliothèque
d'affichage de messages à l'écran dans une console. Il permet également de
récupérer ce que saisit l’utilisateur au clavier
using namespace std Indique dans quel lot de fonctionnalités le fichier source va chercher les fonctions
utilisées. Quand on charge plusieurs bibliothèques, chacune va proposer de
nombreuses fonctionnalités qui ont parfois le même nom. Pour éviter la confusion
il existe les espaces de noms (namespaces) qui sont des sortes de dossiers à noms.
Ici on indique qu’on utilise l'espace de noms std : il correspond à la bibliothèque
standard (std) livrée par défaut avec le langage C++ et dont iostream fait partie.
Namespace est similaire à l’utilisation des packages en Java
int main() {
...
}
Les accolades déterminent le début et la fin de la fonction main.
La fonction main retourne un entier suite à son exécution
Tous les programmes commencent par la fonction main
cout << "Hello world!\n"; Ici, la première instruction de la fonction main
cout : afficher un message à l’écran
chaque instruction se termine par un point-virgule
Autre exemple :
cout << "Bonjour" << endl << "tout le monde" << endl ;
"Bonjour" et "tout le monde": c’est le message à afficher ;
endl : crée un retour à la ligne dans la console.
return 0 ; Instruction qui clôt généralement les fonctions.
Ici, la fonction main renvoie 0 pour indiquer que tout s'est bien passé
// affichage d’un message // permet d’écrire un commentaire court sur une seule ligne
/* permet d’écrire un commentaire
sur plusieurs lignes */
D. DUBOIS Version 3 – Mars 2016 Page 5 / 33
II. Variables
Une variable est une information stockée en mémoire.
Il existe différents types de variables en fonction de l'information à stocker : int, char, bool…
Une variable doit être déclarée avant utilisation. Sa valeur peut être affichée avec cout.
1. Règles de base
Les noms de variables sont constitués de lettres, de chiffres et de l’underscore uniquement ;
le premier caractère doit être une lettre majuscule ou minuscule ;
Les accents et les espaces dans le nom sont interdits ;
Le C++ est sensible à la casse : il fait la différence entre les majuscules et les minuscules.
2. Conventions d’usage
Pour plus de lisibilité, utiliser des noms de variables qui décrivent ce qu'elles contiennent
(ageDuCapitaine pour désigner la variable qui contiendra l’âge du capitaine)
les noms de variables commencent par une minuscule ; si le nom se décompose en plusieurs
mots, ceux-ci sont collés les uns aux autres ;
chaque nouveau mot (excepté le premier) commence par une majuscule.
3. Type de variable
Type booléen
o bool - type capable de contenir l'une des deux valeurs : vrai (true) ou faux (false).
Types de caractères
o signed char - type pour la représentation des caractères signés.
o unsigned char - type pour la représentation des caractères non signés.
o char - type représentant un caractère (équivalent à signed char )
o wchar_t - type représentant caractère large. (ex. UTF-8)
o char16_t - type représentant un caractère UTF-16. (depuis C++11)
o char32_t - type représentant un caractère UTF-32. (depuis C++11)
Type entier
Type de variable
Largeur (bits) en mémoire
Valeurs C++
Win16
API
Win32
API
Unix
Win64
API Unix
short, short int
signed short, signed short int 16 -32 768 à 32 767
unsigned short, unsigned short int 0 à 65535
int, signed,
signed int 16 16 32 32 32
-32 768 à 32 767 ou
-2 147 483 648 à 2 147 483 647
unsigned,
unsigned int
0 à 65535 ou
0 à 4 294 967 295
long, long int,
signed long, signed long int 32 32 32 32 64
-2 147 483 648 à 2 147 483 647 ou
-9 223 372 036 854 775 808 à 9 223 372 036 854 775 807
unsigned long
unsigned long int
0 à 4 294 967 295 ou
0 à 18 446 744 073 709 551 615
long long
long long int (C++11)
signed long long
signed long long int 64
-9 223 372 036 854 775 808 à 9 223 372 036 854 775 807
unsigned long long (C++11)
unsigned long long int 0 à 18 446 744 073 709 551 615
D. DUBOIS Version 3 – Mars 2016 Page 6 / 33
Type à virgule flottante :
o float : type à virgule flottante à précision simple = type IEEE-754 32 bits.
Valeurs : ± 3.4 · 10± 38
o double : type à virgule flottante à précision double = type IEEE-754 64 bits.
Valeurs : ± 1.7 · 10± 308
o long double : type à virgule flottante à précision étendue = 80-bit x87
4. Déclaration d’une variable
a. Sans initialisation
TYPE NOM ; // déclaration d’une variable nommée NOM de type TYPE
b. Déclaration d’une variable avec initialisation
Les trois initialisations suivantes sont strictement équivalentes. Il est conseillé d'utiliser la
première ou la seconde pour respecter la norme C++
TYPE NOM (VALEUR) ; // syntaxe d’initialisation d’une variable en C++
TYPE NOM {VALEUR} ; // révision du C++ en 2011
TYPE NOM = VALEUR ; // syntaxe d’initialisation d’une variable en C
c. Exemple sur les variables
#include <iostream>
using namespace std;
int main()
{
string nomJoueur ; // déclaration d’une chaine de caractère
int nombreJoueurs ; // déclaration d’un entier
bool estGagnant ; // déclaration d’un booléen
int ageDuCapitaine(61); // déclaration et initialisation d’un entier
double pi(3.14169); // déclaration et initialisation d’un double
bool estVerifie(false); // valeurs possible pour type bool : true ou false
char lotte('a'); // apostrophe pour déclarer un char
string nomUser("dado"); // guillemet pour déclarer une chaîne
int a(-1),b(12),c(0) ; // déclaration de trois entiers en cascade
a = b ; // affectation de la valeur de b à a
pi = 3.1 ; // changement de la valeur de pi
cout << "Le capitaine a " << ageDuCapitaine << " ans." << endl; // affichage
return 0;
}
5. Déclarer une constante
On déclare une variable normalement et on ajoute le mot-clé const entre le type et le nom :
int const maxNiveaux(10) ;
double const pi(3.14);
unsigned int const pointVieMax(1000);
6. Déclarer une chaine de caractères
Trois façons pour déclarer et initialiser une chaine de caractères
string mystring = "Vers l’infini et au-delà";
string mystring("To infinity and beyond");
string mystring {"Buzz lightyear"};
D. DUBOIS Version 3 – Mars 2016 Page 7 / 33
III. Les références (&)
Une variable est un emplacement mémoire et cette variable est accessible par son nom dans
le programme.
Il est possible d’attribuer à une variable plusieurs « étiquettes » ce qui procure un autre
moyen d’accéder au même emplacement mémoire que la variable nommée. C’est ce qu’on
appelle la référence. La référence doit impérativement être du même type que la variable
à laquelle elle fait référence.
int ageDuCapitaine(61); // Déclaration et initialisation d’un entier
int& maVar(ageDuCapitaine); // Déclaration d'une référence nommée maVar
// maVar fait référence à ageDuCapitaine
int& autreVar = ageDuCapitaine ; //autreVar est une référence initialisée à ageDuCapitaine
cout << "le capitaine a " << ageDuCapitaine << " ans." << endl; // via variable
cout << "le capitaine a " << maVar << " ans. " << endl; // via référence
IV. Lecture depuis la console
flux sortant : cout les chevrons associés :
flux entrant : cin les chevrons associés :
#include <iostream>
using namespace std;
int main()
{ cout << "Quel age avez-vous ?" << endl;
int ageUtilisateur(0);
cin >> ageUtilisateur;
cout << "Vous avez " << ageUtilisateur << " ans !" << endl;
return 0;
}
Attention : le problème des espaces dans les chaines de caractères saisies (comme en C)
Quand on valide une saisie par la touche Entrée, l'ordinateur copie en mémoire ce qui a été tapé
par l'utilisateur… MAIS s’arrête au premier espace ou retour à la ligne rencontré.
Pour les nombres, pas de problème (puisqu'il n'y a pas d'espace dans les nombres)
Pour les string, problème : Il peut y avoir un ou plusieurs espaces dans une chaîne de
caractères : il faut alors utiliser la fonction getline()
Remplacer la ligne cin nomUtilisateur; par getline(cin, nomUtilisateur);
Quand on mélange l'utilisation des chevrons et de getline(), il faut toujours placer
l'instruction cin.ignore() après la ligne cin …
#include <iostream>
#include <string>
using namespace std;
int main()
{
cout << "Quel est votre taille ?" << endl;
double tailleUtilisateur(-1.0);
cin >> tailleUtilisateur;
cin.ignore();
cout << "Quel est votre nom ?" << endl;
string nomUtilisateur("Sans nom");
getline(cin, nomUtilisateur);
cout << "Vous vous appelez " << nomUtilisateur << " et vous
mesurez " << tailleUtilisateur << "m" << endl;
return 0;
}
D. DUBOIS Version 3 – Mars 2016 Page 8 / 33
V. Arithmétique
Opérateurs arithmétique, incrémentation, décrémentation Voir langage C
Utilisation des fonctions mathématiques comme sqrt(), fabs, ceil(),floor(), pow(),sin(),…
#include<cmath>
VI. Les structures de contrôle
if() else , switch(), while(), do...while(), for() Voir langage C
Variante pour l’initialisation en C++ dans la boucle for :
#include <iostream>
using namespace std;
int main ()
{
for (int n=10; n>0; n--) { // déclaration du type dans la boucle for
cout << n << ", ";
}
cout << "GO!\n";
}
La boucle a aussi une autre syntaxe qui est utilisée pour les intervalles:
#include <iostream>
#include <string>
int main ()
{
std::string str {"Hello les ITI !"};
for (char c : str)
{
std::cout << c << ' ' ; // H e l l o l e s I T I !
}
std::cout << '\n';
}
VII. Les fonctions
Créer et utiliser une fonction Voir langage C
Passage par valeur Voir langage C
Passage par référence :
Code C++
void echange(int& x, int& y)
{
int buffer = x;
x = y;
y = buffer;
}
int main()
{
int a(3), b(5);
cout << "a = " << a << endl ;
cout << "b = " << b << endl ;
echange(a,b);
cout << "a = " << a << endl ;
cout << "b = " << b << endl ;
return 0 ;
}
Code C
void echange (int *x, int *y)
{
int buffer = *x;
*x = *y;
*y = buffer;
}
int main()
{
int a = 3, b = 5;
printf("a = %d\n",a);
printf("b = %d\n",b);
echange (&a, &b);
printf("a = %d\n",a);
printf("b = %d\n",b);
return 0 ;
}
D. DUBOIS Version 3 – Mars 2016 Page 9 / 33
VIII. Les tableaux
1. Les tableaux statiques
Déclaration d’un tableau statique : TYPE NOM [taille] ;
Exemple:
#include <iostream>
using namespace std;
double moyenne(double tableau[] , int taille )
{
double moy(0) ;
for (int i(0);i<taille ;i++)
moy += tableau [i] ;
moy /= taille ;
return moy ;
}
int main ()
{
double notes[5] ; // déclaration d’un tableau de 5 « double »
notes[0] = 12.5 ;
notes[1] = 19.5 ;
notes[2] = 8. ;
notes[3] = 15. ;
notes[4] = 5.3 ;
cout << "Moyenne : " << moyenne(notes,5) << endl ;
return 0;
}
2. Les tableaux dynamiques (vector)
Un tableau dynamique est un tableau dont la taille peut varier. Elle n’est pas fixée comme
un tableau statique
Pour utiliser les tableaux dynamiques, il faut ajouter la ligne #include<vector> en début
de programme
Déclaration d’un tableau dynamique : VECTOR<TYPE> NOM (taille) ;
a. Exemple de déclaration :
#include <iostream>
#include <vector>
using namespace std;
int main ()
{
vector<int> t(5) ; // t est un tableau de 5 entiers
vector<int> tab(5,3) ; // tab est un tableau de 5 entiers valant tous 3
vector<string> listeNoms(10,"anonyme") ; // tableau de 10 string valant « anonyme »
vector<double> tab ; // création d’un tableau tab de 0 nombre à virgule
return 0 ;
}
D. DUBOIS Version 3 – Mars 2016 Page 10 / 33
b. Exemple pour accéder aux éléments d’un tableau
int const nbHighScores(3) ; // taille du tableau
vector<int> highScores(nbHighScores) ; // déclaration du tableau highScores
highScores[0] = 813652 ;
highScores[1] = 71294 ;
highScores[2] = 58693 ;
c. Changer la taille du tableau
#include <iostream>
#include<vector>
using namespace std;
int main ()
{
vector<int> tableau(3,2) ; // déclaration du tableau de 3 entiers valant tous 2
tableau.push_back(8) ; // ajout d’une 4ème case au tableau qui contient 8
tableau.push_back(4) ; // ajout d’une 5ème case au tableau qui contient 4
tableau.push_back(18) ; // ajout d’une 6ème case au tableau qui contient 18
tableau.pop_back() ; // suppression de la dernière case du tableau
for (int i(0);i<tableau.size();i++)
cout << tableau[i] << endl;
return 0 ;
}
d. Vector : Les méthodes
int size() : renvoie la taille du vecteur (son nombre d’éléments)
bool empty() : indique si le vecteur est vide ( vect.empty() vect.size() == 0 )
void clear : vide le vecteur en supprimant tous ses éléments
void pop_back() : supprime le dernier élément du vecteur
void push_back(const type element) : ajoute element à la fin du vecteur
type front() : renvoie une référence vers le 1er élément du vecteur (vect.front()vect[0])
type back() : renvoie une référence vers le dernier élément du vecteur
e. Les tableaux multidimensionnels
int tableau[5][4] ;
double grilledelaudela[7][8][1][4][8] ;
vector<vector<int> > grille ;
grille.push_back(vector<int>(5)) ; // ajoute une ligne de 5 cases à la grille
grille.push_back(vector<int>(3,4)) ; // ajoute une ligne de 3 cases contenant 4
grille[0].push_back(8) ; // ajoute une case contenant 8 à la première ligne du tableau
grille[2][3] = 9 ; // change la valeur de la cellule (2,3) de la grille
f. Les strings comme tableaux
#include <iostream>
#include<string>
using namespace std ;
int main()
{
string user("Lucien") ;
user[0]= 'J' ; // modification de la première lettre
user[2]= 'L' ; // modification de la troisième lettre
cout << "Vous etes : " << user << endl ;
return 0 ;
}
D. DUBOIS Version 3 – Mars 2016 Page 11 / 33
IX. Les surcharges & les modèles
1. La surcharge (overloading functions)
En C ++, deux fonctions différentes peuvent avoir le même nom
si leurs paramètres sont différents : o soit parce qu'elles ont un certain nombre de paramètres différents
o soit parce que l'un de leurs paramètres est d'un type différent.
#include <iostream>
using namespace std;
int calcul (int a, int b)
{
return (a*b);
}
double calcul (double a, double b)
{
return (a/b);
}
int main ()
{
int x=10,y=30;
double z=15.0,w=27.0;
cout << calcul (x,y) << '\n';
cout << calcul (z,w) << '\n';
return 0;
}
.
2. Le modèle (Function Templates)
Exemple qui se prête à l’application des templates:
// overloaded functions
#include <iostream>
using namespace std;
int sum (int a, int b)
{
return a+b;
}
double sum (double a, double b)
{
return a+b;
}
int main ()
{
cout << sum (10,20) << '\n';
cout << sum (1.0,1.5) << '\n';
return 0;
}
Ici, la somme est surchargée avec des types de
paramètres différents, mais avec le même
corps d’instructions.
Cette fonction pourrait être alors surchargée
par beaucoup plus de types.
C++ a la capacité de définir des fonctions avec
des types génériques, connus sous le nom des
modèles de fonction.
Déclaration d’un modèle :
template <paramètres> Fonction déclaration
Les paramètres de modèle sont une série de paramètres séparés par des virgules.
Attention : Cet exemple montre deux fonctions qui portent le même nom et qui ont ici des comportements différents sur les variables : Ce qui n’est vraiment pas une bonne chose ! Généralement elles sont censées avoir un comportement identique sur les variables
D. DUBOIS Version 3 – Mars 2016 Page 12 / 33
Exemple 1:
#include <iostream>
using namespace std;
template <typename T>
T sum (T a, T b)
{
T result;
result = a + b;
return result;
}
int main ()
{
int i=5, j=6, k;
double f=2.0, g=0.5, h;
k=sum<int>(i,j);
h=sum<double>(f,g);
cout << k << '\n';
cout << h << '\n';
// autre ecriture
k=sum(i,j);
h=sum(f,g);
cout << k << '\n';
cout << h << '\n';
return 0;
}
Exemple 2:
#include <iostream>
using namespace std;
template < typename T, typename U>
bool egalite (T a, U b)
{
return (a==b);
}
int main ()
{
if (egalite<int,double>(10,10.1))
cout << "x = y \n";
else
cout << "x != y\n";
// autre ecriture
if (egalite(10,10.0))
cout << "x = y \n";
else
cout << "x != y\n";
return 0;
}
Exemple 3:
#include <iostream>
using namespace std;
template <typename T>
T maximum (const T& a, const T& b)
{
if (a>b)
return a ;
else
return b ;
}
int main ()
{
// utilise la version double
double pi(3.14) ;
double e(2.71) ;
cout << maximum<double>(pi,e) ;
cout << endl ;
// utilise la version int
int rdc(0) ;
int cave(-1) ;
cout << maximum<int>(rdc,cave) ;
cout << endl ;
return 0;
}
Exemple 4:
#include <iostream>
using namespace std;
template < typename T, typename S>
S moyenne (T tableau[],int taille)
{
S somme(0) ;
for (int i(0);i<taille;++i)
somme += tableau[i] ;
return somme/taille ;
}
int main ()
{
int tab[5] = {5,7,3,4,5} ;
cout << "Moyenne :" ;
cout << moyenne<int,double>(tab,5) ;
cout << endl ;
return 0;
}
D. DUBOIS Version 3 – Mars 2016 Page 13 / 33
X. C++ et la POO
Une classe est constituée :
o de variables appelées attributs ou variables membres
o de fonctions appelées méthodes ou fonctions membres
o Il n’y a pas d’ordre mais il est d’usage de commencer par la déclaration des attributs
o Les droits d'accès
1. Les classes
Code minimal pour créer une classe :
// Création de la classe Joueur
class Joueur
{
} ; // Ne pas oublier le point-virgule
a. Les attributs :
C’est ce qui caractérise la classe, ici le Joueur. Ce sont des variables qui peuvent évoluer
au fil du programme.
class Joueur
{
int niveauVie ; // niveau de vie du joueur
int niveauMagie ; // niveau de magie du joueur
string armeJoueur ; // arme du joueur
int puissanceArme ; // puissance de l’arme du joueur
} ;
b. Les méthodes :
Ce sont les actions que peut effectuer le joueur. Les méthodes lisent et modifient les attributs
class Joueur
{
void diminuerVie (int vieEnMoins) { … } // diminution de la vie du joueur
void augmenterVie (int vieEnPlus) { … } // augmenter la vie du joueur
void attaquer (Joueur &cible) { … } // Attaquer un autre joueur
void changerArme (string nomNouvelleArme,int puissanceNouvelleArme) { … }
bool estVivant(){ … } // vérification que le joueur est vivant
} ;
c. Droits d’accès et encapsulation
Chaque attribut ou méthode d’une classe peut posséder son propre droit d’accès. Il existe 2
principaux droits d’accès différents :
public : l'attribut ou la méthode peut être appelé depuis l'extérieur de l'objet.
private : l'attribut ou la méthode ne peut pas être appelé depuis l'extérieur de
l'objet.
Par défaut (sans spécification aucune), tous les éléments d'une classe sont private.
Principe d’encapsulation :
Tous les attributs d’une classe doivent toujours être privés ! !
On ne peut (doit) pas modifier les attributs depuis l’extérieur de la classe
Prérequis pour cette partie :
Langage JAVA - POO
D. DUBOIS Version 3 – Mars 2016 Page 14 / 33
d. Séparation des prototypes et des implémentations
Ce n’est pas une obligation mais vivement conseillé car cela permet d’avoir un code
modulaire et plus lisible. On sépare donc la fonction main() du fichier main.cpp des classes.
Pour chaque classe, on créera : o Un header (fichier *.h) qui contiendra les attributs et les prototypes de la classe
o Un fichier source (fichier *.cpp) qui contiendra la définition des méthodes et leur implémentation
Exemple :
Joueur.h
Joueur.cpp
main.cpp
#ifndef DEF_H_JOUEUR
#define DEF_H_JOUEUR
#include <string>
class Joueur
{
private:
int niveauVie ; // niveau de vie du joueur
int niveauMagie ; // niveau de magie
std::string armeJoueur ; // arme du joueur
int puissanceArme ; // puissance de l’arme
public:
void diminuerVie (int vieEnMoins) ;
void augmenterVie (int vieEnPlus) ;
void attaquer (Joueur &cible) ;
void changerArme (std::string nomNouvelleArme,
int puissanceNouvelleArme) ;
bool estVivant() ;
} ;
#endif
#include "Joueur.h"
using namespace std ;
void Joueur::diminuerVie (int vieEnMoins) {
niveauVie -= vieEnMoins ;
if (niveauVie < 0) niveauVie = 0 ;
}
void Joueur::augmenterVie (int vieEnPlus) {
niveauVie += vieEnPlus;
if (niveauVie > 100) niveauVie = 100 ;
}
void Joueur::attaquer (Joueur &cible) {
cible.diminuerVie(puissanceArme) ;
}
void Joueur::changerArme (string
nomNouvelleArme,int puissanceNouvelleArme) {
armeJoueur = nomNouvelleArme ;
puissanceArme = puissanceNouvelleArme ;
}
bool Joueur::estVivant {
return (niveauVie>0) ;
}
#include <iostream>
#include "Joueur.h"
using namespace std ;
int main()
{
Joueur arkonovi,forceBleue ; // creation de deux objets de type Joueur
Joueur forceRouge ; // creation joueur
Joueur forceJauneDevantMarronDerriere ; // creation joueur
arkonovi.attaquer(forceBleue ) ; // arkonovi attaque forceBleue
forceBleue.augmenterVie(20) ; // forceBleue augemente sa vie de 20 pts
arkonovi.attaquer(forceBleue ) ; // arkonovi attaque forceBleue
forceRouge.attaquer(arkonovi) ; // forceRouge défend forcebleue
forceBleue.changerArme("rayon laser bx 4000",40) ; // force bleue change d’arme
arkonovi.attaquer(forceJaune) ; // arkonovi attaque forceJaune
forceRouge.changerArme("rayon laser bx 6000",60) ; // force rouge change d’arme
forceRouge.attaquer(arkonovi) ; // forceJaune contre-attaque
}
Arkonowi : « Ah, je vais vous transformer en bouillie radioactive tellement
liquide qu'on pourra même plus vous manger avec des baguettes ! »
...
Arkonowi : Tiens, prends ça dans ta g euaahhh ! C'est un venin
intersidéral qui te transformera en poudre thermospasmique et qui te fera
des taches sur les habits que même Skip machine pourra pas les enlever !
D. DUBOIS Version 3 – Mars 2016 Page 15 / 33
e. Constructeur, surcharge du constructeur, constructeur de copie
Constructeur :
Le constructeur une méthode appelée automatiquement à chaque fois que l’on crée un
objet basé sur cette classe.
Un constructeur par défaut est automatiquement créé par le compilateur. Il est vide et ne
fait rien de particulier.
On peut alors créer son constructeur qui remplace le constructeur vide par défaut. Son
principal rôle sera généralement d’initialiser les attributs qui n’ont pas de valeur par
défaut.
Le constructeur est une méthode respectant les deux règles :
o Il porte le même nom que la classe
o La méthode ne doit RIEN renvoyer (même pas void) : c’est une méthode sans aucun type de retour
Exemple : pour la classe Joueur, le constructeur par défaut s’écrira Joueur();
Surcharge du constructeur :
Il est possible de surcharger le constructeur en réécrivant une (ou plusieurs) autre(s)
méthode(s) mais respectant les règles suivantes :
o La surcharge du constructeur porte le même nom que la classe
o Il y a un nombre de paramètres différents,
o S’il y a le même nombre de paramètres, ceux-ci diffèrent d’au moins un type
Exemple : pour la classe Joueur :
- le constructeur par défaut s’écrira : Joueur();
- un autre constructeur peut s’écrire : Joueur(std::string P_armeJoueur , int P_puissanceArme)
- un autre constructeur peut s’écrire : Joueur(std::string P_armeJoueur , int P_puissanceArme, int NiveauVie)
Constructeur de copie
// le constructeur par défaut est appliqué à buzz
// on donne ici une « meilleure arme à woody » avec l’autre constructeur
Joueur buzz, woody("Lance flamme",25) ;
// creation du joueur « david » en copiant les attribut de « woody »
Joueur david(woody) ;
Exemple :
D. DUBOIS Version 3 – Mars 2016 Page 16 / 33
#ifndef JOUEUR_H_INCLUDED Joueur.h
#define JOUEUR_H_INCLUDED
class Joueur {
private:
int niveauVie ;
int niveauMagie ;
std::string nomJoueur ;
std::string armeJoueur = "Pistolet a eau" ;
int puissanceArme = 10 ;
public:
Joueur();
Joueur(int nV,int nM,int pA,std::string nJ,std::string aJ);
void afficher(void);
};
#endif // JOUEUR_H_INCLUDED
#include <iostream> Joueur.cpp
#include "joueur.h"
// constructeur par defaut
Joueur::Joueur(){
niveauVie = 50 ;
niveauMagie = 0 ;
nomJoueur = "inconnu" ;
}
// surcharge constructeur
Joueur::Joueur(int nV,int nM,int pA,std::string nJ,std::string aJ) {
niveauVie = nV ;
niveauMagie = nM ;
nomJoueur = nJ ;
armeJoueur = aJ ;
puissanceArme = pA ;
}
// afficher
void Joueur::afficher() {
std::cout << "Nom : " << nomJoueur << std::endl ;
std::cout << "Arme : " << armeJoueur << " ("<< puissanceArme << ")"<< std::endl ;
std::cout << "Vie : " << niveauVie << std::endl ;
std::cout << "Magie: " << niveauMagie << std::endl <<std::endl ;
}
Fichier
#include <iostream> main.cpp
#include "Joueur.h"
using namespace std;
int main() {
Joueur joueur1;
Joueur joueur2(100,0,30,"Feroce","Char FOCH");
joueur1.afficher();
joueur2.afficher();
return 0;
}
La liste d’initialisation : autre moyen pour initialiser avec un constructeur
Joueur::Joueur():niveauVie(100),niveauMagie(100),armeJoueur("Pistolet à eau"),puissanceArme(10)
{
// rien à mettre ici... Tout a déjà été fait !
}
L’implémentation dans « Joueur.cpp » sera :
Joueur::Joueur(string aJ, int pA):
niveauVie(100),niveauMagie(100),armeJoueur(aJ),puissanceArme(pA)
Remarque : le prototype ne change pas dans le fichier « Joueur.h »
D. DUBOIS Version 3 – Mars 2016 Page 17 / 33
f. Destructeur
C’est une méthode appelée automatiquement lorsqu’un objet est détruit.
Son principal rôle est de « désallouer » la mémoire qui a été allouée
dynamiquement.
Ici, le cas de notre classe Joueur, on n'a fait aucune allocation dynamique (il n'y a aucun
new). Le destructeur est donc inutile.
On en a besoin quand on est amené à faire des allocations dynamiques.
Un destructeur est une méthode qui commence par un tilde (~) suivi du nom de la classe.
Un destructeur ne renvoie aucune valeur, pas même void (comme le constructeur).
Le destructeur ne prendre aucun paramètre. Il n’y a donc qu’un seul destructeur,
Fichier Joueur.h (prototype)
class Joueur
{
public:
~Joueur() ; // destructeur
...
} ;
Fichier Joueur.cpp (implémentation)
Joueur::~Joueur()
{
// rien ICI
}
g. Les méthodes constantes
o Ce sont des méthodes en lecture seule
o Elles possèdent le mot-clé « const » à la fin de leur prototype et de leur déclaration
o La méthode ne modifie pas l’objet (aucune valeur d’attribut modifié)
o Ce sont généralement des méthodes qui se contentent de renvoyer des infos sans modification
// Prototype de la méthode (dans le fichier Joueur.h)
bool estVivant() const ;
// Implémentation de la méthode (dans le fichier Joueur.cpp)
bool Joueur::estVivant() const
{
return (niveauVie>0) ;
}
h. Multiplier les classes et les associer entre-elles
En reprenant l’exemple précédent,
créons une classe « arme » et les
deux fichiers de définition et
d’implémentation : « arme.h » et
« arme.cpp »
Voir les fichiers page suivante
Résultat de l’exécution ci-contre :
D. DUBOIS Version 3 – Mars 2016 Page 18 / 33
Arme.h
Arme.cpp
Il est alors nécessaire de modifier la classe Joueur pour qu’elle utilise l’objet Arme
Joueur.cpp
Joueur.h
main.cpp
#ifndef ARME_H_INCLUDED
#define ARME_H_INCLUDED
#include<iostream>
#include<string>
class Arme
{
private :
std::string nom ; // arme du joueur
int puissance ; // puissance de l’arme
public:
Arme() ; //constructeur par défaut
Arme(std::string pNom,int pPuissance) ;
void changerArme(std::string pNom,int pPuissance) ;
void afficher() const ;
int getPuissance() const ; // getter
} ;
#endif
#include "Arme.h"
using namespace std ;
Arme::Arme() : nom("couteau"),puissance(5) { }
Arme::Arme(string pNom,int pPuissance) : nom(pNom), puissance(pPuissance) { }
void Arme::changerArme(string pNom,int pPuissance)
{
nom = pNom ;
puissance = pPuissance ;
}
void Arme::afficher() const
{
cout << "Arme : " << nom << " (Puissance : " << puissance << ")" << endl ;
}
int Arme::getPuissance() const
{
return puissance ;
}
#include "Joueur.h"
using namespace std ;
void Joueur::diminuerVie (int vieEnMoins)
{
niveauVie -= vieEnMoins ;
if (niveauVie < 0)
niveauVie = 0 ;
}
void Joueur::augmenterVie (int vieEnPlus)
{
niveauVie += vieEnPlus;
if (niveauVie > 100)
niveauVie = 100 ;
}
void Joueur::attaquer (Joueur &cible)
{
cout << nomJoueur << " attaque " << cible.nomJoueur ;
cout << endl ;
cible.diminuerVie(jArme.getPuissance()) ;
}
void Joueur::changerArme (string nomNouvelleArme,
int puissanceNouvelleArme)
{
jArme.changerArme(nomNouvelleArme,puissanceNouvelleArme) ;
}
bool Joueur::estVivant () const
{
return (niveauVie>0) ;
}
Joueur::Joueur(): niveauVie(0),
niveauMagie(0),
nomJoueur("inconnu") {}
Joueur::Joueur(string pNomJoueur) : niveauVie(50),
niveauMagie(50),
nomJoueur(pNomJoueur){}
Joueur::Joueur(string pNomJoueur,string pArme,int pPuissance)
{
niveauVie = 100 ;
niveauMagie = 100 ;
nomJoueur = pNomJoueur ;
jArme = Arme(pArme,pPuissance) ;
}
void Joueur::afficher() const
{
cout << "Joueur: " << nomJoueur ;
cout << "\tVie: " << niveauVie ;
cout << "\tMagie: " << niveauMagie ;
jArme.afficher() ;
}
#ifndef DEF_H_JOUEUR
#define DEF_H_JOUEUR
#include<iostream>
#include<string>
#include "Arme.h" // NE PAS OUBLIER
class Joueur
{
private:
int niveauVie ; // niveau de vie du joueur
int niveauMagie ; // niveau de magie du joueur
std::string nomJoueur ; // nom du joueur
Arme jArme ; // le joueur possède une arme
public:
Joueur();
Joueur(std::string pNomJoueur) ;
Joueur(std::string pNomJoueur, std::string pArme,
int pPuissance) ;
void diminuerVie (int vieEnMoins) ;
void augmenterVie (int vieEnPlus) ;
void attaquer (Joueur &cible) ;
void changerArme (std::string nomNouvelleArme,
int puissanceNouvelleArme) ;
bool estVivant() const ;
void afficher() const ;
} ;
#endif
#include <iostream>
#include "Joueur.h"
using namespace std ;
int main()
{
Joueur arkonovi("arkonovi","bazouka",40);
Joueur forceBleue("force bleue","Famas",35);
Joueur forceRouge("force rouge") ;
Joueur forceJauneDevantMarronDerriere ;
cout << " ******* AVANT *******" << endl ;
arkonovi.afficher() ;
forceRouge.afficher() ;
forceJauneDevantMarronDerriere.afficher() ;
forceBleue.afficher() ;
cout << endl << "****** COMBAT A MORT *****" << endl ;
arkonovi.attaquer(forceBleue) ;
forceBleue.augmenterVie(20) ;
arkonovi.attaquer(forceBleue) ;
forceRouge.attaquer(arkonovi) ;
forceBleue.changerArme("rayon laser bx 4000",40) ;
arkonovi.attaquer(forceRouge);
forceRouge.changerArme("rayon laser bx 6000",60) ;
forceRouge.attaquer(arkonovi) ;
cout <<endl << " ******* APRES *******" << endl ;
arkonovi.afficher() ;
forceRouge.afficher() ;
forceJauneDevantMarronDerriere.afficher() ;
forceBleue.afficher() ;
}
D. DUBOIS Version 3 – Mars 2016 Page 19 / 33
2. La surcharge d’opérateurs
Réalisation d’opérations mathématiques (+,-,*,==,>,…) entre les objets
Exemple :
duree.h
duree.cpp
main.cpp
On pourrait écrire une fonction qui fait :
Total = addition(d1,d2) ;
L’objectif ici est de pouvoir faire directement:
Total = d1 + d2 ;
Ou toute autre opération du genre :
if ( d1 == d2) cout << " durées identiques" ;
if (d1 < d2) cout << "d1 plus petit que d2" ;
Solution :
Créer une fonction (en dehors de toute classe) ayant pour nom operator suivi de l’opérateur à
implémenter.
Exemple de prototype pour == : bool operator== (Duree const& a, Duree const& b) ;
Exemple de prototype pour + : Duree operator+ (Duree const& a, Duree const& b) ;
Exemple de prototype pour != : bool operator != (Duree const& a, Duree const& b) ;
Implémentation incorrecte :
bool operator== (Duree const& a, Duree const& b)
{
if ( (a.heure==b.heure) && (a.minute==b.minute) &&(a.seconde==b.seconde) )
return true ;
else
return false ;
}
On ne peut pas lire les attributs des objets a et b :
Ils sont privés et donc inaccessibles depuis l’extérieur de la classe.
Solution : créer des accesseurs aux variables membres (ie getters) :
getHeures(), getMinutes(),getSecondes()
Implémentation correcte :
bool operator== (Duree const& a, Duree const& b) {
if ( (a.getHeure()==b.getHeure()) && (a.getMinute()==b.getMinute())
&&(a.getSeconde()==b.getSeconde()) )
return true ;
else
return false ;
}
#ifndef DUREE_H_INCLUDED
#define DUREE_H_INCLUDED
class Duree
{
private :
int heure ;
int minute ;
int seconde ;
public:
// Constructeur avec valeurs par defaut
Duree(int pHeure=0,int pMinutes=0,int pSeconde=0) ;
} ;
#endif
#include "Duree.h"
Duree::Duree(int pHeure,int pMinutes,int pSeconde) :
heure(pHeure),minute(pMinute),seconde(pPseconde)
{
}
#include "Duree.h"
int main()
{
Duree Total ;
Duree d1(0,15,30) ;
Duree d2(1,10) ;
...
return 0 ;
}
D. DUBOIS Version 3 – Mars 2016 Page 20 / 33
Autre solution : Créer une méthode de la classe qui accède aux attributs privés sans passer
les accesseurs (ie getters). On crée alors une méthode dans la classe qui fait la comparaison et
on demande à l’opérateur d’appeler cette fonction
Implémentation de la méthode estEgal
bool Duree::estEgal (Duree const& d) const
{
if ( (heure==d.heure) && (minute==d.minute) && (seconde==d.seconde) )
return true ;
else
return false ;
}
Création de la fonction operateur== qui utilise la méthode estEgal
bool operator== (Duree const& a, Duree const& b)
{
return ( a.estEgal(b) ) ;
}
Dans le main(), on peut maintenant écrire :
int main()
{
Duree d1(0,5,30), d2(0,6,30) ;
if (d1 == d2)
cout << "les durées sont identiques" ;
else
cout << "les durées sont différentes" ;
}
Remarque sur l’implémentation de la fonction :
Pour être sûr que la comparaison fonctionne comme dans le cas suivant Duree d1(1,5,30), d2(1,4,180) ;
Il faudrait convertir les durées (h,min,s) en seconde et effectuer la comparaison
sur les secondes
Pour vous entrainer,
Créer les opérateurs suivant :
bool operator!= (Duree const& a, Duree const& b) ;
bool operator< (Duree const& a, Duree const& b) ;
Duree operator+ (Duree const& a, Duree const& b) ;
void operator+= (Duree const& d) ;
Si l’envie vous démange, poussez alors votre curiosité pour savoir faire :
int main()
{
Duree d1(0,5,30), d2(0,6,30), d3(1,0,50) ;
Duree total1, total2 ;
total1 = d1 + d2 + d3 ; // comment faire ?????
total2 = d1 + 30 ; // comment faire ?????
}
D. DUBOIS Version 3 – Mars 2016 Page 21 / 33
3. L’héritage
a. Création d’une classe et d’un héritage
C’est créer une classe à partir d’une autre classe
Dans notre exemple précédent, on peut dire que « un
gentil est un joueur » ou « un méchant est un joueur ».
Ce qui permet de définir l’héritage :
La classe Gentil herite de Joueur
la classe Mechant herite de Joueur
En reprenant l’exemple traité précédemment,
Joueur.h
Joueur.cpp
La classe Mechant hérite de Joueur :
La classe Joueur est appelée classe « mère »
La classe Mechant est appelée classe « fille »
La classe Mechant contiendra tous les attributs et toutes les méthodes de la classe Joueur
Il est alors possible d’ajouter des attributs et des méthodes spéciales dans la classe Mechant
Mechant.h
On peut faire la même chose pour la classe Gentil :
Gentil.h
#ifndef DEF_H_JOUEUR
#define DEF_H_JOUEUR
#include<iostream>
#include<string>
class Joueur
{
private:
int niveauVie ; // niveau de vie du joueur
std::string nomJoueur ; // nom du joueur
public:
Joueur();
void souffrir (int vieEnMoins) ;
void attaquer (Joueur &cible) const ;
} ;
#endif
#include "Joueur.h"
Using namespace std ;
Joueur::Joueur() : niveauVie(100), nomJoueur("Inconnu")
{
}
void Joueur::souffrir(int vieEnMoins)
{
niveauVie -= vieEnMoins ;
}
void Joueur::attaquer(Joueur &cible) const
{
cible.souffrir(5) ;
}
#ifndef DEF_H_MECHANT
#define DEF_H_MECHANT
#include<iostream>
#include<string>
#include "Joueur.h" // A inclure pour pouvoir en hériter !
class Mechant : public Joueur
{
public:
void frapperCommeUneBruteSansCervelle() const ;
} ;
#endif
#ifndef DEF_H_GENTIL
#define DEF_H_GENTIL
#include<iostream>
#include<string>
#include "Joueur.h" // A inclure pour pouvoir en hériter !
class Gentil : public Joueur
{
private :
int pouvoirTelekinesie ;
public:
void ushiroGeri() const ;
void mawashiGeri() const ;
} ;
#endif
Joueur
Gentil
Mechant
D. DUBOIS Version 3 – Mars 2016 Page 22 / 33
b. La dérivation de type
Exemple d’utilisation :
Joueur rocEtGravillon ;
Mechant satanasEtDiabolo ;
rocEtGravillon.attaquer(satanasEtDiabolo) ;
satanasEtDiabolo.attaquer(rocEtGravillon) ;
L’instruction satanasEtDiabolo.attaquer(rocEtGravillon); envoie un Joueur en paramètre.
L’instruction rocEtGravillon.attaquer(satanasEtDiabolo) ;
est fonctionnelle également même si on lui transmet un Mechant (objet fille)
C'est une propriété de l'héritage en C++ : la méthode attaquer peut être utilisée sur n’importe quel
joueur (objet mère), qu’il soit Mechant ou Gentil (objet fille)
c. Constructeur
Constructeur par défaut (mère) : Joueur(); // défini dans joueur.h
Implémentation dans joueur.cpp :
Joueur::Joueur() : niveauVie(100), nomJoueur("Inconnu") {}
Lors de la création de l’objet (mère), le constructeur est appelé en premier
Lors de la création de l’objet (fille), voici ce qu’il se produit : o Le compilateur appelle d'abord le constructeur de la classe mère (Joueur) ;
o Le compilateur appelle ensuite le constructeur de la classe fille (Mechant).
Pour initialiser le constructeur de la classe fille, on écrit :
Gentil::Gentil() : Joueur(), pouvoirTelekinesie (1000) { }
Le 1er élément de la liste d'initialisation fait appel d’abord au constructeur de la classe
parente Joueur puis on effectue les initialisations propres à l’objet fille Gentil
Transmission de paramètres :
Gentil::Gentil(string nomJ ) : Joueur(nomJ), pouvoirTelekinesie (1000) { }
d. La portée « protected »
Rappel :
La portée « public » : les éléments qui suivent sont accessibles depuis l'extérieur de la
classe
La portée « private » : les éléments qui suivent ne sont pas accessibles depuis l'extérieur
de la classe.
L’encapsulation empêche systématiquement depuis l’extérieur d'accéder aux attributs des
classes.
La portée « protected » est un autre type de droit d'accès qui n'a de sens que pour les classes qui
se font hériter (les classes mères).
D. DUBOIS Version 3 – Mars 2016 Page 23 / 33
Les éléments qui suivent « protected » ne sont pas accessibles depuis l'extérieur de la classe, sauf
si c'est une classe fille ; ce qui n’est pas le cas avec la portée « private »
e. Le masquage
Quand on fait un héritage, la classe fille reçoit automatiquement toutes les méthodes de la classe
mère. Si une de ces méthodes ne convient pas, on la réécrit dans la classe fille. Quand on écrit une
fonction qui a le même nom que celle héritée de la classe mère, on parle de masquage. La fonction
héritée de Joueur est masquée.
Classe mère
void Joueur::sePresenter() const
{
cout << "Bonjour, je m'appelle " << nomJoueur << endl;
cout << "J'ai " << niveauVie << " points de niveau de vie." << endl;
}
La fonction est héritée dans la classe fille (Mechant). On peut donc écrire :
int main(){
Mechant Mechant1("Arkanovi");
Mechant1.sePresenter();
return 0;
}
On peut écrire une fonction spéciale de présentation pour les Mechant :
void Mechant::sePresenter() const
{
cout << "Bonjour, je m'appelle " << nomJoueur << endl;
cout << "J'ai "<< niveauVie << " points de niveau de vie." << endl;
cout << "Je suis le mechant et je veux tuer la gentille." << endl;
}
f. Le démasquage
void Mechant::sePresenter() const
{
Joueur::sePresenter();
cout << "Je suis le mechant et je veux tuer la gentille." << endl;
}
Dans cet exemple, la fonction sePrésenter du méchant ne fait que compléter la fonction de la
classe Mère. Pour éviter de recopier ce que fait déjà la classe mère, on démasque alors la fonction
en l’appelant dans la classe fille. On complète ensuite avec ce que l’on souhaite faire.
On parle dans ce cas de démasquage puisqu'on a utilisé une fonction qui était masquée.
On a utilisé pour cela l'opérateur de résolution de portée :: qui a servi à déterminer quelle
fonction utiliser quand il y a ambiguïté ou s’il y a plusieurs possibilités.
D. DUBOIS Version 3 – Mars 2016 Page 24 / 33
4. Le polymorphisme
a. Constat
On reprend la notion d’héritage ci-dessus et on ajoute une méthode permettant l’affichage :
void Joueur::affiche() const {
cout << "Je suis un joueur." << endl;
}
void Gentil::affiche() const {
cout << "je suis un gentil." << endl;
}
void Mechant::affiche() const {
cout << "je suis un méchant." << endl;
}
On affiche sans problème les messages différents en utilisant le masquage :
int main()
{
Joueur j ;
j.affiche(); // affiche « Je suis un joueur. »
Gentil g ;
g.affiche(); // affiche « je suis un gentil »
}
Nous allons une fonction supplémentaire qui
reçoit en paramètre un Joueur.
On modifie le main() de la façon suivante :
void presenter(Joueur pJ)
{
pJ.affiche() ;
}
int main()
{
Joueur j ;
presenter(j);
Mechant m ;
presenter(m);
}
Le résultat de l’affichage est sans appel !
On pouvait s’en douter !
Lors de l’appel de la fonction, la nature
cruelle du méchant s’est perdue et est
redevenue un joueur
On parle ici de résolution statique de lien : la
fonction presenter() reçoit un Joueur : c’est
donc toujours la « version Joueur » des
méthodes qui sera utilisée
On aimerait bien que la
bonne méthode soit appelée
en reconnaissant la nature du Joueur !
La fonction presenter() doit appeler la bonne version de la méthode : elle doit connaitre la vraie
nature du Joueur : C'est ce qu'on appelle la résolution dynamique des liens.
Lorsque le programme s’exécute, il utilise la bonne version des méthodes car il sait si l'objet est de
type mère ou de type fille. Pour faire cela, il faut :
utiliser des méthodes virtuelles.
utiliser un pointeur (ou une référence)
Je suis un joueur.
Je suis un joueur.
D. DUBOIS Version 3 – Mars 2016 Page 25 / 33
a. Déclaration d’une méthode virtuelle
Il suffit d'ajouter le mot-clé virtual uniquement dans le prototype de la classe (fichier .h et pas
dans le fichier .cpp)
class Joueur {
public:
virtual void affiche() const; // Affiche une description du Joueur
};
class Gentil: public Joueur {
public:
virtual void affiche() const;
};
class Mechant : public Joueur {
public:
virtual void affiche() const;
};
Remarque :
Il n'est pas nécessaire de mettre le mot clé « virtual » devant les méthodes des classes filles. Elles
sont automatiquement virtuelles par héritage.
b. Utilisation d’une référence
Il est nécessaire de modifier la fonction presenter() avec un passage par référence pour les
paramètres :
void presenter(Joueur& pJ)
{
pJ.affiche() ;
}
int main()
{
Joueur j ;
presenter(j);
Mechant m ;
presenter(m);
}
Le résultat devient correct
Ouf !
La fonction presenter() a appelé la bonne
version de la méthode.
___________________________
Pour aller plus loin en C++ :
- La gestion des erreurs avec les exceptions : try{…} , throw, catch() {…}
- L’héritage multiple
- Les pointeurs et les collections (tableaux contenant des types différents)
La bibliothèque Qt, version actuelle 5.5 : bibliothèque externe indépendante
permet de créer vos propres fenêtres sous Windows, Linux ou Mac OS
(bibliothèque graphique multiplateforme) : ouverture de fenêtres, ajout de
boutons, création de menus, de listes déroulantes...
Je suis un joueur.
Je suis un méchant.
D. DUBOIS Version 3 – Mars 2016 Page 26 / 33
XI. Exercices
1. Exercice 1 : trier/échanger
Créez une fonction "trier" permettant de trier un tableau de 10 données avec un tri par sélection.
Le tableau devra être de n'importe quel type. Cette fonction utilisera une autre fonction qu’on nommera "echanger" qui
échangera les éléments du tableau. Les 10 données seront générées de manière aléatoire. Le Tri par sélection est décrit
par la procédure suivante :
1. Rechercher le plus petit élément du tableau et l'échanger avec l'élément d'indice 1
2. Rechercher le second plus petit élément du tableau, et l'échanger avec l'élément d'indice 2
3. Et ainsi de suite jusqu'à ce que le tableau soit entièrement trié.
2. Exercice 2 : Référence
Ecrire une fonction f ayant en paramètres un tableau t de taille quelconque et un entier n indiquant la taille du tableau. f
possède un autre paramètre v entier passé par référence.
f doit renvoyer un booléen b indiquant s'il existe une valeur comprise entre 1 et 10 dans les n premières cases du tableau t.
Si f renvoie true, v est égal à la valeur de la première case du tableau comprise entre 0 et 10
3. Exercice 3 : Recopie
Ecrire une fonction f ayant en paramètres un tableau t1 de taille quelconque et un entier n indiquant la taille du tableau,
ainsi qu'un tableau t2 de la même taille que t1. f doit renvoyer un entier nb indiquant le nombre de valeurs comprises
entre 0 et 10 dans le tableau t1. f doit mettre dans le tableau t2 les différentes valeurs comprise entre 0 et 10 qu'il a
rencontrées dans le tableau t1.
4. Exercice 4 : String
Ecrire un programme qui saisit une chaîne pouvant contenir des espaces et qui affiche chaque mot
de la chaîne, le séparateur étant l'espace. Exemple, on tape : je pense donc je suis
5. Exercice 5 : Classe
On souhaite implémenter une classe représentant un compteur entier. Un tel objet se caractérise par :
Une valeur entière, positive ou nulle, nulle à l'origine. Le fait qu'il ne peut varier que par pas de 1 (incrémentation ou
décrémentation). On convient qu'une décrémentation d'un compteur nul est sans effet. Il s'agit de créer une classe
Compteur pour rendre le service demandé. On écrira en outre un petit programme de test qui :
1. créera un compteur et affichera sa valeur
2. l'incrémentera 10 fois, puis affichera à nouveau sa valeur
3. le décrémentera 20 fois, puis affichera une troisième fois sa valeur
La sortie de ce programme doit donner (quelque chose comme) "0 10 0"
6. Exercice 6 : classe
Réaliser une classe point permettant de manipuler un point d'un plan. On prévoira :
- un point est définit par ses coordonnées x et y (des membres privés)
- un constructeur ou plusieurs constructeurs
- une fonction membre déplace effectuant une translation définie par ses deux arguments dx et dy
- une fonction membre affiche se contentant d'afficher les coordonnées cartésiennes du point.
- une fonction membre saisir se contentant de saisir les coordonnées cartésiennes du point.
- une fonction membre distance effectuant calculant la distance entre deux points.
- une fonction membre milieu donnant le milieu d'un segment.
On écrira séparément :
- un fichier source constituant la déclaration et la définition de la classe.
- un programme d’essai (main) gérant ne la classe point.
7. Exercice 7 : surcharge d’opérateurs
En reprenant les éléments du cours, compléter le programme ci-dessous en surchargeant les opérateurs ==, != et < pour
que le programme fonctionne
int main() {
Duree duree1(0, 10, 28), duree2(0, 10, 29);
if (duree1 == duree2) cout << "Les durees sont identiques";
if (duree1 != duree2) cout << "Les durees sont differentes";
if (duree1 < duree2) cout << "La premiere duree est plus petite";
else cout << "La premiere duree n'est pas plus petite";
return 0 ;
}