Chapitre 2 : des classes et des objets #
Slides #
Version imprimable (faire CTRL+P)
Exemples #
1242.2_02.01_PointClass
Point.h
#pragma once
// @warning: this class is verbose for educational purposes only.
class Point
{
public:
// ========== BASIC METHODS ==========
// Without C++ contructors, we would need to use an init method.
// This is not needed in C++
// void init(int px, int py)
// {
// m_x = px;
// m_y = py;
// }
// Implicitly inline methods
void setX(int x) {m_x = x;}
void setY(int y) {m_y = y;}
void setXY(int x, int y){m_x = x; m_y = y;}
// Inline with definition in header (see end of this file)
int getX() const;
int getY() const;
void translate(int dx, int dy);
// ========== CONSTRUCTORS ==========
// Default constructor
Point();
// Constructor
Point(int x, int y);
// Copy constructor
Point(const Point &p);
// Conversion constructor from int to Point
explicit Point(int v);
// ========== DESTRUCTOR ==========
virtual ~Point(); // virtual: see chapter on polymorphism
// ========== COPY ASSIGNMENT ==========
// Just make it default (compiler-generated)
Point& operator=(const Point &p) = default;
// ========== CONST METHODS ==========
void print() const;
// ========== 'this' POINTER USAGE ==========
// Demonstrates: using 'this' pointer
bool isSameObject(const Point &other) const;
// Demonstrates: '*this' (dereferencing)
Point getCopy() const;
// ========== STATIC MEMBERS ==========
// Do not belong to a specific instance
static int getCounts();
// ========== FRIEND FUNCTION ==========
friend double distance(const Point &p1, const Point &p2);
// ========== RETURNING A REFERENCE ==========
// Allows: p.X() = 5; or p.X()++;
// NOTE: This breaks encapsulation (equivalent to making m_x public).
// In practice, prefer getX()/setX() for simple attributes.
int X() const {return m_x;}
// Cannot be const
int &X() {return m_x;}
int Y() const {return m_y;}
// Cannot be const
int &Y() {return m_y;}
private:
// Attributes
int m_x{0};
int m_y{0};
// Shared attribute for all instances
static int m_counts;
};
inline int Point::getX() const {return m_x;}
inline int Point::getY() const {return m_y;}
// Non-member function
double distance(const Point &p1, const Point &p2);Point.cpp
#include "Point.h"
#include <print>
#include <cmath>
// ========== STATIC MEMBERS INITIALIZATION ==========
int Point::m_counts = 0;
// ========== 1. BASIC METHODS ==========
void Point::translate(int dx, int dy)
{
m_x += dx;
m_y += dy;
}
// ========== 2. CONSTRUCTORS ==========
Point::Point() // : m_x(0), m_y(0) -> not needed. See in-class initialization
{
m_counts++;
std::println(" ->default ctor (counts={})", m_counts);
}
// member initializer list
Point::Point(int x, int y) : m_x(x), m_y(y)
{
m_counts++;
std::println(" ->standard ctor (with initializer list, counts={})", m_counts);
}
Point::Point(int v) : m_x(v), m_y(v)
{
m_counts++;
std::println(" ->conversion ctor (counts={})", m_counts);
}
Point::Point(const Point &p) : m_x(p.m_x), m_y(p.m_y)
{
m_counts++;
std::println(" ->copy ctor (counts={})", m_counts);
}
// ========== 3. DESTRUCTOR ==========
Point::~Point()
{
m_counts--;
std::println(" ~Point() dtor called (counts={})", m_counts);
}
// ========== 4. CONST METHODS ==========
void Point::print() const
{
// C++23
std::println("({}, {})", m_x, m_y);
// Prior to C++23:
// std::cout << "(" << m_x << "," << m_y << ")" << std::endl;
}
// ========== 5. 'this' POINTER USAGE ==========
bool Point::isSameObject(const Point &otherPoint) const
{
return (this == &otherPoint);
}
Point Point::getCopy() const
{
return *this;
}
// ========== 6. STATIC METHODS ==========
int Point::getCounts()
{
return m_counts;
}
// ========== 7. FRIEND FUNCTION ==========
double distance(const Point &p1, const Point &p2)
{
// Friend of Point: can access private members
int dx = p1.m_x - p2.m_x;
int dy = p1.m_y - p2.m_y;
return std::sqrt(dx * dx + dy * dy);
}main.cpp
#include "Point.h"
#include <print>
#include <vector>
void f1(Point p)
{
p.print();
}
void f2(const Point &p)
{
p.print();
}
void f3(Point *p)
{
p->print();
}
Point f4()
{
Point p(5, 6);
p.print();
return p;
}
void example01_basic()
{
Point p1;
p1.print();
p1.translate(10, 10);
p1.print();
}
void example02_conversion()
{
// Explicitly calls CTor
Point p1 = Point(4);
p1.print();
// GCC: error: conversion from 'int' to non-scalar type 'Point' requested
// Point p2 = 4;
}
void example03_constructors()
{
Point p1 = Point();
p1.print();
p1.setXY(100, 200);
p1.print();
Point p2(1, 2);
p2.print();
Point p3 = Point(1, 2);
p3.print();
Point p4(p1);
p4.print();
Point p5 = p1;
p5.print();
Point p6(10);
p6.print();
// GCC: error: conversion from 'int' to non-scalar type 'Point' requested
// Point p7 = 11;
// GCC: error: no match for 'operator=' (operand types are 'Point' and 'int')
// Point p8;
// p8 = 4;
Point *pP1 = new Point();
pP1->setXY(150, 250);
pP1->print();
delete pP1;
pP1 = nullptr;
}
void example04_copyConstructor()
{
Point p1(3, 4);
Point p4(p1);
p4.print();
Point p5 = Point(p1);
p5.print();
f1(p4);
f2(p4);
f3(&p4);
Point p6 = f4();
}
void example05_destructor()
{
{
Point p1(1, 1);
Point pn[4];
}
auto ptrP = new Point(2, 3);
ptrP->setXY(150, 250);
ptrP->print();
delete ptrP;
ptrP = nullptr;
auto ptrTabPoint = new Point[5];
delete[] ptrTabPoint;
ptrTabPoint = nullptr;
}
void example06_this()
{
Point pt(5, 7);
Point p2(pt);
Point &refPt = pt;
Point *ptrPt = &pt;
Point clone = pt.getCopy();
pt.print();
p2.print();
refPt.print();
ptrPt->print();
clone.print();
std::println("pt.isSameObject(p2): {}", pt.isSameObject(p2));
std::println("pt.isSameObject(refPt): {}", pt.isSameObject(refPt));
std::println("pt.isSameObject(*ptrPt): {}", pt.isSameObject(*ptrPt));
std::println("pt.isSameObject(clone): {}", pt.isSameObject(clone));
}
void example07_static()
{
std::println("Point::getCounts() = {}", Point::getCounts());
Point p1(2, 3);
Point p2(4, 5);
// Both return the same value: counts is shared by all instances
// Works, but calling a static method on an instance is misleading.
// Prefer: Point::getCounts()
std::println("p1.getCounts() = {}", p1.getCounts());
std::println("p2.getCounts() = {}", p2.getCounts());
std::println("Point::getCounts() = {}", Point::getCounts());
}
void example08_friend()
{
Point p1(7, 9);
p1.print();
Point p2(3, 4);
double d = distance(p1, p2);
std::println("distance(p1, p2) = {}", d);
}
void example09_returnRef()
{
// X() returns a reference: allows direct modification
// Convenient, but use with care (see header comment)
Point p1(1, 1);
p1.X()++;
p1.Y() += 10;
p1.print();
}
void example10_constGet()
{
// p1 cannot be changed afterwards
const Point p1(1, 1);
// GCC: error: passing 'const Point' as 'this' argument discards qualifiers
// p1.setX(10);
p1.getX();
}
void example11_pointArray()
{
// Use default CTor
Point p3[10];
// Use Point(int, int), then use default CTor
Point p4[4] = {Point(5, 3)};
auto *p5 = new Point(6, 3);
delete p5;
p5 = nullptr;
auto *p6 = new Point[10];
for (int i = 0; i < 10; i++)
{
p6[i] = Point(2, 3);
}
delete[] p6;
p6 = nullptr;
std::vector<Point> v(10, Point(2, 3));
}
int main()
{
example01_basic();
example02_conversion();
example03_constructors();
example04_copyConstructor();
example05_destructor();
example06_this();
example07_static();
example08_friend();
example09_returnRef();
example10_constGet();
example11_pointArray();
return 0;
}1242.2_02.02_CTorsMisc
A.h
#pragma once
// GCC: use compiler flag -Wshadow to get shadowing warnings
class A
{
public:
A() = default;
// GCC warning: declaration of 'value' shadows a member of 'A'
explicit A(int value) : value(value) {}
// Expressions in initializer list are ok
A(int value1, int value2) : value(add(value1, value2)) {}
// Members are initialized in the order of their declaration!!!
// -> value = 10, then angle = 20
// GCC warning: 'A::angle' will be initialized after
A(int otherValue, [[maybe_unused]] double otherAngle) : angle(value + 10),
value(otherValue) {}
// No copy allowed
A(const A &other) = delete;
int add(int a, int b) const { return a + b; }
int getValue() const { return value; }
double getAngle() const { return angle; }
private:
int value{0};
double angle{0.0};
};main.cpp
#include "A.h"
#include <print>
int main()
{
A a1; // default constructor
std::println("a1: value={}, angle={}", a1.getValue(), a1.getAngle());
A a2(1); // constructor with one parameter
std::println("a2: value={}, angle={}", a2.getValue(), a2.getAngle());
A a3(1, 2); // constructor with two parameters
std::println("a3: value={}, angle={}", a3.getValue(), a3.getAngle());
A a4(1, 2.0);
std::println("a4: value={}, angle={}", a4.getValue(), a4.getAngle());
// GCC: error: use of deleted function 'A::A(const A&)'
// auto a5 = a4;
return 0;
}Série 2.1 #
Exercice 1 : classe compte bancaire #
On veut développer un programme de gestion d’un compte bancaire.
Pour cela, implémenter une classe BankAccount, avec laquelle on puisse :
- initialiser le montant à zéro
- déposer de l’argent (il faut vérifier que le montant déposé soit positif)
- retirer de l’argent (il faut vérifier que le montant retiré soit positif et pas supérieur au montant disponible sur le compte)
La classe peut être définie dans le fichier main.cpp
Avec le programme main ci-dessous :
int main()
{
BankAccount myBankAccount;
myBankAccount.show();
myBankAccount.withdraw(100); // ERROR
myBankAccount.show();
myBankAccount.deposit(100);
myBankAccount.show();
myBankAccount.withdraw(200); // ERROR
myBankAccount.show();
myBankAccount.withdraw(20);
myBankAccount.show();
return 0;
}Le résultat sera le suivant:
The amount on your bank account is : 0.00
The amount on your bank account is : 0.00
The amount on your bank account is : 100.00
The amount on your bank account is : 100.00
The amount on your bank account is : 80.00Exercice 2 : classe Time #
Créer une classe Time permettant de manipuler des mesures de temps (heure, minute) selon le diagramme UML suivant :
Elle disposera donc :
- des attributs privés :
hour,minute - des constructeurs : par défaut (–>12H00), standard, et de conversion (5.75 –> 5H45')
- des accesseurs :
getHour(),getMinute() - des modificateurs :
setHour(h),setMinute(m)qui valideront l’argument avant de modifier l’objet - de la méthode
show()qui affichera heures et minutes avec 2 digits : (“18H45”)
Suggestion : utiliser une horloge comptant sur 24 heures. Les valeurs excessives (>23, >59) seront remplacées par la valeur maximum possible. Les valeurs négatives seront considérées comme 0.
Time.h, Time.cppTester cette classe dans un programme qui :
- appelle les différents constructeurs
- utilise les méthodes pour régler une heure à 16h33, ou à 16h87
- utilise accesseurs et modificateur pour augmenter un objet
Timede 5 minutes - affiche les objets
Timeavec la méthodeshow
Exemple d’utilisation :
Time t1;
Time t2(8,15), t3(11.25);
...
t1.setHour(36);
t1.show();Exercice 3 : classe Point #
- En s’inspirant des exemples du cours, concevoir puis implémenter une classe
Pointpermettant de manipuler un point dans le plan. Cette classe est résumé dans le diagramme UML suivant :
Un point sera caractérisé par un label et ses coordonnées dans le plan.
La classe Point disposera des méthodes suivantes :
- un constructeur qui permette de créer un point avec un label et de manière optionnelle spécifier ses coordonnées x,y (0 par défaut)
- une méthode
show()qui affiche le nom du point et ses coordonnées - une méthode
translate()qui prend en arguments les composantes du vecteur de translation et modifie les attributs du point courant en conséquence.
Point.h, Point.cpp- Écrire une fonction
main()permettant de tester la classePointavec :
- instanciation de points avec chacun des constructeurs :
Point p1,Point p2('A', 3, 4) - affichage des points
- translation de l’un des points et nouvel affichage
- instanciation dynamique d’un objet point (syntaxe C++), et stockage de son adresse dans un pointeur
- affichage de ce point
- suppression de ce point (récupération de la mémoire)
Point** generatePolygon(int n) qui instancie dynamiquement autant de points que nécessaire à la réalisation d’un polygone régulier à n côtés, dont les points sont à une distance unitaire de l’origine.
Par exemple : triangle(n=3), hexagone(n=6), …
La fonction renvoie un pointeur sur le tableau de pointeurs alloué dynamiquement qui contient les n pointeurs.
Libérer la mémoire des points créés et du tableau de pointeurs avant la fin du programme.
Note : le premier point sera toujours en (1;0), le second illustré en rouge (à un angle de 120°, 90°, …, 60° du premier, etc.).Série 2.2 #
Exercice 1 : classe Point améliorée #
Reprendre la classe Point de la série 2.1 et la compléter avec les éléments suivants :
- une variable membre statique privée
counterinitialisée à 0 qui sera incrémentée dans tous les constructeurs et décrémentée dans le destructeur de la classe - une méthode statique publique
getCounter()qui permettra au programmemaind’afficher la valeur decounter - Un destructeur
~Point()
Son diagramme de classe UML sera donc :
- Compléter le programme pour créer des objets
Pointet afficher la valeur du compteur. Vérifier en particulier qu’à la fin du programme il n’y ait plus d’objetsPointen mémoire grâce au compteur (encapsulermaindans une paire de { } supplémentaire).
Par exemple :
...
int main()
{
Point *ptrPoint = nullptr;
{
Point p1('A');
Point p2('B', 3, 4);
// 1. Afficher le nombre de points avec getCounter() (doit afficher 2)
// 2. Créer dynamiquement un point et mettre son adresse dans le pointeur
// 3. Afficher le nombre de points (doit afficher 3)
} // fin du bloc --> p1 et p2 sont détruits ici
// 4. Afficher le nombre de points (doit afficher 1)
delete ptrPoint; // destruction du point alloué dynamiquement
ptrPoint = nullptr;
// 5. Afficher le nombre de points (doit afficher 0)
return 0;
}Exercice 2 : classe Rectangle (composition de points) #
Une composition est une relation très forte. Quand l’objet est détruit, les éléments qui le composent doivent aussi être détruits (si je détruis ma maison, les pièces qui la composaient sont détruites).
En s’appuyant sur la classe Point, réaliser la classe Rectangle.
Un rectangle sera composé de 2 points : son coin inférieur gauche cornerLL et son coin supérieur droit cornerUR.
La classe possèdera les méthodes :
contains(const Point& p): renvoietruesi le pointpest à l’intérieur du rectanglegetPerimeter(): retourne le périmètre du rectangleshow(): affiche les informations du rectangle (coordonnées des coins et périmètre)translate(double dx, double dy): translate les deux points du rectangle
ainsi que 2 constructeurs :
Rectangle(double xLL, double yLL, double xUR, double yUR)Rectangle(const Point& cornerLL, const Point& cornerUR)
À faire :
- Donner le diagramme UML de la classe
Rectangle - Implémenter la classe
Rectangleen s’appuyant sur la classePoint - Ne pas oublier le mot clé
constpour les méthodes constantes (ex.void show() const). - Écrire un programme pour tester la classe
Rectangle. Le programme construira un objet rectangle avec chacun des constructeurs, les affichera avecshow(), utilisera ses méthodes, puis modifiera les arguments de constructions donnés en paramètre aux constructeurs et les affichera à nouveau.
Question : comment donner accès aux coordonnées des points aux fonctions de Rectangle ?
Question : est-il possible de construire un rectangle à partir d’un autre ? Essayer et afficher le nouveau rectangle pour voir si cela fonctionne bien.
Exemple d´extrait de programme :
Point pt1('A', 5., 5.), pt2('B', 10., 12.), ptX('X', 7., 8.);
Rectangle R(pt1, pt2);
R.show();
std::println("Périmètre : {}", R.getPerimeter()); // 24
std::println("is ptX contained in the area of rectangle R ? {}", R.contains(ptX));
pt1.translate(3,5);
R.show();
std::println("Périmètre : {}", R.getPerimeter()); // ??
// construire un rectangle R2 à partir de R et l'afficher
Exercice 3 : classe RectangleAgreg (agrégation de points) #
Une agrégation est une relation moins forte qu’une composition. Quand l’objet est détruit, les éléments qui y étaient associés continuent à exister (si une entreprise disparait, on ne liquide pas ses employés !).
En s’appuyant sur la classe Point, réaliser la classe RectangleAgreg.
Un RectangleAgreg sera associé à 2 points par les pointeurs : ptrLLCorner et ptrURCorner.
La classe possèdera les méthodes : show() , getPerimeter(), et translate(double dx, double dy)
La classe RectangleAgreg offrira un constructeur :
RectangleAgreg(Point* cornerLL, Point* cornerUR)
À faire :
- implémenter la classe
RectangleAgregen s’appuyant sur la classePoint - écrire un programme qui :
- crée deux points
- agrège les deux points pour construire le rectangle
Rde classeRectangleAgreg - utilise
translate(dx, dy)sur un des points - affiche le rectangle et les points avec
show(); qu’observez-vous ? - crée un second
RectangleAgregcopie du rectangle original avec la déclarationRectangleAgreg copyR(Ra1); - utilise
translatesur un des deux rectangles puis affiche les deux rectangles; qu’observez-vous ?
Exercice 4 : classe RectangleComp (composition de points avec copie en profondeur) #
En vous basant sur la classe RectangleAgreg, réaliser la classe RectangleComp qui est une composition de points avec copie en profondeur.
En particulier, il faut :
- supprimer la dépendance (le lien observé entre les deux rectangles) : implémenter le constructeur par copie
RectangleComp(const RectangleComp&)et le destructeur~RectangleComp()qui utilisent l’allocation dynamique pour faire une copie en profondeur des éléments associés au rectangle original (ses points). Enfin, exécuter à nouveau le programme et vérifier que le problème soit résolu (les rectangles peuvent être translatés indépendamment).