Programmation orientée objet (C++)
Autre cours
Devoir sur table le 11 mars 2022 (héritage compris) -> il peut que améliorer la note (même fonctionnement que le premier semestre, exemple de travail :
- est-ce que le code compile ?
- qu’affiche le constructeur/destructeur, etc…
- implanter des fonctions d’une classe
- la syntaxe ne contras pas trop
- la cohérence des classes, etc. est importanteExamen : Vendredi 22 avril 2022 : 16h30-18h.
- Tout est suscpetible d’apparaître (héritage, …)
Séance de rattrapage le 11 avril 2022
Choisir son projet le plus tôt possible
Projet à rendre avant la soutenance.
Séance d’oral entre le 16 et le 22 mai.
-
C C++ #include <stdio.h>
#include <cstdio>
gcc source.c
g++ source.cpp -std=c++11
typedef struct maStruct { ... } MaStruct;
struct MaStruct { ... };
Les ajouts du C++ par rapport au C
- Mécanisme de POO (Programmation Orientée Objet)
- Encapsulation
- il y a des champs et des fonctions publique ou privé
- Polymorphisme
- Template (patron de classe ou de fonction)
- Référence
- Exceptions
- Espace de noms
- Héritage
- Encapsulation
flowchart TD 1[Voiture]-->2[Camion] 1-->3[Vélo] 1-->4[Voiture]
Exemple de modélisation d’une fraction en C++
#include<cstdio> int calcPGCD(int a, int b) { if(b == 0) return a; return pgcd(b, a % b); } struct Fraction { int num; // numérateur int den; // dénominateur int pgcd; void afficher(void) { /* c'est une méthode, pas une fonction, elle est * liée à un objet */ printf("%d/%D\n", num, den); } Fraction produit(Fraction b) { Fraction res; res.num = num * b.num; res.den = den * b.den; return res; } void agrandir(int a) { num *= a; den *= a; } void calculePGCD(void) { pgcd = calcPGCD(num, den); } void simplifierFraction(void) { calculePGCD(); // Wow! on utilise une méthode de l'objet num = num / pgcd; den = den / pgcd; } }; int main() { // Définition d'une fraction Fraction a; a.num = 6; a.den = 2; a.afficher(); // Utilisation d'une méthode de l'objet Fraction c = a.produit(b); c.afficher(); // On peut donner modifier la valeur de l'objet c.agrandir(2); c.afficher(); // Encore une exemple c.calculePGCD(); printf("PGCD: %d\n", c.pgcd); // Encore un exemple a.afficher(); a.simplifierFraction(); a.afficher(); }
On ajoute un constructeur à notre programme
#include<cstdio> int calcPGCD(int a, int b) { if(b == 0) return a; return pgcd(b, a % b); } struct Fraction { private : // attributs/membre privés, pas accessible à l'extérieur de la classe int num; // numérateur int den; // dénominateur // Constructeur du programme quand aucun argument renseigné Fraction(void) { } public : // attribut/membre publics, accessible à l'extérieur de la classe int pgcd; // Constructeur du programme quand 2 arguments renseignés Fraction(int nump, int denp) { printf("Appelle d'un constructeur\n"); num = nump; den = denp; pgcd = calcPGCD(nump, denp); } void afficher(void) { /* c'est une méthode, pas une fonction, elle est * liée à un objet */ printf("%d/%D\n", num, den); } Fraction produit(Fraction b) { Fraction res; res.num = num * b.num; res.den = den * b.den; return res; } // Accesseur num void setNum(int pnum) { num = pnum; pgcd = calculePGCD(); } // Accesseur den void setDen(int pden) { den = pden; pgcd = calculePGCD(); } void agrandir(int a) { num *= a; den *= a; } void calculePGCD(void) { pgcd = calcPGCD(num, den); } void simplifierFraction(void) { calculePGCD(); // Wow! on utilise une méthode de l'objet num = num / pgcd; den = den / pgcd; } }; int main() { // Définition d'une fraction Fraction a(6, 2); printf("PGCD: %d\n", a.pgcd); // Modification du dénominateur a.setDen(3); printf("PGCD: %d\n", a.pgcd); // incroyable, le PGCD reste vrai ! a.afficher(); a.simplifierFraction(); a.afficher(); }
struct
ouclass
sont équivalent, la seule différence est que l’intérieur d’unestruct
est publique par défaut à l’instar du contenu d’uneclass
qui est privée par défaut// Différence entre struct et class struct MaClasse { // public: implicite }; class MaClasse { // private: implicite };
On peut définir une méthode d’une struct en dehors de la struct. C’est mieux de définir directement à l’intérieur que à l’extérieur quand même
struct MaClasse { int maMethode(); }; int MaClasse::maMethode() { return 3; }
Il est préférable de définir une
class
à l’extérieur du fichier.cpp
, dans un fichier.hpp
.Fichier
xd.hpp
#ifndef xd #define xd struct MaClasse { int maMethode(void); }; int MaClasse::maMethode(void) { return 3; } #endif
Fichier
main.cpp
#include <iostream> #include "xd.hpp" using namespace std; int main(void) { MaClasse a; cout << a.maMethode() << endl; }
- Mécanisme de POO (Programmation Orientée Objet)
-
Constructeur et destructeur
On peut définir un constructeur de classe qui sera appellée lorsque l’on instancie un nouvel objet.
struct MaClasse { MaClasse(void) { cout << "coucou" << endl; } };
Pour éviter d’appeller le constructeur, il faut utiliser
malloc
(faut pas faire ça, c’est pas cohérent).
Il ne faut donc pas utilisermalloc
pour utiliser l’objet avec des*
, il faut utilisernew
(etdelete
remplacefree
).int main(void) { /* On réserve de la place, comme malloc, mais * là on appelle bien le constructeur */ MaClasse* b = new MaClasse(); // ... delete b; return 1; }
On utilises
delete
car on peut aussi définir un destructeur (comportement lorsqu’on libère la classe), etfree
ne le permet pas.Il y a donc plusieurs méthodes qui sont crées implicitement : constructeur, destructeur.
#include <iostream> using namespace std; struct Tableau { private: int taille; int* tab; public: // Constructeur Tableau(int ptaille) { tab = new int[ptaille]; taille = ptaille; for(int i = 0; i < taille; i++) tab[i] = 0; } void afficher(void) { for(int i = 0; i < taille; i++) cout << ' ' << tab[i]; cout << endl; } // Destructeur ~Tableau(void) { delete[] tab; } }; int main(void) { Tableau tab1(10); tab1.afficher(); }
Résumé
Le(s) constructeur(s) possède(nt) le même nom que la classe. Pour le destructeur on ajoute un
~
devant.
Exemple :class MaClasse { MaClasse(...) { // corps du constructeur } ~MaClasse(...) { // corps du destructeur } };
Il ne peut y avoir qu’un seul destructeur. Le destructeur ne prend jamais de paramètre. Par défaut pour toute classe un constructeur et un destructeur implicite sont générés.
Le mot cléthis
est une expression qui donne l’adresse de l’objet de la classe.Les opérateurs
new
etdelete
(ainsi quenew[]
etdelete[]
) sont à utiliser sur les classes car ils appellent les constructeurs et destructeurs.
Exemple :MaClasse* a = new MaClasse(...); MaClasse* tab = new MaClasse[13]; delete a; delete[] tab;
Cf. Chapitre 4 pour d’autre façon d’appeller des constructeurs (il y aussi le constructeur de recopie).
-
Nom C++ Sens Binaire () [] ->
->
Unaire + - ++ -- ~ * &
<-
Unaire new new[] delete delete[] (cast)
->
Binaire * / %
->
Binaire + -
->
Binaire << >>
->
Binaire == !=
->
Binaire &
->
Binaire ^
->
Binaire ||
->
Binaire &&
->
Binaire |
->
Binaire = += -= *= /= %= &= ^= |= <<= >>=
<-
Décomposition de comment une expression est gérer par l’ordinateur
((a+((f\times b)\times c)) = (d = (++a)))
flowchart TD 1[=] --> 2 & 3 2[+] --> 4 & 5 3[=] --> 8 & 9 4[a] 5[*] --> 6 & 7[c] 6[f] 7[b] 8[d] 9[++] --> 10 10[a]
Test d’opérateurs
testOp.cpp
:#include<iostream> class Frac { int num; int den; public: Frac(int n, int d) { num = n; den = d; } void afficher(void) { std::cout << "num:" << num << ", den:" << den << std::endl; } Frac operator +(const Frac& a) const { Frac retour(0, 0); retour.num = a.num * den + num * a.den; retour.den = a.den * den; return retour; } friend Frac operator *(const Frac& a, const Frac& b); }; Frac operator *(const Frac& a, const Frac& b) { Frac retour(a.num, a.den); retour.num *= b.num; retour.den *= b.den; return retour; } int main(void) { Frac const a(3, 2); Frac const b(1, 4); Frac c = a + b; // Frac = a.operator + (b); c.afficher(); return 0; }
Il faut penser à modifier l’opérateur d’affectation quand on créer une classe qui utilises de la mémoire (pour renvoyer une copie d’un élément…) et éviter de perdre un élément.
Exemple depuis l’exemple précédent :Frac operator =(const Frac& b) { std::cout << "opérateur affectation" << std::endl; num = b.num; den = b.den; return *this; }
Ainsi dans le main…
c = a; // on garde a et on a un nouveau c
void maFonction(Frac a) { a.afficher(); } int main(void) { maFonction(3); }
Dans ce cas, le compilateur ne rale pas, malgré que la fonction
maFonction
prenne unFrac
et non unint
, car il y a une conversion implicite deint
versFrac
.Bilan
Lorsqu’un classe (ex:
class A { ... };
)est définie, alors par défaut est construit :- un constructeur sans paramètre (ex:
A() { }
) - un destructeur (forcément sans paramètre) (ex:
~A() { }
) - un constructeur de recopie (ex:
A(A const & a) { ... }
) - un opérateur d’affectation (ex:
A operator =(const &) { }
)
Remarque
- L’écriture explicite par le concepteur de la classe d’un de ces quatres méthodes l’efface (en gros ces constructeurs par défaut ne sont pas construit si le dev en a écrit).
- On peut forcer le compilateur a ne pas construire l’une de ces quatres méthodes en écrivant :
<methode(...)=delete;
, exemple :
class A { A(void) = delete; // constructeur ~A(void) = delete; // desctucteur void operator =(...) = delete; // affectation A(const A& a) = delete; // recopie };
- On peut surcharger les opérateurs classiques de C++, exemple :
class A { ... }; A operator +(A a, A b) {// fonction qui renvoie un A ... } int main() { A a; A b; ... ... a + b ... sera transformé en : ... operator +(a, b) ... ... return ...; }
- Les opérateurs peuvent être surchargés à l’intérieur de la classe, exemple :
class A { ... A operator +(A a) { // un seul paramètre car l'autre est *this ... ... return ...; } };
- Conversion implicite par constructeur :
- SI A est une classe et E est l’identificateur d’un type alors un constructeur de la classe A(E\ a) définit une conversion implicite de E vers A.
- un constructeur sans paramètre (ex: