Exercice 1 : Composition d'objets graphiques
Implémentons une architecture de widgets élémentaires illustrant la relation de composition entre classes.
Fichier bouton.hpp
#pragma once
#include <iostream>
#include <string>
using std::string;
using std::cout;
class Bouton {
public:
explicit Bouton(const string& libelle);
string lire_texte() const;
void activer();
private:
string texte;
};
Bouton::Bouton(const string& libelle) : texte{libelle} {}
inline string Bouton::lire_texte() const {
return texte;
}
void Bouton::activer() {
cout << "Activation du bouton '" << texte << "'\n";
}
Fichier fenetre.hpp
#pragma once
#include "bouton.hpp"
#include <vector>
#include <iostream>
using std::vector;
using std::cout;
using std::endl;
class Fenetre {
public:
explicit Fenetre(const string& titre_fenetre);
void afficher() const;
void fermer();
void ajouter_bouton(const string& libelle);
private:
string titre;
vector<Bouton> controles;
};
Fenetre::Fenetre(const string& titre_fenetre) : titre{titre_fenetre} {
controles.emplace_back("fermer");
}
void Fenetre::afficher() const {
string bordure(40, '=');
cout << bordure << endl;
cout << "Titre : " << titre << endl;
cout << "Nombre de boutons : " << controles.size() << endl;
for (const auto& c : controles)
cout << " - " << c.lire_texte() << endl;
cout << bordure << endl;
}
void Fenetre::fermer() {
cout << "Fermeture de '" << titre << "'" << endl;
controles.at(0).activer();
}
void Fenetre::ajouter_bouton(const string& libelle) {
controles.emplace_back(libelle);
}
Fichier exercice1.cpp
#include "fenetre.hpp"
using std::cout;
void demonstration() {
Fenetre f1("Interface principale");
f1.ajouter_bouton("agrandir");
f1.afficher();
f1.fermer();
}
int main() {
cout << "Simulation d'une interface graphique simple :\n";
demonstration();
}
Analyse des méthodes const et inline
Les acccesseurs comme lire_texte() et afficher() se prêtent bien à la qualification const car ils ne modifinet pas l'état interne. Les méthodes simples (un ou deux instructions) profitent du mot-clé inline pour éviter le surcoût d'un appel de fonction. À l'inverse, fermer() et activer() ne peuvent être const car ils modifient potentiellement l'état ou déclenchent des effets de bord.
Exercice 2 : Sémantique de copie des conteneurs
Examinons le comportement des copies de std::vector.
#include <iostream>
#include <vector>
using namespace std;
void imprimer_ligne(const vector<int>& v) {
for (const auto& val : v)
cout << val << " | ";
cout << "\n";
}
void imprimer_tableau(const vector<vector<int>>& t) {
for (const auto& ligne : t) {
for (const auto& val : ligne)
cout << val << " | ";
cout << "\n";
}
}
void test_independance() {
vector<int> a(5, 42);
const vector<int> b(a);
a.at(0) = -999;
cout << "a : "; imprimer_ligne(a);
cout << "b : "; imprimer_ligne(b);
}
void test_imbrication() {
vector<vector<int>> grille{{1, 2, 3}, {4, 5, 6}};
const vector<vector<int>> copie(grille);
grille.at(0).push_back(-999);
cout << "grille :\n"; imprimer_tableau(grille);
cout << "copie :\n"; imprimer_tableau(copie);
}
La copie d'un vector réalice une duplication profonde : chaque élément est recopié valeur par valeur. Les objets source et copie deviennent totalement indépendants. L'accès via at() sur un objet const requiert la version surchargée const de cette méthode.
Exercice 3 : Tableau dynamique personnalisé
Fichier tableau.hpp
#pragma once
#include <iostream>
#include <stdexcept>
using std::cout;
using std::endl;
class TableauInt {
public:
explicit TableauInt(int capacite);
TableauInt(int capacite, int valeur_par_defaut);
TableauInt(const TableauInt& autre);
~TableauInt();
int& acces(int indice);
const int& acces(int indice) const;
TableauInt& copier_depuis(const TableauInt& source);
int taille() const;
private:
int nombre;
int* donnees;
};
TableauInt::TableauInt(int capacite) : nombre{capacite}, donnees{new int[capacite]{}} {}
TableauInt::TableauInt(int capacite, int valeur_par_defaut)
: nombre{capacite}, donnees{new int[capacite]} {
for (int i = 0; i < nombre; ++i)
donnees[i] = valeur_par_defaut;
}
TableauInt::TableauInt(const TableauInt& autre)
: nombre{autre.nombre}, donnees{new int[autre.nombre]} {
for (int i = 0; i < nombre; ++i)
donnees[i] = autre.donnees[i];
}
TableauInt::~TableauInt() {
delete[] donnees;
}
const int& TableauInt::acces(int indice) const {
if (indice < 0 || indice >= nombre)
throw std::out_of_range("Indice invalide");
return donnees[indice];
}
int& TableauInt::acces(int indice) {
if (indice < 0 || indice >= nombre)
throw std::out_of_range("Indice invalide");
return donnees[indice];
}
TableauInt& TableauInt::copier_depuis(const TableauInt& source) {
delete[] donnees;
nombre = source.nombre;
donnees = new int[nombre];
for (int i = 0; i < nombre; ++i)
donnees[i] = source.donnees[i];
return *this;
}
int TableauInt::taille() const {
return nombre;
}
Fichier exercice3.cpp
#include "tableau.hpp"
void afficher(const TableauInt& t) {
for (int i = 0; i < t.taille(); ++i)
cout << t.acces(i) << " ";
cout << "\n";
}
int main() {
TableauInt original(5);
for (int i = 0; i < original.taille(); ++i)
original.acces(i) = i * i + 1;
TableauInt clone(original);
original.acces(0) = -999;
afficher(original);
afficher(clone);
const TableauInt constant(3, 7);
TableauInt destination(10, 0);
destination.copier_depuis(constant);
afficher(destination);
}
Le constructeur de copie effectue une allocation nouvelle et copie élément par élément. La méthode acces retourne une référence pour permettre l'affectation. La surcharge const garantit l'utilisation sur des objets immuables.
Exercice 4 : Matrice numérique
Fichier matrice.hpp
#pragma once
#include <iostream>
#include <stdexcept>
using std::cout;
using std::endl;
class Matrice {
public:
Matrice(int lignes, int colonnes);
explicit Matrice(int ordre);
Matrice(const Matrice& autre);
~Matrice();
void remplir(const double* valeurs);
void zero();
double& element(int i, int j);
const double& element(int i, int j) const;
int nb_lignes() const;
int nb_colonnes() const;
void imprimer() const;
private:
int hauteur;
int largeur;
double* valeurs;
};
Matrice::Matrice(int lignes, int colonnes)
: hauteur{lignes}, largeur{colonnes}, valeurs{new double[lignes * colonnes]()} {}
Matrice::Matrice(int ordre) : Matrice(ordre, ordre) {}
Matrice::Matrice(const Matrice& autre)
: hauteur{autre.hauteur}, largeur{autre.largeur}, valeurs{new double[autre.hauteur * autre.largeur]} {
for (int i = 0; i < hauteur * largeur; ++i)
valeurs[i] = autre.valeurs[i];
}
Matrice::~Matrice() {
delete[] valeurs;
}
void Matrice::remplir(const double* source) {
for (int i = 0; i < hauteur * largeur; ++i)
valeurs[i] = source[i];
}
void Matrice::zero() {
for (int i = 0; i < hauteur * largeur; ++i)
valeurs[i] = 0.0;
}
double& Matrice::element(int i, int j) {
if (i < 0 || i >= hauteur || j < 0 || j >= largeur)
throw std::out_of_range("Coordonnées invalides");
return valeurs[i * largeur + j];
}
const double& Matrice::element(int i, int j) const {
if (i < 0 || i >= hauteur || j < 0 || j >= largeur)
throw std::out_of_range("Coordonnées invalides");
return valeurs[i * largeur + j];
}
int Matrice::nb_lignes() const { return hauteur; }
int Matrice::nb_colonnes() const { return largeur; }
void Matrice::imprimer() const {
for (int i = 0; i < hauteur; ++i) {
for (int j = 0; j < largeur; ++j)
cout << element(i, j) << "\t";
cout << endl;
}
}
Exercice 5 : Gestion d'utilisateurs
Fichier utilisateur.hpp
#ifndef UTILISATEUR_HPP
#define UTILISATEUR_HPP
#include <iostream>
#include <string>
#include <limits>
class Utilisateur {
private:
std::string identifiant;
std::string mot_de_passe;
std::string courriel;
public:
Utilisateur(const std::string& id,
const std::string& mdp = "123456",
const std::string& mail = "")
: identifiant(id), mot_de_passe(mdp), courriel(mail) {}
void definir_email() {
std::string saisie;
while (true) {
std::cout << "Adresse email : ";
std::cin >> saisie;
if (saisie.find('@') == std::string::npos) {
std::cout << "Format invalide. Réessayez.\n";
} else {
courriel = saisie;
std::cout << "Email enregistré.\n";
break;
}
}
}
void modifier_mot_de_passe() {
std::string ancien, nouveau;
int tentatives = 0;
const int max_tentatives = 3;
while (tentatives < max_tentatives) {
std::cout << "Mot de passe actuel : ";
std::cin >> ancien;
if (ancien == mot_de_passe) {
std::cout << "Nouveau mot de passe : ";
std::cin >> nouveau;
mot_de_passe = nouveau;
std::cout << "Mot de passe modifié.\n";
return;
}
tentatives++;
if (tentatives == max_tentatives)
std::cout << "Trop de tentatives. Accès bloqué.\n";
else
std::cout << "Incorrect. Il reste " << max_tentatives - tentatives << " essai(s).\n";
}
}
void presenter() const {
std::cout << "Identifiant : " << identifiant << "\n";
std::cout << "Mot de passe : " << std::string(mot_de_passe.length(), '*') << "\n";
std::cout << "Email : " << courriel << "\n";
}
};
#endif
Exercice 6 : Système bancaire avec dates
Fichier calendrier.h
#ifndef CALENDRIER_H
#define CALENDRIER_H
class Calendrier {
private:
int annee;
int mois;
int jour;
int jours_ecoules;
static const int JOURS_AVANT_MOIS[];
int max_jour() const;
public:
Calendrier(int a, int m, int j);
int lire_annee() const { return annee; }
int lire_mois() const { return mois; }
int lire_jour() const { return jour; }
bool est_bissextile() const {
return (annee % 4 == 0 && annee % 100 != 0) || (annee % 400 == 0);
}
void afficher() const;
int ecart(const Calendrier& autre) const {
return jours_ecoules - autre.jours_ecoules;
}
};
#endif
Fichier calendrier.cpp
#include "calendrier.h"
#include <iostream>
#include <cstdlib>
namespace {
const int Calendrier::JOURS_AVANT_MOIS[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
}
Calendrier::Calendrier(int a, int m, int j) : annee(a), mois(m), jour(j) {
if (jour <= 0 || jour > max_jour()) {
std::cout << "Date invalide : ";
afficher();
std::cout << "\n";
std::exit(1);
}
int annees_passees = annee - 1;
jours_ecoules = annees_passees * 365 + annees_passees / 4 - annees_passees / 100 + annees_passees / 400
+ JOURS_AVANT_MOIS[mois - 1] + jour;
if (est_bissextile() && mois > 2) jours_ecoules++;
}
int Calendrier::max_jour() const {
if (est_bissextile() && mois == 2) return 29;
static const int durees[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return durees[mois];
}
void Calendrier::afficher() const {
std::cout << annee << '-' << mois << '-' << jour;
}
Fichier compte.h
#ifndef COMPTE_H
#define COMPTE_H
#include "calendrier.h"
#include <string>
#include <cmath>
class CompteEpargne {
private:
std::string numero;
double solde;
double taux_annuel;
Calendrier derniere_operation;
double cumul_interets;
static double total_banque;
void enregistrer(const Calendrier& date, double montant, const std::string& motif);
double calculer_cumul(const Calendrier& date) const {
return cumul_interets + solde * date.ecart(derniere_operation);
}
public:
CompteEpargne(const Calendrier& date, const std::string& num, double taux);
const std::string& lire_numero() const { return numero; }
double lire_solde() const { return solde; }
double lire_taux() const { return taux_annuel; }
static double lire_total() { return total_banque; }
void deposer(const Calendrier& date, double montant, const std::string& motif);
void retirer(const Calendrier& date, double montant, const std::string& motif);
void capitaliser(const Calendrier& date);
void montrer() const;
};
#endif
Fichier compte.cpp
#include "compte.h"
double CompteEpargne::total_banque = 0.0;
CompteEpargne::CompteEpargne(const Calendrier& date, const std::string& num, double taux)
: numero(num), solde(0.0), taux_annuel(taux), derniere_operation(date), cumul_interets(0.0) {
date.afficher();
std::cout << "\t#" << num << " ouvert\n";
}
void CompteEpargne::enregistrer(const Calendrier& date, double montant, const std::string& motif) {
cumul_interets = calculer_cumul(date);
derniere_operation = date;
montant = std::floor(montant * 100 + 0.5) / 100.0;
solde += montant;
total_banque += montant;
date.afficher();
std::cout << "\t#" << numero << "\t" << montant << "\t" << solde << "\t" << motif << "\n";
}
void CompteEpargne::deposer(const Calendrier& date, double montant, const std::string& motif) {
enregistrer(date, montant, motif);
}
void CompteEpargne::retirer(const Calendrier& date, double montant, const std::string& motif) {
if (montant > solde)
std::cout << "Erreur (#" << numero << ") : fonds insuffisants\n";
else
enregistrer(date, -montant, motif);
}
void CompteEpargne::capitaliser(const Calendrier& date) {
double interets = calculer_cumul(date) * taux_annuel / date.ecart(Calendrier(date.lire_annee() - 1, 1, 1));
if (interets != 0.0)
enregistrer(date, interets, "intérêts annuels");
cumul_interets = 0.0;
}
void CompteEpargne::montrer() const {
std::cout << numero << "\tSolde : " << solde;
}
Fichier banque.cpp
#include "compte.h"
#include <iostream>
int main() {
Calendrier debut(2008, 11, 1);
CompteEpargne portefeuille[] = {
CompteEpargne(debut, "03755217", 0.015),
CompteEpargne(debut, "02342342", 0.015)
};
const int nb_comptes = sizeof(portefeuille) / sizeof(CompteEpargne);
portefeuille[0].deposer(Calendrier(2008, 11, 5), 5000, "salaire");
portefeuille[1].deposer(Calendrier(2008, 11, 25), 10000, "vente actions");
portefeuille[0].deposer(Calendrier(2008, 12, 5), 5500, "salaire");
portefeuille[1].retirer(Calendrier(2008, 12, 20), 4000, "achat ordinateur");
std::cout << "\n";
for (int i = 0; i < nb_comptes; i++) {
portefeuille[i].capitaliser(Calendrier(2009, 1, 1));
portefeuille[i].montrer();
std::cout << "\n";
}
std::cout << "Total banque : " << CompteEpargne::lire_total() << "\n";
}