Vous êtes sur la page 1sur 5

Master 1 Informatique

Programmation objet avancée en C++


Durée : 2 heures – Documents autorisés : 2 feuilles manuscrites recto-verso.

1 Éléments de langage
Exercice 1 (2 points)
Soit le programme C++ suivant :
1 #include<iostream>
2
3 class Test {
4 public:
5 Test();
6 };
7 Test::Test() {
8 std::cout<<"Constructor Called \n";
9 }
10
11 int main() {
12 std::cout<<"Start \n";
13 Test t1();
14 std::cout<<"End \n";
15 return 0;
16 }

Indiquer ce qu’affiche ce programme en expliquant précisément les raisons de cet affichage.


— Affichage du programme :
Start
End
— Explications
La ligne Test t1() n’est pas la construction d’un objet mais la définition d’une fonction t1, ne
prenant pas de paramètre et renvoyant un objet de type Test.

Exercice 2 (2 points)
Soit le programme C++ suivant :
1 #include<iostream>
2
3 class Test {
4 int &t;
5 public:
6 Test (int &x):t(x) { }
7 int getT() { return t; }
8 };
9
10 int main() {
11 int x = 20;

C++ - 12 décembre 2016 1/5


Master 1 Informatique

12 Test t1(x);
13 std::cout << t1.getT() << " ";
14 x = 30;
15 std::cout << t1.getT() << endl;
16 return 0;
17 }

Indiquer ce qu’affiche ce programme en expliquant précisément les raisons de cet affichage.


— Affichage du programme :
20 30
— Explications
L’objet t1 est construit à partir d’une référence, ici une référence à la variable x. L’attribut t étant
aussi une référence, l’objet construit stocke une référence à la variable passée à son constructeur.
La fonction getT() renvoie donc le contenu de la variable x lors de son appel et non pas lors de
la construction de l’objet.

Exercice 3 (2 points)
Soit le programme C++ suivant :
1 #include<iostream>
2
3 class Point {
4 int x;
5 int y;
6 public:
7 Point(int i = 0, int j = 0); // Normal Constructor
8 Point(const Point &t); // Copy Constructor
9 };
10 Point::Point(int i, int j) : x(i), y(j) {
11 std::cout << "Normal Constructor called\n";
12 }
13 Point::Point(const Point &t) : x(t.x), y(t.y) {
14 std::cout << "Copy Constructor called\n";
15 }
16
17 int main() {
18 Point ∗t1, ∗t2;
19 t1 = new Point(10, 15);
20 t2 = new Point(∗t1);
21 Point t3 = ∗t1;
22 Point t4;
23 t4 = t3;
24 return 0;
25 }

Indiquer ce qu’affiche ce programme en expliquant précisément les raisons de cet affichage.


— Affichage du programme :
Normal Constructor called
Copy Constructor called
Copy Constructor called
Normal Constructor called
— Explications
1 Point ∗t1, ∗t2; // No constructor call
2 t1 = new Point(10, 15); // Normal constructor call
3 t2 = new Point(∗t1); // Copy constructor call

2/5 C++ - 12 décembre 2016


Master 1 Informatique

4 Point t3 = ∗t1; // Copy Constructor call


5 Point t4; // Normal Constructor call
6 t4 = t3; // Assignment operator call
Un constructeur est appelé lors de la création d’un nouvel objet. C’est le cas ici sur les lignes 19
à 22.
Lorsque un objet est crée à partir de données brutes ou à partir de rien, c’est le constructeur
normal qui est appelé (ici, ligne 19 et 22).
Lorsqu’un objet est créé à partir d’un autre, c’est le constructeur par copie qui est appelé (ici
lignes 20 et 21).
— A la ligne 18, les objets ne sont pas construits, il n’y a donc pas d’appel à constructeur
— A la ligne 19, un objet est créé à partir des données brutes ”10” et ”15”. Le constructeur
appelé est donc le constructeur normal.
— A la ligne 20, l’objet t2 est crée à partir de l’objet t1. Le constructeur par copie est appelé.
— A la ligne 21, l’objet t3 est créé et initialisé à la valeur de t1. Le constructeur par copie est
appelé.
— A la ligne 22, l’objet t4 est créé et initialisé par défaut. Le constructeur appelé est donc le
constructeur normal.
— A la ligne 23, aucun constructeur n’est appelé puisque l’objet t4 existe déjà. Il s’agit donc
d’une affectation.

Exercice 4 (2 points)
Soit le programme C++ suivant :
1 #include <iostream>
2
3 template <int N>
4 class A {
5 int arr[N];
6 public:
7 virtual void fun() { cout << "A::fun()\n"; }
8 };
9
10 class B : public A<2> {
11 public:
12 void fun() { cout << "B::fun()\n"; }
13 };
14
15 class C : public B { };
16
17 int main() {
18 B ∗x = new C;
19 A<2> &y = ∗x;
20 A<2> z = y;
21 x−>fun();
22 y.fun();
23 z.fun();
24 return 0;
25 }
Indiquer ce qu’affiche ce programme en expliquant précisément les raisons de cet affichage.
— Affichage du programme :
B::fun()
B::fun()
A::fun()
— Explications

C++ - 12 décembre 2016 3/5


Master 1 Informatique

La classe A est une classe template ayant un entier i comme paramètre template.
La classe B hérite de A<2> et redéfinit donc la méthode A<2> : :fun()
La classe C hérite de B sans redéfinir de méthodes.
Dans le main,
— le pointeur x, de type B* est initialisé avec un objet de type C alloué sur le tas. Cette
initialisation est correcte puisque la classe C hérite de la classe B. L’appel de x->fun() est
polymorphe en raison du pointeur a et l’affichage est donc B : :fun()
— la référence y, de type A<2>& est initialisée avec l’objet x. Cette initialisation est correcte
puisque la classe C hérite indirectement de la classe A<2>. La variable y étant une référence,
elle désigne donc l’objet réel x de type B. L’appel de y.fun() est polymorphe en raison de la
référence.
— L’objet z est initialisé avec le contenu de l’objet y. Cette initialisation est possible puisque B
est une référence de même type. L’appel de z.fun() n’est pas polymorphe puisque z est un
objet et non pas une référence ou un pointeur.

2 Programmation C++
On souhaite écrire une classe permettant de gérer les nombres rationnels avec le maximum
de précision. Un nombre rationnel est un nombre de la forme a/b avec a et b des entiers (b !=0).
Les nombres rationnels peuvent être additionnés, soustraits, multipliés et divisé comme tout
nombre. On souhaite pouvoir afficher un nombre rationnel dans sa forme canonique réduite (i.e.
a/b avec et et b premiers entre eux) mais aussi pouvoir le convertir vers un nombre réel à double
précision.
Comme exemple d’utilisation de notre classe, nous souhaitons pouvoir écrire le programme
principal suivant
1 int main() {
2 Rationnel f {2, 5};
3 Rationnel f1 {1, 2};
4 auto f2=(f1+2)∗f / Rationnel(3, 4);
5
6 std::cout << "f2 = " << f2 << ’(’ << double(f2) << ’)’;
7 return 0;
8 }
Ce programme affiche sur la sortie standard f2 = 4/3 (1.33333333).
1. Quel est le type de f2 ?
Le type de f2 est Rationnel puisque résultat d’opérations entre rationnels.
2. Qu’est-ce qui nous permet d’écrire (f1+2) sans que le compilateur n’indique une erreur.
2 est un Rationnel pouvant s’écrire sous forme canonique 2/1. Si l’on dispose d’une fonction
de conversion d’un entier vers un Rationnel, ou d’un constructeur à partir d’un Rationnel, le
compilateur l’appellera automatiquement pour convertir 2 vers le Rationnel 2/1.
3. On suppose disposer de la fonction suivante :
1 template <typename T>
2 T pgcd(T a, T b){
3 return b ? pgcd(b, a%b) : a;
4 }

Écrire un constructeur de la classe Rationnel permettant de créer un nombre rationnel


sous forme réduite.
On considère qu’un nombre Rationnel est représenté à partir de deux nombres entiers : le numé-
rateur (attribut num de la classe) et le dénominateur (attribut den de la classe). Ces deux valeurs
doivent être premières entre elles et le dénominateur différent de 0.

4/5 C++ - 12 décembre 2016


Master 1 Informatique

1 Rationnel::Rationnel(int n, int d=1) {


2 std::assert( d != 0);
3 int p = pgcd(n, d);
4 num = n/p;
5 den = d/p;
6 }

Ce constructeur sera aussi utilisé pour convertir un entier en un Rationnel en raison de la valeur
par défaut du deuxième paramètre .
4. Quels sont les opérateurs nécessaires pour pouvoir calculer f2.
Les opérateurs nécessaires sont les opérateurs binaires +, * et /, prenant 2 Rationnels en para-
mètres et retournant un Rationnel résultat.
Par soucis de généralité et d’homogénéité des opérateurs, l’addition d’un entier à un Rationnel
(f2+2) ne se fera pas en implantant un opérateur de somme entre un Rationnel et un entier mais
un constructeur de Rationnel à partir d’un entier. Ce constructeur est donné dans la question 1.
5. Programmez les opérateurs permettant de calculer f2
1 friend Rationnel operator+(const Rationnel &l, const Rationnel &r) {
2 return Rationel(l.num∗r.den + r.num∗l.den, r.den∗l.den);
3 }
4
5 friend Rationnel operator∗(const Rationnel &l, const Rationnel &r) {
6 return Rationel(l.num∗r.num, l.den∗r.den);
7 }
8
9 friend Rationnel operator/(const Rationnel &l, const Rationnel &r) {
10 std::assert(r.num != 0);
11 return Rationel(l.num∗r.den, l.den∗r.num);
12 }

Les opérateurs sont déclarés en friend puisque ne modifiant pas l’objet courant.
6. Programmer l’opérateur permettant d’envoyer un Rationnel vers un flux standard de
sortie.
1 friend std::ostream &operator<<(std::ostream& o, const Rationnel &r){
2 o << r.num;
3 if (r.den!=1)
4 o << ’/’ << r.den << ’ ’;
5 else
6 o << ’ ’;
7 return o;
8 }

7. Programmer l’opérateur de transformation d’un Rationnel en un double de façon à ce


qu’il n’y ait pas d’ambiguı̈té lors du calcul de f2.
1 explicit operator double() const {
2 return double(num)/den;
3 }

L’opérateur de transformation en double est ici déclaré explicit afin d’interdire les conversions
automatiques par le compilateur. Sans cela, il y aurait ambigüité pour le calcul de (f1+2), le
compilateur ne sachant pas s’il faut traduire f1 en un double (comme 2) ou convertir 2 en un
rationnel (comme f1). En mettant un opérateur de conversion vers double explicit, le compilateur
ici transformera la valeur 2 en un Rationnel via le constructeur proposé en question 3.

C++ - 12 décembre 2016 5/5

Vous aimerez peut-être aussi