INF1010 Examen final Hiver 2006 Question 1 Allocation dynamique ou polymorphisme (1 point) Répondez à une des questions suivantes (si vous décidez de répondre aux deux, vous recevrez le maximum des deux pointages) a) Dans le code ci-dessous, nous aurions dû définir un constructeur de copie. Dites quelle erreur grave se produit à cause de cette lacune. class B public : B(int x); ; class A A(int x = 0); ~A(); B* att; ; A::A(int x) att = new B(x); A::~A() delete att; void f(a objet) int main() A obj(2); f(obj); return 0; Réponse : Lorsqu on entre dans la fonction f(), un nouvel objet est construit par copie. Comme le constructeur de copie par défaut est appelé, on aura deux pointeurs sur obj. À la sortie de la fonction, le destructeur sera appelé et la mémoire sera désallouée. L objet obj aura donc un pointeur invalide. b) Expliquez la différence entre le polymorphisme implémenté avec des méthodes virtuelles et le polymorphisme implémenté avec des fonctions et classes génériques. Réponse : En utilisant des méthodes virtuelles, il n est pas nécessaire que le type de l objet soit connu lors de la compilation. Ainsi, si le type de l objet n est réellement connu que lors de l exécution, on pourra utiliser cette approche et pas des fonctions et classes génériques. Par contre, ces dernières sont plus efficaces, puisqu elles sont résolues lors de la compilation, et ne requièrent donc pas de code spécial appelé lors de l exécution du programme pour identifier l objet passé en paramètre.
Question 2 Constructeurs, héritage et polymorphisme ( 6 points) Soit le code suivant : class Operateur Operateur(char = +); int operator()(int x, int y) const; char op; ; Operateur::Operateur(char oper) : op(oper) cout << constructeur - Operateur" << endl; int Operateur::operator()(int x, int y) const switch (op) case +: return (x + y); case -: return (x - y); class Operation Operation(int x = 0, int y = 0); Operation(char operateur, int x = 0, int y = 0); virtual int calcul() const; int getoperande1() const; int getoperande2() const; void affiche() const; protected: int operande1; int operande2; Operateur operateur; ; Operation::Operation(int op1, int op2) : operande1(op1), operande2(op2) cout << "constructeur 1 - Operation " << endl; Operation::Operation(char op, int op1, int op2) : operateur(op), operande1(op1), operande2(op2) cout << "constructeur 2 - Operation " << endl; int Operation::calcul() const return 0; int Operation::getOperande1() const return operande1; int Operation::getOperande2() const return operande2; void Operation::affiche() const cout << "resultat = "<< calcul() << endl; Page 1 de 13
class Soustraction: public Operation Soustraction(int op1 = 0, int op2 = 0); virtual int calcul() const; void affiche() const; int resultat; ; Soustraction::Soustraction(int op1, int op2) : Operation('-',op1, op2) cout << "constructeur - Soustraction "<< endl; int Soustraction::calcul() const return operateur(operande1,operande2); void Soustraction::affiche() const cout << "le résultat de " << operande1 << " - " ; cout << operande2 << " est egal a "<< calcul() << endl; a) (3 points) Dites ce qui sera affiché à l exécution du programme suivant : Operation inverser(operation oper) return Operation(oper.getOperande2(), oper.getoperande1()); int main() cout << " - 1 -" << endl; Operation a(25,13); cout << " - 2 -" << endl; Soustraction b(25,13); cout << " - 3 -" << endl; Operation c; cout << " - 4 -" << endl; c = inverser(a); cout << " - 5 -" << endl; return 0; Réponse: - 1 - constructeur Operateur constructeur 1 Operation - 2 - constructeur Operateur constructeur 2 Operation constructeur Soustraction - 3 - constructeur Operateur constructeur 1 Operation - 4 - constructeur Operateur constructeur 1 Operation - 5- Page 2 de 13
b) (3 points) Dites ce qui sera affiché à l exécution des fonctions f1 et f2 du programme suivant (attention : pour chaque appel de méthode, vérifiez bien si elle est virtuelle ou non): void f1(const Operation& oper) oper.affiche(); void f2(operation oper) oper.affiche(); int main() Soustraction b(25,13); // Dites ce qui sera affiché a partir d'ici cout << " - 1 -" << endl; f1(b); cout << " - 2 -" << endl; f2(b); cout << " - 3 -" << endl; return 0; Réponse: - 1 - resultat = 12-2 resultat = 0-3 - Question 3 Polymorphisme (3 points) Ahmed, un étudiant finissant de Polytechnique, vient d être embauché par Ha Ha inc., une compagnie de jeux vidéo. On l intègre à l équipe de développement d un nouveau jeu. Dans ce jeu, on trouve deux types de monstres, les Grumps et les Chroks, et deux types d armes, soient des lasers et des bombes à neutrinos. Les monstres ne réagissent pas de la même manière à ces armes. Lorsqu ils sont attaqués par un laser, les Grumps s enfuient, alors les Chroks contreattaquent. Dans le cas d une bombe à neutrinos, c est l inverse : les Grumps contre-attaquent et les Chroks s enfuient. Considérez d abord les interfaces pour les armes et les monstres (pour simplifier, nous supposons que les méthodes fuir() et attaquer() ne font qu afficher un message, mais il est évident que dans une situation réelle ces méthodes seraient beaucoup plus complexes) : class Arme string get_type() const return "Arme"; ; class Laser : public Arme string get_type() const return "Laser"; ; Page 3 de 13
class BombeNeutrino: public Arme string get_type() const return "BombeNeutrino"; ; class Monstre string get_type() const return "Monstre"; void attaquer() cout << "Monstre attaque\n"; void fuir() cout << "Monstre fuit\n"; ; class Grump : public Monstre string get_type() const return "Grump"; void attaquer() cout << "Grump attaque\n"; void fuir() cout << "Grump fuit\n"; ; class Chrok : public Monstre string get_type() const return "Chrok"; void attaquer() cout << "Chrok attaque\n"; void fuir() cout << "Chrok fuit\n"; ; Voici comment Ahmed a programmé la réaction des monstres aux différentes armes: int main() Arme* a; Monstre* m; bool gameover = false; while (!gameover) if (a->get_type() == "Laser" && m->get_type() == "Grump") m->fuir(); else if (a->get_type() == "Laser" && m->get_type() == "Chrok") m->attaquer(); if (a->get_type() == "BombeNeutrino" && m->get_type() == "Grump") m->attaquer(); else if (a->get_type() == "BombeNeutrino" && m->get_type() == "Chrok") m->fuir(); else m->fuir();... Page 4 de 13
Le problème avec Ahmed, c est qu il dormait pendant le cours sur le polymorphisme (malgré un excellent professeur!). a) (1 point) Expliquez pourquoi le code de Ahmed ne fonctionne pas. Réponse : Puisqu elle n est pas virtuelle, la méthode get_type() retournera toujours le type de la classe de base. La méthode fuir() de monstre sera donc toujours exécutée, peu importe le type de monstre ou d arme. b) (2 points) On vous demande de modifier le code en profitant mieux du polymorphisme. Nous vous suggérons d ajouter aux monstres une méthode reagir() qui prend en paramètre une arme. Après vos modifications, la boucle du programme principal devrait ressembler à ceci : while (!gameover) m->reagir(a); Solution : class Arme virtual string get_type() const return "Arme"; ; class Laser : public Arme virtual string get_type() const return "Laer"; ; class BombeNeutrino: public Arme virtual string get_type() const return "BombeNeutrino"; ; class Monstre virtual string get_type() const return "Monstre"; virtual void attaquer() cout << "Monstre attaque\n"; virtual void fuir() cout << "Monstre fuit\n"; virtual void reagir(arme* arme) = 0; ; class Grump : public Monstre virtual string get_type() const return "Grump"; virtual void attaquer() cout << "Grump attaque\n"; virtual void fuir() cout << "Grump fuit\n"; virtual void reagir(arme* arme) if (arme->get_type() == "Laser") fuir(); else if (arme->get_type() == "BombeNeutrino") attaquer(); else throw logic_error("arme inconnue"); ; Page 5 de 13
class Chrok : public Monstre virtual string get_type() const return "Chrok"; virtual void attaquer() cout << "Chrok attaque\n"; virtual void fuir() cout << "Chrok fuit\n"; virtual void reagir(arme* arme) if (arme->get_type() == "Laser") attaquer(); else if (arme->get_type() == "BombeNeutrino") fuir(); else throw logic_error("arme inconnue"); ; Question 4 - STL (6 points) On souhaite construire une classe en C++ pour manipuler des ensembles d'entiers. On part de la déclaration suivante: class EnsembleEntiers EnsembleEntiers(); // Constructeur ~EnsembleEntiers(); // Destructeur int cardinal() const; // Retourne le nombre d items dans l ensemble void ajouter(int element); // Pour ajouter un entier void retirer(int element); // Pour retirer un entier bool operator == (const EnsembleEntiers& ens) const; // Teste l'égalité de 2 ensembles // À FAIRE, VOIR CI-DESSOUS ; a) (2 points) Pour le choix du conteneur, on hésite entre les options suivantes : vector, list et set. Supposons que dans notre application, on ne retire pratiquement jamais d éléments des ensembles. Par contre, le test d égalité est très souvent appelé. Dites quel conteneur est le plus approprié dans ce contexte (justifiez votre choix) et ajoutez la déclaration de ce conteneur à l interface de la classe. Indice : pensez au temps requis pour l exécution de chaque méthode, selon le type de conteneur utilisé. Solution : Il est préférable d utiliser un set, car il est plus rapide de tester l égalité de deux conteneurs triés. En plus, si on utilise un vecteur ou une liste, il faudra toujours vérifier si l item existe déjà avant de l ajouter, ce qui n est pas le cas avec un set, qui n ajoute jamais un élément qui existe déjà. On aura donc la déclaration suivante : set<int> ensemble; b) (2 points) Avec le conteneur que vous avez choisi, donnez le code des méthodes cardinal(), ajouter(), retirer() et ==. Réponse : int EnsembleEntiers::cardinal() const return ensemble.size(); void EnsembleEntiers::ajouter(int element) ensemble.insert(element); Page 6 de 13
void EnsembleEntiers::retirer(int element) ensemble.erase(element); bool EnsembleEntiers::operator==(const EnsembleEntiers& ens) const if (ensemble.size()!= ens.ensemble.size()) return false; Conteneur::iterator iter1 = ensemble.begin(); Conteneur::iterator iter2 = ens.ensemble.begin(); Conteneur::iterator fin1 = ensemble.end(); Conteneur::iterator fin2 = ens.ensemble.end(); while (iter1!= fin1 && iter2!= fin2) if (*iter1!= *iter2) return false; ++iter1; ++iter2; return true; c) (1 point) Définissez un objet fonction qu on construit en lui passant deux valeurs x et y, et qui permet de vérifier si un entier est compris entre ces deux valeurs. Solution : class ComprisEntre ComprisEntre(int x, int y) : min(x), max(y) bool operator() (int x) const return (min <= x && x <= max); int min; int max; ; d) (1 point) En supposant qu un conteneur séquentiel soit utilisé, donnez le code d une méthode de la classe EnsembleEntiers qui élimine de l ensemble toutes les valeurs comprises entre min et max, et dont la signature est la suivante : void filtrer(int min, int max) ; Votre méthode ne doit contenir aucune boucle et utilisera l objet fonction défini à la question précédente. Solution : Si le conteneur est un set : void EnsembleEntiers::filtrer(int min, int max) vector<int> temp(ensemble.begin(),ensemble.end()); vector<int>::iterator pos = remove_if(temp.begin(),temp.end(),comprisentre(min,max)); temp.erase(pos,temp.end()); ensemble.clear(); copy(temp.begin(),temp.end(),inserter(ensemble,ensemble.begin())); Page 7 de 13
Si le conteneur est un conteneur associatif : void EnsembleEntiers::filtrer(int min, int max) Conteneur::iterator fin = remove_if(ensemble.begin(),ensemble.end(),comprisentre(min,max)); ensemble.erase(fin,ensemble.end()); Question 5 Exceptions (2 points) Soit le code suivant : class Etudiant Etudiant(string m, string n); bool operator<(const Etudiant& e2) const; string matricule; string nom; ; Etudiant::Etudiant(string m, string n) if (m.length()!= 7) throw length_error("le matricule n'a pas sept chiffres"); matricule = m; if (n.empty) throw invalid_argument("le nom est vide"); nom = n; bool Etudiant::operator<(const Etudiant& e2) const return (this->matricule < e2.matricule); class comparer_etudiants bool operator()(const Etudiant* e1, const Etudiant* e2) const return ((*e1) < (*e2)); ; class Cours ~Cours(); void ajouter_etudiant(string m, string n); set<etudiant*, comparer_etudiants> etudiants; ; Cours::~Cours() set<etudiant*, comparer_etudiants>::iterator itr; for (itr = etudiants.begin(); itr!= etudiants.end(); ++itr) delete (*itr); void Cours::ajouter_etudiant(string m, string n) Etudiant* e = new Etudiant(m,n); etudiants.insert(e); Page 8 de 13
int main() Cours inf1010; inf1010.ajouter_etudiant("1234567", "Michel"); inf1010.ajouter_etudiant("1234567", "Dominic"); return 0; a) Modifiez la méthode ajouter_etudiant() de manière à ce qu elle lance une exception lorsqu on tente d ajouter un étudiant qui existe déjà. b) Modifiez le main() pour gérer adéquatement les exceptions pouvant survenir dans ce programme. Remarque : Toutes les classes d exceptions utilisées dérivent de la classe logic_error et possèdent l unique méthode publique what() retournant le message d erreur. Solution : a) void Cours::ajouter_etudiant(string m, string n) Etudiant* e = new Etudiant(m,n); if (etudiants.find(e)!= etudiants.end()) delete e; throw logic_error("ajout d'un étudiant déjà inscrit"); etudiants.insert(e) ; b) int main() Cours inf1010; try inf1010.ajouter_etudiants("1234567", "Michel"); inf1010.ajouter_etudiants("1234567", "Dominic"); catch (logic_error& err) cerr <<"Une erreur est survenue : " << err.what() << endl; return 0; Page 9 de 13
Question 6 Programmation événementielle (2 points) Maria, qui dormait pendant les cours sur la programmation événementielle, aimerait se préparer au cas où il y aurait une question sur cette matière à l examen final de INF1010. a) Comment expliquerez-vous à Maria le fonctionnement général de la programmation événementielle, afin qu il puisse répondre de manière complète et concise si une telle question se retrouvait à l examen final? Réponse : Dans la programmation événementielle, on n a pas une séquence claire des opérations effectuées par le programme. Celui-ci réagit plutôt à certains événements pré-déterminés dont on ne peut anticiper l occurrence. Un gestionnaire d événements reçoit et décode les événements qui se produisent. Lorsqu un événement se produit, il cherche la méthode qui a été associée à ce type d événement et l exécute. b) Soit un bouton dans une interface graphique. Expliquez ce qui se passe lorsqu on clique sur un tel bouton. Réponse : Le bouton est un widget qui a un identificateur unique. Le programmeur a crée une «table» qui associe une méthode à chaque identificateur. Ainsi, lorsqu on cliquera sur le bouton, le gestionnaire n aura qu à chercher dans la table la méthode associée à l identificateur du bouton en question. C est à cette méthode qu on donnera ensuite le contrôle. Question 7 - Bonus (0,5 points) Né en 1912 et décédé en 1954, je suis considéré comme un des pères fondateurs de l informatique moderne. Entre autres, je suis à l origine de la formalisation du concept de calculabilité, qui a profondément marqué cette discipline. Durant la seconde guerre mondiale, j ai dirigé les recherches qui ont conduit à déchiffrer les codes secrets générés par la machine Enigma utilisée par les Nazis. Qui suis-je? Réponse : Alan Turing Page 10 de 13
Annexe - STL Tous les conteneurs contiennent les méthodes suivantes: size_t size() Retourne le nombre d'éléments contenus dans le conteneur bool empty() Vérifie si le conteneur est vide iterator begin() Retourne un itérateur positionné sur le premier élément du conteneur iterator end() Retourne un itérateur à accès direct positionné après le dernier élément du conteneur iterator rbegin() Retourne un itérateur inverse positionné sur le dernier élément du conteneur iterator rend() Retourne un itérateur inverse positionné sur le premier élément du conteneur void insert(pos, elem) Insère une copie de l élément à position spécifiée (pour set et map, pos n est qu une indication d un point de départ pour la recherche de la position d insertion) void erase(pos) Élimine l'élément se trouvant à la position spécifiée void clear() Vide le conteneur Interface de vector<t>: vector<t>(int n) size_t capacity() void reserve(n) T& front() T& back() T& operator[] void push_back(elem) void pop_back() size_t size() void resize(n) Constructeur qui fixe la capacité initiale à n Retourne le nombre maximal d'éléments que le vecteur peut contenir sans avoir besoin d'une nouvelle réallocation Fixe la capacité à n éléments Retourne le premier élément Retourne le dernier élément Retourne le nième élément Insère une copie de l élément à la fin Retire le dernier élément Retourne la taille du vecteur (c est-à-dire le nombre d éléments qu il contient) Fixe la taille à n (si la taille augmente, les espaces supplémentaires sont remplis par les constructeurs par défaut) Interface de deque<t>: En plus des méthodes de la classe vector, il contient les méthodes additionnelles suivantes: void push_front(elem) Insère une copie de l élément au début void pop_front() Retire le premier élément Interface de list<t>: T& front() T& back() Void push_back(elem) Void pop_back() void push_front(elem) void pop_front() Void remove(val) Void remove(prédicat) void sort() void reverse() Retourne le premier élément Retourne le dernier élément Insère une copie de l élément à la fin Retire le dernier élément Insère une copie de l élément au début Retire le premier élément Retire toutes les occurrences d'une valeur Retire toutes les occurrences pour lesquelles le prédicat retourne la valeur true Trie les éléments de la liste Inverse les éléments de la liste Interface commune de set<t> et multiset<t>: int count(elem) Compte le nombre d'occurrences d'un élément iterator find(elem) Retourne un itérateur positionné sur la première occurrence de l'élément recherché int erase(elem) Élimine toutes les occurrences d un élément et retourne le nombre d items retirés Page 11 de 13
Méthode unique à set<t> et map<t>: pair<iterator, bool> insert(elem) Insère une copie d un élément. La paire retournée contient la position du nouvel élément et un booléen indiquant si l'insertion s'est faite avec succès, c'est-à-dire si l'élément n'existait pas déjà Méthode unique à multiset<t> et multimap<t>: iterator insert(elem) Insère une copie d un élément et retourne la position du nouvel élément Interface commune de map<t> et multimap<t>: int count(k) Compte le nombre d'occurrences d'éléments dont la clé est k iterator find(k) Retourne un itérateur positionné sur le premier élément dont la clé est k int erase(k) operator[ ] Élimine le(s) éléments associé(s) à la clé k et retourne le nombre d items retirés Insère une copie d un élément associé à la clé passée en paramètre Certains algorithmes de la STL for_each(deb, fin, fonction) Applique une fonction à tous les éléments compris entre deb et fin (inclusivement) count(deb, fin, val) Compte le nombre d'occurrences d'une valeur count_if(deb, fin, prédicat) Compte le nombre d'éléments pour lesquels le prédicat retourne true min_element(deb, fin) Retourne un itérateur sur le plus petit élément max_element() Retourne un itérateur sur le plus grand élément find(deb, fin, val) Retourne un itérateur qui pointe sur la première occurrence de la valeur cherchée find_if(deb, fin, prédicat) Retourne un itérateur qui pointe sur la premier élément pour lequel le prédicat retourne true copy(deb1, fin1,deb2) Copie tous les éléments de deb1 à fin1 dans un autre conteneur, à partir de la position deb2 transform(deb1, Applique la fonction à tous les éléments de de deb1 à fin1et, pour chaque fin1,deb2,fonct) élément, met le résultat de la fonction dans un autre conteneur, à partir de la position deb. replace(deb1, fin1,val1, val2) Remplace toutes les occurrences de val1 par la valeur val2 replace_if(deb, fin, prédicat, Remplace par val tous éléments pour lesquels le prédicat retourne true val) remove(deb, fin, valeur) Retire toutes les occurrences qui sont égales à la valeur passée remove_if(deb, fin, prédicat) Retire toutes les items pour lesquels le prédicat retourne true (fonctionne seulement avec les conteneurs séquentiels) reverse(deb, fin) Inverse l ordre des élément du conteneur (seulement pour les conteneurs séquentiels) sort(deb, fin) Trie les éléments du conteneur (seulement pour les conteneurs séquentiels) Foncteurs prédéfinis de la STL negate<t>( ) - param plus<t>() param1 + param2 minus<t>() param1 - param2 multiplies<t>() param1 * param2 divides<t>() param1 / param2 modulus<t>() param1 % param2 equal_to<t>() param1 == param2 not_equal_to<t>() param1!= param2 less<t>() param1 > param2 greater<t>() param1 < param2 less_equal<t>() param1 param2 greater_equal<t>() param1 param2 logical_not<t>()! param logical_and<t>() param1 && param2 logical_or<t>() param1 param2 Page 12 de 13