Programmation orientée objet avec C++
ACOO Analyse, Conception et développement Orientés Objet de logiciels de commande
Thèmes abordés
• Présentation du langage C++
– Principales différences par rapport au C.
• Orientation objet
– Notion de classe et d’objet – Encapsulation, public, privé.
– Héritage, accès protégé.
– Polymorphisme.
• Eléments avancés
– Programmation générique.
– Traitement d’erreurs par exception.
– Surcharge d’opérateurs.
Introduction à la programmation orientée objet avec C++ 2
Introduction
• Historique
– Bjarne Stroustrup crée en 1979 « C With Classes » – Renommé C++ en 1983
– C++: incrémentation, le langage après C – Normalisé par ISO
• Que définit C++ ?
– Un langage orienté objet basé sur le C.
• Syntaxe, instructions et code généré similaires.
• Quelques incompatibilités avec C.
• Support de l’héritage, polymorphisme, généricité.
– Une bibliothèque standard
• Standard C++ library
Le langage C++
Créer un programme C++ avec Visual Studio
• Donner l’extension .cpp au fichier source !
Introduction à la programmation orientée objet avec C++ 4
Les entrées sorties standard du C++
#include <iostream>
int main() {
double x, y;
std::cout << "X:";
std::cin >> x;
std::cout << "Y:";
std::cin >> y;
std::cout << "x * y = " << x * y << std::endl;
return 0;
}
Bibliothèque d’entrées sorties du C++
Flux de sortie
Opérateur surchargé en C++.
Ecriture vers un flux.
Flux d’entrée.
Opérateur surchargé en C++.
Lecture depuis un flux.
Opérateur pouvant être chaîné
Le langage C++
Les espaces de nom - utilisation
• Intérêt des espaces de nom
– Grands projets avec de nombreuses bibliothèques
• On pourrait avoir plusieurs bibliothèques contenant des fonctions différentes avec des noms identiques.
• Impossibilité de construire un tel programme en C.
– Espace de nom
• Préfixe tous les identificateurs de l’espace avec un nom.
• Exemple dans l’espace std : std::cout
• Simplification d’écriture
– Il est possible d’importer un espace de nom.
– Permet d’utiliser les identificateurs de cet espace sans préfixe.
• Syntaxe
using namespace std;
Introduction à la programmation orientée objet avec C++ 6
Les espaces de nom – utilisation - exemple
#include <iostream>
#include <conio.h>
// Importation de l'espace de nom : using namespace std;
int main() {
double x, y;
// Le préfixe n'est plus requis : cout << "X:";
cin >> x;
cout << "Y:";
// Il est toujours possible d'utilier le préfixe : std::cin >> y;
cout << "x * y = " << x * y << endl;
// Les fonctions de la bibliothèque C sont toujours utilisables _getch();
return 0;
}
Le langage C++
Les espaces de nom - définition
• Il est possible de définir un espace de nom.
• Les identificateurs de cet espace sont alors préfixés.
• Syntaxe
namespace nom {
// Placer ici les déclarations faisant partie de // l'espace de nom
}
Introduction à la programmation orientée objet avec C++ 8
Les espaces de nom – définition - exemple
namespace heig_vd {
void afficher_adresse() {
cout << "HEIG-VD" << endl;
cout << "Route de Cheseaux 1" << endl <<
"1401 Yverdon les bains" << endl;
}
void afficher_coordonnees_completes() {
// Préfixe non requis, car dans le même espace de nom : afficher_adresse();
cout << "Tel : 024 55 76 330 \n";
} }
int main() {
heig_vd::afficher_coordonnees_completes();
return 0;
}
Le langage C++
Les types supplémentaires du C++ - bool
• C++ introduit un type pour les conditions logiques
• Son utilisation est recommandée, mais pas obligatoire.
bool valide;
valide = true;
valide = false;
if (valide) . . .
Introduction à la programmation orientée objet avec C++ 10
Les types supplémentaires du C++ - string
• Chaînes de caractères
– Pas de type défini au niveau du langage C++ (idem C).
– Implémenté sous la forme d’une classe dans la bibliothèque standard <string>
• A ne pas confondre avec string.h
– Manipulation beaucoup plus commode qu’avec les tableaux de caractères.
• Exemple
#include <iostream>
#include <string>
using namespace std;
int main() {
string nom;
cout << "Votre nom : ";
// cin >> nom; : ne saisit que le 1er mot. Utiliser getline getline(cin, nom);
cout << "Bonjour " << nom << endl;
char c;
cin >> c;
}
Le langage C++
Les types supplémentaires du C++ - string – quelques opérations
#include <iostream>
#include <string>
using namespace std;
int main() {
string chaine1, chaine2;
chaine1 = "Le loup et l'agneau";
// longueur de la chaine:
cout << chaine1.length() << endl;
// le caractère en position 5:
cout << chaine1.at(5) << endl;
// sous-chaine de 4 caractères à partir de la position 3 chaine2 = chaine1.substr(3, 4);
cout << chaine2 << endl;
char c;
cin >> c;
}
Introduction à la programmation orientée objet avec C++ 12
Contrôles à la compilation renforcés
• Le compilateur C++ est plus strict
– Le prototype d’une fonction doit être défini avant son utilisation.
– Erreur systématique générée en cas contraire.
– Pas de supposition sur le type du résultat et des paramètres.
– Contrôle des types des paramètres lors d’appels de fonctions.
– L’exécution d’une fonction doit toujours se terminer par un return.
Le langage C++
Exemple - compilation en C d’un programme incorrect
Type du paramètre incorrect
Fonction sans prototype
Aucune erreur, juste quelques warnings
La compilation a réussi !
Introduction à la programmation orientée objet avec C++ 14
Exemple - compilation en C++ d’un programme incorrect
Type du paramètre incorrect
Fonction sans prototype
En C++, erreurs de compilation générées.
Le langage C++
Surcharge de fonctions
• Plusieurs fonctions de même nom peuvent être définies
– Elles doivent avoir une signature différente
– La signature est constituée de la liste des paramètres.
• Exemple
int minimum(int a, int b) {
return a < b ? a : b;
}
int minimum(int a, int b, int c) {
return minimum(a, minimum(b, c));
}
Introduction à la programmation orientée objet avec C++ 16
Encodage de la signature des fonctions
• A l’issue de la compilation
– Le code objet contient plusieurs fonctions minimum.
– Comment le lieur peut il retrouver la bonne ?
• Dans le code généré par le C++
– Les noms de fonctions sont suffixés par un encodage de la signature.
– 2 fonctions de signature différente ont ainsi un nom différent.
Le langage C++
Encodage de la signature des fonctions
Fonction non trouvée :
"int __cdecl minimum(int,int)"
(?minimum@@YAHHH@Z)
Introduction à la programmation orientée objet avec C++ 18
Mélanger des fichiers sources C et C++
• Problème lors du mélange de fichiers C et C++
Lib.h
#ifndef __LIB_H_
int minimum(int a, int b);
#endif
Lib.c
#include "lib.h"
int minimum(int a, int b) {
return a < b ? a : b;
}
Main.cpp
#include "lib.h"
int main() {
int a;
a = minimum(1, 2);
return 0;
}
Le langage C++
Mélanger des fichiers sources C et C++
Lib.c
#include "lib.h"
int minimum(int a, int b) {
return a < b ? a : b;
} Main.cpp
#include "lib.h"
int main() {
int a;
a = minimum(1, 2);
return 0;
}
Main.obj
_main, utilise ?minimum@@YAHHH@Z
Lib.obj _minimum
Liaison impossible
Introduction à la programmation orientée objet avec C++ 20
Mélanger des fichiers sources C et C++
• Solution
– Il faut indiquer au compilateur C++ quelles fonctions utilisent les conventions de compilation du C plutôt que C++.
– Syntaxe
extern "C"
{
// déclarations à intepréter comme du C }
• Exemple Main.cpp extern "C"
{
#include "lib.h"
} int main() { int a;
a = minimum(1, 2);
return 0;
}
Le langage C++
Valeurs par défaut des paramètres de fonctions
• Valeurs par défaut des paramètres de fonction
– Ce sont des valeurs utilisées pour un paramètre s’il n’est pas donné au moment de l’appel de la fonction.
• C++ permet de définir des valeurs par défaut
– Lorsqu’un paramètre a une valeur par défaut, tous les paramètres qui suivent doivent également en avoir une.
– Si le paramètre est fourni au moment de l’appel, c’est la valeur fournie qui est utilisée.
– Sinon, c’est la valeur par défaut.
– Une fonction avec des paramètres par défaut peut donc être appelée avec un nombre variable de paramètres.
Introduction à la programmation orientée objet avec C++ 22
Valeurs par défaut des paramètres de fonctions
#include <iostream>
using namespace std;
void afficher_valeur(double valeur, const char * unite = "") {
cout << valeur << " " << unite << endl;
}
int main() {
afficher_valeur(12.5, "m");
afficher_valeur(12.5);
return 0;
}
Le langage C++
Les types référence - Introduction
• Définition
– Un type référence est en fait un pointeur.
– Utilisable sans la syntaxe fastidieuse des pointeurs du C.
• Particularités
– Une variable de type référence DOIT être initialisée.
– L’initialisation indique quelle variable est référencée.
– Il n’est ensuite plus possible de modifier la référence.
• Syntaxe
type & nom_reference = variable_referencee;
Introduction à la programmation orientée objet avec C++ 24
Les types référence - Introduction
• Exemple
double x = 0.0, y = 1.0;
// déclaration et initialisation de la référérence : double & rx = x;
// affectation de la variable référencée rx = y;
cout << "x :" << x << endl;
• Comportement
– A l’initialisation, la référence reçoit l’adresse d’une variable.
– Lors de toutes les affectations ultérieures, c’est la variable référencée qui est modifiée.
– La référence désigne donc toujours la même variable pendant toute sa durée de vie.
Le langage C++
Passage de paramètres par référence
• Une référence est en fait un pointeur
• Permet donc de réaliser le passage de paramètre par adresse.
• Sans la syntaxe lourde des pointeurs.
• Avec les références, C++, offre une solution élégante
pour le passage de paramètres par variable.
Introduction à la programmation orientée objet avec C++ 26
Passage de paramètres par référence void echanger(int & a, int & b) {
int temporaire;
temporaire = a;
a = b;
b = temporaire;
}
int main() {
int i, j;
i = 1;
j = 2;
cout << "i: " << i << " ; j: " << j << endl;
echanger(i, j);
cout << "i: " << i << " ; j: " << j << endl;
}
Le langage C++
Résultat de fonction de type référence
• Une fonction peut retourner une référence (donc une adresse) – On peut affecter la variable référencée retournée.
– On peut ainsi placer un appel de fonction à gauche d’une affectation !
• Exemple
double tableau[100];
double & element(int i) {
return tableau[i];
}
int main() {
element(5) = 123;
cout << element(5) << endl;
cin >> element(10);
cout << element(10) << endl;
}
Gestion de fichiers au format texte - écriture
Introduction à la programmation orientée objet avec C++ 28
#include <fstream>
using namespace std;
int main() {
// déclaration d'un objet flux pour le fichier ofstream fichier;
int i;
// ouverture du fichier fichier.open("carres.txt");
// écriture dans le fichier
fichier << "Les carres de 1 a 100" << endl;
for (i = 1 ; i <= 100; i++)
fichier << i << "^2 : " << i * i << endl;
// fermeture du fichier fichier.close();
}
Le langage C++
Gestion de fichiers au format texte - écriture
#include <fstream>
using namespace std;
int main() {
// déclaration d'un objet flux pour le fichier ofstream fichier;
int i;
// ouverture du fichier fichier.open("carres.txt");
// écriture dans le fichier
fichier << "Les carres de 1 a 100" << endl;
for (i = 1 ; i <= 100; i++)
fichier << i << "^2 : " << i * i << endl;
// fermeture du fichier fichier.close();
}
Gestion de fichiers au format texte - lecture
Introduction à la programmation orientée objet avec C++ 30
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// déclaration d'un objet flux pour le fichier ifstream fichier;
string ligne;
int nombre_ligne = 0;
// ouverture du fichier fichier.open("carres.txt");
while (!fichier.eof()) {
// lecture du fichier, vérification du succès if (getline(fichier, ligne))
nombre_ligne ++;
}
// fermeture du fichier fichier.close();
cout << "Le fichier comporte " << nombre_ligne << " lignes." << endl;
}
Le langage C++
Gestion de fichiers – open
• Prototype
void open(const char *_Filename, ios_base::openmode _Mode;
• Openmode
– app : append, va à la fin avant chaque ajout.
– ate : ouvre le fichier et va à la fin 1 fois.
– binary, : ouvre le fichier en mode binaire.
– in : ouvre en lecture – out : ouvre en écriture
– trunc : écrase le fichier à l’ouverture.
• Exemple
fstream fichier;
fichier.open ("test.bin", ios::out | ios::app | ios::binary);
Introduction à la programmation orientée objet avec C++ 32
La gestion des erreurs exceptionnelles
int ListeDouble::Ajouter(double x) {
int indice;
if (nombre_elements < CAPACITE) {
indice = nombre_elements;
tableau[indice] = x;
nombre_elements++;
} else
indice = INDICE_INVALIDE;
return indice;
}
int Filtre::Echantilloner(double amplitude) {
if (liste.Ajouter(amplitude) != INDICE_INVALIDE) return 1;
else {
printf("Erreur");
return 0;
} }
double FiltreMedian(double valeurs[], int nombre) {
int i;
Filtre f;
for (i = 0; i < nombre_valeurs; i++) if (!f.Echantilloner(valeurs[i])) return AMPLITUDE_INVALIDE;
return f.CalculerValeurMediane();
}
Le code produit pour gérer les erreurs exceptionnelles représente 50 % Beaucoup d’effort pour propager l’erreur « liste pleine » vers les fonctions de niveau supérieur.
Le langage C++
Les exceptions
• Lors de la détections d’une erreur
– On « lève » une exception.
– Une exception est un signal logiciel, accompagné d’une donnée.
– La donnée peut être de n’importe quel type
• Souvent un objet, de classe (ou sous classe) « exception ».
• Le traitement en cours est interrompu.
• L’exception se propage à travers les fonctions appelantes.
– Sans avoir aucun code à écrire pour cela.
• Jusqu’à ce qu’elle soit capturée.
– Au moment de la capture, la donnée associée est récupérable.
• Si elle n’est pas traitée
– En général : terminaison de l’application.
Introduction à la programmation orientée objet avec C++ 34
Les exceptions - Syntaxe
• Lever une exception
throw exception("Liste pleine");
throw 1.5; // pas très usuel
• Capturer une exception try
{
// Placer ici le code pouvant générer une exception // que l'on veut capturer
}
// blocs de capture : un a plusieurs selon les besoins catch (exception & e)
{
cout << e.what() << endl;
}
catch (...) // ... signifie capturer tout {
cout << "Exception de type inattendu\n" << endl;
throw; // redéclenchement de l'exception capturée }
Le langage C++
La gestion des erreurs avec les exceptions
int ListeDouble::Ajouter(double x) {
int indice;
if (nombre_elements < CAPACITE) {
indice = nombre_elements;
tableau[indice] = x;
nombre_elements++;
} else
throw exception("Liste pleine");
return indice;
}
double FiltreMedian(double valeurs[], int nombre) {
int i;
Filtre f;
for (i = 0; i < nombre_valeurs; i++) f.Echantilloner(valeurs[i]);
return f.CalculerValeurMediane();
}
void Filtre::Echantilloner(double amplitude) {
liste.Ajouter(amplitude);
}
Plus aucun code à écrire pour la propagation des erreurs ! Programmation beaucoup plus efficace.
Introduction à la programmation orientée objet avec C++ 36
La gestion des erreurs avec les exceptions
• Traitement de l’exception
– Se fait seulement au niveau où l’on sait comment réagir.
– Exemple : dans main, afficher simplement un message d’erreur.
• Exemple simple
double acquisitions[100];
double valeur_filtree;
. . . try {
valeur_filtree = filtre_median(acquisitions, 100);
}
catch (exception & e) {
cout << "Erreur durant le filtrage : " << e.what() << endl;
}
Le langage C++
La gestion des erreurs avec les exceptions
• Recommandation
– Utiliser la gestion par exception seulement pour les situations exceptionnelles.
– Préférer des tests avec if pour les situations probables.
Introduction à la programmation orientée objet avec C++ 38
Allocation dynamique de mémoire
• C++ offre un opérateur pour l’allocation dynamique
– Opérateur new.
– Syntaxe et utilisation beaucoup plus clairs que les malloc du C.
• Syntaxe
TYPE * variable;
variable = new TYPE;
• Exemple – allocation d’une cellule pour une file chainée
struct CELLULE_FILE_CHAINEE * cellule;
cellule = new CELLULE_FILE_CHAINEE;
cellule->element = 10.5;
Le langage C++
Allocation dynamique de mémoire
• C++ offre également un opérateur pour la libération
– Opérateur delete.
• Syntaxe
TYPE * variable;
variable = new TYPE;
. . .
delete variable;
Introduction à la programmation orientée objet avec C++ 40
Allocation dynamique de mémoire – les tableaux
• L’opérateur new permet d’allouer un tableau d’éléments
tableau = new type[taille];
• Syntaxe de l’opérateur delete pour les tableaux
delete []tableau;
• Allocation dynamique du C++
– Plus facile à utiliser.
– Plus lisible.
– A préférer pour tout développement en C++.
– A utiliser impérativement dès qu’on alloue des objets.
Le langage C++
Allocation dynamique de mémoire – les tableaux - exemple
int main() {
double * tableau;
int i, taille;
cout << "Taille:";
cin >> taille;
// Allocation dynamique du tableau tableau = new double[taille];
for (i = 0; i < taille; i++) {
cout << "Element " << i + 1 << " : ";
cin >> tableau[i];
}
// Libération du tableau delete []tableau;
_getch();
return 0;
}
Programmation orientée objet en C++
Introduction : Rappel – le TDA Nombre complexe en C
typedef struct {
double reel, imaginaire;
} COMPLEXE;
void complexe_initialiser(COMPLEXE* z);
void complexe_ecrire_cartesien(COMPLEXE* z, double re, double im);
void complexe_ecrire_polaire(COMPLEXE* z, double module, double argument);
double complexe_lire_reelle(COMPLEXE z);
double complexe_lire_imaginaire(COMPLEXE z);
double complexe_lire_module(COMPLEXE z);
double complexe_lire_argument(COMPLEXE z);
COMPLEXE complexe_oppose(COMPLEXE z);
COMPLEXE complexe_conjugue(COMPLEXE z);
COMPLEXE complexe_somme(COMPLEXE z1, COMPLEXE z2);
Introduction à la programmation orientée objet avec C++ 44
Exemple d’utilisation correcte
#include "complexes.h"
int main() {
COMPLEXE z1, z2, z3;
complexe_initialiser(&z1);
complexe_initialiser(&z2);
complexe_ecrire_cartesien(&z1, 3, 0);
complexe_ecrire_cartesien(&z2, 0, 2);
z3 = complexe_somme(z2, z1);
printf("z3 = %lf + i . %lf\n", complexe_lire_reelle(z3), complexe_lire_imaginaire(z3));
system("PAUSE");
return 0;
}
Attention. Mauvaise utilisation, car ne respecte pas l’abstraction du type.
Les types de donnée abstraits
Exemple d’utilisation incorrecte
#include "complexes.h"
int main() {
COMPLEXE z1 = {3.0, 0.0}, z2, z3;
z2.reel = 0.0;
z2.imaginaire = 2.0;
z3 = complexe_somme(z2, z1);
printf("z3 = %lf + i . %lf\n", complexe_lire_reelle(z3), complexe_lire_imaginaire(z3));
system("PAUSE");
return 0;
}
Introduction à la programmation orientée objet avec C++ 46
Introduction : Rappel – le TDA Nombre complexe en C - Analyse
void complexe_initialiser(COMPLEXE* z);
void complexe_ecrire_cartesien(COMPLEXE* z, double re, double im);
void complexe_ecrire_polaire(COMPLEXE* z, double module, double argument);
• Syntaxe fastidieuse
– Noms de méthodes préfixés par « complexe »
• pour rappeler le TDA d’appartenance
– Il faut toujours passer la structure en paramètre par adresse
• C++ : nouvelle syntaxe facilitant la programmation des TDA
– Des fonctions peuvent être déclarées dans un struct.
– On les appelle alors des « méthodes ».
– Ces méthodes ont accès directement aux variables de la struct.
Programmation orientée objet en C++
Les méthodes : des fonctions associées à la structure - déclaration struct Complexe
{
double reelle, imaginaire;
void DefinirCartesien(double re, double im);
void DefinirPolaire(double module, double argument);
double LireReelle();
double LireImaginaire();
double LireModule();
double LireArgument();
Complexe Oppose();
Complexe Conjugue();
};
Introduction à la programmation orientée objet avec C++ 48
Les méthodes : des fonctions associées à la structure - implémentation
void Complexe::DefinirCartesien(double re, double im) {
reelle = re;
imaginaire = im;
}
double Complexe::LireReelle() {
return reelle;
}
double Complexe::LireModule() {
return sqrt(reelle * reelle + imaginaire * imaginaire);
}
Complexe Complexe::Conjugue() {
Complexe resultat;
resultat.DefinirCartesien(reelle, -imaginaire);
return resultat;
}
Dans l’implémentation, les méthodes sont préfixées par le nom du type.
Tous les champs de la structure sont accessibles directement depuis les méthodes.
Programmation orientée objet en C++
Les méthodes : des fonctions associées à la structure - utilisation
#include <iostream>
#include "complexe.h"
using namespace std;
int main() {
Complexe z1, z2;
z1.DefinirCartesien(1.5, 2.0);
z2 = z1.Conjugue();
cout << "z1 : " << z1.LireReelle() << " + i . " <<
z1.LireImaginaire() << endl;
cout << "z2 : " << z2.LireReelle() << " + i . " <<
z2.LireImaginaire() << endl;
}
On utilise le point pour accéder aux méthodes (comme pour les champs)
Syntaxe allégée. Le mot struct n’a pas besoin d’être répété.
Introduction à la programmation orientée objet avec C++ 50
Les méthodes : accès aux membres – comparaison C / C++
• En langage C
void complexe_definir_cartesien(COMPLEXE* z, double re, double im) {
z->reel = re;
z->imaginaire = im;
}
• En langage C++
void Complexe::DefinirCartesien(double re, double im) {
reelle = re;
imaginaire = im;
}
L’adresse de la structure est évidemment requise pour accéder aux champs.
L’adresse de la structure n’est pas passée, et pourtant ça fonctionne !?
Programmation orientée objet en C++
Les méthodes : accès aux membres
• Le pointeur « this »
– En C++, les méthodes reçoivent un paramètre invisible.
– Il s’appelle « this ».
– C’est un pointeur sur la structure sur laquelle la méthode est appelée.
• Code écrit :
void Complexe::DefinirCartesien(double re, double im) {
reelle = re;
imaginaire = im;
}
• De façon interne et silencieuse, le code vraiment compilé est :
void Complexe::DefinirCartesien (Complexe * this, double re, double im)
{
this->reelle = re;
this->imaginaire = im;
}
Introduction à la programmation orientée objet avec C++ 52
Gérer la visibilité : public et private
• Rappel –TDA
– TDA : Type de Données Abstrait.
– On ne devrait jamais accéder directement aux champs de la structure.
– Mais rien ne nous empêche de le faire !
• Mauvais exemple d’utilisation du TDA:
int main() {
Complexe z1, z2;
z1.reelle = 1.5;
z1.imaginaire = 2.0;
z2 = z1.Conjugue();
. . . . }
Accès direct aux champs.
Rompt l’abstraction.
Programmation orientée objet en C++
Gérer la visibilité : public et private
• C++ apporte des améliorations pour les TDA
– Possibilité d’interdire l’accès aux membres par les utilisateurs d’un type.
– Syntaxe : modificateurs public et private
• Exemple :
struct Complexe {
private:
double reelle, imaginaire;
public:
void DefinirCartesien(double re, double im);
void DefinirPolaire(double module, double argument);
double LireReelle();
double LireImaginaire();
. . . };
L’accès direct à ces champs hors des méthodes du type Complexe sera interdit par le compilateur.
Le type complexe peut seulement être utilisé avec les méthodes publiques prévues.
Garantit le respect de l’abstraction
Introduction à la programmation orientée objet avec C++ 54
Notion de classe
• La programmation orientée objet
– Elle est basée sur la notion de classe. (explicitée plus loin)
• Comment déclarer une classe en C++ ?
– Il suffit d’utiliser le mot class à la place du mot struct.
class Complexe {
private:
double reelle, imaginaire;
public:
void DefinirCartesien(double re, double im);
void DefinirPolaire(double module, double argument);
double LireReelle();
double LireImaginaire();
. . . . };
Programmation orientée objet en C++
Différence entre class et struct
• Points communs
– class et struct permettent tous deux de déclarer des classes.
– Le code généré est 100 % identique.
• Différences
– Avec la déclaration struct
• Par défaut, tous les membres sont publics.
– Avec la déclaration class
• Par défaut, tous les membres sont privés.
• Lequel préférer ?
– En programmation orientée objet, on utilise habituellement class.
– Par défaut, tout ce qui n’est pas explicitement rendu public par le programmeur est ainsi privé.
Introduction à la programmation orientée objet avec C++ 56
Notion d’objet
• Définition
– Un objet est une instance d’une classe.
– C’est-à-dire une variable de ce type.
• Exemples
Complexe z1, z2;
Complexe * z3;
z3 = new Complexe;
– z1 et z2 sont des objets.
– z3 est un pointeur sur un objet (son adresse).
Programmation orientée objet en C++
C++ et compilation séparée
• Le fichier .h
– Contient habituellement les déclarations de classes, méthodes, …
• Le fichier .cpp
– Contient l’implémentation des méthodes.
Introduction à la programmation orientée objet avec C++ 58
Surcharge des opérateurs
• Surcharge des opérateurs
– Permet de donner un sens nouveau aux opérateurs.
– Seulement pour les types définis par le programmeur.
• Exemple :
surcharge de << pour le type « Complexe » – Opérateur déclaré EN DEHORS de la classe.ostream & operator<<(ostream & stream, const Complexe & z) {
stream << z.LireReelle();
if (z.LireImaginaire() < 0.0)
stream << " - " << - z.LireImaginaire() << " . i";
else if (z.LireImaginaire() > 0.0)
stream << " + " << z.LireImaginaire() << " . i";
return stream;
}
Programmation orientée objet en C++
Méthodes const
• Problème
– « z » est un paramètre const.
– A-t-on le droit d’appeler une méthode sur un const ? – Et si la méthode modifiait la constante ?
– Compilateur: refus des appels de méthodes normales sur des objets const
• Solution : déclarer les méthodes const
class Complexe {
. . . .
double LireReelle() const;
double LireImaginaire() const;
double LireModule() const;
double LireArgument() const;
. . . . };
Introduction à la programmation orientée objet avec C++ 60
Surcharge des opérateurs - utilisation
#include <iostream>
#include "complexe.h"
using namespace std;
int main() {
Complexe z1, z2;
z1.DefinirCartesien(1.5, 2.0);
z2 = z1.Conjugue();
cout << "z1 : ";
cout << z1 << endl;
cout << "z2 : ";
cout << z2 << endl;
char c;
cin >> c;
}
Utilisation du nouvel opérateur
<<
Programmation orientée objet en C++
Surcharge des opérateurs – l’addition de complexes
• Déclaration d’un opérateur dans la classe
class Complexe {
public:
. . . .
Complexe operator+(const Complexe & operande);
. . . . };
• Implémentation
Complexe Complexe::operator+(const Complexe & operande) {
Complexe resultat;
resultat.reelle = reelle + operande.reelle;
resultat.imaginaire = imaginaire +
operande.imaginaire;
return resultat;
}
Introduction à la programmation orientée objet avec C++ 62
Surcharge des opérateurs – l’addition de complexes - utilisation
#include <iostream>
#include "complexe.h"
using namespace std;
int main() {
Complexe z1, z2;
z1.DefinirCartesien(1.5, 2.0);
z2.DefinirCartesien(3.0, 4.0);
cout << "z1 : ";
cout << z1 << endl;
cout << "z2 : ";
cout << z2 << endl;
cout << "z1 + z2 : " << z1 + z2 << endl;
}
Style de programmation naturel grâce aux opérateurs définis.
Programmation orientée objet en C++
Usages pour les noms des accesseurs
• Usage général
– Le code source est écrit en langue anglaise
• Méthodes d’accès en lecture
– On utilise le terme « get » : obtenir double GetReal() const();
double GetImaginary() const;
• Méthodes d’accès en écriture
– On utilise le terme « set » : définir void SetReal(double value);
• Terminologie
– On parle de « getter » et de « setter »
Introduction à la programmation orientée objet avec C++ 64
Initialisation et finalisation d’un TDA
• Principes d’utilisation des TDA
– Initialisation : préparer les variables internes avant l’utilisation.
– Utilisation normale.
– Finalisation : libérer les ressources allouées
• Exemple : libérer la mémoire allouée dynamiquement avec malloc.
– En C: initialisation et finalisation à appeler explicitement.
• Apport du C++
– Notion de constructeur
• Méthode d’initialisation d’un objet.
• Appelée automatiquement à la création de l’objet.
– Notion de destructeur
• Méthode de finalisation d’un objet.
• Appelée automatiquement à la destruction de l’objet.
Programmation orientée objet en C++
Constructeur - Introduction
• Exemple – objet non initialisé
• int main()
{
Complexe z1;
cout << "z1 : ";
cout << z1 << endl; // non initialisé ! }
Introduction à la programmation orientée objet avec C++ 66
Constructeur – déclaration et définition
• Déclaration
class Complexe {
private:
double reelle, imaginaire;
public:
Complexe();
. . . };
• Définition
Complexe::Complexe() {
reelle = 0.0;
imaginaire = 0.0;
}
Méthode déclarée:
- Avec le nom de la classe.
- Sans aucun type de résultat.
Programmation orientée objet en C++
Constructeur
• Exemple – objet initialisé automatiquement
int main() {
Complexe z1; // Initialisé par le constructeur !
cout << "z1 : ";
cout << z1 << endl;
}
Introduction à la programmation orientée objet avec C++ 68
Constructeur paramétré
• Déclaration
class Complexe {
public:
Complexe();
Complexe(double re, double im);
. . . };
• Définition
Complexe::Complexe(double re, double im) {
DefinirCartesien(re, im);
}
Programmation orientée objet en C++
Constructeur
• Exemple – objet initialisé avec des paramètres
int main() {
Complexe z1(1.0, 2.5);
cout << "z1 : ";
cout << z1 << endl;
}
Introduction à la programmation orientée objet avec C++ 70
Objets membres et constructeur
• On construit une classe « SimulationImpedance »
• En utilisant un objet « Complexe » pour l’impédance
class SimulationImpedance {
private:
Complexe impedance;
double frequence;
public:
void DefinirFrequence(double f);
void DefinirImpedance(const Complexe & z);
};
Programmation orientée objet en C++
Objets membres et constructeur
• Utilisation
int main() {
SimulationImpedance s1, s2;
}
• Que se passe-t-il à la construction de s1, s2 ?
– Construction des objets interne.
– Appel automatique du constructeur de « Complexe ».
Introduction à la programmation orientée objet avec C++ 72
Une liste tableau simple en C++
• Exemple : liste de double
#define CAPACITE 100 class ListeDouble {
private:
double * tableau;
int nombre_elements;
public:
ListeDouble();
~ListeDouble();
ListeDouble(const ListeDouble & liste);
ListeDouble & operator=(const ListeDouble & valeur);
int Ajouter(double x);
int LireNombreElement() const;
double LireElement(int indice) const;
double operator[](int indice) const;
};
Programmation orientée objet en C++
Une liste tableau simple en C++
• Constructeur par défaut
ListeDouble::ListeDouble() {
nombre_elements = 0;
tableau = new double[CAPACITE];
}
• Libération de la mémoire
– La mémoire allouée dynamiquement doit être libérée.
– Solution en C++ : le destructeur
• Méthode appelée automatiquement lorsque l’objet disparait.
Introduction à la programmation orientée objet avec C++ 74
Destructeur
• Déclaration
class ListeDouble {
public:
ListeDouble();
~ListeDouble();
. . . };
• Définition
ListeDouble::~ListeDouble() {
delete []tableau;
}
Destructeur:
- Nom de la classe, précédé de ~ - Sans aucun type de résultat.
Programmation orientée objet en C++
Constructeur et destructeur – illustration int main()
{
ListeDouble liste1; // construction de liste1 ListeDouble liste2; // construction de liste2
// liste1 et liste2 sont utilisables liste1.Ajouter(2);
liste1.Ajouter(7);
liste1.Ajouter(4);
liste2 = liste1;
// A la sortie du bloc ou de la fonction // destruction automatique de liste2 // destruction automatique de liste1 }
Introduction à la programmation orientée objet avec C++ 76
Une liste tableau simple en C++
• Utilisation
int main() {
ListeDouble liste1, liste2;
liste1.Ajouter(3.0);
liste2 = liste1;
}
• Constat
– Objets très facile à utiliser.
– Construits et détruits automatiquement.
• Problème
// Que fait cette instruction ? liste2 = liste1;
– Exception à la fin du programme !
Programmation orientée objet en C++
Méthodes par défaut
• En C++, toute classe a au minimum
– 1 Constructeur par défaut.
– 1 Constructeur copie.
• Appelé lors de l’initialisation d’un objet.
• Copie chaque champ.
– 1 Opérateur d’affectation.
• Appelé lors de l’affectation d’un objet.
• Copie chaque champ.
• Par défaut, ces méthodes sont générées automatiquement.
Introduction à la programmation orientée objet avec C++ 78
Une liste tableau simple en C++ - initialisation incorrecte de l’objet
liste1 tableau nombre_elements
3 5 4 1 0 9 2 8 7 2 4 7
liste2 tableau nombre_elements
Avant liste2 = liste1;
Programmation orientée objet en C++
Une liste tableau simple en C++ - initialisation incorrecte de l’objet
liste1 tableau nombre_elements liste2
tableau nombre_elements
Après liste2 = liste1;
3 5 4 1 0 9 2 8 7 2 4 7
Introduction à la programmation orientée objet avec C++ 80
Une liste tableau simple en C++ - initialisation incorrecte de l’objet
• Conséquences
– Une modification ultérieure de liste1 modifie aussi liste2 ! – Lors de la destruction
• La destruction de liste2 libère le bloc de mémoire de liste1.
• Lors de la destruction de liste1, ce bloc est libéré une deuxième fois.
• D’où une erreur fatale.
• Comportement souhaitable
– Lors de l’affectation ou de l’initialisation de liste2, il faudrait
• Copier le contenu de liste1 dans le tableau de liste2.
• Copier le nombre d’éléments.
– Ce comportement peut être programmé :
• Initialisation : surcharge du constructeur copie.
• Affectation : surcharge de l’opérateur « = »
Programmation orientée objet en C++
Une liste tableau simple en C++ - affectation correcte de l’objet
• Déclaration
class ListeDouble {
public:
ListeDouble & operator=(const ListeDouble & valeur);
. . . . };
• Définition
ListeDouble & ListeDouble::operator=(const ListeDouble & liste) {
int i;
// Copie des éléments du tableau
for (i = 0; i < liste.nombre_elements; i++) tableau[i] = liste[i];
// Copie du nombre d’éléments
nombre_elements = liste.nombre_elements;
// l’affectation renvoie la valeur de gauche
// Requis pour permettre le chaînage des affectations.
return *this;
}
Introduction à la programmation orientée objet avec C++ 82
Une liste tableau simple en C++ - initialisation correcte de l’objet
• Déclaration
class ListeDouble {
public:
ListeDouble();
~ListeDouble();
ListeDouble(const ListeDouble & liste);
. . . . };
• Définition
ListeDouble::ListeDouble(const ListeDouble & liste) {
int i;
// Constructeur : création du tableau nécessaire tableau = new double[CAPACITE];
// Copie des éléments utilisés
for (i = 0; i < liste.nombre_elements; i++) tableau[i] = liste[i];
// Copie du nombre d’éléments
nombre_elements = liste.nombre_elements;
}
Introduction à la programmation orientée objet avec C++ 84
Héritage - Introduction
• Développement d’un logiciel de simulation électrique
• Composants pris en compte sous forme de classes séparées : – Resistance
– Inductance – Condensateur
• Observation
– Nombreux points communs
Resistance - resistance: double - nom: string
+ CalculerImpedance(double) : Complexe + DefinirNom(string) : void + LireNom() : string + AfficherCaracteristiques() : void
Inductance - inductance: double - nom: string
+ CalculerImpedance(double) : Complexe + DefinirNom(string) : void + LireNom() : string + AfficherCaracteristiques() : void
Condensateur - capacite: double - nom: string
+ CalculerImpedance(double) : Complexe + DefinirNom(string) : void + LireNom() : string + AfficherCaracteristiques() : void
Programmation orientée objet en C++
Héritage - Introduction
• Possibilité orientée objet du C++
– Créer une classe de base contenant les éléments communs – Créer chaque classe particulière en « héritant » des
caractéristiques communes de la classe de base
• Intérêt
– Eviter les répétitions de code.
– Approche hiérarchique des problèmes.
Introduction à la programmation orientée objet avec C++ 86
Héritage - Introduction
• Nouvelle répartition – pas de répétition de code
Condensateur hérite de ComposantPassif :
-Il reçoit tous les attributs et méthodes de cette classe.
- L’héritage étant public, tous les éléments publics hérités de ComposantPassif seront publics sur la classe Condensateur.
Programmation orientée objet en C++
Héritage – Réalisation en C++
class ComposantPassif {
private:
string nom;
public:
void DefinirNom(const string & valeur);
string LireNom() const;
void AfficherCaracteristiques() const;
};
class Condensateur: public ComposantPassif {
private:
double capacite;
public:
Complexe CalculerImpedance(double frequence) const;
void DefinirCapacite(double valeur);
double LireCapacite() const;
};
Introduction à la programmation orientée objet avec C++ 88
Héritage – Réalisation en C++
// Classe composant passif
void ComposantPassif::DefinirNom(const string & valeur) {
nom = valeur;
}
string ComposantPassif::LireNom() const {
return nom;
}
void ComposantPassif::AfficherCaracteristiques() const {
cout << "Nom : " << nom << endl;
}
Programmation orientée objet en C++
Héritage – Réalisation en C++
// Classe Condensateur
Complexe Condensateur::CalculerImpedance(double frequence) const {
Complexe resultat;
resultat.DefinirCartesien(0, - 1.0 / (capacite * 2 * M_PI * frequence));
return resultat;
}
void Condensateur::DefinirCapacite(double valeur) {
capacite = valeur;
}
double Condensateur::LireCapacite() const {
return capacite;
}
Introduction à la programmation orientée objet avec C++ 90
Méthodes définies seulement dans ComposantPassif.
Disponibles dans Condensateur par héritage public.
Héritage – Réalisation en C++
int main() {
ComposantPassif compo1;
Condensateur c1;
compo1.DefinirNom("Composant 1");
c1.DefinirNom("Condensateur 1");
c1.DefinirCapacite(4.7e-6); // 4.7 µF compo1.AfficherCaracteristiques();
cout << endl;
c1.AfficherCaracteristiques();
cout << endl;
pause();
}
Programmation orientée objet en C++
Héritage – Réalisation en C++
• Résultat :
• Observation
– Condensateur a bien hérité des attributs et méthodes de ComposantPassif
– Résultat incomplet :
• Pour un condensateur, il faudrait aussi afficher la capacité.
• Solution : redéfinir la méthode « AfficherCaracteristiques ».
Introduction à la programmation orientée objet avec C++ 92
Appel de la méthode de la classe parent.
Redéclaration de la méthode AfficherCaracteristiques.
Héritage – Redéfinir une méthode pour enrichir le comportement
• Déclaration
class Condensateur: public ComposantPassif {
public:
. . .
void AfficherCaracteristiques() const;
};
• Définition
void Condensateur::AfficherCaracteristiques() const {
ComposantPassif::AfficherCaracteristiques();
cout << "Capacite : " << capacite * 1.0e6 << " uF" << endl;
}
• Résultat
Programmation orientée objet en C++
Héritage – Représentation en mémoire
ComposantPassif string nom
Condensateur string nom
double capacite
Organisation en mémoire identique
Conséquences – affectations possibles
compo1 = c1; // Ok, définit entièrement compo1
c1 = compo1; // Interdit, ne définit pas complètement c1