INF1010 – Contrôle périodique
Automne 2006
Solutionnaire
1. Questions théoriques (3 pts)
1. (0.5 pt) Que signifie le mot const dans la ligne suivante :
void Polygone::afficher(ostream &out) const;
Le const signifie que la méthode ne peut modifier aucun attribut de l’objet de type Polygone à
partir duquel elle a été appelée. Par conséquence, elle n’a pas le droit non plus d’appeler des
méthodes à partir de l’objet courant qui ne sont pas elles-mêmes constantes.
2. (0.5 pt) Quelle est l’utilité des membres protégés (protected) dans une classe ?
Les membres protégés restent inaccessibles aux classes et fonctions qui ne sont pas amies, sauf
pour les classes dérivées. Ils permettent donc d’ouvrir une interface uniquement pour les
classes dérivées.
3. (0.5 pt) Donnez une application du pointeur this.
− Appels en cascade
− Vérification d’une affectation d’un objet vers lui-même
− Accès à un attribut lorsqu’un paramètre ou une variable possède le même nom
4. (0.5 pt) Donnez un avantage et un inconvénient de l’utilisation d’une liste vis-à-vis d’un vecteur.
Avantage : L'ajout et retrait ont un cout fixe (ne dépend pas du nombre d’éléments)
Inconvénient : Accès aléatoire impossible ; on doit parcourir séquentiellement les éléments.
5. (0.5 pt) Qu’est-ce qu’un argument générique non type ?
Un argument dont la valeur est générique, mais le type est fixe. Exemple :
template <int ROWS>
6. (0.5 pt) À quoi sert le passage par référence constante ?
Sert à éviter une copie couteuse lors d’un passage, tout en s’assurant que l’objet ne sera pas
modifié (simule donc un passage par valeur en évitant la copie couteuse qui peut être inutile)
Page 1 de 10
2. Composition d'objets (3 pts)
Donnez l'affichage que produit le code suivant:
class A
{
public:
A();
~A();
A( int a );
void afficher() const;
private:
int att;
};
A::A()
{
cout << "A::A()" << endl;
att = 0;
}
A::A( int a )
{
cout << "A::A(int " << a << ")"
<< endl;
att = a;
}
A::~A()
{
cout << "A::~A()" << endl;
}
void A::afficher() const
{
cout << "A::afficher(" << att << ")"
<< endl;
}
class B
{
public:
B();
B(int a);
~B();
A get_att() const;
void afficher() const;
private:
A att;
};
debut
A::A()
B::B(int 2)
A::A(int 2)
A::~A()
ligne 1
B::afficher()
A::A()
A::afficher(0)
A::~A()
ligne 2
A::afficher(2)
B::B()
{
cout << "B::B()" << endl;
}
B::~B()
{
cout << "B::~B()" << endl;
}
B::B( int a )
{
cout << "B::B(int " << a << ")"
<< endl;
att = A(a);
}
A B::get_att() const
{
return att;
}
void B::afficher() const
{
cout << "B::afficher()" << endl;
A a;
a.afficher();
}
int main()
{
cout << "debut" << endl;
B b(2);
cout << "ligne 1" << endl;
b.afficher();
cout << "ligne 2" << endl;
b.get_att().afficher();
cout << "fin" << endl;
}
Page 2 de 10
A::~A()
fin
B::~B()
A::~A()
3. Implémentation de classes et vecteur (4 pts)
a.
(1 pt) Soit la classe Joueur possédant les attributs nom et numéro (le numéro du joueur dans l'équipe),
Publicité
ainsi que les méthodes permettant de lire ces attributs et un constructeur qui crée un objet Joueur à partir
de son nom et de son numéro. Donnez la définition et l'implémentation de cette classe.
class Joueur
{
public:
Joueur(const string & n, int num);
string get_nom() const;
int get_numero() const;
bool operator<(const Joueur & j);
private:
string nom;
int numero;
};
Joueur::Joueur(const string & n, int num)
: nom(n), numero(num)
{
}
string Joueur::get_nom() const
{
return nom;
}
int Joueur::get_numero() const
{
return numero;
}
b. (1 pt) Implémentez une méthode de la classe Joueur qui surcharge l'opérateur < (plus petit que) et qui
détermine si un joueur est plus petit qu'un autre joueur en fonction de leur numéro.
bool Joueur::operator<(const Joueur & j)
{
return numero < j.numero;
}
c.
(2 pts) Implémentez l’interface de la classe Equipe ci-dessous (l’ordre des items dans le vecteur de
joueurs est sans importance).
class Equipe
{
public:
Equipe( const string& nom_, Joueur& capitaine_ );
void ajouter_joueur( const Joueur& joueur );
void retirer_joueur( const string& nom );
void afficher() const;
Page 3 de 10
private:
string nom;
Joueur& capitaine;
vector< Joueur > joueurs;
};
Soit le programme suivant :
int main()
{
Joueur david("david", 1);
Joueur mohammed("mohammed", 8);
Joueur michel("michel", 12);
Equipe bleus("les Bleus", david);
bleus.ajouter_joueur(david);
bleus.ajouter_joueur(mohammed);
bleus.ajouter_joueur(michel);
bleus.retirer_joueur("david");
bleus.afficher();
}
L’exécution de ce programme doit afficher les lignes suivantes :
Equipe les Bleus
capitaine: david
michel 12
mohammed 8
class Equipe
{
public:
Equipe(const string & n, Joueur & cap);
void ajouter_joueur(const Joueur & j);
void retirer_joueur(const string & nom);
void afficher() const;
private:
string nom;
Joueur & capitaine;
vector< Joueur > joueurs;
};
Equipe::Equipe(const string & n, Joueur & cap)
: nom(n), capitaine(cap)
{
}
void Equipe::ajouter_joueur(const Joueur & j)
{
joueurs.push_back(j);
}
void Equipe::retirer_joueur(const string & nom)
{
for (size_t i = 0; i < joueurs.size(); ++i) {
if (joueurs[i].get_nom() == nom) {
joueurs[i] = joueurs[taille-1];
joueurs.pop_back();
return;
}
}
}
Page 4 de 10
void Equipe::afficher() const
{
cout << "Equipe " << nom << endl;
cout << "capitaine: " << capitaine.get_nom() << endl;
size_t taille = joueurs.size();
for (size_t i = 0; i < taille; ++i) {
cout << joueurs[i].get_nom() << " "
<< joueurs[i].get_numero() << endl;
}
}
Page 5 de 10
4. Allocation dynamique (3 pts)
Soit les classes Individu et Famille suivantes :
class Individu
{
public:
Individu( const string& prenom_,
int age_,
Individu* pere_,
Individu* mere_ );
string get_prenom() const;
Individu* get_mere() const;
Individu* get_pere() const;
void afficher() const;
private:
int age;
string prenom;
Individu* pere;
Publicité
Individu* mere;
};
Individu::Individu( const string& nom_,
int age_,
Individu* pere_,
Individu* mere_ )
: prenom(nom_), age(age_),
pere(pere_), mere(mere_)
{
}
string Individu::get_prenom() const
{
return prenom;
}
Individu* Individu::get_mere() const
{
return mere;
}
Individu* Individu::get_pere() const
{
return pere;
}
void Individu::afficher() const
{
cout << prenom << " - " << age
<< " ans";
}
class Famille
{
public:
Famille( const string& nom );
string get_nom_famille() const;
void ajouter_membre( Individu* membre );
void afficher() const;
private:
string nom_famille;
vector< Individu* > membres;
};
Famille::Famille(const string& nom)
: nom_famille(nom)
{
}
string Famille::get_nom_famille() const
{
return nom_famille;
}
void Famille::ajouter_membre( Individu *
membre )
{
membres.push_back(membre);
}
void Famille::afficher() const
{
Individu* ind;
cout << "La famille " << nom_famille
<< endl;
size_t taille = membres.size();
for (size_t i = 0; i < taille; ++i) {
membres[i]->afficher();
cout << endl;
ind = membres[i]->get_pere();
if (ind != NULL) {
cout << " Pere: ";
ind->afficher();
cout << endl;
}
ind = membres[i]->get_mere();
if (ind != NULL) {
cout << " Mere: ";
ind->afficher();
cout << endl;
}
}
}
Page 6 de 10
a.
(2 pts) Vous devez implémenter la fonction principale (main) qui crée une famille de cinq individus et
qui produit l’affichage suivant. Tout objet créé doit obligatoirement être alloué dynamiquement. Vous
devez utiliser le code fourni pour l’affichage.
La famille Girard
Emile - 54 ans
Berthe - 50 ans
Eric - 33 ans
Pere: Emile - 54 ans
Mere: Berthe - 50 ans
Anne - 34 ans
Pere: Emile - 54 ans
Mere: Berthe - 50 ans
Maxime - 12 ans
Pere: Eric - 33 ans
int main()
{
// à compléter...
}
int main()
{
Individu* emile = new Individu("Emile", 54, 0, 0);
Individu* berthe = new Individu("Berthe", 50, 0, 0);
Individu* eric = new Individu("Eric", 33, emile, berthe);
Individu* anne = new Individu("Anne", 34, emile, berthe);
Individu* max = new Individu("Maxime", 12, eric, 0);
Famille* girard = new Famille("Girard");
girard->ajouter_membre(emile);
girard->ajouter_membre(berthe);
girard->ajouter_membre(eric);
girard->ajouter_membre(anne);
girard->ajouter_membre(max);
girard->afficher();
delete emile;
delete berthe;
delete eric;
delete anne;
delete max;
delete girard;
}
b. (1 pt) Expliquez pourquoi ce n'est pas la classe Famille qui est responsable de créer les individus (lors
de l'ajout d'un membre) et de les détruire.
Les individus sont créés dans le main, et doivent donc être détruits dans le main. Ce n’est donc
pas une relation de composition (la famille ne possède pas d’individus) mais plutôt une relation
d’agrégation (la famille connait un individu). Avec cette relation, on peut donc avoir qu’une
personne appartient à plusieurs familles.
5. Héritage (5 pts)
Soit la hiérarchie de classes suivante :
Page 7 de 10
Publicité
class Processeur
{
public:
Processeur( unsigned int vitesse_ );
unsigned int get_vitesse() const;
private:
unsigned int vitesse;
};
class Ordinateur : public Appareil
{
public:
Ordinateur( string nom,
string date,
unsigned int vitesse );
private:
Processeur processeur;
};
class Ecran
{
public:
Ecran( unsigned int taille_ );
unsigned int get_taille() const;
private:
unsigned int taille;
};
class Appareil
{
public:
Appareil( string nom_,
string date_ );
private:
string nom;
string date;
};
class Portable : public Ordinateur
{
public:
Portable( string nom,
string date,
unsigned int vitesse,
unsigned int taille_ecran );
private:
Ecran ecran;
};
Page 8 de 10
1. (1 pt) Dessinez le diagramme de classes correspondant.
Appareil
Processeur
Ordinateur
Ecran
Portable
2. (2 pts) Implémentez tous les constructeurs des classes. Assurez-vous que, lors de la construction
d’un objet, tous les attributs soient initialisés, incluant ceux des objets qui composent l’objet créé.
Processeur :: Processeur ( unsigned int vitesse_ ) : vitesse(vitesse_)
{
}
Ecran :: Ecran ( unsigned int taille_ ) : taille(taille_)
{
}
Appareil :: Appareil ( string nom_, string date_ ) : nom(nom_),
date(date_)
{
}
Ordinateur :: Ordinateur ( string nom, string date, unsigned int vitesse_
)
: Appareil(nom, date), vitesse(vitesse_)
{
}
Portable :: Portable ( string nom, string date, unsigned int vitesse,
unsigned int taille_ecran ) : Ordinateur(nom, date, vitesse),
ecran(taille_ecran)
{
}
3. (1 pt) Quels sont les constructeurs invoqués (dans l’ordre) lors de l’exécution de l’instruction
suivante :
Portable portable("A inc.", "05/09/06", 3100, 17);
Appareil( string nom_, string date_ );
Processeur( unsigned int vitesse_ );
Ordinateur( string nom, string date, unsigned int vitesse );
Ecran( unsigned int taille_ );
Portable( string nom, string date, unsigned int vitesse, unsigned int
taille_ecran );
4. Soit le programme principal suivant :
int main()
{
1
2
3
4
}
Portable lesPortables[10];
Ordinateur* ptr = new Ordinateur("ABC", "10/10/06", 2333);
cout << ptr->processeur.getVitesse() << endl;
delete ptr;
(1 pt) Précisez quelle(s) instruction(s) est(sont) invalide(s) et pourquoi.
Page 9 de 10
− L’instruction 1 est invalide : le constructeur par défaut n‘existe pas
− L’instruction 3 est invalide : On essaye d’accéder à un attribut privé (on a également
accepté que la fonction est non existante, puisque le nom n’était pas identique)
6. Programmation générique et conteneurs (2 pts)
1. (1 pt) Écrivez la fonction afficherVector qui prend un vecteur STL générique et qui affiche tous les
éléments de ce vecteur sur l’écran. Pour l’affichage, utilisez l’opérateur standard de sortie.
template <typename T>
void afficherVector(const vector<T>& vecteur)
{
for (size_t i=0 ;i<vecteur.size() ;i++) {
cout << vecteur[i] << endl;
}
}
2. (1 pt) Peut-on passer à cette fonction un vecteur de n’importe quel type? Justifiez.
Il faut que le type contenu dans le vecteur soit « imprimable », c'est-à-dire que s’il s’agit
d’une classe, la surcharge de l’opérateur << doit exister.
7. Question bonus (0.5 pt)
Que fait la fonction mystère suivante ?
void Fonction_Mystere(int& a, int& b)
{
a = a + b;
b = a – b;
a = a – b;
}
Elle échange les valeurs de deux entiers, sans passer par un 3ème entier temporaire
Page 10 de 10