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 }
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;
12 Test t1(x);
13 std::cout << t1.getT() << " ";
14 x = 30;
15 std::cout << t1.getT() << endl;
16 return 0;
17 }
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 }
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
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 }
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 }
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.