Corrigé
Contrôle périodique- Corrigé
Ce corrigé détaillé de contrôle périodique en C++ couvre les concepts clés de la POO : encapsulation, gestion de la mémoire dynamique, templates, surcharges d'opérateurs et ordres d'exécution des constructeurs.
D'après le document Contrôle périodique- Corrigé
Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Document source
Programming, Math, etc. · PDF · 10 pages · 2007
Afficher l'aperçu du document
Ce contrôle périodique porte sur la programmation orientée objet en C++, avec un accent sur les concepts fondamentaux tels que l'encapsulation, l'héritage, la surcharge d'opérateurs, la programmation générique, ainsi que la gestion dynamique de la mémoire. Il évalue les compétences en conception de classes, implémentation de méthodes, et compréhension des mécanismes du langage C++.
Exercice 1 : Classes (5 points)
Cet exercice teste la compréhension théorique des concepts fondamentaux de la programmation orientée objet en C++.
- Définir le concept d'encapsulation et donner une motivation.
L'encapsulation consiste à rendre tous les attributs d'un objet privés, donc invisibles en dehors de la classe. Toute interaction avec ces attributs doit se faire via l'interface publique de la classe. La motivation principale est de garder le contrôle sur l'accès aux attributs, ce qui protège l'intégrité des données. - Pourquoi, pour une classe générique, il n'est pas suffisant d'inclure uniquement sa définition ?
Le compilateur doit vérifier que toutes les opérations effectuées sur le type générique sont valides. Cette vérification ne peut être faite que lorsque le type concret qui remplace le type générique est connu. - Donner une utilisation possible du pointeur this.
Le pointeurthisest utile pour implémenter des fonctions pouvant être appelées en cascade, c'est-à-dire permettant d'enchaîner plusieurs appels sur le même objet. - À quoi sert un membre protégé ?
Un membre protégé dans une classe de base permet aux classes dérivées d'y accéder, tout en interdisant l'accès aux autres classes ou fonctions extérieures. - Pour une méthode dans une classe de base, quelles sont les trois possibilités d'implémentation dans une classe dérivée ?
On peut :- Donner une nouvelle implémentation (redéfinir la méthode).
- Étendre l'ancienne implémentation en appelant la méthode de la classe de base.
- Ne pas redéfinir la méthode (hériter telle quelle).
- Donner la signature de la surcharge de l'opérateur d'affectation de la classe Fraction en tant que fonction membre.
Fraction& Fraction::operator=(const Fraction& droite); - Donner la signature de la surcharge de l'opérateur += de la classe Fraction en tant que fonction globale.
Fraction& operator+=(Fraction& gauche, const Fraction& droite); - Énumérer les trois relations possibles entre des classes avec une brève description.
- Héritage : création d'une classe qui étend une autre classe.
- Composition : utilisation d'un objet comme attribut d'un autre objet.
- Agrégation : utilisation d'un objet comme faisant partie d'un autre objet, mais avec un lien plus faible que la composition.
Exercice 2 : Méthode de classe (2 points)
L'implémentation d'un appel en cascade nécessite de retourner une référence vers l'objet courant.
Point& Point::incrementer()
{
x_++;
y_++;
return *this;
}
Cette méthode incrémente les attributs x_ et y_ puis retourne une référence à l'objet courant (*this) pour permettre l'appel en cascade.
Réponse finale : la méthode incrementer est correctement implémentée comme ci-dessus.
Exercice 3 : Programmation générique et passage de paramètres (3 points)
La déclaration d'une classe template nécessite de spécifier les paramètres de type pour chaque membre et attribut.
template <typename T1, typename T2, typename T3>
class Triplet
{
public:
Triplet(const T1& premier, const T2& deuxieme, const T3& troisieme);
T1 getPremier() const;
T2 getDeuxieme() const;
T3 getTroisieme() const;
void setPremier(const T1& premier);
void setDeuxieme(const T2& deuxieme);
void setTroisieme(const T3& troisieme);
private:
T1 premier_;
T2 deuxieme_;
T3 troisieme_;
};
Les méthodes permettent d'accéder et de modifier les trois valeurs stockées. Les types sont génériques et peuvent être différents.
Réponse finale : la classe est complétée comme ci-dessus.
Exercice 4 : Concepts de base (7 points)
La gestion manuelle de la mémoire dynamique au sein d'une classe nécessite la définition explicite de la forme de Coplien.
- Pourquoi implémenter le constructeur de copie et surcharger l'opérateur d'affectation ?
Sans ces implémentations, la copie d'un objetBasecopierait les pointeurs vers les étudiants, ce qui ferait que deux objetsBasepartageraient les mêmes pointeurs vers les mêmes étudiants. Cela conduit à des problèmes de gestion mémoire et de double suppression. - Pourquoi implémenter le destructeur ?
Sans destructeur, la mémoire allouée dynamiquement pour les objetsEtudiantne serait pas libérée, causant une fuite mémoire. - Implémenter le destructeur.
Base::~Base() { for (size_t i = 0; i < etudiants_.size(); i++) { delete etudiants_[i]; } } - Implémenter la surcharge de l'opérateur d'affectation.
Base& Base::operator=(const Base& base) { if (this != &base) { // Libérer la mémoire existante for (size_t i = 0; i < etudiants_.size(); i++) { delete etudiants_[i]; } etudiants_.clear(); // Copier les étudiants de base for (size_t i = 0; i < base.etudiants_.size(); i++) { int matricule = base.etudiants_[i]->getMatricule(); string nom = base.etudiants_[i]->getNom(); Etudiant* e = new Etudiant(matricule, nom); etudiants_.push_back(e); } } return *this; } - Implémenter la méthode ajouterEtudiant qui ajoute un étudiant si le matricule n'existe pas.
bool Base::ajouterEtudiant(int matricule, string nom) { for (size_t i = 0; i < etudiants_.size(); i++) { if (etudiants_[i]->getMatricule() == matricule) { return false; } } etudiants_.push_back(new Etudiant(matricule, nom)); return true; } - Implémenter la méthode getNomEtudiant qui retourne le nom d'un étudiant à partir de son matricule, ou une chaîne vide si non trouvé.
string Base::getNomEtudiant(int matricule) const { for (size_t i = 0; i < etudiants_.size(); i++) { if (etudiants_[i]->getMatricule() == matricule) { return etudiants_[i]->getNom(); } } return ""; }
Exercice 5 : Constructeurs (3 points)
L'ordre d'exécution des constructeurs dépend de la hiérarchie d'héritage et de l'ordre d'initialisation des membres de classe.
- Diagramme de classes
La classeBhérite deA. La classeChérite deBet possède un attributattrC_de typeB(composition). - Quel sera l'affichage à l'exécution du programme ?
L'exécution deC ObjetC(10,20);produit l'affichage suivant :
A(10) B(20) A() B() C(10,20)
Exercice 6 : Bonus (+1 point)
La suppression efficace dans un tableau dynamique non ordonné s'effectue en remplaçant l'élément à supprimer par le dernier élément.
bool Base::retirerEtudiant(int matricule)
{
for (size_t i = 0; i < etudiants_.size(); i++) {
if (etudiants_[i]->getMatricule() == matricule) {
delete etudiants_[i];
etudiants_[i] = etudiants_[etudiants_.size() - 1];
etudiants_.pop_back();
return true;
}
}
return false;
}
Cette méthode supprime l'étudiant trouvé en libérant sa mémoire, remplace sa position dans le vecteur par le dernier élément, puis réduit la taille du vecteur.
Méthode : techniques récompensées et erreurs pénalisées
Ce contrôle valorise la maîtrise des concepts fondamentaux de la programmation orientée objet en C++ :
- Encapsulation : respecter la confidentialité des données en utilisant des membres privés et des interfaces publiques.
- Gestion de la mémoire dynamique : implémenter correctement le destructeur, le constructeur de copie et l'opérateur d'affectation pour éviter les fuites mémoire et les doubles suppressions.
- Programmation générique : définir des classes templates avec des méthodes adaptées à des types génériques.
- Surcharge d'opérateurs : respecter les signatures correctes pour les opérateurs membres et globaux.
- Appels en cascade : retourner une référence à l'objet courant pour permettre l'enchaînement des appels.
- Compréhension des relations entre classes : distinguer héritage, composition et agrégation.
- Rigueur dans l'implémentation : vérifier les conditions d'existence avant d'ajouter ou de retirer des éléments, et gérer correctement les pointeurs.
Les erreurs les plus pénalisées sont :
- Ne pas gérer la mémoire dynamique, causant fuites ou corruptions.
- Oublier de vérifier les doublons lors de l'ajout d'éléments.
- Confondre les relations entre classes ou mal implémenter les constructeurs.
- Ne pas respecter les signatures des méthodes et opérateurs.
- Ne pas retourner la bonne valeur pour permettre les appels en cascade.