Exercice 2.3 : Rebonds de balles - le retour (boucles, niveau 1)
Exercice n°7 (page 21 et 203) de l'ouvrage C++ par la pratique.
La différence entre cet exercice et le précédent réside dans la condition d'arrêt du calcul. Au lieu d'avoir un nombre fixe d'itération entré par l'utilisateur, facilement utilisable dans une boucle for, celui-ci est variable. Une boucle do..while qui compare le résultat du dernier calcul avec la valeur h_fin entrée par l'utilisateur est donc plus judicieuse.
#include <iostream>
#include <cmath>
using namespace std;
constexpr double g(9.81); // la constante de gravité terrestre
int main()
{
// Saisie des valeurs, avec test de validité ------------
double eps; // coefficient de rebond de la balle
do {
cout << "Coefficient de rebond (0 <= coeff < 1) : ";
cin >> eps;
} while ( (eps < 0.0) or (eps >= 1.0) );
double h0; // hauteur avant rebond
do {
cout << "Hauteur initiale (h0 > 0) : ";
cin >> h0;
} while (h0 <= 0.0);
double h_fin; // hauteur finale
do {
cout << "Hauteur finale (0 < h_fin < h0) : ";
cin >> h_fin;
} while ( (h_fin <= 0.0) or (h_fin >= h0) );
// Declarations -----------------------------------------
double h1; // hauteur après le rebond
double v0, v1; // vitesses avant et après le rebond
int rebonds(0); // nombre de rebonds
// Boucle de calcul -------------------------------------
do {
++rebonds; // une iteration par rebond
v0 = sqrt(2.0 * g * h0); // vitesse avant rebond
v1 = eps * v0; // vitesse apres le rebond
h1 = (v1 * v1) / (2.0 * g); // hauteur après rebond
h0 = h1; // qui devient nouvelle haut. avant rebond suivant
} while (h0 > h_fin);
// Affichage du resultat --------------------------------
cout << "La balle rebondit " << rebonds
<< " fois avant que la hauteur de rebond (" << h0
<< ") soit inférieure à la limite de " << h_fin << endl;
return 0;
}
Exercice 2.4 : Nombres premiers
(variables, expressions arithmétiques, branchements conditionnels; niveau 1)
Exercice n°9 (pages 22 et 205) de l'ouvrage C++ par la pratique.
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int n; // le nombre à tester
bool premier(true); // résultat du test de primalité
int diviseur(1);
// Saisie du nombre à tester
do {
cout << "Entrez un nombre entier > 1 : ";
cin >> n;
} while (n <= 1);
if (n % 2 == 0) {
// le nombre est pair
if (n != 2) {
premier = false;
diviseur = 2;
}
} else {
const double borne_max(sqrt(n));
for (int i(3); (premier) and (i <= borne_max); i += 2) {
if (n % i == 0) {
premier = false;
diviseur = i;
}
}
}
cout << n ;
if (premier) {
cout << " est premier" << endl;
} else {
cout << " n'est pas premier, car il est divisible par "
<< diviseur << endl;
}
return 0;
}
Exercice n°11 (page 30 et 209) de l'ouvrage C++ par la pratique.
#include <iostream>
using namespace std;
// ceci est une variable globale
int variable(10);
main()
{
{ // ici nous avons un nouveau bloc : disons bloc1
int variable(5); // variable locale à bloc1
cout << "Un, la variable vaut : " << variable << endl; // affiche 5
}
{ /* ici nous avons un nouveau bloc : bloc2
* mais il ne definit pas de variable "variable"
* L'appel ci-dessous fait donc appel à la variable globale
*/
cout << "Deux, la variable vaut : " << variable << endl; // affiche 10
}
// ici un bloc "for" avec une variable locale au bloc
for (int variable(0); variable < 3; ++variable) {
cout << "Trois, la variable vaut : " << variable << endl;
// affiche 0, 1, 2
}
/* encore la variable globale car aucune variable "variable"
* n'est définie au niveau de ce bloc (main())
*/
cout << "Quatre, la variable vaut : " << variable << endl; // affiche 10
/* ici un autre bloc "for", mais attention il NE déclare PAS la
* variable "variable" (c'est ici une affectation, pas une
* déclaration) et donc il utilise la variable GLOBALE
*/
for (variable = 0; variable < 3; ++variable) {
cout << "Cinq, la variable vaut : " << variable << endl;
// affiche 0, 1, 2
}
/* c'est toujours la variable GLOBALE dont il s'agit ici mais
* elle a été modifiée par le for précédent et sa valeur est donc 3,
* la valeur qui a fait sortir du "for".
*/
cout << "Six, la variable vaut : " << variable << endl; // affiche 3
}
Exercice 2 : Pointeurs et références (niveau 1)
Question 1 :
Le code fourni produit l'affichage suivant :
2
3
2
2
3
3
Explication : ptri et ri permettent toutes deux de désigner indirectement la variable i. La différence majeure entre les deux dans cet exemple est que ri ne peut désigner que i alors que ptri peut désigner n'importe quel entier lors de l'exécution de ce programme.
Ainsi, l'affectation ri = j veut dire que la variable désignée par ri, càd i, prend la valeur de j (2 au moment de l'exécution). Cette instruction ne signifie pas que l'adresse de i devient celle de j. Ceci explique l'affichage d'un "2" produit par cette instruction.
Dans le cas de l'instruction ptri = &j, ptri qui désignait jusqu'alors i désigne désormais j, d'où l'affichage d'un "3".
Question 2 :
1
int& ri(i);
Correct : déclaration d'un alias pour la variable i
2
int* ptri(&i);
Correct : déclaration d'un pointeur sur un entier initialisé avec l'adresse de i
3
int& rj(0);
Faux : une référence non constante doit être associée à une variable
4
int* ptrj(0);
Correct en C++98 : le pointeur nul existe bel et bien
en C++11 : le 0 est à remplacer par nullptr
5
int& rk;
Faux : une référence doit absolument être liée à une variable
6
int* ptrk;
Correct (mais pas recommandable) : on peut très bien déclarer un pointeur sans l'initialiser
Exercice 3 : généricité (pointeurs, niveau 1)
Exercice n°23 (page 61 et 226) de l'ouvrage C++ par la pratique.
#include <iostream>
using namespace std;
switch (demander_nombre(1,3))
{
case 1: choix = &valeur1; break;
case 2: choix = &valeur2; break;
case 3: choix = &valeur3; break;
}
cout << "Vous avez choisi " << *choix << endl;
}
int demander_nombre(int a, int b)
{
int res;
if (a > b) { res=b; b=a; a=res; }
do {
cout << "Entrez un nombre entier compris entre "
<< a << " et " << b <<" : " << flush;
cin >> res;
} while ((res < a) || (res > b));
return res;
}
Réponse à la question subsidiaire : non car une référence ne peut désigner qu'une seul objet !
Exercice 4 : Surcharge de fonction (niveau 1)
Exercice n°13 (page 32 et 212) de l'ouvrage C++ par la pratique.
En C++, une fonction est identifiée par son nom et par les types de ses paramètres. On peut en effet imaginer que certaines fonctions se comportent différemment selon le type de paramètre transmis : on n'inverse par exemple pas une matrice de la même manière que l'on inverse un nombre réel.
Dans cet exercice par contre, le contenu de la fonction n'est pas modifié car on échange toujours 2 objets de la même façon. Ce qui change par contre ici, c'est le type des paramètres et de la variable intermédiaire.
void echange(double& a, double& b)
{
double copie(a);
a=b;
b=copie;
}
void echange(char& a, char& b)
{
char copie(a);
a=b;
b=copie;
}
Il n'est pas possible de définir dans le même fichier une fonction :
bool echange(double&, double&)
car le type de retour ne fait pas partie de la signature de la fonction. La raison à cela est que sans cela, un appel du style :
echange(2.5, 6.7);
deviendrait ambigu. A-t-on appelé :
bool echange(double&, double&)
ou
void echange(double&, double&)
(c'est à dire avancé !) Notez que le langage C++ offre une possibilité de faire ce genre de surcharges plus facilement : définir des fonctions indépendamment du type de leurs arguments. Ce sont les templates, que nous verrons plus tard dans le semestre.
C'est un peu comme si on écrivait la fonction générique :
void echange(<type>& a, <type>& b)
{
<type> copie(a);
a=b;
b=copie;
}
justement sans avoir à préciser le type.
[avancé !] [C++11] Notez par ailleurs que depuis la version 2011, le langage C++ offre une possibilité de faire cela de façon plus efficace, en évitant les copies lorsque cela est approprié, en utilisant la «sémantique de déplacement» («move semantics»). Ceci est une optimisation avancée ; à n'utiliser que si vous savez exactement ce que vous faites :
template<typename Type>
void echange(Type& a, Type& b)
{
Type tmp(std::move(a)); // Cela peut rendre a invalide
a = std::move(b); // Cela peut rendre b invalide
b = std::move(tmp); // Cela peut rendre tmp invalide (mais peu importe !)
}
Exercice 5 : Calcul approché d'une intégrale (niveau 2)
Exercice n°15 (page 33 et 214) de l'ouvrage C++ par la pratique.
#include <iostream>
#include <cmath>
using namespace std;
// PROTOTYPES DES FONCTIONS
double f(double x);
double demander_nombre();
double integre(double a, double b);
// PROGRAMME PRINCIPAL
int main()
{
double a(demander_nombre());
double b(demander_nombre());
// on définit la précision de l'affichage à 12 chiffres après la virgule
cout.precision(12);
cout << "Integrale de sin(x) entre " << a
<< " et " << b << " :" << endl;
cout << integre(a,b) << endl;
return 0;
}
Exercice 6 : Recherche dichotomique (fonctions récursives, niveau 1-2)
Exercice n°34 (page 83 et 256) de l'ouvrage C++ par la pratique.
Voici un code possible pour cet exercice :
#include <iostream>
#include <string>
using namespace std;
int demander_nombre(int min, int max);
unsigned int cherche(const unsigned int borneInf, const unsigned int borneSup);
const unsigned int MIN(1);
const unsigned int MAX(100);
// ----------------------------------------------------------------------
int main()
{
cout << "Pensez à un nombre entre " << MIN << " et " << MAX << "."
<< endl;
cout << "Tapez sur 'Entrée' quand vous êtes prêt(e)." << endl;
string poubelle;
getline(cin, poubelle);
cout << endl << "Votre nombre était " << cherche(MIN,MAX) << '.' << endl;
return 0;
}
/* ----------------------------------------------------------------------
* Recherche d'un nombre par dichotomie dans un intervalle [a b]
* Entrée : les bornes de l'interval
* Sortie : la solution
* ---------------------------------------------------------------------- */
unsigned int cherche(const unsigned int a, const unsigned int b)
{
// cout << "[ " << a << ", " << b << " ]" << endl;
if (b < a) {
cerr << "ERREUR: vous avez répondu de façon inconsistante !" << endl;
return b;
}
unsigned int pivot((a+b)/2);
char rep;
do {
cout << "Le nombre est il <, > ou = à " << pivot << " ? " << flush;§
cin >> rep;
} while ((rep != '=') && (rep != '<') && (rep != '>'));
switch (rep) {
case '=':
return pivot;
case '<':
return cherche(a,pivot-1);
case '>':
return cherche(pivot+1,b);
}
}
Le seul petit truc auquel il faut penser est peut être le switch : oui ! on peut faire un switch sur un caractère.
Exercice 7 : Intégrales revisitées (pointeurs sur fonctions, niveau 3)
Exercice n°27 (page 65 et 235) de l'ouvrage C++ par la pratique.
Voici une version du corrigé compilant sous l'ancienne norme (C++98). Pour la variante C++11,voir ci-dessous.
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
cout.precision(12);
cout << "Integrale de " << choix[rep].description << " entre " << a
<< " et " << b << " :" << endl;
cout << integre(choix[rep].f, a, b) << endl;
return 0;
}
Pour une version C++11 :
Ajouter aux inclusions:
#include <functional>
et remplacer le
typedef double (*Fonction)(double);
par
typedef function<double(double)> Fonction;
Exercice 1 : Échauffement avec les tableaux dynamiques (niveau 1)
Exercice n°18 (page 56 et 220) de l'ouvrage C++ par la pratique.
Le code fourni remplit le vecteur tab (de taille 10) d'éléments allant de 0 à 9.
En effet, push_back ajoute un élément à la fin du tableau. Au moment de l'ajout tab.size() vaut la taille du vecteur avant l'ajout (puisque l'élément n'est pas encore ajouté).
Vérification : Le code suivant (C++98) :
for (int i(0); i < tab.size(); ++i) cout << tab[i] << endl;
ou comme ceci en C++11 :
for (auto x : tab) cout << x << endl;
affiche(nt) :
0
1
2
3
4
5
6
7
8
9
B)
Ajoute à la fin de tab2 un vecteur de même taille que le vecteur tab et contenant que des éléments de même valeur : la valeur du premier élément de tab.
Dans le cas où tab2 est un tableau vide (et dans ce cas seulement), on pourrait aussi faire :
// déménagement de Pierre (p1)
p1.home = &m2;
cout << "maintenant : ";
affiche(p1); affiche(p2); affiche(p3); affiche(p4);
return 0;
}
Exercice 3 : Multiplication de matrices (tableaux dynamiques, typedef, niveau 1)
Exercices n°20 (pages 57 et 221) de l'ouvrage C++ par la pratique.
Le code fourni ici est en C++98. Pour une version compilant avec l'ancien standard (C++11) voir ci-dessous. Voir aussi les commentaires en fin de corrigé.
#include <iostream>
#include <vector>
using namespace std;
// Définition d'un type synonyme (alias)
typedef vector < vector<double> > Matrice;
for (size_t i(0); i < M1.size(); ++i)
for (size_t j(0); j < M2[0].size(); ++j) {
prod[i][j] = 0.0;
for (size_t k(0); k < M2.size(); k++)
prod[i][j] += M1[i][k] * M2[k][j];
}
return prod;
}
/* Amélioration possible : stocker le résultats des appels à la fonction *
* size dans des variables locales (pour éviter de ré-invoquer la fonction *
* plusieurs fois)*/
// ----------------------------------------------------------------------
void affiche_matrice(const Matrice& M)
{
for (size_t i(0); i < M.size(); ++i) {
for (size_t j(0); j < M[i].size(); ++j)
cout << M[i][j] << " ";
cout << endl;
}
}
Version C++11
#include <iostream>
#include <vector>
using namespace std;
// Définition d'un type synonyme (alias)
/* remarquez que par rapport à la version C++98 *
* on a le droit d'écrire: vector < vector<double>> *
* au lieu de vector < vector<double> > (plus besoin de *
* séparer les deux symboles > par un espace ) */
typedef vector < vector<double>> Matrice;
for (size_t i(0); i < M1.size(); ++i)
for (size_t j(0); j < M2[0].size(); ++j) {
prod[i][j] = 0.0;
for (size_t k(0); k < M2.size(); k++)
prod[i][j] += M1[i][k] * M2[k][j];
}
return prod;
}
/* Amélioration possible : stocker le résultats des appels à la fonction *
* size dans des variables locales (pour éviter de ré-invoquer la fonction *
* plusieurs fois)*/
// ----------------------------------------------------------------------
void affiche_matrice(const Matrice& M)
{
// On aurait aussi pu utiliser auto dans les boucles
// des autres fonctions
for (auto ligne : M) {
for (auto element : ligne) {
cout << element << " ";
}
cout << endl;
}
}
Nous avons avec cet exercice une belle illustration des progrès introduits dans C++11. En C++98, le code fourni produit en effet plusieurs copies inutiles : les fonctions lire_matrice et multiplication créent leur propre Matrice, laquelle est recopiée en sortie (échange de l'information entre le return de la fonction et son appel). Il y a donc à chaque fois deux Matrices : celle de l'instruction qui fait l'appel et celle de la valeur de retour de la fonction appellé. Cela est inutile et coûteux (surtout si les matrices sont de grande taille).
Une solution pour éviter cette duplication des Matrices est, en C++98, d'utiliser les pointeurs.
Tout ceci n'est plus nécessaire en C++11. Cette nouvelle version du langage introduit en effet la notion de «sémantique de déplacement» (move semantics), laquelle permet entre autres au compilateur, avec le MÊME code d'éviter ces copies (techniquement c'est parce que les vector ont maintenant un «constructeur de déplacement» (move constructor)). Cela permet aux programmeurs d'écrire des codes plus efficaces, sans avoir rien de particulier à faire !
Vous pouvez même expliciter cette optimisation (mais ce n'est pas nécessaire, le compilateur devrait pouvoir le faire pour vous), en transformant M1 et M2 en des «références vers des transitoires» (r-value refences).
const Matrice&& M1(lire_matrice()), M2(lire_matrice()); // Notez le symbole &&
(et c'est tout !!) Cela impose que les seules matrices existant dans tout le programme soient celles crées dans les deux appels de lire_matrice et celle résultant de la multiplication. Plus aucune copie inutile supplémentaire...
Vive la move semantics !
[avancé !] Arcanes de la move semantics
[avancé !] Profitons-en pour pousser encore un peu plus loin pour ceux que cela intéresse (mais cela est vraiment avancé, hors des objectifs du cours) : peut on encore faire mieux et ne pas introduire la 3e matrice (le résultat de la multiplication) ?
La réponse serait «oui» avec un autre algorithme pour le calcul de la multiplication permettant le calcul «sur place». Dans le cas de la multiplication matricielle, ce calcul est inutilement complexe pour illustrer notre propos. Nous allons simplifier en prenant le cas de l'addition matricielle : l'idée de faire l'addition «sur place» est de calculer le résultat de A+B dans A lui-même (ou dans B), sans utiliser une troisième matrice pour stocker le résultat.
Peut-on coder cela en C++ ? i.e. éviter de créer une 3e Matrice ? La réponse est «oui» en C++11.
Il faut pour cela tout d'abord se rendre compte qu'on ne peut le faire que si A ou B sont des «transitoires» (r-value reference). En effet si A et B sont des «vraies données» (= des variables, l-values en termes techniques) alors on est obligé de créer une 3e Matrice car on ne peut (souhaite) pas toucher à A ni à B. Il faut donc expliciter le cas où A ou B, ou les deux, sont des r-value references. Cela nous conduit aux quatres prototypes suivants :
// deux données stockées, à préserver
Matrice addition(const Matrice& M1 , const Matrice& M2);
// deux "transitoires"
Matrice&& addition(Matrice&& M1 , Matrice&& M2 );
Cependant, il est clair qu'on ne vas pas écrire quatre fois l'addition !! (jamais de copier-coller !) Comment faire ?
Cela nous donne donc :
/* -------------------------------------------------------------------- *
* Cas ou M1 est un temporaire. *
* On code ici la "vraie" addition et on utilisera cette version dans *
* les autres cas. *
* Note : on suppose ici que M1 et M2 sont de même taille. Dans un *
* programme plus complet, il faudrait bien sûr s'en assurer ! */
Matrice&& addition(Matrice&& M1, const Matrice& M2)
{
for (size_t i(0); i < M1.size(); ++i)
for (size_t j(0); j < M1[i].size(); ++j)
M1[i][j] += M2[i][j];
return move(M1);
}
Exercice 4 : Segmentation en mots (string, niveau 2)
Exercice n°19 (page 56 et 220) de l'ouvrage C++ par la pratique.
#include <iostream>
#include <string>
using namespace std;
bool nextToken(const string& str, int& from, int& len);
bool issep (char c); // teste si le caractère est un separateur
int main()
{
string phrase;
cout << "Entrez une chaîne : ";
getline(cin, phrase);
cout << "Les mots de \"" << phrase << "\" sont :" << endl;
int debut(0);
int longueur(0);
while (nextToken(phrase, debut, longueur)) {
cout << "'" << phrase.substr(debut, longueur) << "'" << endl;
debut += longueur;
}
return 0;
}
/* La fonction suivante teste si le caractère est un séparateur.
*
* Ecrire une fonction présente l'avantage de pouvoir redéfinir facilement
* la notion de séparateur (et éventuellement d'en définir plusieurs).
*/
bool issep (char c)
{
return (c == ' ');
}
/* Il y a de multiples façons d'écrire cette fonction.
* Nous trouvons celle-ci est assez élégante.
*/
bool nextToken(const string& str, int& from, int& len)
{
const int taille(str.size());
// saute tous les separateurs à partir de from
while ((from < taille) and issep(str[from])) ++from;
// avance jusqu'au prochain séparateur ou la fin de str
len = 0;
for (int i(from); ((i < taille) and not issep(str[i])); ++len, ++i);
return (len != 0);
}
Commentaires
Le corrigé proposé pour nextToken est assez compact et nécessite d'avoir bien compris les structures de contrôle (while, for). Il peut être, de ce fait, un peu plus difficile à comprendre que d'autres solutions.
Comme indiqué, vous pouvez le faire de plein de façons différentes ; mais examinons justement celle proposée :
bool nextToken (string const&; str, int& from, int& len)
{
int const taille(str.size());
Bon, jusque là probablement que "ça va"... ; juste peut être préciser les arguments :
string const& str : la chaîne à traiter. Le passage par « const ref » est, bien sûr, une optimisation ; et l'on pourra dans un premier temps se contenter d'un simple passage par valeur : string str ;
int& from : contient au départ la première position à partir de laquelle il faut chercher un nouveau « token », et devra contenir à la sortie de nextToken la position du début du token suivant ; il va donc être modifié par nextToken, et c'est pour cela qu'il est passé par référence ;
int& len : va également être modifié par nextToken pour contenir la longueur du nouveau « token » (éventuellement) trouvé.
Continuons avec :
while ((from < taille) && issep(str[from])) ++from;
from est ici incrémenté(/augmenté) du nombre de séparateurs rencontrés. En effet, on ne peut pas commencer le nouveau « token » par un séparateur.
Comment fait-on pour sauter tous ces séparateurs ?
-> tant que le caractère courant est un séparateur, on avance. Ça, c'est le « while (issep(str[from])) ».
Mais il faut penser aussi à ne pas déborder de la chaîne (imaginez le cas où la chaîne ne contient que des séparateurs). Ça, c'est le « (from < taille) » dans le test du while.
Passons au bloc suivant. Son but est de trouver la fin du « token » (puisqu'on vient de trouver le début avec la boucle while précédente).
Cette fin de « token » est en fait indiquée par la longueur, len, du « token ». Au départ le « token » est vide (on a très bien pu sortir du while précédent par la condition « from ≥ taille ». Repensez encore une fois au cas d'une chaîne ne contenant aucun « token », mais uniquement des séparateurs). On a donc :
len = 0;
Puis on cherche la fin du « token » ; c'est-à-dire le prochain séparateur. C'est-à-dire que tant que l'on n'a pas de séparateur (« !issep(str[i]) »), on avance.
Ca veut dire quoi « on avance » ?
-> on passe au caractère suivant, ça c'est le « ++i », et on met à jour la taille du « token » en la faisant augmenter de 1, ça c'est le « ++len ».
D'où part-on ?
-> du caractère « from » précédemment trouvé.
Il ne reste plus qu'à ne pas oublier de ne pas déborder de la chaîne (« i < taille »), et le tour est joué :
for (int i(from); ((i < taille) && !issep(str[i])); ++len, ++i);
Pour essayer d'être encore plus clair, ceci peut aussi s'écrire de la façon moins compacte suivante (rappel : « from » représente le début de « token ») :
bool continuer(true);
int position_courante(from);
do {
// si on déborde de la chaine il faut s'arreter
if (position_courante >= taille) {
continuer = false ;
}
// si on rencontre un séparateur, il faut s'arreter
// (c'est la fin du token)
else if (issep(str[position_courante])) {
continuer = false ;
}
// sinon, tout va bien, ...
else {
// ...on augmente la longueur du token de 1...
len = len + 1;
// ...et on passe au caractère suivant.
++position_courante;
}
} while (continuer);
Voilà pour cette partie.
Et pour finir, on renvoie « true » si on a trouvé un « token », c.-à-d. si len n'est pas nulle, et « false » sinon. Soit :
return (len != 0);
qui revient exactement à formulation explicite mais inélégante :
//Tournure à éviter
void affiche(const QCM& question);
unsigned int demander_nombre(unsigned int min, unsigned int max);
unsigned int poser_question(const QCM& question);
Examen creer_examen();
// ======================================================================
main()
{
unsigned int note(0);
Examen exam(creer_examen());
for (size_t i(0); i < exam.size(); ++i)
if (poser_question(exam[i]) == exam[i].solution)
++note;
// ======================================================================
unsigned int demander_nombre(unsigned int a, unsigned int b)
{
unsigned int res;
if (a > b) { res=b; b=a; a=res; }
do {
cout << "Entrez un nombre entier compris entre "
<< a << " et " << b <<" : " << flush;
cin >> res;
} while ((res < a) || (res > b));
q.question = "Combien de dents possède un éléphant adulte";
q.reponses.clear();
q.reponses.push_back("32");
q.reponses.push_back("12");
q.reponses.push_back("beaucoup");
q.reponses.push_back("48");
q.reponses.push_back("4");
q.solution=2;
retour.push_back(q);
q.question = "Laquelle des instructions suivantes est un prototype de fonction";
q.reponses.clear();
q.reponses.push_back("int f(0);");
q.reponses.push_back("int f(int 0);");
q.reponses.push_back("int f(int i);");
q.reponses.push_back("int f(i);");
q.solution=3;
retour.push_back(q);
q.question = "Qui pose des questions stupides";
q.reponses.clear();
q.reponses.push_back("le prof. de math");
q.reponses.push_back("mon copain/ma copine");
q.reponses.push_back("le prof. de physique");
q.reponses.push_back("moi");
q.reponses.push_back("le prof. d'info");
q.reponses.push_back("personne, il n'y a pas de question stupide");
q.reponses.push_back("les sondages");
q.solution=6;
retour.push_back(q);
return retour;
}
Version C++11
La principale différence est qu'en C++98 la syntaxe d'initialisation simplifiée n'est pas permise ni pour les vectors, ni en affectation pour les structures. Cela change assez substantiellement la fonction creer_examen.
L'autre changement est l'utilisation des boucle for(:) (range-based for) qui n'existent pas en C++98.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
void affiche(const QCM& question);
unsigned int demander_nombre(unsigned int min, unsigned int max);
unsigned int poser_question(const QCM& question);
Examen creer_examen();
// ======================================================================
int main()
{
unsigned int note(0);
Examen exam(creer_examen());
for (auto question : exam) {
if (poser_question(question) == question.solution) {
++note;
}
}
// ======================================================================
unsigned int demander_nombre(unsigned int a, unsigned int b)
{
unsigned int res;
if (a > b) { res=b; b=a; a=res; }
do {
cout << "Entrez un nombre entier compris entre "
<< a << " et " << b <<" : ";
cin >> res;
} while ((res < a) or (res > b));
// ======================================================================
Examen creer_examen()
{
return {
// Question 1
{ "Combien de dents possède un éléphant adulte",
{ "32", "de 6 à 10", "beaucoup", "24", "2" },
2 // réponse
},
// Question 2
{ "Laquelle des instructions suivantes est un prototype de fonction",
{ "int f(0);" ,
"int f(int 0);" ,
"int f(int i);" ,
"int f(i);" },
3 // réponse
},
// Question 3
{ "Qui pose des questions stupides",
{ "le prof. de math",
"mon copain/ma copine",
"le prof. de physique",
"moi",
"le prof. d'info",
"personne, il n'y a pas de question stupide",
"les sondages" } ,
6 // réponse
}
};
}
Cet exercice se fait de façon assez similaire à l'exercice d'introduction (niveau 0) sur les rectangles.
Comment commencer ? À ce niveau, il suffit de suivre l'enoncé :
« définissez une classe Cercle » :
class Cercle {
};
« ayant comme attributs privés »
class Cercle {
private:
};
« le rayon du cercle (de type double), et les coordonnées de son centre. »
class Cercle {
private:
double rayon;
double x; // abscisse du centre
double y; // ordonnée du centre
};
Remarque : les meilleurs analystes peuvent faire remaquer que le centre est un "point" et écrire, de façon encore plus propre, le code suivant :
class Cercle {
private:
double rayon;
Point centre;
};
Remarque 2 : les meilleurs analystes et programmeurs « objet » en feront bien sûr une classe plutôt qu'une struct...
On continue ensuite à suivre l'énoncé à la lettre : Déclarez ensuite les méthodes «get» et «set» correspondantes :
class Cercle {
void getCentre(double &x, double &y) const {
x = this->x;
y = this->y;
}
void setCentre(const double x, const double y) {
this->x = x;
this->y = y;
}
double getRayon() const {
return rayon;
}
void setRayon(double r) {
if (r < 0.0) r = 0.0;
rayon = r;
}
private:
double rayon;
double x; // abscisse du centre
double y; // ordonnée du centre
};
Attention à bien faire la différence entre x en tant qu'argument de la méthode et x en tant qu'attribut de l'instance. Dans les cas ambigus comme ci-dessus, il faut lever l'ambiguité en utilisant le pointeur this (pour indiquer l'attribut).
Ah! QUESTION : ces méthodes sont-elles privées ou publiques ?
Publiques évidemment car on doit pouvoir les utiliser hors de la classe (elles font partie de l'interface).
class Cercle {
public: //ICI
void getCentre(double &x, double &y) const {
... // comme avant
};
Et on termine notre classe de façon très similaire à ce qui précède :
#include <cmath> // pour M_PI
class Cercle {
public:
double surface() const { return M_PI * rayon * rayon; }
bool estInterieur(const double x, const double y) const {
return (((x-this->x) * (x-this->x) +
(y-this->y) * (y-this->y))
<= rayon * rayon);
}
void getCentre(double &x, double &y) const {
... // comme avant
};
Reste plus qu'à tester :
#include <iostream> // pour cout et endl
#include <cmath> // pour M_PI et sqrt()
using namespace std;
// ... la classe Cercle comme avant
int main () {
Cercle c1, c2;
c1.setCentre(1.0, 2.0);
c1.setRayon(sqrt(5.0)); // passe par (0, 0)
c2.setCentre(-2.0, 1.0);
c2.setRayon(2.25); // 2.25 > sqrt(5) => inclus le point (0, 0)
return 0;
}
Exercice 2 : Conception d'un programme OO (POO, niveau 2)
Code de triangle.cc :
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
class Point {
private:
double x, y;
public:
double getX () {
return x;
}
double getY () {
return y;
}
/* la méthode suivante sera remplacée par un constructeur après
le prochain cours*/
void lire_point(){
cout <<"Contruction d'un nouveau point" << endl;
cout <<"Veuillez entrer x: " << endl;
cin >> x;
cout <<"Veuillez entrer y: " << endl;
cin >> y;
}
double calculer_distance (Point p) {
/* Calcule la distance entre deux points. Le premier point est
l'objet actuel (this). Le deuxième point (p) est envoyé en
paramètre. */
double x1 = x;
double y1 = y;
double x2 = p.getX();
double y2 = p.getY();
double xdiff = x1 - x2;
double ydiff = y1 - y2;
double somme = xdiff*xdiff + ydiff*ydiff;
double distance = sqrt(somme);
return distance;
}
};
class Triangle {
private:
Point p1, p2, p3;
double longueur1, longueur2, longueur3;
public:
/* la méthode suivante sera remplacée par un constructeur après
le prochain cours*/
void initialiser() {
// Initialisation des trois points:
Point p1,p2,p3;
p1.lire_point();
p2.lire_point();
p3.lire_point();
/* Les distances sont calculées et stockées dans des attributs.
Les méthodes calculer_perimetre et tester_isocele peuvent ainsi
accéder aux valeurs précalculées et nous évitons de les
recalculer plusieurs fois. */
longueur1 = p1.calculer_distance(p2);
longueur2 = p2.calculer_distance(p3);
longueur3 = p3.calculer_distance(p1);
}
double calculer_perimetre () {
return (longueur1 + longueur2 + longueur3);
}
bool tester_isocele () {
if ((longueur1 == longueur2) ||
(longueur2 == longueur3) ||
(longueur3 == longueur1))
return true;
else
return false;
}
};
int main (){
Triangle t;
t.initialiser();
double perimetre = t.calculer_perimetre();
cout << "Périmètre : " << perimetre<< endl;
bool isocele = t.tester_isocele();
if (isocele)
cout << "Le triangle est isocèle"<< endl;
else
cout << "Le triangle n'est pas isocèle" << endl;
return 0;
}
int main ()
{
Fleur f1("Violette", "bleu");
Fleur f2(f1);
cout << "dans un cristal ";
f2.eclore();
cout << "Donne un poeme un peu fleur bleue" << endl
<< "ne laissant plus ";
return 0;
}
Exercice 2 : balles rebondissante, le retour (Constructeurs/destructeurs, niveau 2)
Exercice n°51 (page 122 et 303) de l'ouvrage C++ par la pratique.
Une proposition de solution pour cet exercice :
void rebondit(const unsigned int nrebonds = 1);
void rebondit(const double haut, const unsigned int nrebonds);
private:
void prochainRebond();
private:
double epsilon; // coefficient de rebond de la balle
double h; // maximum hauteur pour rebond courant
double v; // vitesse initiale rebond courant
};
int main()
{
Balle tennis(0.75);
/* 1ère question, on peut directement utiliser le modèle */
tennis.rebondit(20.0, 4);
cout << "Partant d'une hauteur de 20, une balle " << endl
<< "de tennis (eps=0.75) normalement constituée" << endl
<< "atteint une hauteur de " << tennis.hRebond()
<< " à son 4è rebond." << endl;
cout << endl;
Balle pingpong(0.9, 10.0);
/* 2è question, notre modèle ne mémorisant pas en interne
le nombre de rebonds effectué, il faut le faire pour lui */
unsigned int rebonds(0); // compteur de rebonds
do
{
pingpong.rebondit();
++rebonds;
} while (pingpong.vRebond() > 3.0);
cout << "Ce n'est qu'au " << rebonds << "e rebonds" << endl
<< "qu'une balle de ping-pong (eps=0.9)" << endl
<< "lâchée d'une hauteur de 10," << endl
<< "rebondit avec une vitesse inférieur à 3..." << endl;
return 0;
}
void Balle::rebondit(const unsigned int nrebonds)
{
for (unsigned int i(0); i<nrebonds; ++i)
prochainRebond();
}
void Balle::rebondit(const double haut,
const unsigned int nrebonds)
{
h = haut;
rebondit(nrebonds);
}
void Balle::prochainRebond()
{
v = epsilon * sqrt(2*h*g);
h = (v*v)/(2*g);
}
Exercice 3 : encore un peu plus de polynômes
Cet exercice est de niveau 2 principalement en raison de sa longueur.
Pour la première partie, voir l'exercice 0.
Concernant la seconde partie :
Ajoutez les opérateurs pour l'addition et la soustraction :
À ajouter à la classe Polynome :
Polynome& operator+=(const Polynome&);
Polynome& operator-=(const Polynome&);
Polynome operator+(const Polynome&) const;
Polynome operator-(const Polynome&) const;
Les opérateurs + et - sont effectivement const puisqu'il ne modifient pas la classe concernée mais crée un nouveau résultat.
Pour la définition des opérateurs, écrire hors de la classe :
La méthode simplifie est une méthode privée :
void Polynome::simplifie()
{
while ((!p.empty()) && (top() == 0.0)) p.pop_back();
if (p.empty()) p.push_back(0.0);
}
Ajoutez les opérateurs de comparaison == et !=
bool Polynome::operator!=(const Polynome& q) const {
return p != q.p;
}
Ajoutez une méthode top qui retourne la valeur du coefficient de plus haut degré du polynôme.
Définissez une classe Forme en la dotant d'une méthode qui affiche [...]
#include <iostream> // pour cout
using namespace std; // pour ne pas écrire std::cout et std::endl
class Forme {
public:
void description() const {
cout << "Ceci est une forme !" << endl;
}
};
Ajoutez au programme une classe Cercle héritant de la classe Forme, et possédant une méthode void description() qui affiche [...]
class Cercle : public Forme {
public:
void description() const {
cout << "Ceci est un cercle !" << endl;
}
};
[...] Testez ensuite à nouveau votre programme.
Voyez-vous vu la nuance ?
Pourquoi a-t-on ce fonctionnement ?
La différence vient de ce que c.description(); appelle la méthode description() de la classe Cercle (c'est-à-dire Cercle::description()), alors que f2.description(); appelle celle de la classe Forme (c'est-à-dire Forme::description()), bien que elle ait été construite par une copie d'un cercle.
Le polymorphisme n'opère pas ici car aucune des deux conditions nécessaires n'est remplie : la méthode n'est pas virtuelle et on ne passe par pas par des références ni pointeurs.
[...] Ajoutez encore au programme une fonction void affichageDesc(Forme& f) [...]
Le résultat vous semble-t-il satisfaisant ?
Avec cette fonction, nous apportons une solution au second aspect puisqu'en effet nous passons l'argument par référence.
Le résultat n'est cependant toujours pas satisfaisant (c'est toujours la méthode Forme::description() qui est appelée) car le premier problème subsiste : la méthode n'est pas virtuelle.
Modifiez le programme (ajoutez 1 seul mot) pour que le résultat soit plus conforme à ce que l'on pourrait attendre.
Il suffit donc d'ajouter virtual devant le prototype de la méthode description de la classe Forme.
Voici le programme complet :
#include <iostream>
using namespace std;
class Forme {
public:
virtual void description() const {
cout << "Ceci est une forme !" << endl;
}
};
class Cercle : public Forme {
public:
void description() const {
cout << "Ceci est un cercle !" << endl;
}
};
void affichageDesc(Forme& f) { f.description(); }
int main()
{
Forme f;
Cercle c;
// f.description();
// c.description();
// Forme f2(c);
// f2.description();
affichageDesc(f);
affichageDesc(c);
return 0;
}
1.2 Formes abstraites
Modifiez la classe Forme de manière à en faire une classe abstraite [...]
class Forme {
public:
virtual void description() const {
cout << "Ceci est une forme !" << endl;
}
virtual double aire() const = 0;
};
Ce qui en fait une méthode virtuelle pure c'est le =0 derrière qui indique que pour cette classe cette méthode ne sera pas implémentée (i.e. pas de définition, c.-à-d. pas de corps).
Écrivez une classe Triangle et modifiez la classe Cercle existante héritant toutes deux de la classe Forme, et implémentant les méthodes aire() et description(). [...]
class Cercle : public Forme {
public:
Cercle(double x = 0.0) { rayon = x; }
void description() const {
cout << "Ceci est un cercle !" << endl;
}
double aire() const { return 3.141592653 * rayon * rayon; }
private:
double rayon;
};
class Triangle : public Forme {
public:
Triangle(double h = 0.0, double b = 0.0) { base = b; hauteur = h; }
void description() const {
cout << "Ceci est un triangle !" << endl;
}
double aire() const { return 0.5 * base * hauteur; }
private:
double base; double hauteur;
};
Modifiez la fonction affichageDesc pour qu'elle affiche, en plus, l'aire [...]
void affichageDesc(Forme& f) {
f.description();
cout << " son aire est " << f.aire() << endl;
}
et le programme complet :
#include <iostream>
using namespace std;
class Forme {
public:
// void description() const {
virtual void description() const {
cout << "Ceci est une forme !" << endl;
}
virtual double aire() const = 0;
};
class Cercle : public Forme {
public:
Cercle(double x = 0.0) { rayon = x; }
void description() const {
cout << "Ceci est un cercle !" << endl;
}
double aire() const { return 3.141592653 * rayon * rayon; }
private:
double rayon;
};
class Triangle : public Forme {
public:
Triangle(double h = 0.0, double b = 0.0) { base = b; hauteur = h; }
void description() const {
cout << "Ceci est un triangle !" << endl;
}
double aire() const { return 0.5 * base * hauteur; }
private:
double base; double hauteur;
};
void affichageDesc(Forme& f) {
f.description();
cout << " son aire est " << f.aire() << endl;
}
int main()
{
Cercle c(5);
Triangle t(10, 2);
affichageDesc(t);
affichageDesc(c);
return 0;
}
qui donne comme résultat :
Ceci est un triangle !
son aire est 10
Ceci est un cercle !
son aire est 78.5398
Exercice 2 : encore des vaccins (niveau 1, polymorphisme)
Voici une reprise du code de la série 8 à laquelle a été ajoutée la classe Compagnie :
virtual ~Vaccin()
{}
// surcharge de l'opérateur << pour afficher les
// données relatives à un vaccin
friend ostream& operator<< (ostream& out, const Vaccin& v)
{
out << v.nom << endl;
out << "volume/dose : " << v.volume_dose << endl;
out << "nombre de doses: " << v.nb_doses << endl;
out << "mode de fabrication ";
if (v.mode_fabrication == HighTech)
out << "haute technologie" << endl;
else out << "standard" << endl;
return out;
}
// méthode du calcul du cout de conditionnement
virtual double conditionnement() const{
return (volume_dose * nb_doses) * COND_UNITE;
}
// méthode du calcul du cout de fabrication
virtual double fabrication() const
{
double prix(volume_dose * nb_doses * PRIX_BASE);
if (mode_fabrication == HighTech)
{
prix += prix * MAJORATION_HIGHTECH;
}
return prix;
}
// méthode du calcul du cout de production
virtual double production() const
{
return fabrication()+conditionnement();
}
private:
// nom du vaccin
string nom;
// volume par dose de vaccin
double volume_dose;
// nombre de doses
unsigned int nb_doses;
// mode de fabrication du vaccin
Fabrication mode_fabrication;
};
/*******************************************
* Une classe pour représenter un vaccin
* pouvant etre produit de façon délocalisée
*******************************************/
class Delocalise: public Vaccin{
public:
Delocalise(string _nom, double _volume_dose,
unsigned int _nb_doses, Fabrication _fabrication,
bool _frontalier)
:Vaccin(_nom,_volume_dose , _nb_doses, _fabrication),
frontalier(_frontalier) {}
virtual ~Delocalise()
{}
// masquage de la méthode héritée de Vaccin
double production() const
{
double prix = Vaccin::production();
if (frontalier)
{
prix -= prix * REDUCTION_DELOC;
}
else
{
prix /= 2;
}
return prix;
}
private:
// indique si la production est délocalisée
// dans un pays frontalier ou non
bool frontalier;
};
// un nouveau type pour représenter un vecteur de Vaccin*
/*************************************************
* Une classe pour modéliser une entreprise
* pharmaceutique
*********************************************/
// on aurait aussi pu faire hériter Compagnie de vector<Vaccin*>
class Compagnie {
public:
Compagnie(string _nom)
:nom(_nom)
{}
// ajoute un vaccin produit au stock
void produire(Vaccin* v) {vaccins.push_back(v); }
// calcule le cout de production de l'ensemble des vaccins
double calculer_cout() const;
//vide le stock de vaccins
void vider_stock() { vaccins.clear();
}
// détruit tous les vaccins
void supprimer_vaccins();
// affiche tous les vaccins
void afficher() const
{
for (int i(0); i < vaccins.size(); ++i)
{
cout << *vaccins[i] << endl;
}
}
private:
Stock vaccins;
string nom;
};
void Compagnie::supprimer_vaccins(){
for(unsigned int v(0); v < vaccins.size(); ++v)
delete vaccins[v];
vider_stock();
}
double Compagnie::calculer_cout() const
{
double prix(0);
for(unsigned int v(0); v < vaccins.size(); ++v){
prix += vaccins[v]->production();
}
return prix;
}
// ======================================================================
// un petit main pour tester tout ca
int main() {
cout << "le cout deproduction de v1 et v2 est : ";
cout << v1.production() + v2.production() << endl;
cout << "test des parties suivantes ..." << endl;
Delocalise v3("Zamiflu", 0.55, 15000, HighTech, false);
Delocalise v4("Triphas", 0.20, 15000, Standard, true);
cout << "le cout de production de v3 et v4 est : ";
cout << v3.production() + v4.production() << endl;
// test de la partie de cette semaine
Compagnie c("ICIBA");
c.produire(&v1);
c.produire(&v2);
c.produire(&v3);
c.produire(&v4);
cout << endl;
cout << "Coûts de production de l'ensemble du stock:" << endl;
c.afficher();
cout <<"le cout de production a été de : " << c.calculer_cout() << endl;
return 0;
}
Exercice 3 : encore des figures géométriques (polymorphisme, niveau 2)
Prototypez et définissez les classes [...]
#include <iostream> // pour cout
using namespace std; // pour écrire cout au lieu de std::cout
class Figure {
public:
virtual void affiche () const = 0;
virtual Figure* copie() const = 0;
};
[...] Trois sous-classes (héritage publique) de Figure : Cercle, Carre et Triangle.
class Cercle : public Figure {
};
class Carre : public Figure {
};
class Triangle : public Figure {
};
Une classe nommée Dessin, qui modélise une collection de figures. [...]
class Dessin : private vector<Figure*> {
public:
void ajouteFigure();
};
Plutôt que d'encapsuler la collection dans la classe, je préfère ici dire que la classe Dessin EST UNE collection, et donc hériter de la classe vector. Pour pouvoir bénéficier du polymorphisme, les constituants de cette collection doivent être des pointeurs.
[...] définissez les attributs requis pour modéliser les objets correspondant
class Cercle : public Figure {
private:
double rayon;
};
class Carre : public Figure {
private:
double cote;
};
class Triangle : public Figure {
private:
double base; double hauteur;
};
Définissez également, pour chacune de ces sous-classe, un constructeur pouvant être utilisé comme constructeur par défaut, un constructeur de copie et un destructeur. [...]
class Cercle : public Figure {
public:
Cercle(double x = 0.0) {
cout << "Et hop, un cercle de plus !" << endl;
rayon = x;
}
Cercle(const Cercle& c) {
cout << "Et encore un cercle qui fait des petits !" << endl;
rayon = c.rayon;
}
~Cercle() { cout << "le dernier cercle ?" << endl; }
private:
double rayon;
};
class Carre : public Figure {
public:
Carre(double x = 0.0) {
cote = x;
cout << "Coucou, un carré de plus !" << endl;
}
Carre(const Carre& c) {
cout << "Et encore un carré qui fait des petits !" << endl;
cote = c.cote;
}
~Carre() { cout << "bou... un carré de moins." << endl; }
private:
double cote;
};
class Triangle : public Figure {
public:
Triangle(double h = 0.0, double b = 0.0) {
base = b; hauteur = h;
cout << "Un Triangle est arrivé !" << endl;
}
Triangle(const Triangle& t) {
cout << "Et encore un triangle qui fait des petits !" << endl;
base = t.base;
hauteur = t.hauteur;
}
~Triangle() { cout << "La fin du triangle." << endl; }
private:
double base; double hauteur;
};
Définissez la méthode de copie en utilisant le constructeur de copie.
Cette partie, quoique finalement simple, est peut être d'un abord plus difficile.
Ne vous laissez pas démonter par l'apparente difficulté conceptuelle et, une fois de plus, décomposez le problème :
Que veut-on ?
Retourner le pointeur sur une copie de l'objet :
Figure* copie() const { }
(jusque là c'était déjà donné dans l'énoncé au niveau de Figure
comment fait-on une copie ?
ben .. en utilisant le constructeur de copie.
Par exemple pour la classe Cercle, cela s'écrit Cercle(...)
De qui fait-on une copie ?
de nous-même.
Comment ça s'écrit "nous-même" ?
*this (contenu de l'objet pointé par this).
On a donc : Cercle(*this)
Que veut-on de plus ?
Que la copie soit effectivement placée en mémoire et on veut en retourner l'adresse (allocation dynamique).
Cela se fait avec new
Et donc finalement on aboutit à :
class Cercle : public Figure {
...
Figure* copie() const { return new Cercle(*this); }
...
};
class Carre : public Figure {
...
Figure* copie() const { return new Carre(*this); }
...
};
class Triangle : public Figure {
...
Figure* copie() const { return new Triangle(*this); }
...
};
Finalement, définissez la méthode virtuelle affiche, affichant le type de l'instance et la valeur de ses attributs.
Trivial :
class Cercle : public Figure {
...
void affiche() const {
cout << "Un cercle de rayon " << rayon << endl;
}
...
};
class Carre : public Figure {
...
void affiche() const {
cout << "Un carre de de coté " << cote << endl;
}
...
};
class Triangle : public Figure {
...
void affiche() const {
cout << "Un triangle " << base << "x" << hauteur << endl;
}
...
};
Ajoutez un destructeur explicite pour la classe Dessin [...]
~Dessin() {
cout << "Le dessins s'efface..." << endl;
for (unsigned int i(0); i < size(); ++i) delete (*this)[i];
}
Notez que puisque que la classe Dessin hérite de vector elle possède elle-même une méthode size et un opérateur [].
Prototypez et définissez ensuite les méthodes suivantes à la classe Dessin : [...]
class Dessin : private vector<Figure*> {
public:
~Dessin() {
cout << "Le dessins s'efface..." << endl;
for (unsigned int i(0); i < size(); ++i) delete (*this)[i];
}
void ajouteFigure(const Figure& fig) {
push_back(fig.copie());
}
void affiche() const {
cout << "Je contiens :" << endl;
for (unsigned int i(0); i < size(); ++i) {
(*this)[i]->affiche();
}
}
};
void Gadget::affiche() const {
cout << "Mon nom est " << nom << endl;
}
void Gadget::affiche_prix() const {
cout << "Achetez-moi pour " << prix
<< " francs et vous contribuerez à me sauver!" << endl;
}
L'ajout des constructeurs et destructeurs se fait aussi trivialement :
Gadget::~Gadget() {
cout<< "Je ne suis plus un gadget"<< endl;
}
Définissez une classe Peluche héritant des classes Animal, EnDanger et Gadget :
class Peluche : public Animal, public EnDanger, public Gadget {
};
Dotez votre classe Peluche d'une méthode etiquette [...] être codée au moyen des méthodes affiche et affiche_prix des super-classes. :
class Peluche : public Animal, public EnDanger, public Gadget {
void etiquette() const;
};
void Peluche::etiquette() const {
cout << "Hello," << endl;
Gadget::affiche();
Animal::affiche();
EnDanger::affiche();
affiche_prix();
cout<<endl;
}
puis les constructeurs et destructeurs :
class Peluche : public Animal, public EnDanger, public Gadget {
void etiquette() const;
Peluche(string, string, string, unsigned int, double);
~Peluche();
};
...
Peluche::Peluche(string nom_animal, string nom_gadget, string continent,
unsigned int nombre, double prix)
: Animal(nom_animal, continent), EnDanger(nombre), Gadget(nom_gadget, prix)
{ cout << "Nouvelle peluche" << endl; }
Peluche::~Peluche() {
cout << "Je ne suis plus une peluche" << endl;
}
Testez votre programme au moyen du main suivant [...]
Le résultat obtenu est le suivant :
Nouvel animal protégé
Nouvel animal en danger
Nouveau gadget
Nouvelle peluche
Nouvel animal protégé
Nouvel animal en danger
Nouveau gadget
Nouvelle peluche
Nouvel animal protégé
Nouvel animal en danger
Nouveau gadget
Nouvelle peluche
Hello,
Mon nom est Ming
Je suis un Panda et je vis en Asie
Il ne reste que 200 individus de mon espèce sur Terre
Achetez-moi pour 20 francs et vous contribuerez à me sauver !
Hello,
Mon nom est ssss
Je suis un Cobra et je vis en Asie
Il ne reste que 500 individus de mon espèce sur Terre
Achetez-moi pour 10 francs et vous contribuerez à me sauver !
Hello,
Mon nom est Bello
Je suis un Toucan et je vis en Amérique du Sud
Il ne reste que 1000 individus de mon espèce sur Terre
Achetez-moi pour 15 francs et vous contribuerez à me sauver !
Je ne suis plus une peluche
Je ne suis plus un gadget
ouf! je ne suis plus en danger
Je ne suis plus protégé
Je ne suis plus une peluche
Je ne suis plus un gadget
ouf! je ne suis plus en danger
Je ne suis plus protégé
Je ne suis plus une peluche
Je ne suis plus un gadget
ouf! je ne suis plus en danger
Je ne suis plus protégé
Vous trouverez ici le code complet.
Exercice 5 : Employés (niveau 1)
Codez une classe abstraite Employe [...]
class Employe {
public:
virtual double calculer_salaire() const = 0;
protected:
string prenom;
string nom;
unsigned int age;
string date;
};
Dotez également votre classe d'un constructeur [...] et d'un destructeur virtuel vide.
Codez une hiérarchie de classes pour les employés en respectant les conditions suivantes [...]
La première constatation que l'on peut faire c'est que les deux commerciaux (vendeur et représentant) ont une base commune de calcul. On peut créer une sur-classe commune à ces deux classes.
Appelons la par exemple Commercial. Elle reste bien entendu une classe virtuelle et il n'y a donc qu'à lui affecter l'attribut nécessaire (chiffre_affaire) et bien sûr le constructeur qui va avec.
class Commercial: public Employe {
public:
Commercial(string prenom, string nom, unsigned int age, string date,
double chiffre_affaire)
: Employe(prenom, nom, age, date), chiffre_affaire(chiffre_affaire)
{}
~Commercial() {}
protected:
double chiffre_affaire;
};
On peut alors écrire les deux classes qui en hérite : la classe Vendeur et la classe Representant :
string Representant::get_nom() const {
return "Le représentant " + prenom + ' ' + nom;
}
[NOTE : pour un programme plus soigné, il serait nécessaire de ne pas coder les constantes 400 et 800 "en dur" mais de définir une constante statique à la classe.
Pour un programme aussi petit et délimité cela ne change pas grand chose, mais pour un projet d'envergure, cela a son importance : ne pas mettre de constante en dur dans le code car c'est plus difficile à maintenir (on risque de ne plus savoir où on les a mise ni pourquoi elle on telle ou telle valeur) ].
Les classes Technicien et Manutentionnaire se font de la même façon, sans difficulté :
class Technicien: public Employe {
public:
Technicien(string prenom, string nom, unsigned int age, string date,
unsigned int unites)
: Employe(prenom, nom, age, date), unites(unites)
{}
~Technicien() {}
double calculer_salaire() const;
string get_nom() const;
protected:
unsigned int unites;
};
string Manutentionnaire::get_nom() const {
return "Le manut. " + prenom + ' ' + nom;
}
Employés à risques
Voici finalement l'héritage multiple. Mais avant il faut juste définir la super-classe d'employé à risque :
class ARisque {
public:
ARisque(double prime = 100) : prime(prime) {}
virtual ~ARisque();
protected:
double prime;
};
Nous avons donc ensuite nos deux classes d'employés à risque. Concernant l'ordre d'héritage, il semble ici évident qu'ils sont avant tous des employés (avant d'être « à risque »).
On a donc naturellement :
class TechnARisque: public Technicien, public ARisque {
};
class ManutARisque: public Manutentionnaire, public ARisque {
};
qu'il suffit ensuite compléter par les éléments habituels :
class TechnARisque: public Technicien, public ARisque {
public:
TechnARisque(string prenom, string nom, unsigned int age, string date,
unsigned int unites, double prime)
: Technicien(prenom, nom, age, date, unites), ARisque(prime)
{}
double calculer_salaire() const;
};
Exercice n°28 (page 74 et 239) de l'ouvrage C++ par la pratique.
#include <string>
#include <fstream>
#include <iostream>
#include <limits>
using namespace std;
const string nom_fichier("data.dat"); // le nom du fichier
int main()
{
ofstream fichier(nom_fichier.c_str()); // le flot à destination du fichier
// on teste si l'ouverture du flot s'est bien réalisée
if (fichier.fail()) {
cerr << "Erreur: le fichier " << nom_fichier
<< " ne peut etre ouvert en écriture !" << endl;
} else {
string nom; // la donnée "nom" à lire depuis le clavier
unsigned int age; // la donnée "age" à lire depuis le clavier
// itération sur les demande à entrer
do {
cout << "Entrez un nom (CTRL+D pour terminer) : " << flush;
cin >> nom;
if (!cin.eof()) {
// L'utilisateur a bien saisi un nom, on peut donc lui demander
// de saisir l'age.
cout << "âge : " << flush;
cin >> age;
if (cin.fail()) {
cout << "Je vous demande un age (nombre entier positif) pas "
<< "n'importe quoi !" << endl;
cout << "Cet enregistrement est annulé." << endl;
cin.clear();
// "jette" tout le reste de la ligne
cin.ignore(numeric_limits<streamsize>::max(), '\n');
} else {
// ecriture dans le fichier
fichier << nom << ' ' << age << endl;
}
} else {
cout << endl; // purisme pour allez à la ligne après le dernier flush
}
} while (!cin.eof()); // et on continue tant que cin est lisible.
Exercice n°29 (page 75 et 240) de l'ouvrage C++ par la pratique.
#include <string>
#include <fstream>
#include <iostream>
#include <iomanip>
using namespace std;
const string nom_fichier("data.dat"); // le nom du fichier
int main()
{
// le flot d'entrée en provenance du fichier
ifstream fichier(nom_fichier.c_str());
if (fichier.fail()) {
cerr << "Erreur: le fichier " << nom_fichier
<< " ne peut etre ouvert en lecture !" << endl;
} else {
// si l'ouverture s'est bien produite...
string nom; // les données à lire dans le fichier...
unsigned int age; // ...pas nécessaire de les initialiser
unsigned int nb(0); // variables nécessaires aux différents calculs
unsigned int age_max(0);
unsigned int age_min(300); // je pense que 300 ans est assez large !!
double total(0.0);
// On commence par l'affichage du cadre
cout << "+"
<< setfill('-') << setw(18) << "+"
<< setfill('-') << setw(6) << "+" << endl
<< setfill(' ');
/*
* Et on boucle directement sur la condition de lecture correcte
* du couple <nom,age> (en fait, sur la condition de lecture correcte
* de 'age', mais comme il n'est pas possible de lire 'age' si la
* lecture de 'nom' à échoué...
*/
do {
fichier >> nom >> age;
if (!fichier.fail()) {
// mise à jour des variables utilisées pour les calculs finaux
++nb;
total += age;
if (age_min > age) age_min = age;
if (age_max < age) age_max = age;
Exercice n°30 (page 76 et 242) de l'ouvrage C++ par la pratique.
#include <iostream>
#include <iomanip>
#include <fstream>
using namespace std;
// ===== CONSTANTES =====
// nombre maximum de demandes en cas d'erreur
const unsigned short int NB_DEMANDES(3);
// taille maximum d'une Statistique : au plus 256 car il n'y a pas plus
// que 256 char - attention, avec des entiers signés, on ne peut aller
// au-dela du 127è caractère (sinon, les indices sont négatifs).
const unsigned short int TAILLE(256);
// bornes sur les caractères à prendre en compte
const char start(' ');
const char stop('}');
// ===== DEFINITIONS DE TYPES ======
typedef unsigned long int Statistique[TAILLE];
// ===== FONCTIONS ======
bool demander_fichier(ifstream& f,
unsigned short int max_demandes = NB_DEMANDES);
/* ======================================================================
* Fonction demander_fichier
* ----------------------------------------------------------------------
* In: Un ifstream (par référence) à ouvrir et le nombre maximum de demande
* (par défaut NB_DEMANDES).
* Out: Ouvert ou non ?
* What: Demande à l'utilisateur (au plus max fois) un nom de fichier et
* essaye de l'ouvrir
* ====================================================================== */
bool demander_fichier(ifstream& f, unsigned short int max)
{
string nom;
unsigned short int nb(0);
do {
f.clear(); ++nb;
// demande le nom du fichier
do {
cin.clear();
cout << "Nom du fichier à lire : " << flush;
cin >> nom;
} while (cin.fail());
// essaye d'ouvrir le fichier
f.open(nom.c_str());
// est-ce que ça a marché ?
if (f.fail()) {
cout << "-> ERREUR, je ne peux pas lire le fichier "
<< nom << endl;
} else {
cout << "-> OK, fichier " << nom << " ouvert pour lecture."
<< endl;
}
} while (f.fail() && (nb < NB_DEMANDES));
return !f.fail();
}
/* ======================================================================
* Fonction initialiser_statistique
* ----------------------------------------------------------------------
* In: Une Statistique (par référence) à initialiser.
* What: Initialiser tous les éléments d'une Statistique à zéro.
* ====================================================================== */
void initialise_statistique(Statistique& stat)
{
for (int i(0); i < TAILLE; ++i) {
stat[i] = 0;
}
}
/* ======================================================================
* Fonction collecte_statistique
* ----------------------------------------------------------------------
* In: Une Statistique (par référence) à remplir et le fichier à lire.
* Out: Le nombre d'éléments comptés dans la Statistique.
* What: Lit tous les caractères dans le fichier et compte dans la Statistique
* combien de fois chaque caractère apparait dans le fichier.
* ====================================================================== */
unsigned long int collecte_statistique(Statistique& stat, ifstream& f)
{
char c; // le charactère lu
unsigned long int nb(0); // le nombre d'éléments comptés
while (f.get(c)) {
if ((c >= start) && (c <= stop)) {
++(stat[c-start]);
++nb;
}
}
return nb;
}
/* ======================================================================
* Fonction affiche
* ----------------------------------------------------------------------
* In: La Statistique à afficher, le nombre par rapport auquel on affiche
* les pourcentages (si 0 recalcule ce nombre comme la somme des
* éléments) et la taille du tableau.
* What: Affiche tous les éléments d'une Statistique (valeurs absolue et
* relative).
* ====================================================================== */
void affiche(const Statistique stat, unsigned long int nb,
unsigned short int taille)
{
if (nb == 0) {
for (unsigned short int i(0); i < taille; ++i)
nb += stat[i];
}
const double total(nb);
cout << "STATISTIQUES :" << endl << setprecision(3);
for (unsigned short int i(0); i < taille; ++i) {
if (stat[i] != 0) {
cout << char(i+start) << " : " << setw(11) << stat[i] << " - "
<< setw(5) << 100.0 * stat[i] / total << "%" << endl;
}
}
}
Remarques
En C/C++, le type char peut être un type signé (prenant valeur dans [-128,127]) ou non-signé ([0,255]), selon les systèmes (avec la plupart des compilateurs - dont gcc - il est cependant possible de préciser, lors de la compilation, la convention que l'on souhaite voir appliquer). Par conséquent, si l'on souhaite établir une statistique comprenant l'ensemble des caractères du code ANSI (ou simplement des caractères au-dela du 127è), le code précédent ne fonctionne plus.
Il faut alors soit procéder à une gymnastique compliquée lors des comparaisons des valeurs ou des calculs d'indices, soit déclarer comme caractères non signés tous les char du programme (soit unsigned char). Le problème est qu'on ne peut demander à la fonction get() d'extraire un caractère signé(signed char) ou non signé(unsigned char); elle ne sait qu'extraire un caractère (char). Pour pallier ce manque au niveau de la librairie standard, il n'y a guère d'autre choix que de lire un caractère (signé ou non, celui qui écrit le programme ne pouvant le savoir à l'avance) au moyen d'une variable intermédaire de type char, et de convertir ensuite ce caractère en un entier non signé; idéalement, cela se fait avec l'un des opérateurs de transtypage (cast)...
Comme nous ne souhaitons pas entrer sur ce terrain (il y aurait beaucoup à dire pour que vous puissiez comprendre les subtilités de ces opérations et ce qu'elles impliquent), voici une version 'calculatoire' (donc peu efficace relativement à l'autre solution) :
// bornes sur les caractères à prendre en compte
const unsigned char start(' ');
const unsigned char stop('ÿ');
...
/* ======================================================================
* Fonction collecte_statistique
* ----------------------------------------------------------------------
* In: Une Statistique (par référence) à remplir et le fichier à lire.
* Out: Le nombre d'éléments comptés dans la Statistique.
* What: Lit tous les caractères dans le fichier et compte dans la Statistique
* combien de fois chaque caractère apparait dans le fichier.
* ====================================================================== */
unsigned long int collecte_statistique(Statistique& stat, ifstream& f)
{
char c_lu; // le caractère lu (signé ou non, suivant le système)
unsigned char c; // le caractère lu dans sa forme non signée
unsigned long int nb(0); // le nombre d'éléments comptés
while (f.get(c_lu)) {
c = (256 + c_lu) % 256; // obtenir la version positive de la représentation
if ((c >= start) && (c <= stop)) {
++(stat[c-start]);
++nb;
}
}
Exercice n°31 (page 77 et 245) de l'ouvrage C++ par la pratique.
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
using namespace std;
// nombre maximum de demandes en cas d'erreur
const unsigned short int NB_DEMANDES(3);
// ======================================================================
Examen creer_examen(ifstream& fichier)
{
QCM q;
Examen retour;
bool erreur(false); // une erreur de format s'est produite
bool dansquestion(false); // entrain de lire une question
string line; // ligne à lire
do {
getline(fichier, line); // lit une ligne
if (line[0] != '#') {
// si ce n'est pas un commentaire
if ((line[0] != 'Q') || (line[1] != ':')) {
// si la ligne ne commence pas par "Q:"
if (! dansquestion) {
// Si on n'a pas encore eu de question : qqchose ne va pas !
cerr << "Mauvais format de fichier : pas de \"Q:\"" << endl;
erreur = true;
} else {
// on a déjà eu une question => c'est donc une ligne de réponse :
// lecture de la réponse à la question
if ((line[0] == '-') && (line[1] == '>')) {
// reponse correcte
line.replace(0,2,""); // supprime le "->" initial
if (q.solution != 0) {
cerr << "Hmmm bizard, j'avais déjà une réponse correcte pour"
<< " cette question !" << endl;
cerr << "Q: " << q.question << endl;
}
if (enlever_blancs(line) == "") {
cerr << "??? la réponse indiquée comme correcte est vide !"
<< endl;
erreur = true;
} else {
q.solution = q.reponses.size() + 1;
}
}
// ajoute la réponse courante
if (enlever_blancs(line) != "") q.reponses.push_back(line);
}
} else {
// ligne de question : "Q: ..."
if (dansquestion)
retour.push_back(q); // ajoute la question précédente à l'examen
line.replace(0,2,""); // supprime le "Q:" initial
if (enlever_blancs(line) == "") {
cerr << "??? Question vide !!" << endl;
erreur = true;
} else {
q.question = line; // copie la question
q.reponses.clear(); // remets à zéro les réponses...
q.solution = 0; // ...et la solution
dansquestion = true; // on a une question
}
}
}
} while (!fichier.eof() && !erreur);
// Ne pas oublier la dernière question si elle existe
if (! erreur && dansquestion) retour.push_back(q);
return retour;
}
/* ======================================================================
* Fonction demander_fichier
* ----------------------------------------------------------------------
* In: Un ifstream (par référence) à ouvrir et le nombre maximum de demande
* (par défaut NB_DEMANDES).
* Out: Ouvert ou non ?
* What: Demande à l'utilisateur (au plus max fois) un nom de fichier et
* essaye de l'ouvrir
* ====================================================================== */
bool demander_fichier(ifstream& f, unsigned short int max)
{
string nom;
unsigned short int nb(0);
do {
f.clear(); ++nb;
// demande le nom du fichier
do {
cin.clear();
cout << "Nom du fichier à lire : " << flush;
cin >> nom;
} while (cin.fail());
// essaye d'ouvrir le fichier
f.open(nom.c_str());
// est-ce que ça a marché ?
if (f.fail()) {
cout << "-> ERREUR, je ne peux pas lire le fichier "
<< nom << endl;
} else {
cout << "-> OK, fichier " << nom << " ouvert pour lecture."
<< endl;
}
} while (f.fail() && (nb < NB_DEMANDES));
return !f.fail();
}
// ======================================================================
string& enlever_blancs(string& chaine)
{ // Supprime les blancs initiaux et finaux d'une chaine
unsigned int i;
for (i = 0; (i < chaine.size()) && (chaine[i] == ' '); ++i);
if (i > 0) chaine.replace(0,i,"");
for (i = chaine.size()-1; (i >= 0) && (chaine[i] == ' '); --i);
if (i < chaine.size()-1) chaine.replace(i+1,chaine.size(),"");
return chaine;
}
Exemple de fichier d'examen :
Q:Combien de dents possède un éléphant adulte
32
-> de 6 à 10
beaucoup
24
2
Q: Laquelle des instructions suivantes est un prototype de fonction
int f(0);
int f(int 0);
-> int f(int i);
int f(i);
Q:Qui pose des questions stupides
le prof. de math
mon copain/ma copine
le prof. de physique
moi
le prof. d'info
->personne, il n'y a pas de question stupide
les sondages
Q: Quel signe est le plus étrange
#
->
->->##<-
#b
a
Définissez une classe Vehicule qui a pour attributs des informations valables pour tout type de véhicule : sa marque ; sa date d'achat ; son prix d'achat ; et son prix courant.
Solution :
class Vehicule
{
protected:
string marque;
unsigned int date_achat;
double prix_achat;
double prix_courant;
};
Ces attributs sont «protected» car on ne souhaite pas pouvoir y accéder en dehors de la classe, mais on veut tout de même pouvoir en hériter.
Définissez un constructeur prenant en paramètre la marque, la date d'achat et le prix d'achat .
Une solution possible (une autre dans le fichier complet à la fin) :
class Vehicule
{
public:
Vehicule(string marque, unsigned int date, double prix)
: marque(marque), date_achat(date), prix_achat(prix), prix_courant(prix)
{}
protected:
string marque;
unsigned int date_achat;
double prix_achat;
double prix_courant;
};
Définissez une méthode publique affiche qui affiche la valeur des attributs.
class Vehicule
{
public:
Vehicule(string marque, unsigned int date, double prix)
: marque(marque), date_achat(date), prix_achat(prix), prix_courant(prix)
{}
void Vehicule::affiche(ostream& affichage) const
{
affichage << "marque : " << marque
<< ", date d'achat : " << date_achat
<< ", prix d'achat : " << prix_achat
<< ", prix actuel : " << prix_courant
<< endl;
}
Cette méthode est const car elle ne modifie pas l'état de l'objet.
Note : On pourrait aussi ici surcharger l'opérateur externe ostream& operator<<(ostream&, const Vehicule&).
1.2 La classe Voiture et la classe Avion
Définissez deux classes Voiture etAvion, héritant de la classe Vehicule et ayant les attributs supplémentaires suivants : [...]
Commençons par la classe Voiture. Elle doit hériter de la classe Véhicule :
class Voiture : public Vehicule
{
};
Nous faisons cet héritage de façon publique (comme dans 90% des cas) car on ne souhaite pas cacher le fait que Voiture est un Vehicule (pensez par exemple à une autre classe Limousine qui hériterait de Voiture, on voudrait que cette classe offre également à ses utilisateurs la possibilités d'utiliser les éléments (attributs/méthodes) publics de Vehicule [éléments qui seront ajoutés dans la suite de l'exercice]).
On ajoute ensuite les champs spécifiques à la classe Voiture :
class Voiture : public Vehicule
{
protected:
double cylindree;
unsigned int nb_portes;
double puissance;
double kilometrage;
};
Pour la classe Avion, on procède de même :
class Avion : public Vehicule
{
protected:
Type_Avion moteur;
unsigned int heures_vol;
};
Définissez un constructeur, ainsi que méthode affichant la valeur des attributs. Ces deux méthodes doivent bien entendu être publiques puisqu'elles sont précisément faite pour être utilisée hors de la classe.
class Voiture : public Vehicule
{
public:
Voiture(string marque, unsigned int date, double prix,
double cylindree, unsigned int portes, double cv, double km);
void affiche(ostream&) const;
protected:
double cylindree;
unsigned int nb_portes;
double puissance;
double kilometrage;
};
Ces deux méthodes doivent bien entendu être publiques puisqu'elles sont précisément faites pour être utilisées hors de la classe.
On pourrait tout aussi bien déclarer affiche sans lui passer de paramètre :
void affiche() const;
et la faire directement opérer sur cout.
La définition de ces deux méthodes ne pose aucune difficulté. Voici un exemple possible :
void Voiture::affiche(ostream& affichage) const
{
affichage << " ---- Voiture ----" << endl;
Vehicule::affiche(affichage);
affichage << cylindree << " litres, "
<< nb_portes << " portes, "
<< puissance << " CV, "
<< kilometrage << " km." << endl;
}
Notez que pour le constructeur de Voiture, on fait appel au constructeur de Vehicule :
Voiture::Voiture(string marque, unsigned int date, double prix,
double cylindree, unsigned int portes, double cv,
double km)
: Vehicule(marque, date, prix)
{
...
Les méthodes de la classe Avion s'implémentent de même :
class Avion : public Vehicule
{
public:
Avion(string marque, unsigned int date, double prix,
Type_Avion moteur, unsigned int heures);
void affiche(ostream&) const;
protected:
Type_Avion moteur;
unsigned int heures_vol;
};
void Avion::affiche(ostream& affichage) const
{
affichage << " ---- Avion à ";
if (moteur == HELICES)
affichage << "hélices";
else
affichage << "réaction";
affichage << " ----" << endl;
Vehicule::affiche(affichage);
affichage << heures_vol << " heures de vol." << endl;
}
Encore des méthodes
Ajoutez une méthode void calculePrix() dans la classe Vehicule [...]
Le prix doit rester positif (i.e., s'il est négatif, on le met à 0).
class Vehicule
{
public:
Vehicule(string marque, unsigned int date, double prix);
void affiche(ostream&) const;
void calculePrix();
...
};
...
void Vehicule::calculePrix()
{
double decote((2010 - date_achat) * .01);
prix_courant = max(0.0, (1.0 - decote) * prix_achat);
}
Redéfinissez cette méthode dans les deux sous-classesVoiture et Avion. [...]
Le prototype est le même pour Voiture et Avion que pour Vehicule, par contre les définitions diffèrent.
~Vaccin()
{}
// surcharge de l'opérateur << pour afficher les
// données relatives à un vaccin
friend ostream& operator<< (ostream& out, const Vaccin& v)
{
out << v.nom << endl;
out << "volume/dose : " << v.volume_dose << endl;
out << "nombre de doses: " << v.nb_doses << endl;
out << "mode de fabrication ";
if (v.mode_fabrication == HighTech)
out << "haute technologie" << endl;
else out << "standard" << endl;
return out;
}
// méthode du calcul du cout de conditionnement
double conditionnement() const{
return (volume_dose * nb_doses) * COND_UNITE;
}
// méthode du calcul du cout de fabrication
double fabrication() const
{
double prix(volume_dose * nb_doses * PRIX_BASE);
if (mode_fabrication == HighTech)
{
prix += prix * MAJORATION_HIGHTECH;
}
return prix;
}
// méthode du calcul du cout de production
double production() const
{
return fabrication()+conditionnement();
}
protected:
// nom du vaccin
string nom;
// volume par dose de vaccin
double volume_dose;
// nombre de doses
unsigned int nb_doses;
// mode de fabrication du vaccin
Fabrication mode_fabrication;
};
/*******************************************
* Une classe pour représenter un vaccin
* pouvant etre produit de façon délocalisée
*******************************************/
class Delocalise: public Vaccin{
public:
Delocalise(string _nom, double _volume_dose,
unsigned int _nb_doses, Fabrication _fabrication,
bool _frontalier)
:Vaccin(_nom,_volume_dose , _nb_doses, _fabrication),
frontalier(_frontalier) {}
~Delocalise()
{}
// masquage de la méthode héritée de Vaccin
double production() const
{
double prix = Vaccin::production();
if (frontalier)
{
prix -= prix * REDUCTION_DELOC;
}
else
{
prix /= 2;
}
return prix;
}
private:
// indique si la production est délocalisée
// dans un pays frontalier ou non
bool frontalier;
};
// ======================================================================
// un petit main pour tester tout ca
int main() {
cout << "le cout deproduction de v1 et v2 est : ";
cout << v1.production() + v2.production() << endl;
cout << "test des parties suivantes ..." << endl;
Delocalise v3("Zamiflu", 0.55, 15000, HighTech, false);
Delocalise v4("Triphas", 0.20, 15000, Standard, true);
cout << "le cout de production de v3 et v4 est : ";
cout << v3.production() + v4.production() << endl;
return 0;
}
Exercice 3 : Héritage d'un vector (niveau 2)
Ici l'héritage privé impose quelques contraintes sur le codage de la surcharge d'opérateur. Voici une solution possible :
#include <iostream>
#include <vector>
using namespace std;
// un Porte monnaie est implémenté comme un vecteur d'entiers.
class PorteMonnaie : private vector <int>
{
public:
void gagner(int sous)
{
// un porte monnaie hérite de vecteur
// il possède la méthode push_back par héritage
// (idem pour toutes les autres méthodes héritées)
push_back(sous);
}
void vider()
{
clear();
}
// cette méthode (ou un équivalent) doit etre définie pour permettre
// à l'opérateur d'affichage d'avoir accès à la taille du vecteur
// Utiliser directement le size hérité de vector n'est pas possible
// à l'extérieur de la classe PorteMonnaie car l'héritage est privé.
//
// Le const doit etre mis. Autrement, l'opérateur d'affichage ne
// pourrait pas avoir un object constant comme second argument.
int size() const
{
// cette méthode réutilise la méthode masquée héritée de
// vector
return vector<int>::size();
}
// meme commentaire que pour la méthode size.
// notez la syntaxe utilisée pour l'indexation
int getI(int i) const
{
return (*this)[i];
}
};
//OPERATEUR D'AFFICHAGE
ostream& operator<<(ostream& out, const PorteMonnaie& p)
{
int total(0);
for (int i(0); i < p.size(); ++i){
total += p.getI(i);
//total += p[i]; // pas possible car l'héritage est privé.
}
out << "Mon porte monnaie contient : " << total << " francs" << endl;
return out;
}
//PROGRAMME PRINCIPAL
int main()
{
PorteMonnaie p;
p.gagner(2);
p.gagner(3);
p.gagner(12);