Académique Documents
Professionnel Documents
Culture Documents
La programmation
en
C++
avec
Unix
7è Edition
Notes de cours
IFT-19965
Denis Laurendeau
& Denis Dion Jr.
CHAPITRE 1 Introduction..............................................................................................................5
CHAPITRE 2 La compilation et l’exécution d’un programme simple...........................................9
CHAPITRE 3 La déclaration des variables en C++......................................................................17
CHAPITRE 4 Les expressions arithmétiques ...............................................................................23
CHAPITRE 5 La lecture des informations au clavier ...................................................................31
CHAPITRE 6 Les fonctions en C++.............................................................................................35
CHAPITRE 7 De l’intérêt d’utiliser les fonctions en C++ ...........................................................45
CHAPITRE 8 Les variables locales et les variables globales .......................................................47
CHAPITRE 9 La création des classes et des objets en C++ .........................................................55
CHAPITRE 10 La définition de fonctions membres ......................................................................65
CHAPITRE 11 Les constructeurs ...................................................................................................73
CHAPITRE 12 Les fonctions membres de lecture et d’écriture .....................................................79
CHAPITRE 13 Comment tirer profit de l’abstraction des données ................................................89
CHAPITRE 14 La protection contre les accès accidentels aux membres des classes ....................91
CHAPITRE 15 Les instructions de préprocesseur ..........................................................................99
CHAPITRE 16 La notion d’héritage en C++................................................................................107
CHAPITRE 17 La conception de hiérarchies de classes ..............................................................123
CHAPITRE 18 Les tests utilisant les prédicats numériques .........................................................127
CHAPITRE 19 Les énoncés conditionnels ...................................................................................131
CHAPITRE 20 La combinaison logique d’expressions booléennes.............................................139
CHAPITRE 21 Les itérations en C++...........................................................................................147
CHAPITRE 22 Le traitement des données contenues dans des fichiers .......................................155
CHAPITRE 23 Les tableaux de nombres......................................................................................161
CHAPITRE 24 Les tableaux d’objets ...........................................................................................165
CHAPITRE 25 La créations de flots de données d’entrée et de sortie .........................................169
CHAPITRE 26 La création d’objets au “run-time” ......................................................................175
CHAPITRE 27 Le stockage des pointeurs aux objets d’une classe. .............................................183
CHAPITRE 28 Introduction aux fonctions virtuelles (virtual) .....................................................189
CHAPITRE 29 Les énoncés conditionnels multiples ...................................................................211
CHAPITRE 30 Les énumérations en C++ ....................................................................................215
CHAPITRE 31 Appel de constructeurs à partir d’autres constructeurs ........................................219
CHAPITRE 32 Fonctions membres appelant d’autres fonctions membres ..................................225
CHAPITRE 33 Les variables privées (private) et protégées (protected) ......................................229
CHAPITRE 34 Les dérivations de classes protected et private ....................................................239
CHAPITRE 35 Les fonctions qui retournent des chaînes de caractères .......................................243
CHAPITRE 36 Le passage des paramètres par référence .............................................................255
CHAPITRE 37 La surdéfinition de l’opérateur d’insertion ..........................................................269
CHAPITRE 38 La surdéfinition des opérateurs: notions fondamentales......................................277
CHAPITRE 1 Introduction
• la non-duplication
• la visibilité locale
• la simplicité
• les connaissances nécessaires
• la réduction des calculs inutiles
Résumé
10 ✔Le langage C++ repose sur la programmation objet qui insiste
sur les notions de types de données et de hiérarchie entre les
types de données.
✔C++ résulte de l’évolution du langage C.
CHAPITRE 2 La compilation et
l’exécution d’un
programme simple
Correction des
erreurs appa-
Exécution du code exécutable raissant lors de
l’exécution
1. Notez qu’il n’y a aucun lien entre le code objet et les objets dont il est question en programma-
tion objet. Pour des raisons historiques, l’appellation code objet est apparue bien avant la notion
de programmation objet et la nomenclature n’a pas été mise à jour.
Notion avancée
17 En mathématiques, une fonction sert à relier des variables
d’entrée à des variables de sortie. En programmation en C++
cependant, le terme fonction est utilisé avec un sens différent
puisqu’une fonction peut avoir des effets secondaires1 cachés
autres que le simple lien entre les variables d’entrée et les
variables de sortie. Le terme procédure serait plus approprié
mais celui de fonction est tellement passé à l’usage depuis
longtemps que nous l’adopterons dans la suite des notes.
22 Il faut noter que le C++ fait fi des espaces blancs1. C++ traite
donc les espaces blancs, suite d’espaces blancs, tabulations et
retours de chariot comme s’il s’agissait d’un seul espace blanc.
Les différents programmes suivants sont équivalents:
#include <iostream.h>
main () {
cout << “La puissance dissipée est: “;
cout << 10 * 20 * 20;
cout << endl;
}
#include <iostream.h>
main () {
cout << “La puissance dissipée est: “;
cout << 10 * 20 * 20; cout << endl;
}
#include <iostream.h>
main () { cout << “La puissance dissipée est: “;
cout << 10 * 20 * 20;
cout << endl;
}
#include <iostream.h>
main () { cout << “La puissance dissipée est: “;
cout << 10 * 20 * 20; cout << endl; }
Il n’y a pas vraiment de standard pour l’écriture du code source.
Cependant, il y a tout avantage à le garder très aéré, ce qui
permet de bien identifier les différentes parties du programme.
1. Un peu comme lorsqu’on appuie sur la touche “return” d’une machine à dactylographier.
1. En anglais, on dit que C++ est “blank insensitive”
Exercice
28 Ecrivez un programme calculant le volume de la terre en
mètres cubes
Résumé
29 ✔En C++, comme dans plusieurs autres langages de
programmation, on écrit d’abord du code source qu’on compile
pour obtenir le code objet. Ce code objet est traité par l’éditeur
de lien pour produire le code exécutable.
✔Les fonctions en C++ contiennent une série d’énoncés
permettant d’accomplir une tâche donnée.
✔Tous les programmes en C++ contiennent une fonction
appelée main. Lors de l’exécution d’un programme, on démarre
les opérations contenues dans cette fonction main.
✔Plusieurs instructions impliquent l’utilisation d’opérateurs
intégrés au C++ tels * et <<. Ces opérateurs travaillent sur des
opérandes.
✔Si l’opérateur << est utilisé, il faut en informer le compilateur
en incluant la ligne suivante au début du programme:
#include <iostream.h>
1. “underscore” en anglais.
Résultat
La puissance dissipée est: 400000
Notion avancée
42 Pour les plus curieux, il est possible de connaître la quantité de
mémoire qui est réservée pour un type de données grâce à
l’opérateur sizeof du C++. Le code suivant montre
l’utilisation de cet opérateur:
//&% sizeof.C. Ceci est un commentaire qui contient le nom
//du fichier Unix dans lequel est stockée la source.
#include <iostream.h>
main () {
cout<< “Type de donnees Octets” << endl
<< “char“ << sizeof(char) << endl
<< “short“ << sizeof(short) << endl
<< “int“ << sizeof(int) << endl
<< “long“ << sizeof(long) << endl
<< “float“ << sizeof(float) << endl
<< “double“ << sizeof(double) << endl
<< “long double“ << sizeof(long double) << endl;
}
Résultat
Type de donnees Octets
char 1
short 2
int 4
long 4
float 4
double 8
long double 16
Notion avancée
43 Parce que long et long double sont souvent trop grands pour
stocker les nombres usuels, plusieurs compilateurs ne
supportent pas ces types de données et les convertissent
souvent en int et double.
Résumé
47 ✔ Une variable est un identificateur qui correspond à un espace
de mémoire.
1. “slash” en anglais
(6 + 3) * 2; //Resultat = 18
Remarquez qu’il n’est pas interdit de renforcer la priorité par
défaut du C++ pour la rendre explicite et plus facilement
compréhensible:
6 + 3 * 2;
et
6 + (3 * 2);
sont identiques et produisent le même résultat. Cependant, la
deuxième expression rend explicite la priorité des opérateurs et
clarifie le code. L’utilisation des parenthèses est une bonne
habitude de programmation, surtout lorsque vous avez à
travailler avec des expressions compliquées.
1. “warnings” en anglais
Notion avancée
63 Contrairement aux autres opérateurs du C++, l’opérateur =
procède à une associativité de droite à gauche, ce qui signifie
que:
x = y = 5; //equivalent a x = (y = 5)
autrement, cela signifierait que l’on aurait (x = y) = 5, ce
qui n’a aucun sens.
<< endl;
}
Résultat
La puissance dissipée est: 20000
Notion avancée
68 La raison pour laquelle l’opérateur = a une priorité inférieure à
celle de l’opérateur de sortie << vient du fait que << a une
autre signification pour laquelle la priorité a un sens. Plus
spécifiquement, quand l’expression à la gauche de << est une
donnée de type entier, << a alors le sens de l’opérateur de
décalage à gauche, hérité du C.Nous verrons plus loin le sens
de cet opérateur.
Opérateurs Associativité
- (unaire), + (unaire) droite à gauche
*,/,% gauche à droite
+,- gauche à droite
<< gauche à droite
= droite à gauche
Résumé
71 ✔C++ offre des opérateurs de négation, d’addition, de
soustraction, de multiplication, de division, de sortie et
d’affectation et suit les règles courantes de priorité et
d’associativité.
✔Pour rendre les expressions arithmétiques plus claires,
l’usage des parenthèse est recommandé.
✔L’opérateur de sortie à une priorité inférieure à celle des
opérateurs arithmétiques. L’opérateur d’affectation a une
priorité encore plus basse que tous ces opérateurs.
Résultat
SVP, entrer des entiers au clavier:
1 2 3
Le produit des nombres est: 6
Notion avancée
76 Nous verrons un peu plus loin comment lire des données
contenues dans un fichier de données autre que le clavier. En
fait, le clavier est en quelque sorte un fichier spécial...
En attendant de voir plus en profondeur les mécanismes de
manipulation des fichiers à partir d’un programme en C++, les
utilisateurs de DOS ou de UNIX peuvent avoir recours au
mécanisme de redirection qui permet de spécifier à un
programme que les données provenant du clavier proviendront
plutôt d’un fichier de données. Par exemple, si le fichier
“test.data” contient les nombres 1 2 3, le programme
Exercice
77 Concevez un programme qui calcule le volume d’une boîte
rectangulaire. Les longueurs des côtés de la boîte doivent être
lus au clavier de l’ordinateur lors de l’exécution du programme.
Résumé
78 ✔ Si l’opérateur d’entrée >> est utilisé, il faut en informer le
compilateur en incluant la ligne suivante au début du
programme: #include <iostream.h>
✔ Si le programme doit utiliser des données fournies au clavier,
il suffit d’utiliser un énoncé incluant cin: cin >> variable;
✔ Si on désire rediriger l’entrée d’un programme du clavier vers
un fichier de données, il suffit d’utiliser la redirection: nom du
programme < nom du fichier
return r * c * c;
main () {
Résultat
La puissance dissipee est:4000
La puissance dissipee est:250000
Notion avancée
89 Quand la valeur de retour d’une fonction est de type entier int,
il n’est pas nécessaire d’écrire ce détail dans la déclaration de la
fonction. Par exemple, la déclaration de la fonction
puissance_dissipee du paragraphe 86 pourrait s’écrire:
puissance_dissipee (int r, int c);
parce que lorsque le type de la valeur de retour est omis, le
compilateur assume que le type est int. Cependant, les bons
programmeurs incluent toujours le type de la valeur de retour
par souci de clarté.
#include <iostream.h>
int fonction_1() {
return 1;
}
void fonction_appel(){
cout << “appel de la fonction “ << fonction_1();
cout << endl;
}
void main () {
fonction_appel();
Résultat
appel de la fonction 1
#include <iostream.h>
int puissance_dissipee (int r, int c){
cout << “Version (int): “;
return r * c * c;
}
void main () {
Résultat
La puissance dissipee est:Version (int): 4000
La puissance dissipee est Version (double): 2744.86
Exercice
96 Concevez une fonction qui reçoit en argument la valeur de deux
résistances et qui retourne la valeur de la combinaison en série
de ces résistances. Le prototype de la fonction devrait être le
suivant:
double r_serie(double r1, double r2);
La valeur de la combinaison en série de deux résistances est
simplement la somme de la valeur de chaque résistance.
Résumé
97 ✔ Lorsqu’une fonction est appelée dans un programme, ses
arguments sont évalués et copiés dans les paramètres. Ensuite,
les instructions du corps de la fonction sont exécutés. Lorsque
la fonction retourne une valeur, l’argument de l’énoncé return
est évalué et cette valeur devient la valeur de la fonction à
l’appel.
✔ La déclaration d’une fonction exige que le type de chaque
argument soit spécifié de même que le type de la valeur de
retour (qui peut être void si la fonction ne retourne aucune
valeur).
✔ Le C++ supporte la surdéfinition de fonctions qui consiste à
définir deux fonctions ayant le même nom mais qui diffèrent de
par leurs paramètres ou leur valeur de retour.
1. debugger quoi!
1. “extent” en anglais.
2. “scope” en anglais.
void main() {
int a = 5;
double b = 8.9;
fonction_1(a,b);
Résultat
Avant l’appel de la fonction
a: 5 b: 8.9
Dans la fonction
a: 15 b: 29.1
Apres l’appel de la fonction
a: 5 b: 8.9
111 Les règles pour les variables globales, celles qui sont définies à
l’extérieur de toutes les fonctions, sont très différentes:
1. la valeur des variables globales est accessible de toutes les
fonctions définies après la définition de la variable sauf dans
le cas où un paramètre ou une variable locale de la fonction
possède le même nom. Dans ce cas, on dit que la variable
locale masque la variable globale.
2. aux endroits où une variable globale n’est pas masquée par
une variable ou un paramètre local, sa valeur peut être
modifiée par une opération d’affectation. Ce changement est
permanent dans le sens où la nouvelle valeur stockée dans la
variable globale efface l’ancienne valeur qui s’y trouvait et
cette valeur est définitivement perdue.
ω = 2πf EQ 1
void main() {
double f = 20;
double t = 8.7;
Résultat
Phase: 1093.27
Notion avancée
114 Au lieu de définir nous-même la constante mathématique pi
dans le programme ci-dessous, nous aurions pu procéder de
façon plus judicieuse en utilisant directement les ressources
mises à notre disposition par les librairies de constantes et de
fonctions mathématiques du C++. Dans cette librairie, dont les
ressources sont disponibles dans le fichier d’inclusion math.h,
la constante mathématique π est identifiée par la macro M_PI
(notez bien les majuscules). Une macro est traitée par le
compilateur de façon telle que lorsqu’elle est rencontrée dans
un programme, elle est immédiatement remplacée dans le code
par sa valeur (ici 3.14159 pour M_PI). Le programme de calcul
de phase prendrait donc la forme suivante:
//&% var_glob_math.C
#include <iostream.h>
#include <math.h> //Inclusion de la lib. mat. du C++
void main() {
double f = 20;
double t = 8.7;
Résultat
Phase: 1093.27
Notion avancée
116 Les variables globales static sont des variables dont la portée
s’étend à un fichier d’un programme réparti sur plusieurs
fichiers. Une variable globale static peut être évaluée ou être
l’objet d’une affectation en tout point d’un fichier après qu’elle
ait été déclarée mais ne peut être évaluée ou être l’objet d’une
affectation dans les autres fichiers. Nous traiterons de ce détail
plus loin.
Résumé
117 ✔ Une variable locale est une variable qui est déclarée à
l’intérieur d’une fonction. Une variable globale est pour sa part
définie à l’extérieur de toutes les fonctions d’un programme.
✔ C++ isole les variables locales et les paramètres des variables
globales, ce qui permet d’en réutiliser le nom. La valeur des
paramètres et des variables locales n’est plus disponible après
le retour de la fonction. Lorsqu’une fonction appelle une autre
fonction, la valeur des paramètres et des variables locales de la
fonction appelante ne sont pas accessibles durant l’exécution de
la fonction appelée.
✔ La valeur d’une variable globale est accessible partout sauf
lorsque cette variable globale est masquée par une variable
118 Pour décrire une porte logique NAND à deux entrées, on pense
naturellement à la valeur des deux entrées et à la valeur de
sortie compte tenu de ces entrées. Ces quantités forment un
tout pour chaque porte NAND prise individuellement. Chaque
porte NAND à deux entrées d’un circuit peut être vue comme
un objet. Il en est de même pour une porte NOR à deux entrées.
Dans ce chapitre, nous abordons l’un des concepts qui font du
C++ un langage de programmation très puissant. Ce concept,
celui des classes et des objets, permet au programmeur de
créer, construire, et manipuler des groupes de données qui
décrivent des individus ou des catégories d’objets du monde
réel. Ce concept distingue le C++ de la plupart des autres
langages de programmation, y compris le C.
120 Les types de données standards tel int, double, long, char,
float, sont également des classes. Plus spécifiquement, ils
sont des classes intrinsèques1 au C++. Les classes conçues
par le programmeur et utilisant les classes intrinsèques sont
appelés classes extrinsèques2.
Les individus appartenant à ces classes intrinsèques sont
appelés objets de données intrinsèques.
class
NAND_2
{
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int out_1; /*sortie */
};
La FIGURE 2 montre la signification de chaque partie du code.
La définition de la classe NAND ne décrit que les variables
in_1, in_2 et out_1 seulement. Aucune définition de fonction
n’est incluse dans la définition de la classe. De plus, la
définition de la classe indique au compilateur, via le mot
réservé public:, que les variables de la classe seront
125 Une fois que la classe NAND est définie, on peut créer un objet
de type NAND_2 en déclarant une variable de la façon
suivante:
NAND_2 nnd;
126 Une fois qu’un objet de la classe NAND_2 a été créé, on peut
accéder au contenu des variables membres de cet objet en
utilisant le nom de l’objet et en faisant appel à l’opérateur
d’accès aux membres d’une classe, symbolisé par un point “.”,
suivi du nom de la variable membre. Par exemple, pour accéder
au contenu de la variable membre in_1 de l’objet nnd de la
classe NAND_2, il suffit d’utiliser la notation suivante:
nnd.in_1.
Une fois la méthode d’accès aux variables membres établie, on
peut lire le contenu des variables membres ou même en
modifier le contenu en effectuant des affectations à ces
variables. Par exemple, on peut concevoir un programme qui
affecte des valeurs aux variables membres d’entrée et qui
calcule, via une fonction, la valeur de la variable de sortie d’une
porte NAND. Le programme suivant exécute cette tâche:
//&% nand_ch_9_2.C
#include <iostream.h>
class NAND_2 {
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int out_1; /*sortie */
};
void main() {
NAND_2 nnd;
cout << “La table de verite d’une porte nand” << endl;
cout << “est: “ << endl;
cout << endl << endl;
cout << “entree 1 entree 2 sortie” << endl;
nnd.in_1 = 0 ; nnd.in_2 = 0 ;
nnd.in_1 = 0 ; nnd.in_2 = 1 ;
cout << “ “ << nnd.in_1
<< “ “ << nnd.in_2 << “ “
<< set_out_1(nnd.in_1, nnd.in_2) << endl;
nnd.in_1 = 1 ; nnd.in_2 = 0 ;
cout << “ “ << nnd.in_1
<< “ “ << nnd.in_2 << “ “
<< set_out_1(nnd.in_1, nnd.in_2) << endl;
nnd.in_1 = 1 ; nnd.in_2 = 1 ;
cout << “ “ << nnd.in_1
<< “ “ << nnd.in_2 << “ “
<< set_out_1(nnd.in_1, nnd.in_2) << endl;
}
Résultat
Notion avancée
127 On peut se demander pourquoi une définition de classe doit se
terminer par un “;” comme c’est le cas pour la classe NAND_2 et
toute classe en C++. Pourquoi l’accolade droite “}” ne suffit-elle
//&% nor_ch_9_1.h
class
NOR_2
{
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int out_1; /*sortie */
};
class
NAND_2
{
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int out_1; /*sortie */
};
class NOR_2 {
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int out_1; /*sortie */
};
void main() {
NAND_2 nnd;
NOR_2 nor;
cout << “La table de verite d’une porte nand” << endl;
cout << “est: “ << endl;
cout << endl << endl;
cout << “entree 1 entree 2 sortie” << endl;
nnd.in_1 = 0 ; nnd.in_2 = 0 ;
cout << “ “ << nnd.in_1
<< “ “ << nnd.in_2 << “ “
<< set_out_1(nnd) << endl;
nnd.in_1 = 0 ; nnd.in_2 = 1 ;
cout << “ “ << nnd.in_1
<< “ “ << nnd.in_2 << “ “
<< set_out_1(nnd) << endl;
nnd.in_1 = 1 ; nnd.in_2 = 0 ;
cout << “ “ << nnd.in_1
<< “ “ << nnd.in_2 << “ “
<< set_out_1(nnd) << endl;
nnd.in_1 = 1 ; nnd.in_2 = 1 ;
cout << “ “ << nnd.in_1
<< “ “ << nnd.in_2 << “ “
<< set_out_1(nnd) << endl << endl;
cout << “La table de verite d’une porte nor” << endl;
cout << “est: “ << endl;
cout << endl << endl;
cout << “entree 1 entree 2 sortie” << endl;
nor.in_1 = 0 ; nor.in_2 = 0 ;
cout << “ “ << nor.in_1
<< “ “ << nor.in_2 << “ “
<< set_out_1(nor) << endl;
nor.in_1 = 0 ; nor.in_2 = 1 ;
cout << “ “ << nor.in_1
<< “ “ << nor.in_2 << “ “
<< set_out_1(nor) << endl;
nor.in_1 = 1 ; nor.in_2 = 0 ;
cout << “ “ << nor.in_1
<< “ “ << nor.in_2 << “ “
<< set_out_1(nor) << endl;
nor.in_1 = 1 ; nor.in_2 = 1 ;
cout << “ “ << nor.in_1
<< “ “ << nor.in_2 << “ “
<< set_out_1(nor) << endl;
}
Résultat
La table de verite d’une porte nand
est:
est:
Notion avancée
130 La fonction set_out_1 ci-dessus reçoit un objet en paramètre.
Le C++ se comporte dans ce cas de la même manière que pour
le cas d’un type de données intrinsèque: l’objet passé en
argument est copié dans l’espace réservé pour le paramètre lors
de l’appel de la fonction et la fonction travaille donc sur une
copie de l’objet défini dans le programme main. Nous verrons
plus tard que cette façon de procéder est déconseillée en
programmation C++ et nous verrons les moyens de contourner
cette pratique.
Exercice
131 Concevez une classe OR_2 pour décrire une porte OR à deux
entrées.
Exercice
132 Concevez une fonction qui reçoit un objet de type OR_2 et qui
affiche sa table de vérité.
Résumé
133 ✔ En C++, les classes correspondent à des catégories et les
objets appartenant à cette classe correspondent à des individus
de cette catégorie.
CHAPITRE 10 La définition de
fonctions membres
int set_out_1() {
return 1 - (in_1 * in_2);
}
};
int set_out_1() {
return 1 - (in_1 * in_2);
}
};
void main() {
NAND_2 nnd;
cout << “La table de verite d’une porte nand” << endl;
cout << “est: “ << endl;
cout << endl << endl;
cout << “entree 1 entree 2 sortie” << endl;
nnd.in_1 = 0 ; nnd.in_2 = 0 ;
nnd.in_1 = 0 ; nnd.in_2 = 1 ;
cout << “ “ << nnd.in_1
<< “ “ << nnd.in_2 << “ “
<< nnd.set_out_1() << endl;
nnd.in_1 = 1 ; nnd.in_2 = 0 ;
cout << “ “ << nnd.in_1
nnd.in_1 = 1 ; nnd.in_2 = 1 ;
cout << “ “ << nnd.in_1
<< “ “ << nnd.in_2 << “ “
<< nnd.set_out_1() << endl;
}
Résultat
La table de verite d’une porte nand
est:
return val;
}
};
Notion avancée
142 Remarquez qu’à proprement parler, l’inclusion du prototype de
la fonction membre dans la définition de la classe est une
déclaration de la fonction.
class
NAND_2
{
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int out_1; /*sortie */
int set_out_1();
};
int set_out_1();
};
int NAND_2::set_out_1() {
return 1 - (in_1 * in_2);
}
Le nom de la classe et les deux points précédant le nom de la
fonction ont deux utilités:
Notion avancée
144 Nous verrons plus loin la notion de fonction en ligne. Le
compilateur tente de compiler ces fonctions définies à
l’intérieur des classes. Par contre, pour les fonctions déclarées
dans les classes grâce à un prototype et définies à l’extérieur de
celles-ci grâce à l’opérateur d’évaluation de portée, le
compilateur ne tente aucune compilation.
Résumé
146 ✔ Si l’on désire transformer une fonction acceptant un
paramètre appartenant à une classe en une fonction membre de
la classe, il suffit de définir la fonction à l’intérieur de la
définition de la classe et d’éliminer le paramètre de la fonction
correspondant à cette classe de la liste des paramètres de la
fonction et du nom des variables membres accédées via
l’opérateur d’accès aux membres d’une classe.
✔ Si une fonction membre comporte un grand nombre
d’énoncés, il est préférable d’inclure seulement son prototype à
la définition de la classe et d’écrire le code qui lui est associé à
l’extérieur de la définition de la classe en utilisant l’opérateur
d’évaluation de portée “::” flanqué à gauche du nom de la
classe et à droite du nom de la fonction membre pour spécifier à
quelle classe appartient la fonction membre:
nom_classe::nom_fonction
✔ Pour appeler une fonction membre, il suffit d’utiliser
l’opérateur d’accès aux membres d’une classe avec le nom de
l’objet pour lequel la fonction est appelée et le nom de la
1. “constructor” en anglais
NAND_2() {
in_1 = 0; in_2 = 0;
out_1 = set_out_1();
}
int set_out_1() {
return 1 - (in_1 * in_2);
}
};
int set_out_1() {
return 1 - (in_1 * in_2);
}
};
Notion avancée
154 Il faut absolument inclure un constructeur par défaut dans une
classe dans la situation suivante: un constructeur avec
arguments est défini dans la classe et on écrit, dans le
programme, une définition de variable telle que NAND_2 nnd;.
Dans ce cas, en l’absence de constructeur par défaut, le
compilateur conclut à une erreur de syntaxe puisque le
constructeur de la classe est appelé mais les arguments
manquent. Le compilateur ne prend pas sur lui d’appeler un
constructeur par défaut implicite.
Notion avancée
155 Il serait aussi possible de concecoir un constructeur avec
initialisation qui remplacerait en une seule étape le
constructeur par défaut et le constructeur avec arguments tels
qu’implantés au paragraphe 152. Ce constructeur aurait la
forme suivante:
NAND_2(int i1=0, int i2=0){
in_1 = i1; in_2 = i2;
out_1 = set_out_1();
}
Exercice
156 Définissez un constructeur par défaut pour la classe NOR_2.
Résumé
157 ✔ Les constructeurs effectuent des opérations, tel
l’initialisation de variables membres, lors de la création d’objets
d’une classe.
✔ Un constructeur porte le nom de sa classe et ne retourne
aucune valeur.
✔ Le constructeur par défaut ne reçoit aucun paramètre. Il est
conseillé de toujours définir un constructeur par défaut
explicitement dans une classe.
✔ Si on veut initialiser les objets aux valeurs par défaut, il
suffit de déclarer ces objets en respectant la syntaxe suivante:
nom_de_la_classe nom_de_l’objet;.
✔ Si on veut initialiser un objet à des valeurs spécifiques lors de
sa création, il suffit de définir un constructeur avec paramètres
et de déclarer l’objet en respectant la syntaxe suivante:
class
NAND_2 {
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int out_1; /*sortie */
int set_out_1() {
return 1 - (in_1 * in_2);
}
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
};
class
NAND_2 {
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int out_1; /*sortie */
int set_out_1() {
return 1 - (in_1 * in_2);
}
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
};
void main() {
NAND_2 nnd_1;
NAND_2 nnd_2(0,1);
Résultat
entree 1, nand 1: 0
entree 2, nand 1: 0
sortie, nand 1: 1
entree 1, nand 2: 0
entree 2, nand 2: 1
sortie, nand 2: 1
161 Un autre attrait des fonctions membres de lecture est qu’il est
possible d’accéder des variables membres fictives d’un objet.
Ces variables membres sont en fait calculées à partir des
valeurs des variables membres réelles. Par exemple, on
pourrait être intéressés à accéder au complément (appelé ici
out_1_not) de la valeur de la variable membre de sortie
out_1 d’un objet de la classe NAND_2. Il suffit simplement de
créer la fonction membre de lecture suivante et de l’inclure à la
définition de la classe:
int lire_out_1_not() {
return 1 - out_1;
}
162 D’autre part, il est aussi possible d’affecter une valeur à une
variable membre par le biais d’une fonction membre
d’écriture plutôt que par l’opérateur d’accès aux membres “.”.
Par exemple, on peut définir une fonction membre d’écriture
pour changer la valeur des entrées d’une porte NAND_2. La
classe NAND_2 devient dans ce cas:
//&% nand_ch_12_2.h
class
NAND_2 {
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int out_1; /*sortie */
int set_out_1() {
return 1 - (in_1 * in_2);
}
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
int lire_out_1() {
return out_1;
}
};
#include <iostream.h>
class
NAND_2 {
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int out_1; /*sortie */
int set_out_1() {
return 1 - (in_1 * in_2);
}
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
};
void main() {
NAND_2 nnd_1;
nnd_1.ecrire_in_1(1);
nnd_1.ecrire_in_2(1);
cout << “in_1: “ << nnd_1.lire_in_1() << endl;
cout << “in_2: “ << nnd_1.lire_in_2() << endl;
cout << “out_1: “ << nnd_1.lire_out_1() << endl;
Résultat
in_1: 0
in_2: 0
out_1: 1
in_1: 1
in_2: 1
out_1: 0
164 Un autre attrait des fonctions membres d’écriture est qu’il est
possible d’affecter des variables membres fictives d’un objet.
Les variables membres réelles sont en fait calculées à partir
des valeurs des variables fictives. Par exemple, on pourrait être
intéressés à affecter une valeur au complément (appelé ici
in_1_not) de la valeur de la variable membre d’entrée in_1
Exercice
166 Ecrivez des fonctions membres de lecture et d’écriture pour la
classe NOR_2
Résumé
167 ✔ Les fonctions membres de lecture et d’écriture sont une
approche indirecte d’accès aux variables membres des objets
d’une classe.
✔ On peut définir des fonctions membres de lecture ou
d’écriture pour des variables membres réelles ou fictives.
Résumé
173 ✔ Les constructeurs et les fonctions membres d’écriture et de
lecture sont appelées fonctions d’accès.
✔ Ces fonctions d’accès permettent de faire de l’abstraction de
données.
✔ Les avantages de l’abstraction de données sont: de faciliter la
réutilisation du code, de faciliter la lecture du code, de
permettre de modifier une classe sans avoir à modifier le
programme qui l’utilise et, enfin, de permettre d’améliorer la
façon dont les données d’une classe sont manipulées.
174 Au CHAPITRE 12, nous avons appris à initialiser les objets via
les constructeurs et à aiguiller l’accès aux variables membres
des classes en définissant des fonctions membres d’accès à
l’intérieur même des classes. Dans ce chapitre, nous décrivons
comment on peut s’assurer que les accès aux variables
membres (affectation et lecture) sont canalisés uniquement à
travers de telles fonctions.
Un tel énoncé peut toujours être écrit par inattention même s’il
n’existe pas de fonction membre d’écriture pour la variable
out_1. Cela tient du fait que la variable membre out_1
appartient à la partie publique de la classe (via le mot clé
public: dont nous ne nous sommes pas souciés jusqu’ici).
private:
int set_out_1() {
return 1 - (in_1 * in_2);
}
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
};
//&% nand_ch_14_1.C
#include <iostream.h>
class
NAND_2 {
private:
int set_out_1() {
return 1 - (in_1 * in_2);
}
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
};
void main() {
NAND_2 nnd;
Résultat
CC nand_ch_14_1.C -o nand_ch_14_1
class
NAND_2 {
private:
int set_out_1() {
return 1 - (in_1 * in_2);
}
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
};
void main() {
NAND_2 nnd;
Résultat
in_1: 0
in_2: 0
out_1: 1
Notion avancée
183 Il faut toujours que le constructeur défaut et les
constructeurs avec arguments soient placés dans la partie
publique de la classe. Autrement, le compilateur n’a pas accès
à ces fonctions membres spéciales lors de la création d’un objet
de la classe!
Notion avancée
184 La notation UML et la programmation orientée objet. Il existe
présentement un intérêt croissant pour une méthode graphique
de représentation des classes (ou des abstractions) en
programmation orientée objet. Cette méthode graphique est en
fait un langage formé de symboles simples qui permettent de
saisir d’un coup d’oeil la composition d’une classe et des
relations entre les classes. Ce langage s’appelle UML pour
Unified Modeling Language et a été mis au point par Booch,
Jacobsen et Rumbaugh, trois chercheurs dont les travaux
portent sur la conception d’applications en langage orienté
objet. Dans la notation UML, une classe est représentée par un
rectangle. Le rectangle est divisé en trois parties. La partie du
haut renferme le nom de la classe. La partie du centre contient
Nom de la classe
Liste des
attributs
Liste des
fonctions
membres
Exercice
185 Redéfinissez la classe NOR_2 pour que la variable out_1 et la
fonction membre set_out_1 soient situées dans la partie
privée de la classe.
Résumé
186 ✔ L’accès à une variable membre par erreur via l’opérateur
d’accès aux membres détruit souvent les efforts d’abstraction
des données.
✔ On peut empêcher ces accès en plaçant les membres dans la
partie privée d’une classe et en créant une interface publique
adéquate.
✔ Les fonctions membres en général, les constructeurs,
fonctions membres d’écriture et de lecture plus
particulièrement, ont accès aux variables membres, qu’elles
soient publiques ou privées.
✔ Les variables et fonctions membres situées dans la partie
publique de la classe forment l’interface publique de la classe.
✔ L’opposition public: - private: permet d’implanter
l’abstraction des données de façon plus systématique.
Notion avancée
189 L’option -e du compilateur permet de voir quelles seront les
traitements qui seront effectués sans que ces traitements ne
s’effectuent à proprement parler.
C_A() {
cout << “Constructeur classe C_A” << endl;
}
};
class C_B {
public:
int n_1;
C_B() {
cout << “Constructeur classe C_B” << endl;
}
};
#include <iostream.h>
#include “C_A.h”
#include “C_B.h”
void main() {
C_A objet_1;
C_B objet_2;
#include <iostream.h>
#ifndef C_A_FLAG_
#define C_A_FLAG_
class C_A {
public:
int n_1;
C_A() {
cout << “Constructeur classe C_A” << endl;
}
};
#endif C_A_FLAG_
1. Voir un manuel de référence sur UNIX pour plus de renseignements sur les variables d’envi-
ronnement.
1. “flag” en anglais
#include <iostream.h>
#include “C_A.h”
#include “C_A.h”
#include “C_B.h”
void main() {
C_A objet_1;
C_B objet_2;
void main() {
//#define DEBUG_
#ifdef DEBUG_
#ifdef DEBUG_
cout << “Ce message n’apparait que lorsque” << endl
<< “le drapeau DEBUG_ est defini” << endl;
#endif DEBUG_
Résultat
Seule cette ligne s’imprime lorsque
le drapeau DEBUG_ n’est pas defini
void main() {
#define DEBUG_
#ifdef DEBUG_
#endif DEBUG_
#undef DEBUG_
#ifdef DEBUG_
#endif DEBUG_
Résultat
Ce message n’apparait que lorsque
le drapeau DEBUG_ est defini
Seule cette ligne s’imprime lorsque
le drapeau DEBUG_ n’est pas defini
195 Dans les chapitres qui suivent, nous allons utiliser ces outils de
preprocessing pour éviter d’avoir à inclure textuellement les
définitions de classes dans les programmes.
1. “inheritance” en anglais
200 Pour l’exemple des portes logiques, on peut par exemple dire
que les portes NAND et NOR sont des composantes
numériques d’un circuit. Les composantes numériques ont
une caractéristique commune soit le fan_out. Les circuits ont
des caractéristiques communes telles les tensions
d’alimentation numérique positive et négative, la tension
d’alimentation analogique et un numéro identifiant chaque
pièce montée sur la plaquette du circuit. Evidemment, toutes
les pièces d’un circuit portent un numéro différent. Cependant,
le fait de posséder un numéro est une caractéristique partagée
par toutes les composantes, qu’elles soient des portes logiques,
des résistances, des condensateurs ou des amplificateurs. Le
fait de posséder un fan_out est une caractéristique partagée
par les composantes numériques d’un circuit mais non pas par
les composantes analogiques comme les résistances par
exemple. Il faut donc organiser les catégories pour qu’elles
contiennent des caractéristiques pertinentes aux objets qu’elles
servent à décrire.
CIRCUIT
Description
v_num_plus Tension d’alim. numérique pos.
v_num_moins Tension d’alim. numérique nég.
v_analog_plus Tension d’alim. analogique pos.
numero Numéro d’une composante
NUMÉRIQUE
Variables membres Description
fan_out Nb. max sorties. ANALOGIQUE
Variables membres
puissance_max puissance max
dissipée.
type type de composante
NAND_2 NOR_2 (active ou passive).
203 Une fois que nous avons défini une hiérarchie, il ne reste plus
qu’à définir les classes. Nous commençons par les classes
supérieures de la hiérarchie et nous descendons graduellement
vers le bas de celle-ci. La superclasse CIRCUIT est simple à
définir:
//&% circuit_15_1.h
#include <iostream.h>
#ifndef CIRCUIT_FLAG_
#define CIRCUIT_FLAG_
class CIRCUIT {
private:
double v_num_plus; //Tension numerique positive (en volts)
double v_num_moins; //Tension numerique negative (en
volts)
double v_analog_plus; //Tension analogique positive (en
volts)
int numero; //Numero du composant
public:
// Reader de v_num_plus
double read_v_num_plus() {
return v_num_plus;
}
// Reader de v_num_moins
double read_v_num_moins () {
return v_num_moins;
}
// Reader de v_analog_plus
double read_v_analog_plus () {
return v_analog_plus;
}
// Reader de numero
double read_numero () {
return numero;
}
// Writer de v_num_plus
void set_v_num_plus(double v) {
v_num_plus = v;
}
// Writer de v_num_moins
void set_v_num_moins(double v) {
v_num_moins = v;
}
// Writer de v_analog_plus
void set_v_analog_plus(double v) {
v_analog_plus = v;
}
// Writer de numero
void set_numero(int n) {
numero = n;
}
};
#endif CIRCUIT_FLAG_
#ifndef NUMERIQUE_FLAG_
#define NUMERIQUE_FLAG_
public:
// Reader de fan_out
int read_fan_out() {
return fan_out;
}
// Writer de fan_out
void set_fan_out(int f_out) {
fan_out = f_out;
}
};
#endif NUMERIQUE_FLAG_
#include <iostream.h>
#include “numerique_15_1.h”
#ifndef NAND_FLAG_
#define NAND_FLAG_
private:
int out_1; /*sortie */
int set_out_1() {
return 1 - (in_1 * in_2);
}
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
};
#endif NAND_FLAG_
#include <iostream.h>
#include “numerique_15_1.h”
#ifndef NOR_FLAG_
#define NOR_FLAG_
private:
int out_1; /*sortie */
int set_out_1() {
return ((1-in_1) * (1-in_2));
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
};
#endif NOR_FLAG_
#include <iostream.h>
#include “nand_2_15_1.h”
#include “nor_2_15_1.h”
void main() {
NAND_2 nnd;
NOR_2 nrd;
Résultat
Appel du constructeur defaut de CIRCUIT
Appel du constructeur defaut de NUMERIQUE
Appel du constructeur defaut de NAND_2
Appel du constructeur defaut de CIRCUIT
Appel du constructeur defaut de NUMERIQUE
Appel du constructeur defaut de NOR_2
nand, tension analogique: 12
A() {
var_A_1 = 2;
var_A_2 = 3;
}
int produit() {
return var_A_1 * var_A_2;
}
};
class B : public A {
public:
int var_B_1;
B() {
var_B_1 = 0;
}
int produit() {
return 10 * var_A_1 * var_A_2;
}
};
void main() {
A objet_A;
B objet_B;
Résultat
objet_A.produit(): 6
objet_B.produit(): 60
A() {
var_A_1 = 2;
var_A_2 = 3;
}
int produit() {
return var_A_1 * var_A_2;
}
};
class B : public A {
public:
intvar_B_1;
B() {
var_B_1 = 0;
}
/* int produit() {
return 10 * var_A_1 * var_A_2;
}
*/
};
void main() {
A objet_A;
B objet_B;
le résultat est:
Résultat
objet_A.produit(): 6
objet_B.produit(): 6
Notion avancée
208 Pour forcer l’utilisation de la fonction produit de A (et non de
B), la syntaxe suivante est permise dans la fonction main():
cout << “objet_B.produit():” << objet_B.A::produit() << endl;
209 Dans les exemples, nous avons vu des relations entre une
classe dérivée et une classe de base dont l’expression de
l’héritage utilisait le mot réservé public :
class nom_classe_derivee : public nom_classe_de_base {...}
L’expression d’un tel héritage est appelée dérivation
publique.
Notion avancée
210 Nous verrons plus loin qu’il est aussi possible de définir des
dérivations protégées1 ou des dérivations privées2.
1. “protected” en anglais
2. “private” en anglais
Notion avancée
211 Dans les exemples précédents, les classes de base avaient
seulement un constructeur par défaut qui était
automatiquement appelé lorsqu’une variable du type d’une
classe dérivée était déclarée. Nous verrons plus loin qu’il est
possible de définir des constructeurs avec paramètres même
pour les classes de base et que ces constructeurs peuvent être
appelés par les constructeurs des classes dérivées lors de la
création d’un objet.
Exercice
212 Définissez une classe NOT décrivant un inverseur logique. Les
variables membres de la classe NOT doivent porter le nom de in
et out respectivement. La classe NOT hérite de la classe
NUMERIQUE qui hérite elle-même de la classe CIRCUIT.
N’oubliez pas d’inclure les constructeurs et les fonctions
membres d’accès à la définition de la classe NOT. Stockez la
définition de la classe NOT dans le fichier not.h et utilisez les
instructions de préprocesseur et les drapeaux pour rendre son
utilisation possible dans un programme.
Résumé
213 ✔ Les hiérarchies reflètent les relations sous-classe -
superclasse entre les classes.
✔ Il est intéressant d’utiliser une hiérarchie parce que cela
permet: de décrire des catégories d’objets réels, d’éviter la
duplication inutile de variables membres, d’éviter d’ajouter des
bugs dans du code déjà déverminé et d’utiliser du code
disponible commercialement.
✔ Un objet hérite non seulement des variables membres et des
fonctions membres de la classe dont il est dérivé mais
également des variables membres et des fonctions membres de
la superclasse de cette classe.
✔ Quand une sous-classe contient des fonctions membres ayant
le même nom que des fonctions membres de sa superclasse, les
fonctions membres de la classe masquent celles de la
superclasse.
✔ Il y a plusieurs types de dérivation. Jusqu’à maintenant,
nous n’avons traité que des dérivations publiques.
✔ Il est recommandé de tracer un diagramme d’une hiérarchie
CHAPITRE 17 La conception de
hiérarchies de classes
Notion avancée
223 Le langage graphique UML permet de bien montrer les liens
entre les classes grâce à un ensemble de symboles représentant
Aggrégation
(composition)
Généralisation
(héritage)
class B : public A{
public:
int k;
int readK();
}
class C {
public:
double u;
double readU();
void useB(B t);
}
A
int A
C w
int readI()
C
double u
void useB(B t)
B
int k
int readK()
1. “true” en anglais
2. “false” en anglais
Prédicat Utilité
== Est-ce que les deux nombres sont égaux
!= Est-ce que deux nombres ne sont pas égaux
> Est-ce que le premier nombre est strictement
supérieur au second
< Est-ce que le premier nombre est strictement
inférieur au second
>= Est-ce que le premier nombre est supérieur ou
égal au second
<= Est-ce que le premier nombre est inférieur ou
égal au second
228 Nous savons maintenant qu’à chaque fois que le caractère “!”
est immédiatement suivi du caractère “=”, les deux caractères
ensembles représentent l’opérateur d’inégalité.
Il faut noter cependant que le caractère “!” peut apparaître
seul. Dans ce cas, il représente l’opérateur de négation1 en
C++. Par exemple, la valeur de !0 est 1 et la valeur de !1 est
231 Notez également que les opérateurs ==, !=, >, <, >=, <= ont
une priorité inférieure à l’opérateur d’insertion <<. Il faut donc
utiliser des parenthèses comme dans l’exemple suivant:
cout << (i == (int)d) << endl;
Exercice
232 Ecrivez un programme qui accepte un nombre et qui affiche 1 si
le nombre est plus petit que 100 et 0 s’il est plus grand ou égal à
100.
1. “not” en C++
#include <iostream.h>
void main() {
int puissance;
Résultat
3
Puissance OK
#include <iostream.h>
void main() {
int puissance;
Résultat
3
Puissance OK
#include <iostream.h>
void main() {
int puissance;
int drapeau = 0;
Résultat
16
Puissance trop elevee
drapeau: 1
avec le if le plus près qui n’a pas déjà un else qui lui est
pairé. Donc, ci-dessus, il faudrait remplacer le “?” par “trop
élevée”.
Afin d’éviter toute ambiguïté, il est préférable d’utiliser des
crochets pour mieux délimiter les énoncés if-else.
#include <iostream.h>
void main() {
int delta_puissance;
if (delta_puissance == 1)
cout<< “L’augmentation de puissance”
<< “ est de: “ << delta_puissance << “ watt” << endl;
else
cout<< “L’augmentation de puissance”
<< “ est de: “ << delta_puissance << “ watts” << endl;
}
Résultat
1
L’augmentation de puissance est de: 1 watt
Résultat
3
244 La ligne qui suit est la forme générale adoptée par le C++ pour
l’opérateur conditionnel produisant une valeur:
Expression booléenne ? Expression si vrai: Expression si
faux
#include <iostream.h>
void main() {
int delta_puissance;
<< delta_puissance
<< (delta_puissance == 1 ? “ watt” : “ watts”)
<< endl;
Résultat
1
L’augmentation de puissance est de: 1 watt
Résultat
3
L’augmentation de puissance est de: 3 watts
Résumé
246 ✔ Le C++ offre des énoncés conditionnels if et if-else
permettant de traiter les données en fonctions du résultat d’un
prédicat logique.
✔ L’utilisation de crochets rend possible l’exécution de
plusieurs énoncés associés à l’énoncé if ou if-else.
✔ L’opérateur conditionnel permet de produire un résultat en
fonction de la valeur d’un prédicat logique.
#include <iostream.h>
void main() {
int puissance;
Résultat
15
Puissance normale
Notion avancée
253 Les opérateurs && et || sont utilisées pour exprimer le ET et le
OU logiques parce que les opérateurs & et | sont utilisés pour
les opérations logiques ET et OU sur les bits individuels des
opérandes. Les opérateurs & et | ne sont pas traités dans ces
notes parce que la compréhension de la manipulation des bits
individuels n’est pas nécessaire à l’apprentissage des
principaux points du C++. Les détails sur ces opérateurs sont
disponible dans tout livre sur le C ou le C++.
#include <iostream.h>
#include “numerique_15_1.h”
#ifndef NAND_FLAG_
#define NAND_FLAG_
private:
int out_1; /*sortie */
int set_out_1() {
if (in_1 && in_2) return 0;
else return 1;
}
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
};
#endif NAND_FLAG_
#include <iostream.h>
#include “numerique_15_1.h”
#ifndef NOR_FLAG_
#define NOR_FLAG_
private:
int out_1; /*sortie */
int set_out_1() {
if (in_1 || in_2 ) return 0;
else return 1;
}
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
NOR_2() {
cout<< “Appel du constructeur defaut de NOR_2”
<< endl;
in_1 = 0; in_2 = 0;
out_1 = set_out_1();
}
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
};
#endif NOR_FLAG_
#include <iostream.h>
#include “nand_2_20_1.h”
#include “nor_2_20_1.h”
void main() {
NAND_2 nnd;
NOR_2 nrd;
cout << “La table de verite d’une porte nand” << endl;
cout << “est: “ << endl;
cout << endl << endl;
cout << “entree 1 entree 2 sortie” << endl;
nnd.ecrire_in_1(0); nnd.ecrire_in_1(0);
cout << “ “ << nnd.lire_in_1()
<< “ “ << nnd.lire_in_2() << “ “
<< nnd.lire_out_1() << endl;
nnd.ecrire_in_1(0); nnd.ecrire_in_2(1);
cout << “ “ << nnd.lire_in_1()
<< “ “ << nnd.lire_in_2() << “ “
<< nnd.lire_out_1() << endl;
nnd.ecrire_in_1(1); nnd.ecrire_in_2(0);
cout << “ “ << nnd.lire_in_1()
<< “ “ << nnd.lire_in_2() << “ “
<< nnd.lire_out_1() << endl;
nnd.ecrire_in_1(1); nnd.ecrire_in_2(1);
cout << “ “ << nnd.lire_in_1()
<< “ “ << nnd.lire_in_2() << “ “
<< nnd.lire_out_1() << endl << endl;
cout << “La table de verite d’une porte nor” << endl;
cout << “est: “ << endl;
cout << endl << endl;
cout << “entree 1 entree 2 sortie” << endl;
nrd.ecrire_in_1(0); nrd.ecrire_in_2(0);
cout << “ “ << nrd.lire_in_1()
<< “ “ << nrd.lire_in_2() << “ “
<< nrd.lire_out_1() << endl;
nrd.ecrire_in_1(0); nrd.ecrire_in_2(1);
cout << “ “ << nrd.lire_in_1()
<< “ “ << nrd.lire_in_2() << “ “
<< nrd.lire_out_1() << endl;
nrd.ecrire_in_1(1); nrd.ecrire_in_2(0);
cout << “ “ << nrd.lire_in_1()
<< “ “ << nrd.lire_in_2() << “ “
<< nrd.lire_out_1() << endl;
nrd.ecrire_in_1(1); nrd.ecrire_in_2(1);
cout << “ “ << nrd.lire_in_1()
<< “ “ << nrd.lire_in_2() << “ “
<< nrd.lire_out_1() << endl;
}
Résultat
Appel du constructeur defaut de CIRCUIT
Appel du constructeur defaut de NUMERIQUE
Appel du constructeur defaut de NAND_2
Appel du constructeur defaut de CIRCUIT
Appel du constructeur defaut de NUMERIQUE
Appel du constructeur defaut de NOR_2
La table de verite d’une porte nand
est:
Exercice
259 L’énoncé suivant est-il équivalent à celui du paragraphe 258?
while (n) n = n -1;
int puissance_de_2(int n) {
int resultat = 1;
while (n != 0) {
resultat = resultat * 2;
n = n-1;
}
return resultat;
}
void main(){
int exposant= 10;
while (exposant) {
cout << “2 puissance “ << exposant
<< “ egale: “ << puissance_de_2(exposant)
<< endl;
exposant = exposant -1;
}
}
Résultat
2 puissance 10 egale: 1024
2 puissance 9 egale: 512
2 puissance 8 egale: 256
2 puissance 7 egale: 128
2 puissance 6 egale: 64
2 puissance 5 egale: 32
2 puissance 4 egale: 16
2 puissance 3 egale: 8
2 puissance 2 egale: 4
2 puissance 1 egale: 2
261 Le principal ennui avec l’instruction while est qu’il faut trois
lignes pour implanter une itération: l’initialisation du
compteur, le test sur la valeur du compteur et troisièmement,
l’incrémentation (ou la décrémentation du compteur).
L’instruction for est une moyen simple de faire des itérations
en C++ sans avoir la lourdeur de l’instruction while. La
syntaxe de l’instruction for est la suivante:
for (expression d’entrée;
Expression booléenne;
Expression de continuation)
énoncé associé
int puissance_de_2(int n) {
int resultat = 1;
int compteur;
for(compteur=n;compteur;compteur = compteur-1)
resultat = 2 * resultat;
return resultat;
}
void main(){
int exposant;
Résultat
2 puissance 10 egale: 1024
2 puissance 9 egale: 512
2 puissance 8 egale: 256
2 puissance 7 egale: 128
2 puissance 6 egale: 64
2 puissance 5 egale: 32
2 puissance 4 egale: 16
2 puissance 3 egale: 8
2 puissance 2 egale: 4
2 puissance 1 egale: 2
resultat *= 2;
int puissance_de_2(int n) {
int resultat = 1;
int compteur;
for(compteur=n;compteur;--compteur)
resultat = 2*resultat;
return resultat;
}
void main(){
int exposant;
for (exposant=10;exposant;--exposant) {
cout << “2 puissance “ << exposant
<< “ egale: “ << puissance_de_2(exposant)
<< endl;
}
}
265 Il faut retenir que l’opérateur -- (ou ++) produit une valeur.
Le C++ permet en principe de placer l’opérateur -- (ou ++) en
suffixe à une variable plutôt qu’en préfixe. On peut donc écrire
x++ et aussi ++x. Cependant, si cette opération
d’incrémentation est utilisée dans une opération plus complexe,
la valeur produite diffère suivant qu’une syntaxe en suffixe soit
utilisée plutôt qu’une syntaxe en préfixe.
Par exemple, si la valeur de la variable compteur est 3, la
valeur produite par l’expression --compteur est 2 et la
nouvelle valeur de la variable compteur est également 2. Par
ailleurs, si la valeur de la variable compteur est 3, la valeur
produite par l’expression compteur-- est 3 mais la nouvelle
valeur de la variable compteur est 2. Par exemple, le code ci-
dessous:
//&% test_inc_dec.C
#include <iostream.h>
void main() {
int i = 10;
Exercice
266 Concevez un programme qui accepte deux entiers n et m au
clavier et qui calcule nm.
Résumé
267 ✔ Si vous désirez répéter un calcul tant qu’une expression
booléenne n’est pas 0, utilisez l’énoncé while.
✔ Si vous désirez répéter une opération incluant un énoncé de
départ, un test et un énoncé de continuation, utilisez
l’instruction for.
✔ Si vous désirez incrémenter ou décrémenter une variable de
l’unité, utilisez les opérateurs préfixes --variable ou
++variable.
268 Dans ce chapitre, nous voyons comment on peut tirer profit des
instructions while et for pour traiter répétitivement des
données contenues dans des fichiers.
int puissance_de_2(int n) {
int resultat = 1;
int compteur;
for(compteur=n;compteur;--compteur)
resultat = 2*resultat;
return resultat;
}
void main(){
int exposant;
Résultat
3
2 puissance 3 egale: 8
4
2 puissance 4 egale: 16
8
2 puissance 8 egale: 256
12
2 puissance 12 egale: 4096
100
2 puissance 100 egale: 0
45
2 puissance 45 egale: 0
30
2 puissance 30 egale: 1073741824
ou
int puissance_de_2(int n) {
int resultat = 1;
int compteur;
for(compteur=n;compteur;--compteur)
resultat = 2*resultat;
return resultat;
}
void main(){
int exposant;
int cptr;
Résultat
2
2 exposant 2 = 4
4
2 exposant 4 = 16
6
2 exposant 6 = 64
8
2 exposant 8 = 256
16
2 exposant 16 = 65536
32
2 exposant 32 = 0
9
2 exposant 9 = 512
7 donnees ont ete entrees.
Résumé
274 ✔ Si on désire lire des données jusqu’à ce que l’on atteigne la fin
d’un fichier, il suffit d’utiliser l’instruction while avec un
énoncé d’extraction comme expression booléenne. Utiliser une
instruction for permet en plus de compter le nombre de
données qui ont été traitées.
TABLE 4
0 1 2 3 4 indice
58 56 65 12 24 Nombre
1. “array” en anglais
2. un tableau à une dimension est aussi appelé vecteur
3. “index” en anglais
1. N’oubliez pas que le clavier est vu comme un fichier spécial par DOS ou Unix.
#include <iostream.h>
void main() {
int vecteur[100];
int compteur,i;
int somme = 0;
Résultat
1
2
3
4
5
La somme des 5 nombres est: 15
Exercice
284 Ecrivez un programme qui effectue le produit de matrices 3 x 3.
Résumé
285 ✔ Lorsqu’on veut garder des valeurs de même type dans une
même variable, il suffit de définir un tableau. Une telle
définition comprend le type des données, le nom du tableau, les
dimensions du tableau et le nombre d’éléments pour chaque
dimensions.
✔ Les indices des dimensions d’un tableau sont notés en origine
zéro.
#include <iostream.h>
#include “nand_2_15_1.h”
void main() {
NAND_2 vec_nnd[5];
int i;
Résultat
--------------------------------
Porte 0 :
entree 1: 1
entree 2: 0
--------------------------------
Porte 1 :
entree 1: 1
entree 2: 1
--------------------------------
Porte 2 :
entree 1: 0
entree 2: 0
--------------------------------
Porte 3 :
entree 1: 1
entree 2: 0
--------------------------------
Porte 4 :
entree 1: 0
entree 2: 1
--------------------------------
Sortie 0: 1
--------------------------------
Sortie 1: 0
--------------------------------
Sortie 2: 1
--------------------------------
Sortie 3: 1
--------------------------------
Sortie 4: 1
288 Cet exemple permet de voir comment on peut stocker des objets
dans les éléments d’un tableau et comment en récupérer le
contenu. Pour définir un tableau d’objet, on procède exactement
comme avec un tableau d’entiers, c’est-à-dire qu’on donne le
type du tableau, son nom les dimensions et la grandeur de
chaque dimension:
NAND_2 vec_nnd[5];
Notion avancée
290 Lorsqu’on défini un tableau d’objets, le constructeur défaut de
la classe est appelé pour chaque élément de ce tableau.
Résumé
291 ✔ La manipulation des membres d’un objet faisant partie d’un
tableau se fait de la même manière que pour un objet seul. Il
suffit simplement d’identifier l’élément du tableau pour lequel
on désire accéder au membre via l’opérateur d’accès aux
membres “.”.
✔ Le constructeur par défaut est appelé pour chaque élément
d’un tableau d’objets.
La syntaxe est très similaire à celle qui est utilisée pour les
flots d’entrée.
298 Une fois le flot de sortie créé, il suffit de remplacer cout par le
nom du flot pour écrire des données dans le fichier:
nnd_flot_out << out_1 << endl;
Notion avancée
299 Du point de vue de l’implantation dans la bibliothèque de
fonctions du C++, cin et cout sont des objets des classes
istream et ostream respectivement. De même, nnd_flot_in
et nnd_flot_out sont des objets des classes ifstream et
ofstream respectivement.
#include <iostream.h>
#include <stdlib.h>
#include <fstream.h>
#include “nand_2_20_1.h”
void main() {
NAND_2 vec_nnd[5];
int i;
int in_1, in_2;
if (nnd_flot_in == 0){
exit(0);
}
vec_nnd[i].ecrire_in_1(in_1);
vec_nnd[i].ecrire_in_2(in_2);
}
nnd_flot_in.close();
nnd_flot_out.close();
Résultat
Lecture des donnees du fichier d’entree
Donnees lues
Ecriture des donnees dans le fichier de sortie
Donnees sauvegardees dans le fichier de sortie
Résultat
bersimis% more nand_dat.in
0 0 0 1 1 0 1 1 1 0
bersimis% more nand_dat.out
1 1 1 0 1
bersimis%
La syntaxe reflète ici le fait que les flots sont des objets et que
la fonction close() est une fonction membre public accédée
par l’opérateur d’accès aux membres.
Résumé
303 ✔ Pour manipuler les flots de données, il faut inclure la ligne
suivante dans le programme #include <fstream.h>
✔ Pour ouvrir un flot de données d’entrée, il suffit d’adopter la
syntaxe suivante: ifstream nom_flot(“nom_fichier”,
ios::in);
✔ Pour ouvrir un flot de données de sortie, il suffit d’adopter la
syntaxe suivante: ofstream
nom_flot(“nom_fichier”,ios::out);
✔ Pour accéder aux données d’entrée, il suffit d’adopter la
syntaxe nom_flot >> variables d’entrée;
✔ Pour stocker des données dans le flot de sortie, il suffit
d’adopter la syntaxe suivante: nom_flot << données;
✔ pour fermer les flots de données, il suffit d’adopter la syntaxe
suivante: nom_flot.close();
Mémoire de l’ordinateur
Adresse Contenu
0 in_1
}
le nom nnd
identifie 1 in_2
l’objet nnd
2 out_1 variable
nnd
3 .
.
.
4
5
6
pointeur
7 0 } ptr_nnd
le nom 8
ptr_nnd identifie
l’adresse 9
du début de
l’objet nnd 10
11
12
NAND_2 nnd;
Notion avancée
316 Du point de vue du type des données pour l’opération new
double, l’opérateur new génère, à l’interne, un pointeur de type
void *. Ce pointeur est ensuite automatiquement converti1 en
un pointeur de double. Il en est de même pour les types définis
par l’usager tel la classe NAND_2.
Notion avancée
317 Un autre moyen d’associer un pointeur à un objet est d’utiliser
l’opérateur “adresse-de2”, noté &nom_variable, qui retourne
l’adresse d’une variable. Ainsi, le code ci-dessous est correct:
NAND_2 *ptr_nand_2;
NAND_2 nnd;
ptr_nand_2 = &nnd;
Résumé
318 ✔ Une variable ordinaire correspond à une donnée tandis
qu’une variable pointeur correspond à l’adresse d’une donnée.
✔ Les pointeurs à des données occupent en général beaucoup
NAND_2 vec_nnd[100];
void main() {
int i, compteur;
int in_1, in_2;
Résultat
Lecture des donnees du fichier d’entree
5 donnees lues
Ecriture des donnees dans le fichier de sortie
Donnees sauvegardees dans le fichier de sortie
#include <iostream.h>
#include <fstream.h>
#include “nand_2_15_1.h”
NAND_2 *vec_nnd[100];
void main() {
int i, compteur;
int in_1, in_2;
ifstream nnd_flot_in (“nand_dat.in”, ios::in);
ofstream nnd_flot_out(“nand_dat.out”, ios::out);
cout << “Lecture des donnees du fichier d’entree” << endl;
for (compteur = 0; nnd_flot_in >> in_1 >> in_2; ++compteur)
{
vec_nnd[compteur] = new NAND_2;
vec_nnd[compteur]->ecrire_in_1(in_1);
vec_nnd[compteur]->ecrire_in_2(in_2);
}
cout << compteur << “ donnees lues” << endl;
cout << “Ecriture des donnees dans le fichier de sortie” <<
endl;
for (i = 0; i < compteur; ++i) {
nnd_flot_out << vec_nnd[i]->lire_out_1() << “ “;
}
cout << “Donnees sauvegardees dans le fichier de sortie” <<
endl;
1. Les même fichiers de données que pour le programme du paragraphe 300 au CHAPITRE 25
ont été utilisés.
nnd_flot_in.close();
nnd_flot_out.close();
}
Résultat
Lecture des donnees du fichier d’entree
5 donnees lues
Ecriture des donnees dans le fichier de sortie
Donnees sauvegardees dans le fichier de sortie
Exercice
323 L’opérateur sizeof calcule le nombre d’octets requis pour
stocker un objet. Par exemple, sizeof(char) retourne le
Résumé
324 ✔ Si on désire traiter des données dont on ignore le nombre au
départ et si on désire ne pas trop mobiliser d’espace mémoire, il
est judicieux de déclarer un tableau de pointeurs à des objets.
1 1 1 2 0 1 1 1 0
{
{
Porte Porte Porte
nand entrées entrées nand entrées
nor
void main() {
int type;
int in_1, in_2;
flot_in.close();
}
Résultat
NAND_2
NOR_2
NAND_2
328 Une raison pour laquelle C++ exige que les objets dans un
tableau soient tous du même type est qu’une telle exigence
nous assure que chaque objet occupe exactement le même
void main() {
int i, type;
int in_1, in_2;
NUMERIQUE *num_ptr[100];
Résultat
Objet NAND_2 cree
Objet NOR_2 cree
Objet NAND_2 cree
3 objets crees.
#include <iostream.h>
#ifndef CIRCUIT_FLAG_
#define CIRCUIT_FLAG_
class CIRCUIT {
private:
double v_num_plus; //Tension numerique positive (en volts)
double v_num_moins; //Tension numerique negative (en
volts)
double v_analog_plus; //Tension analogique positive (en
volts)
int numero; //Numero du composant
public:
CIRCUIT () {
// cout << “Appel du constructeur defaut de CIRCUIT” <<
endl;
v_num_plus = 5.0;
v_num_moins = 0.0;
v_analog_plus = 12.0;
numero = 0;
}
// Reader de v_num_plus
double read_v_num_plus() {
return v_num_plus;
}
// Reader de v_num_moins
double read_v_num_moins () {
return v_num_moins;
}
// Reader de v_analog_plus
double read_v_analog_plus () {
return v_analog_plus;
}
// Reader de numero
int read_numero () {
return numero;
}
// Writer de v_num_plus
void set_v_num_plus(double v) {
v_num_plus = v;
}
// Writer de v_num_moins
void set_v_num_moins(double v) {
v_num_moins = v;
}
// Writer de v_analog_plus
void set_v_analog_plus(double v) {
v_analog_plus = v;
}
// Writer de numero
void set_numero(int n) {
numero = n;
}
#endif CIRCUIT_FLAG_
#ifndef NUMERIQUE_FLAG_
#define NUMERIQUE_FLAG_
public:
// Reader de fan_out
int read_fan_out() {
return fan_out;
}
// Writer de fan_out
void set_fan_out(int f_out) {
fan_out = f_out;
}
#endif NUMERIQUE_FLAG_
#include <iostream.h>
#include “circuit_28_2.h”
#include “numerique_28_2.h”
#ifndef NAND_FLAG_
#define NAND_FLAG_
private:
int out_1; /*sortie */
int set_out_1() {
return 1 - (in_1 * in_2);
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
// Affichage du type de composant
void affiche_type() {cout<< “Composante NAND_2”
<< endl;}
};
#endif NAND_FLAG_
//&% nor_2_28_2.h
#include <iostream.h>
#include “circuit_28_2.h”
#include “numerique_28_2.h”
#ifndef NOR_FLAG_
#define NOR_FLAG_
private:
int out_1; /*sortie */
int set_out_1() {
return ((1-in_1) * (1-in_2));
}
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
in_2 = val;
out_1 = set_out_1();
}
int lire_out_1() {
return out_1;
}
#endif NOR_FLAG_
#include <iostream.h>
#include <stdlib.h>
#include <fstream.h>
#include “numerique_28_2.h”
#include “nand_2_28_2.h”
#include “nor_2_28_2.h”
void main() {
flot_in.close();
cout << compteur << “ objets crees. “ << endl << endl;
for (i = 0; i < compteur ; ++i) {
num_ptr[i]->affiche_type();
}
}
Le résultat de l’exécution de ce programme est le suivant:
Résultat
4 objets crees.
3
3
3
3
#include <iostream.h>
#ifndef CIRCUIT_FLAG_
#define CIRCUIT_FLAG_
class CIRCUIT {
private:
double v_num_plus; //Tension numerique positive (en volts)
double v_num_moins; //Tension numerique negative (en
volts)
double v_analog_plus; //Tension analogique positive (en
volts)
int numero; //Numero du composant
public:
CIRCUIT () {
// cout << “Appel du constructeur defaut de CIRCUIT” <<
endl;
v_num_plus = 5.0;
v_num_moins = 0.0;
v_analog_plus = 12.0;
numero = 0;
}
// Reader de v_num_plus
double read_v_num_plus() {
return v_num_plus;
// Reader de v_num_moins
double read_v_num_moins () {
return v_num_moins;
}
// Reader de v_analog_plus
double read_v_analog_plus () {
return v_analog_plus;
}
// Reader de numero
int read_numero () {
return numero;
}
// Writer de v_num_plus
void set_v_num_plus(double v) {
v_num_plus = v;
}
// Writer de v_num_moins
void set_v_num_moins(double v) {
v_num_moins = v;
}
// Writer de v_analog_plus
void set_v_analog_plus(double v) {
v_analog_plus = v;
}
// Writer de numero
void set_numero(int n) {
numero = n;
}
#endif CIRCUIT_FLAG_
#ifndef NUMERIQUE_FLAG_
#define NUMERIQUE_FLAG_
public:
// Reader de fan_out
int read_fan_out() {
return fan_out;
}
// Writer de fan_out
void set_fan_out(int f_out) {
fan_out = f_out;
}
#endif NUMERIQUE_FLAG_
#include <iostream.h>
#include “circuit_28_3.h”
#include “numerique_28_3.h”
#ifndef NAND_FLAG_
#define NAND_FLAG_
private:
int out_1; /*sortie */
int set_out_1() {
return 1 - (in_1 * in_2);
}
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
// Affichage du type de composant
#endif NAND_FLAG_
#include <iostream.h>
#include “circuit_28_3.h”
#include “numerique_28_3.h”
#ifndef NOR_FLAG_
#define NOR_FLAG_
private:
int out_1; /*sortie */
int set_out_1() {
return ((1-in_1) * (1-in_2));
}
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int lire_in_1() {
return in_1;
}
out_1 = set_out_1();
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
#endif NOR_FLAG_
#include <iostream.h>
#include <stdlib.h>
#include <fstream.h>
#include “numerique_28_3.h”
#include “nand_2_28_3.h”
#include “nor_2_28_3.h”
void main() {
flot_in.close();
cout << compteur << “ objets crees. “ << endl << endl;
for (i = 0; i < compteur ; ++i) {
num_ptr[i]->affiche_type();
}
Résultat
3 objets crees.
Composante NAND_2
Composante NOR_2
Composante NAND_2
Résumé
342 ✔ Un pointeur qui est défini pour pointer à un objet d’une
superclasse peut aussi pointer à un objet appartenant à une
sous-classe de cette classe.
✔ Si on définit un tableau de pointeurs à des objets d’une classe
et que ces pointeurs pointent en réalité à des objets d’une sous-
classe, il faut définir des fonctions membres virtuelles si on
désire appeler ces fonctions membres.
✔ Une fonction membre virtuelle définie dans une classe est
automatiquement considérée comme étant virtuelle dans toutes
les sous-classes.
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include “numerique_28_3.h”
#include “nand_2_28_3.h”
#include “nor_2_28_3.h”
void main() {
flot_in.close();
cout << compteur << “ objets crees. “ << endl << endl;
for (i = 0; i < compteur ; ++i) {
num_ptr[i]->affiche_type();
}
Notion avancée
348 Si votre programme est appelé par un autre programme plutôt
que d’être appelé par une ligne de commande dans le système
d’exploitation, un énoncé exit provoque alors le retour
immédiat au programme d’appel. Il revient à ce programme de
décider des actions à prendre.
Notion avancée
349 Dans le programme de rapport, on utilise l’énoncé exit(0).Il
existe d’autres choix exit(i) qui ont diverses significations. Il
est hors du programme de ce cours d’aborder ces notions.
Résumé
350 ✔ L’énoncé switch permet de choisir parmi plusieurs actions
en fonction de la valeur entière produite par une expression.
✔ Dans un énoncé switch, l’expression peut produire un entier
appartenant à tous les types associés à des entiers soit int,
long int, char par exemple.
✔ Il est recommandé de pairer chaque mot réservé case avec
un mot réservé break pour éviter des bugs difficile à corriger.
✔ Si on désire que les message d’erreur s’affichent sans être
préalablement stockés dans un tampon, il suffit de les envoyer
au flot de données cerr du C++.
✔ Si on désire interrompre l’exécution d’un programme parce
qu’une erreur a été détectée, il suffit d’ajouter l’instruction
exit(0) dans le programme et d’inclure le fichier stdlib.h.
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include “numerique_28_3.h”
#include “nand_2_28_3.h”
#include “nor_2_28_3.h”
NUMERIQUE *num_ptr[100];
flot_in.close();
cout << compteur << “ objets crees. “ << endl << endl;
for (i = 0; i < compteur ; ++i) {
num_ptr[i]->affiche_type();
}
Résultat
3 objets crees.
Composante NAND_2
Composante NOR_2
Composante NAND_2
Notion avancée
356 On peut généraliser la syntaxe de l’énoncé enum comme suit:
enum {code_a, code_b, code_c, code_d = 5, code_e}
Notion avancée
357 Le C++ offre aussi la possibilité de définir des types de
données d’énumération. Ces types de données permettent
ensuite de définir des variables d’énumération. Par exemple,
la définition suivante crée un type d’énumération ayant pour
nom type_enum:
enum type_enum {code_a, code_b, code_c, code_d};
Par exemple:
enum couleur {rouge,bleu,vert,blanc};
void affiche_couleur(couleur c){
switch(c) {
case rouge: cout << “rouge” << endl; break;
case bleu: cout << “bleu” << endl; break;
case vert: cout << “vert” << endl; break;
case blanc: cout << “blanc” << endl; break;
default: cout << “hamster” << endl; break;
}
}
main() {
couleur c1,c2,c3,c4;
c1 = rouge;
c2 = bleu;
c3 = vert;
c4 = 390;
affiche_couleur(c1);
affiche_couleur(c2);
affiche_couleur(c3);
affiche_couleur(c4);
}
Résumé
358 ✔ Les énumérations facilitent la lecture des programmes en
C++ en associant des constantes à des valeurs numériques
entières.
#ifndef NUMERIQUE_FLAG_
#define NUMERIQUE_FLAG_
public:
// Reader de fan_out
int read_fan_out() {
return fan_out;
}
// Writer de fan_out
void set_fan_out(int f_out) {
fan_out = f_out;
}
#endif NUMERIQUE_FLAG_
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include “numerique_31_1.h”
#include “nand_2_31_1.h”
#include “nor_2_28_3.h”
exit(0);
}
flot_in.close();
cout << compteur << “ objets crees. “ << endl << endl;
for (i = 0; i < compteur ; ++i) {
num_ptr[i]->affiche_type();
cout << “fan_out: “ << num_ptr[i]->read_fan_out() <<
endl;
}
Résultat
3 objets crees.
Composante NAND_2
fan_out: 5
Composante NOR_2
fan_out: 10
Composante NAND_2
fan_out: 5
Résumé
364 ✔ Il est possible d’appeler un constructeur qui en appelle un
autre explicitement.
Notion avancée
367 L’argument implicite dont il est fait mention au paragraphe
366 est en fait un argument appelé le pointeur this. Chaque
fonction membre d’une classe possède un argument implicite
appelé this dont la valeur est un pointeur à l’objet
correspondant à la classe de cette fonction membre. Par
exemple, lorsqu’on accède à la fonction membre lire_in_1()
pour un objet nnd de la classe NAND_2 de la façon suivante:
nnd.lire_in_1();
Résumé
368 ✔ Si on veut appeler une fonction membre public pour un
objet spécifique, il suffit d’utiliser l’opérateur d’accès aux
membres “.” de la façon suivante:
nom_objet.nom_fonction(arguments).
✔ Si on veut appeler une fonction membre à l’intérieur d’une
fonction membre, il suffit simplement d’adopter la syntaxe
suivante: nom_fonction(arguments).
class super_classe {
public:
super_classe() {} //Constructeur par defaut
super_classe(double a, double b) {
sup_a = a; sup_b = b;
}
// Fonction membre calculant le produit a*b
double sup_produit(){
return sup_a * sup_b;
}
private:
double sup_a, sup_b;
};
#endif SUP_CLS_FLAG
class super_classe {
public:
super_classe() {} //Constructeur par defaut
super_classe(double a, double b) {
sup_a = a; sup_b = b;
}
private:
double sup_a, sup_b;
};
#endif SUP_CLS_FLAG_
class super_classe {
public:
super_classe() {} //Constructeur par defaut
super_classe(double a, double b) {
sup_a = a; sup_b = b;
}
protected:
};
#endif SUP_CLS_FLAG_
#include <iostream.h>
#ifndef SUP_CLS_FLAG_
#define SUP_CLS_FLAG_
class super_classe {
public:
super_classe(double a, double b) {
sup_a = a; sup_b = b;
}
double sup_produit(){
protected:
double sup_a, sup_b;
};
#endif SUP_CLS_FLAG_
#include <iostream.h>
#include sup_cls_33_3.h
#ifndef SOUS_CLS_FLAG_
#define SOUS_CLS_FLAG_
public:
sous_classe() : super_classe(3.0,9.0) {}
sous_classe(double c) {
sous_c = c;
}
private:
double sous_c;
};
#endif SOUS_CLS_FLAG_
void main () {
sous_classe objet_1;
cout << objet_1.somme() << endl;
}
Résultat
12
379 On voit donc que par une utilisation judicieuse des différentes
parties d’une classe (public, protected, private) on peut
contrôler l’accès aux variables membres de façon très précise.
Résumé
382 ✔ Pour limiter l’accès aux variables et fonctions membres d’une
classe seulement aux fonctions définies dans la même classe, il
suffit de placer ces variables et fonctions membres dans la
partie private de la classe.
✔ Pour limiter l’accès aux variables et fonctions membres d’une
classe seulement aux fonctions définies dans la même classe ou
dans une sous-classe de la classe, il suffit de placer ces
variables et fonctions membres dans la partie protected de la
classe.
sous_classe() : super_classe(3.0,9.0) {}
sous_classe(double c) {
sous_c = c;
}
private:
double sous_c;
};
#endif SOUS_CLS_FLAG_
#include <iostream.h>
#ifndef SUP_CLS_FLAG_
#define SUP_CLS_FLAG_
class super_classe {
public:
super_classe(double a, double b) {
sup_a = a; sup_b = b;
double sup_produit(){
};
#endif SUP_CLS_FLAG_
et pour sous_classe:
//&% sous_cls_34_1.h
#include <iostream.h>
#ifndef SOUS_CLS_FLAG_
#define SOUS_CLS_FLAG_
public:
sous_classe() : super_classe(3.0,9.0) {}
sous_classe(double c) {
sous_c = c;
private:
double sous_c;
};
#endif SOUS_CLS_FLAG_
Résumé
389 La table ci-dessous résume les effets des divers types de
dérivations:
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include “numerique_31_1.h”
#include “nand_2_31_1.h”
#include “nor_2_28_3.h”
}
for (compteur = 0; flot_in >> type >> in_1 >> in_2 ;
++compteur) {
switch (type) {
case nand_2_code: num_ptr[compteur] = new NAND_2;
break;
case nor_2_code: num_ptr[compteur] = new NOR_2;
break;
default: cerr << “Erreur, piece “
<< type
<< “ non existante”
<< endl;
exit (0);
}
flot_in.close();
cout << compteur << “ objets crees. “ << endl << endl;
for (i = 0; i < compteur ; ++i) {
num_ptr[i]->affiche_type();
}
ce qui est peu élégant parce que l’information comme quoi une
donnée est envoyée au flot cout est cachée dans la fonction
affiche_type(). Il serait intéressant de faire en sorte que
l’instruction d’envoi de données à cout apparaisse
explicitement dans le programme principal. Un programmeur
devant analyser le programme peut ainsi voir facilement où se
trouve l’instruction d’affichage sans avoir à consulter la
définition de la fonction affiche_type() dans les classes
NAND_2 et NOR_2.
393 Nous avons déjà vu qu’en C++, une chaîne de caractères est
représentée par un ensemble de caractères alphanumériques
insérés entre guillemets. Par exemple “porte nand” est une
chaîne de caractères valide. Lorsqu’on délimite une chaîne de
caractères par des guillemets, on commande au compilateur
C++ de créer un tableau dans lequel les éléments sont
justement les caractères contenus entre les guillemets. Le C++
stocke une chaîne de caractères de la façon suivante dans la
mémoire de l’ordinateur:
p o r t e n a n d \0
0111000 01101111 01110010 01110100 01100101 00100000 01101110 01100001 01101110 01100100 00000000
0 1 2 3 4 5 6 7 8 9 10
Indices du tableau
1. Remarquez qu’il est possible de surdéfinir l’opérateur << cependant, nous allons voir cette
notion avancée dans un prochain chapitre.
2. “null character” en anglais.
#include <iostream.h>
#include “circuit_35_1.h”
#include “numerique_35_1.h”
#ifndef NAND_FLAG_
#define NAND_FLAG_
private:
int out_1; /*sortie */
int set_out_1() {
return 1 - (in_1 * in_2);
}
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
// Affichage du type de composant
virtual char *affiche_type() {return “Composante NAND_2”;}
};
#endif NAND_FLAG_
#include <iostream.h>
#include “circuit_35_1.h”
#include “numerique_35_1.h”
#ifndef NOR_FLAG_
#define NOR_FLAG_
private:
int out_1; /*sortie */
int set_out_1() {
return ((1-in_1) * (1-in_2));
}
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
#endif NOR_FLAG_
#ifndef NUMERIQUE_FLAG_
#define NUMERIQUE_FLAG_
public:
// Reader de fan_out
int read_fan_out() {
return fan_out;
}
// Writer de fan_out
void set_fan_out(int f_out) {
fan_out = f_out;
}
#endif NUMERIQUE_FLAG_
#include <iostream.h>
#ifndef CIRCUIT_FLAG_
#define CIRCUIT_FLAG_
class CIRCUIT {
private:
double v_num_plus; //Tension numerique positive (en volts)
double v_num_moins; //Tension numerique negative (en
volts)
double v_analog_plus; //Tension analogique positive (en
volts)
int numero; //Numero du composant
public:
CIRCUIT () {
// cout << “Appel du constructeur defaut de CIRCUIT” <<
endl;
v_num_plus = 5.0;
v_num_moins = 0.0;
v_analog_plus = 12.0;
numero = 0;
}
// Reader de v_num_plus
double read_v_num_plus() {
return v_num_plus;
}
// Reader de v_num_moins
double read_v_num_moins () {
return v_num_moins;
}
// Reader de v_analog_plus
double read_v_analog_plus () {
return v_analog_plus;
}
// Reader de numero
double read_numero () {
return numero;
}
// Writer de v_num_plus
void set_v_num_plus(double v) {
v_num_plus = v;
}
// Writer de v_num_moins
void set_v_num_moins(double v) {
v_num_moins = v;
}
// Writer de v_analog_plus
void set_v_analog_plus(double v) {
v_analog_plus = v;
}
// Writer de numero
void set_numero(int n) {
numero = n;
}
};
#endif CIRCUIT_FLAG_
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include “numerique_35_1.h”
#include “nand_2_35_1.h”
#include “nor_2_35_1.h”
}
for (compteur = 0; flot_in >> type >> in_1 >> in_2 ;
++compteur) {
switch (type) {
case nand_2_code: num_ptr[compteur] = new NAND_2;
break;
case nor_2_code: num_ptr[compteur] = new NOR_2;
break;
default: cerr << “Erreur, piece “
<< type
<< “ non existante”
<< endl;
exit (0);
}
flot_in.close();
cout << compteur << “ objets crees. “ << endl << endl;
for (i = 0; i < compteur ; ++i) {
cout << num_ptr[i]->affiche_type() << endl;
}
Résultat
3 objets crees.
Composante NAND_2
Composante NOR_2
Composante NAND_2
Résumé
400 ✔ Les chaînes de caractères sont stockées dans un tableau à
une dimension et se terminent par le caractère nul.
✔ Pour déclarer une variable dont la valeur est une chaîne de
caractères, il suffit d’adopter la syntaxe suivante: char
*var_str = “chaine”.
✔ Pour qu’une fonction retourne une chaîne de caractères, il
suffit d’adopter la syntaxe suivante: char* nom_fonction...
#include <iostream.h>
#ifndef CIRCUIT_FLAG_
#define CIRCUIT_FLAG_
class CIRCUIT {
private:
double v_num_plus; //Tension numerique positive (en volts)
double v_num_moins; /*Tension numerique negative (en
volts)*/
double v_analog_plus; /*Tension analogique positive
(en volts)*/
int numero; //Numero du composant
public:
CIRCUIT () {
v_num_plus = 5.0;
v_num_moins = 0.0;
v_analog_plus = 12.0;
numero = 0;
}
// Readers
double read_v_num_plus() {
return v_num_plus;
}
double read_v_num_moins () {
return v_num_moins;
}
double read_v_analog_plus () {
return v_analog_plus;
}
double read_numero () {
return numero;
}
// Writers
void set_v_num_plus(double v) {
v_num_plus = v;
}
void set_v_num_moins(double v) {
v_num_moins = v;
}
void set_v_analog_plus(double v) {
v_analog_plus = v;
}
void set_numero(int n) {
numero = n;
}
#endif CIRCUIT_FLAG_
#ifndef ANALOGIQUE_FLAG_
#define ANALOGIQUE_FLAG_
public:
//Readers
double read_puissance_max() {
return puissance_max;
}
int read_type() {
return type;
}
//Writers
void set_puissance_max(double p_max) {
puissance_max = p_max;
}
void set_type(int typ) {
type = typ;
}
//Calcul du courant
virtual double calcule_courant() {return 0.0;}
};
#endif ANALOGIQUE_FLAG_
#include <iostream.h>
#include “circuit_36_1.h”
#include “analogique_36_1.h”
#ifndef RESISTANCE_FLAG_
#define RESISTANCE_FLAG_
private:
double res;
double tolerance;
public:
//Constructeur par defaut
RESISTANCE() {}
//Writers
void ecrire_resistance(double val) {
res = val;
}
void ecrire_tolerance(double val) {
tolerance = val;
}
#endif RESISTANCE_FLAG_
#include <iostream.h>
#include “circuit_36_1.h”
#include “analogique_36_1.h”
#ifndef CONDENSATEUR_FLAG_
#define CONDENSATEUR_FLAG_
private:
double capacite;
double v_claquage;
public:
//Constructeur par defaut
CONDENSATEUR() {}
//Readers
double lire_capacite() {
return capacite;
}
double lire_v_claquage() {
return v_claquage;
}
//Writers
void ecrire_capacite(double val) {
capacite = val;
}
void ecrire_v_claquage(double val) {
v_claquage = val;
}
#endif CONDENSATEUR_FLAG_
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include “circuit_36_1.h”
#include “analogique_36_1.h”
#include “resis_2_36_1.h”
#include “condens_2_36_1.h”
return cir.read_v_analog_plus() *
cir.calcule_courant();
}
void main() {
}
flot_in.close();
cout << “Nb de pieces total: “ << compteur << endl;
cout << “Nb de resistances detectees: “ << r << endl;
Résultat
Nb de pieces total: 6
Nb de resistances detectees: 3
Puissance dans la resistance: 0: 0
Puissance dans la resistance: 1: 0
Puissance dans la resistance: 2: 0
Copie
411 Ce qui précède est aussi valable pour tout type d’argument de
fonction.
Résultat
Nb de pieces total: 6
Nb de resistances detectees: 3
Puissance dans la resistance: 0: 5.76
Puissance dans la resistance: 1: 12
Puissance dans la resistance: 2: 6
Résumé
415 ✔ Le C++ utilise normalement le passage d’argument par
valeur. Dans ce cas, la valeur de l’argument est copiée dans
l’espace de mémoire réservé pour le paramètre lors de l’appel de
la fonction. C’est cet espace qui est utilisé par la fonction. Il est
libéré lorsque l’exécution de la fonction se termine. Il est
impossible de modifier la valeur de l’argument puisque la
fonction ne travaille que sur une copie.
✔ On peut forcer le compilateur C++ à recevoir un argument
par référence. Dans ce cas, l’espace de mémoire occupé par
l’argument est partagé par le paramètre.
✔ Une raison de forcer le passage par référence est que cette
façon de faire permet de nous assurer que toutes les parties
d’un objet sont accessibles de la fonction.
✔ Une autre raison d’utiliser le passage par référence est que
cette approche est plus rapide étant donné que seule l’adresse
de l’objet passé en argument à la fonction est copiée dans le
paramètre.
✔ Une dernière raison de passer des arguments par référence
est que la fonction peut modifier l’objet directement et non
seulement une copie de l’objet tout en évitant les fuites de
mémoire si le constructeur par recopie est absent. La notion de
constructeur par recopie sera abordée au CHAPITRE 48.
✔ Pour indiquer à une fonction qu’un argument est passé par
référence, il suffit d’adopter la syntaxe suivant:
type_de_retour nom_fonction(...,
type_arg & nom_parametre,...) {...}
CHAPITRE 37 La surdéfinition de
l’opérateur d’insertion
417 La mémoire qui est réservée pour chaque paramètre passé par
valeur ou chaque variable locale d’une fonction ne l’est que
temporairement, le temps que la fonction s’exécute. L’espace
est ensuite récupéré par le programme et peut être réutilisé à
d’autres fins. En fait, lorsqu’une fonction est appelée, les
paquets de mémoire réservés aux paramètres sont placés dans
un espace mémoire appelé “pile”3. On dit que les éléments sont
placés sur le dessus de la pile4. Au retour de la fonction, ces
4. “pushed on stack”
Vide
Vide
Réservé pour un paramètre Valeur copiée
passé par valeur qu’on retourne durant le retour
Utilisé de la fonction
via la pile
Utilisé
Utilisé
Mémoire réservée
par le programme
Utilisé pour stocker la
valeur de retour
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include “circuit_37_1.h”
#include “nand_2_37_1.h”
#include “nor_2_37_1.h”
#include “analogique_37_1.h”
#include “resis_2_37_1.h”
#include “condens_2_37_1.h”
void main() {
}
flot_in.close();
Résultat
[RESISTANCE]-[NAND_2]-[CONDENSATEUR]-[NOR_2]-[RESISTANCE]
423 On sait qu’en général, l’opérateur d’insertion << est défini pour
les types de données standard1 supportés par le C++. Si on
pouvait faire en sorte que l’opérateur d’insertion accepte des
types de données définis par l’usager, cela permettrait d’avoir
un code beaucoup plus lisible comme par exemple la nouvelle
version de la boucle for d’affichage dans le programme du
paragraphe 421:
cout << *cir_ptr[0]; //Premier element sans “-”
for (i = 1; i < compteur ; ++i) {
cout << “-” << *cir_ptr[i];
cout
{
cout
1. On n’a qu’à penser à la surdéfinition des constructeurs dans les classes comme exemple.
Résultat
[RESISTANCE]-[NAND_2]-[CONDENSATEUR]-[NOR_2]-[RESISTANCE]
Résumé
432 ✔ Si on veut surdéfinir un opérateur binaire (i.e. à deux
opérandes), on adopte le patron suivant:
type_retour operateur symbole
type_gauche nom, type_droite nom) {...}
✔ Pour surdéfinir l’opérateur d’insertion, on adopte le patron
suivant:
ostream& operator symbole
(ostream& output_stream, type_droite nom) {
énoncés
return output_stream;
}
439 Pour utiliser un opérateur sur un objet d’une classe définie par
un usager, l’opérateur doit être surdéfini. Deux exception
échappent cependant à cette règle:
1. l’opérateur d’affectation = peut être utilisé avec tout type
d’objet, standard ou non, sans avoir été surdéfini. Le compor-
tement par défaut de cet opérateur est une copie membre à
membre des éléments de chaque classe. Ce comportement
par défaut peut parfois donner des résultats erronés (par
exemple dans les cas d’allocation dynamique d’objets) et il
1. “friend” en anglais. Nous verrons comment déclarer une fonction amie un peu plus loin.
452 Un opérateur unaire d’une classe peut être surdéfini via une
fonction membre1 sans argument ou via une fonction ordinaire
avec un argument correspondant à un objet de la classe ou une
référence à un objet de la classe. Prenons par exemple la classe
NAND_2. Il serait intéressant de pouvoir inverser la sortie d’une
porte simplement en utilisant l’opérateur de négation ~ suivi
du nom de la porte. Il faut bien noter qu’on désire faire la
négation de la valeur de sortie mais sans changer la sortie.
Cela peut éventuellement servir en logique booléenne:
NAND_2 nnd;
cout << ~nnd;
#include <iostream.h>
#include “circuit_37_1.h”
#include “numerique_37_1.h”
#ifndef NAND_FLAG_
#define NAND_FLAG_
private:
int out_1; /*sortie */
int set_out_1() {
return 1 - (in_1 * in_2);
}
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
//Readers
int lire_in_1() {
return in_1;
}
int lire_in_2() {
return in_2;
}
int lire_out_1() {
return out_1;
}
//Writers
void ecrire_in_1(int val) {
in_1 = val;
out_1 = set_out_1();
int operator ~ () {
if (lire_out_1()) return 0;
else return 1;
}
#endif NAND_FLAG_
#include <iostream.h>
#include <fstream.h>
#include “circuit_37_1.h”
#include “numerique_37_1.h”
#include “nand_2_39_1.h”
void main() {
int i,j;
NAND_2 nnd;
Résultat
Table de verite d’une porte NAND
--------------------------------
entree 1 entree 2 sortie neg. sortie
0 0 1 0
0 1 1 0
1 0 1 0
1 1 0 1
Résumé
454 ✔ La surdéfinition d’un opérateur unaire comme fonction
membre d’une classe suit la syntaxe suivante:
type_valeur_retour operateur symbole ()
{ énoncés;
return valeur_retour;}
1. le code peut être copié textuellement dans la classe NOR_2 sans changement
Notion avancée
458 Pourquoi imposer que la référence soit constante? Simplement
pour s’assurer que la fonction ne modifiera pas l’argument. En
effet, on sait que le fait de passer un argument par référence
permet à la fonction de modifier la valeur de l’argument et que
ce changement se propagera au-delà de l’appel de la fonction.
Pour éviter que l’opérande de droite d’une affectation soit
modifiée par l’opérateur, il suffit simplement de spécifier au
C++ que cette quantité est constante. Le compilateur donnera
alors un message d’erreur si le programmeur tente de modifier
la valeur de l’argument ce qui ajoute un niveau de sécurité
normalement absent lors du passage par référence. Il faut
remarquer que l’utilisation de l’attribut const pour une
class A
{
private:
int n;
public:
int lire_n() const
{
return n;
}
main()
{
A ob1;
ob1.ecrire_n(5);
fonction(ob1);
}
res_temp.ecrire_resistance(res +
right_res.lire_resistance());
res_temp.ecrire_tolerance(right_res.lire_tolerance());
return res_temp;
}
res_temp.ecrire_resistance((res *
right_res.lire_resistance()) /
(res + right_res.lire_resistance()));
res_temp.ecrire_tolerance(right_res.lire_tolerance());
return res_temp;
}
R1 R2
R p = -----------------
- EQ 4
R1 + R 2
#include <iostream.h>
#include “circuit_37_1.h”
#include “analogique_37_1.h”
#include “resis_2_40_1.h”
void main() {
Résultat
Resistance serie: 3000
Resistance parallele: 333.333
Exercice
463 Ecrivez une fonction de surdéfinition des opérateurs =, + et ||
pour les combinaisons en série et en parallèle de
condensateurs. Rappelez-vous que la combinaison parallèle
(opérateur ||) de deux condensateurs se calcule comme suit:
C paral = C 1 + C 2 EQ 5
C1 C 2
C ser = ------------------- EQ 6
C1 + C 2
Résumé
464 ✔ La surdéfinition d’opérateurs binaires comme fonctions
membres d’une classe comprend un argument implicite
(opérande de gauche), un argument ordinaire passé par
référence (opérande de droite) et une valeur de retour.
✔ Il faut utiliser le passage de paramètres par référence avec
prudence en utilisant la notion de référence constante (const)
lorsque nécessaire.
#include <iostream.h>
#ifndef CIRCUIT_FLAG_
#define CIRCUIT_FLAG_
class CIRCUIT {
private:
double v_num_plus; //Ten. num. positive (en volts)
double v_num_moins; //Ten. num. negative (en volts)
double v_analog_plus; //Ten. an. positive (en volts)
int numero; //Numero du composant
public:
CIRCUIT ();
CIRCUIT (double, double, double, int);
double read_v_num_plus();
double read_v_num_moins ();
double read_v_analog_plus ();
int read_numero ();
void set_v_num_plus(double);
void set_v_num_moins(double);
void set_v_analog_plus(double);
void set_numero(int);
virtual char *affiche_type();
virtual double calcule_courant();
};
#endif CIRCUIT_FLAG_
#include <iostream.h>
#include “circuit_41_1.h”
//Constructeur defaut
CIRCUIT::CIRCUIT () {
v_num_plus = 5.0;
v_num_moins = 0.0;
v_analog_plus = 12.0;
numero = 0;
}
// Readers
double CIRCUIT::read_v_num_plus() {
return v_num_plus;
}
double CIRCUIT::read_v_num_moins () {
return v_num_moins;
}
double CIRCUIT::read_v_analog_plus () {
return v_analog_plus;
}
int CIRCUIT::read_numero () {
return numero;
}
// Writers
void CIRCUIT::set_v_num_plus(double v) {
v_num_plus = v;
}
void CIRCUIT::set_v_num_moins(double v) {
v_num_moins = v;
}
void CIRCUIT::set_v_analog_plus(double v) {
v_analog_plus = v;
}
void CIRCUIT::set_numero(int n) {
numero = n;
}
#ifndef ANALOGIQUE_FLAG_
#define ANALOGIQUE_FLAG_
public:
ANALOGIQUE ();
ANALOGIQUE (double, int);
double read_puissance_max();
int read_type();
void set_puissance_max(double);
void set_type(int);
virtual char *affiche_type();
virtual double calcule_courant();
};
#endif ANALOGIQUE_FLAG_
#include <iostream.h>
#include “circuit_41_1.h”
#include “analogique_41_1.h”
//Readers
double ANALOGIQUE::read_puissance_max() {
return puissance_max;
}
int ANALOGIQUE::read_type() {
return type;
}
//Writers
void ANALOGIQUE::set_puissance_max(double p_max) {
puissance_max = p_max;
}
void ANALOGIQUE::set_type(int typ) {
type = typ;
}
//Calcul du courant
double ANALOGIQUE::calcule_courant() {return 0.0;}
#ifndef NUMERIQUE_FLAG_
#define NUMERIQUE_FLAG_
public:
NUMERIQUE ();
NUMERIQUE (int);
int read_fan_out();
void set_fan_out(int);
virtual char *affiche_type();
};
#endif NUMERIQUE_FLAG_
#include <iostream.h>
#include “circuit_41_1.h”
#include “numerique_41_1.h”
// Readers
int NUMERIQUE::read_fan_out() {
return fan_out;
}
// Writers
void NUMERIQUE::set_fan_out(int f_out) {
fan_out = f_out;
}
#include <iostream.h>
#include “circuit_41_1.h”
#include “numerique_41_1.h”
#ifndef NAND_FLAG_
#define NAND_FLAG_
private:
int out_1; /*sortie */
int set_out_1();
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
NAND_2();
NAND_2(int, int);
int lire_in_1();
int lire_in_2();
int lire_out_1();
void ecrire_in_1(int);
void ecrire_in_2(int);
int operator ~ ();
virtual char *affiche_type();
};
#endif NAND_FLAG_
#include <iostream.h>
#include “circuit_41_1.h”
#include “numerique_41_1.h”
#include “nand_2_41_1.h”
int NAND_2::set_out_1() {
return 1 - (in_1 * in_2);
}
//Readers
int NAND_2::lire_in_1() {
return in_1;
}
int NAND_2::lire_in_2() {
return in_2;
}
int NAND_2::lire_out_1() {
return out_1;
}
//Writers
void NAND_2::ecrire_in_1(int val) {
in_1 = val;
out_1 = set_out_1();
}
in_2 = val;
out_1 = set_out_1();
}
#include <iostream.h>
#include “circuit_41_1.h”
#include “numerique_41_1.h”
#ifndef NOR_FLAG_
#define NOR_FLAG_
private:
int out_1; /*sortie */
int set_out_1();
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
NOR_2();
NOR_2(int, int);
int lire_in_1();
int lire_in_2();
int lire_out_1();
void ecrire_in_1(int);
void ecrire_in_2(int);
int operator ~ ();
virtual char *affiche_type();
};
#endif NOR_FLAG_
#include <iostream.h>
#include “circuit_41_1.h”
#include “numerique_41_1.h”
#include “nor_2_41_1.h”
int NOR_2::set_out_1() {
return ((1-in_1) * (1-in_2));
}
//Readers
int NOR_2::lire_in_1() {
return in_1;
}
int NOR_2::lire_in_2() {
return in_2;
}
int NOR_2::lire_out_1() {
return out_1;
}
//Writers
void NOR_2::ecrire_in_1(int val) {
in_1 = val;
out_1 = set_out_1();
}
void NOR_2::ecrire_in_2(int val) {
in_2 = val;
out_1 = set_out_1();
}
else return 1;
}
// Affichage du type de composant
char * NOR_2::affiche_type() {return “NOR_2”;}
#include <iostream.h>
#include “circuit_41_1.h”
#include “analogique_41_1.h”
#ifndef RESISTANCE_FLAG_
#define RESISTANCE_FLAG_
private:
double res;
double tolerance;
public:
RESISTANCE();
RESISTANCE(double, double);
double lire_resistance() const;
double lire_tolerance() const;
void ecrire_resistance(double);
void ecrire_tolerance(double);
const RESISTANCE& operator= (const RESISTANCE&);
RESISTANCE operator+ (const RESISTANCE&)const;
RESISTANCE operator|| (const RESISTANCE&)const;
virtual char *affiche_type();
virtual double calcule_courant();
};
#endif RESISTANCE_FLAG_
#include <iostream.h>
#include “circuit_41_1.h”
#include “analogique_41_1.h”
#include “resis_2_41_1.h”
//Readers
double RESISTANCE::lire_resistance() const {
return res;
}
//Writers
void RESISTANCE::ecrire_resistance(double val) {
res = val;
}
res_temp.ecrire_resistance(res +
right_res.lire_resistance());
res_temp.ecrire_tolerance(right_res.lire_tolerance());
return res_temp;
}
res_temp.ecrire_resistance((res *
right_res.lire_resistance()) /
(res + right_res.lire_resistance()));
res_temp.ecrire_tolerance(right_res.lire_tolerance());
return res_temp;
}
//Calcul du courant
double RESISTANCE::calcule_courant() {
return read_v_analog_plus() / res;
}
#include <iostream.h>
#include “circuit_41_1.h”
#include “analogique_41_1.h”
#ifndef CONDENSATEUR_FLAG_
#define CONDENSATEUR_FLAG_
private:
double capacite;
double v_claquage;
public:
CONDENSATEUR();
CONDENSATEUR(double, double);
double lire_capacite();
double lire_v_claquage();
void ecrire_capacite(double);
void ecrire_v_claquage(double);
virtual char *affiche_type();
virtual double calcule_courant();
};
#endif CONDENSATEUR_FLAG_
//&% condens_2_41_1.C
#include <iostream.h>
#include “circuit_41_1.h”
#include “analogique_41_1.h”
#include “condens_2_41_1.h”
//Readers
double CONDENSATEUR::lire_capacite() {
return capacite;
}
double CONDENSATEUR::lire_v_claquage() {
return v_claquage;
}
//Writers
void CONDENSATEUR::ecrire_capacite(double val) {
capacite = val;
}
void CONDENSATEUR::ecrire_v_claquage(double val) {
v_claquage = val;
}
//Calcul du courant
double CONDENSATEUR::calcule_courant(){return 0.0;}
#include <iostream.h>
#include “circuit_41_1.h”
#include “analogique_41_1.h”
#include “resis_2_41_1.h”
void main() {
Résumé
487 ✔ Il est préférable, pour respecter les principes de
programmation par objets, de conserver la définition d’une
classe et le code des fonctions membres dans des fichiers
séparés.
✔ La définition de la classe est placée dans le fichier
d’extension .h et le code d’implantation des fonctions membres
est placé dans un fichier source d’extension .C aussi appelé
module.
circuit_41_1.h
circuit_41_1.C
circuit_41_1.o
analogique_41_1.h
analogique_41_1.C
analogique_41_1.o
resis_2_41_1.h
resis_2_41_1.C
resis_2_41_1.o
sur_def_41_1.C
sur_def_41_1.o
sur_def_41_1
1. l’expression “fichier makefile” est souvent remplacée par l’expression abrégée “makefile”
CC -c circuit_41_1.C
CC -c analogique_41_1.C
CC -c resis_2_41_1.C
Projet_41_1
circuit_41_1.h circuit_41_1.C
sur_def_41_1
analogique_41_1.h analogique_41_1.C
resis_2_41_1.h resis_2_41_1.C
sur_def_41_1.C
circuit_41_1.o
resis_2_41_1.o
analogique_41_1.o
makefile
ou encore
${nom}
#*******************************************************
# Definition des macros *
#*******************************************************
PROGRAMME = sur_def_41_1
OBJECTS = circuit_41_1.o analogique_41_1.o resis_2_41_1.o
INC_DIR = ../include_41_1
BIN_DIR = ../bin_41_1
INCLUDES = $(INC_DIR)/circuit_41_1.h\
$(INC_DIR)/ analogique_41_1.h\
$(INC_DIR)/resis_2_41_1.h
CC = CC
CFLAGS =
CPPFLAGS = -I$(INC_DIR)
#*******************************************************
# Definition des regles implicites de dependance *
#*******************************************************
.SUFFIXES : .o .C
.C.o :
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
#*******************************************************
# Regle de construction du programme principal *
#*******************************************************
1. make est un utilitaire écrit par des nerds, documenté par des nerds et utilisé par des nerds. En
résumé, make est un utilitaire particulièrement difficile à maîtriser par une personne ordinaire!
#*******************************************************
# Regle de construction des objets *
#*******************************************************
$(OBJECTS) : $(INCLUDES)
#*******************************************************
# Regle de nettoyage des fichiers objets *
#*******************************************************
clean:
rm *.o; mv $(PROGRAMME) $(BIN_DIR)
1. make contient un très grand nombre de macros prédéfinies. Consulter un livre de référence sur
make pour plus de détails.
2. Par exemple, l’usager peut être intéressé à utiliser un programme de debugging pour le déve-
loppement de son programme. La documentation du compilateur CC indique qu’il faut ajouter
l’option -g lorsque le programme est compilé si on veut utiliser le debugger. Il suffit alors de défi-
nir la macro CFLAGS comme étant justement -g et de lancer le makefile.
3. Pour plus d’informations sur les nombreuses options du compilateur CC, faire la commande
man CC.
.C.o :
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
1. Pour plus de détails, consulter les nombreux ouvrages de référence sur make ou faire la com-
mande man make.
501 Une fois les règles implicites définies, le makefile contient les
lignes d’instructions nécessaires à la construction du
programme principal et des autres cibles essentielles à son bon
fonctionnement. Les dépendances, prérequis et commandes de
constructions sont les suivantes:
#*******************************************************
# Regle de construction du programme principal *
#*******************************************************
#*******************************************************
# Regle de construction des objets *
#*******************************************************
$(OBJECTS) : $(INCLUDES)
La ligne de dépendance
$(PROGRAMME) : $(OBJECTS) $(INCLUDES)
indique, via les macros, que les dépendances sont les suivantes
pour le programme principal:
sur_def_41_1 : circuit_41_1.o analogique_41_1.o\
resis_2_41_1.o\
../include_41_1/circuit_41_1.h\
../include_41_1/analogique_41_1.h\
../include_41_1/resis_2_41_1.h
make voit que tous les fichiers .h et .o sont à jour par rapport
à sur_def_41_1 et qu’il n’y a donc pas lieu de reconstruire
ceux-ci. Il se contente donc d’informer l’utilisateur que
sur_def_41_1, la première cible du makefile, est à jour et
peut donc être utilisée directement.
508 Le makefile conçu dans ce chapitre est très flexible parce qu’il
utilise les macros. Par exemple, si on désire changer le nom du
512 Pour lire une chaîne de caractères dans un fichier, il faut tout
d’abord créer un tableau dans lequel elle sera stockée
temporairement. Il faut s’assurer que le tableau est
suffisamment grand pour les chaînes à traiter en incluant le
caractère nul servant de marqueur de fin de chaîne. Pour notre
programme d’analyse de circuit, une chaîne de caractères de
100 composantes est nettement suffisante:
char input_chaine[100];
Résumé
515 ✔ Le nom d’une chaîne de caractères est en fait le nom d’un
pointeur qui pointe au début de la chaîne.
✔ On ne peut réassigner quelque chose au pointeur d’une
chaîne parce que le C++ considère cet objet comme une
constante.
✔ Pour lire une chaîne de caractères dans un fichier, il faut
d’abord créer un tableau assez grand pour stocker la plus
longue chaîne qu’il contient.
void main() {
int i, compteur;
int in_1,in_2;
double d_1, d_2;
char input_chaine[100];
CIRCUIT *cir_ptr[100];
ifstream flot_in ("it_dat_45_1.in", ios::in);
if (flot_in == 0) {
exit(0);
}
for (compteur = 0; flot_in >> input_chaine; ++compteur) {
switch (extract_code(input_chaine)) {
case nand_2_code: flot_in >> in_1 >> in_2;
cir_ptr[compteur] = new NAND_2(in_1,in_2);
break;
case nor_2_code: flot_in >> in_1 >> in_2;
cir_ptr[compteur] = new NOR_2(in_1,in_2);
break;
case resis_code: flot_in >> d_1 >> d_2;
cir_ptr[compteur] = new RESISTANCE(d_1,d_2);
break;
case condens_code : flot_in >> d_1 >> d_2;
cir_ptr[compteur] = new CONDENSATEUR(d_1,d_2);
break;
default: cerr << "Piece "
<< extract_code(input_chaine)
<< " n'est pas une connue"
<< endl;
exit(0);
}
}
flot_in.close();
Résultat
[RESISTANCE]-[NAND_2]-[CONDENSATEUR]-[NOR_2]-[RESISTANCE]
Résumé
520 ✔ Pour lire une chaîne de caractère dans un fichier, il suffit de
déclarer un vecteur de caractères suffisamment long pour
stocker la plus grande chaîne attendue. Ensuite, l’opérateur
d’extraction >>, qui reçoit le nom de la chaîne en argument, lit
les caractères jusqu’au premier caractère blanc (espace, retour
de chariot, nouvelle ligne ou tabulation).
✔ Le nom d’une chaîne de caractères est un pointeur à son
premier élément. La chaîne se termine par le caractère nul \0.
✔ Pour accéder à un caractère d’une chaîne de caractère, il
suffit simplement d’adresser l’élément du vecteur servant à
stocker cette chaîne. Il faut se rappeler que les vecteurs sont en
origine zéro, c’est-à-dire que le premier élément est situé à
l’indice 0.
#include <iostream.h>
#include <string.h>
class CIRCUIT {
private:
double v_num_plus; /*Tension numerique positive
(en volts)*/
double v_num_moins; /*Tension numerique negative
(en volts)*/
double v_analog_plus; /*Tension analogique positive
(en volts)*/
int numero; //Numero du composant
public:
CIRCUIT ();
CIRCUIT (char *);
CIRCUIT (double, double, double, int);
double read_v_num_plus();
double read_v_num_moins ();
double read_v_analog_plus ();
double read_numero ();
char * read_code();
void set_v_num_plus(double);
void set_v_num_moins(double);
void set_v_analog_plus(double);
void set_numero(int);
virtual char *affiche_type();
virtual double calcule_courant();
};
#endif CIRCUIT_FLAG_
On rajoute simplement:
1. l’instruction de pré-processeur permettant d’inclure les infor-
mations pertinentes à la manipulation de chaînes de caractè-
res:
#include <string.h>
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include "circuit_45_1.h"
#include "nand_2_45_1.h"
#include "nor_2_45_1.h"
#include "analogique_45_1.h"
#include "resis_2_45_1.h"
#include "condens_2_45_1.h"
void main() {
int i, compteur;
int in_1,in_2;
double d_1, d_2;
char input_chaine[100];
CIRCUIT *cir_ptr[100];
ifstream flot_in ("it_dat_45_1.in", ios::in);
if (flot_in == 0){
exit(0);
}
for (compteur = 0; flot_in >> input_chaine; ++compteur) {
switch (extract_code(input_chaine)) {
case nand_2_code: flot_in >> in_1 >> in_2;
cir_ptr[compteur] =
new NAND_2(in_1,in_2,input_chaine);
break;
case nor_2_code: flot_in >> in_1 >> in_2;
cir_ptr[compteur] =
new NOR_2(in_1,in_2,input_chaine);
break;
case resis_code: flot_in >> d_1 >> d_2;
cir_ptr[compteur] =
new RESISTANCE(d_1,d_2,input_chaine);
break;
case condens_code : flot_in >> d_1 >> d_2;
cir_ptr[compteur] =
new CONDENSATEUR(d_1,d_2,input_chaine);
break;
default: cerr << "Piece "
<< extract_code(input_chaine)
<< " n'est pas une connue"
<< endl;
exit(0);
}
}
flot_in.close();
Exercice
526 Modifiez les classes NOR_2, ANALOGIQUE, RESISTANCE et
CONDENSATEUR pour tenir compte des notions énoncées pour la
classe NAND_2.
Résultat
[RESISTANCE]-[NAND_2]-[CONDENSATEUR]-[NOR_2]-[RESISTANCE]
RESIS__3
NAND_2_1
COND___4
NOR__2_2
RESIS__3
Résumé
528 ✔ Lorsqu’on désire créer un tableau au run-time pour stocker
une chaîne de caractères, il suffit d’adopter la syntaxe suivante:
variable de type pointeur de caractère = new
char[nb caractères +1]
✔ Si on désire utiliser les fonctions de traitement de chaînes de
caractères du C++, il suffit d’ajouter la ligne #include
<string.h> au programme.
✔ Pour connaître la longueur d’une chaîne de caractères en
excluant le caractère fin de chaîne, il suffit d’adopter la syntaxe
suivante: strlen(nom_de_la_chaîne).
✔ Pour copier une chaîne dans une autre, il suffit d’adopter la
syntaxe suivante: strcpy(nom_chaîne_destination,
nom_chaîne_source).
CHAPITRE 47 La récupération de la
mémoire (désallocation)
grâce à l’opérateur
delete et aux fonctions
membres appelées
destructeurs
main () {
while(1)
cout << “Entrer un nom de fichier au clavier.”
<< endl <<“:”;
cin >> input_chaine;
ifstream flot_in (input_chaine,ios::in);
if (!flot_in) {
cerr << “Fichier inexistant.” << endl;
return;
}
analyse_circuit(flot_in);
flot_in.close();
}
}
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include "circuit_47_1.h"
#include "nand_2_47_1.h"
#include "nor_2_47_1.h"
#include "analogique_47_1.h"
#include "resis_2_47_1.h"
#include "condens_2_47_1.h"
char input_chaine[100];
CIRCUIT *cir_ptr[100];
/*------------------------------------------------------*/
/*------------------------------------------------------*/
void analyse_circuit(ifstream &flot_entree) {
int i,compteur;
int in_1,in_2;
double d_1, d_2;
//Elements suivants
int main() {
while (1) {
Résultat
Entrer un nom de fichier au clavier.
:it_dat_47_1.in
[RESISTANCE]-[NAND_2]-[CONDENSATEUR]-[NOR_2]-[RESISTANCE]
Entrer un nom de fichier au clavier.
:it_dat_47_2.in
[NAND_2]-[CONDENSATEUR]-[RESISTANCE]-[NAND_2]-
[CONDENSATEUR]-[NOR_2]-[NAND_2]-[CONDENSATEUR]-
[RESISTANCE]
Entrer un nom de fichier au clavier.
:it_dat_47_3.in
[NAND_2]-[CONDENSATEUR]-[RESISTANCE]-[NAND_2]-
[CONDENSATEUR]-[NOR_2]-[NAND_2]-[CONDENSATEUR]-
[RESISTANCE]-[NAND_2]-[CONDENSATEUR]-[RESISTANCE]-
[NAND_2]-[CONDENSATEUR]-[NOR_2]-[NAND_2]-[CONDENSATEUR]-
[RESISTANCE]
Entrer un nom de fichier au clavier.
:in_dat_47_4.in
Fichier inexistant.
Données innaccessibles
car aucun pointeur ne
permet d’en accéder le
contenu Premier objet Deuxième objet Troisième objet
1. “garbage” en anglais.
2. “memory leak” en anglais.
cir_ptr[compteur] =
new NAND_2(in_1,in_2,input_chaine);
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include "circuit_47_1.h"
#include "nand_2_47_1.h"
#include "nor_2_47_1.h"
#include "analogique_47_1.h"
#include "resis_2_47_1.h"
#include "condens_2_47_1.h"
char input_chaine[100];
CIRCUIT *cir_ptr[100];
/*------------------------------------------------------*/
/*-----------------------------------------------------*/
void analyse_circuit(ifstream &flot_entree) {
int i,compteur;
int in_1,in_2;
doubled_1, d_2;
//Elements suivants
void main() {
while (1) {
Après delete
Avant delete
Pointeur Pointeur
Objet
code
C C
h h
a a
î Fuite î
n n
e e
542 Afin de nous assurer que tout l’espace alloué pour un objet est
réclamé avec l’utilisation de l’opérateur delete, il nous faut
inclure des fonctions membres destructeurs à chaque classe.
On inclut donc les déclarations suivantes aux différents fichiers
d’inclusion:
class A
{
public:
A()
{
cout << “Constructeur A” << endl;
}
class B : public A
{
public:
B()
{
cout << “Constructeur B” << endl;
}
~B()
{
cout << “Destructeur B” << endl;
}
};
main()
{
A *ptr;
ptr = new B;
delete ptr;
543 Les instructions des destructeurs sont placées dans les fichiers
source de chaque classe. Pour le cas qui nous intéresse, les
fonctions suivantes ont été implantées:
numerique_47_1.C NUMERIQUE::~NUMERIQUE(){}
nand_2_47_1.C NAND_2::~NAND_2(){}
nor_2_47_1.C NOR_2::~NOR_2(){}
resis_2_47_1.C RESISTANCE::~RESISTANCE(){}
condens_2_47_1.C CONDENSATEUR::~CONDENSATEUR(){}
class A
{
int * tableau;
static int balance;
public:
A()
{
tableau = new int[25];
if (!tableau)
{
cerr << "Erreur..." << endl;
}
balance++;
cout << balance << " new sans delete" << endl;
}
~A()
{
delete [] tableau;
balance--;
cout << balance << " new sans delete" << endl;
}
};
int A::balance=0;
// ou
// int A::balance; // Initialisee a zero par defaut.
main()
{
int i,cpt;
cout << "Creer combien d’objets??" << endl;
cin >> i;
A *vect[100];
//A **vect;
//vect = new A *[i];
// assert(vect);
for (cpt=0;cpt<i;cpt++)
{
vect[cpt] = new A;
assert(vect[cpt]);
}
//delete [] vect;
Notion avancée
545 Un moyen efficace de garder en mémoire le nombre d’objets qui
ont été crées pour une classe est d’inclure une variable membre
static dans chaque classe. Contrairement aux variables
membres non static qui sont dupliquées pour chaque objet
d’une classe, les variables membres static sont partagées par
tous les objets d’une même classe. En incrémentant la variable
à chaque fois que le constructeur est appelé et en la
décrémentant à chaque fois qu’un objet est détruit, la variable
contient toujours le nombre actuel d’objets de la classe.
L’initialisation d’une variable membre static ne peut
évidemment pas se faire via le constructeur. Il faut plutôt
l’initialiser en adoptant la syntaxe suivante:
type nom_classe::nom_variable_static = valeur_initiale;
Résumé
546 ✔ La mémoire allouée mais inaccessible est appelée mémoire
détritus (garbage).
✔ Quand un programme produit de la mémoire détritus, on dit
qu’il possède des fuites (leaks).
✔ Quand on désire colmater une fuite de mémoire, il suffit
d’adopter l’une des syntaxes suivantes:
delete nom_pointeur;
delete nom_tableau[indice];
delete [] nom_tableau ;
✔ Un destructeur est une fonction membre qui est appelée
lorsque la mémoire allouée à un objet est récupérée par le
programme.
✔ Il est nécessaire que les destructeurs récupèrent de la
mémoire additionnelle à celle réservée pour l’objet en
appliquant l’opérateur delete aux variables membres qui sont
Objet Objet
code code
C
h
a
î
n
e
Objet
code
Notion avancée
553 La conception des constructeurs par recopie nous amène à
reconsidérer la surdéfinition de l’opérateur = pour des objets
contenant des pointeurs pointant à des espaces mémoire
alloués dynamiquement. Lorsque nous avons abordé la
surdéfinition de l’opérateur = au paragraphe 457 du
CHAPITRE 40, nous avons vu qu’il fallait concevoir la fonction
membre operator= de façon telle qu’elle affecte chaque
variable membre de l’objet à gauche de l’opérateur avec la
valeur correspondante de la variable membre de l’objet situé à
droite du signe =. Après ce que nous venons de voir sur le
constructeur par recopie, nous pouvons étendre la notion de
surdéfinition de l’opérateur = aux objets ayant des variables
membres qui sont des pointeurs. Dans ce cas, la fonction
operator= doit veiller à ce que non seulement la valeur du
pointeur soit affectée à l’objet de gauche, mais également les
données auxquelles pointe ce pointeur.
Résumé
554 ✔ En général, les objets passés en argument par valeur à des
fonctions peuvent conduire à des problèmes subtils de
désallocation de mémoire. Autant que possible, il faut éviter de
passer des objets par valeur à des fonctions. Un moyen de
s’assurer que les objets sont passés par référence est de définir
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include "circuit_48_1.h"
#include "nand_2_48_1.h"
#include "nor_2_48_1.h"
#include "analogique_48_1.h"
#include "resis_2_48_1.h"
#include "condens_2_48_1.h"
void main() {
#include <iostream.h>
#include <string.h>
#ifndef CIRCUIT_FLAG_
#define CIRCUIT_FLAG_
class CIRCUIT {
private:
double v_num_plus; /*Tension numerique positive
(en volts)*/
double v_num_moins; /*Tension numerique negative
(en volts)*/
double v_analog_plus; /*Tension analogique positive
(en volts)*/
int numero; //Numero du composant
public:
CIRCUIT ();
CIRCUIT (char *);
CIRCUIT (double, double, double, int);
CIRCUIT (const CIRCUIT &);
virtual ~CIRCUIT();
double read_v_num_plus() const;
double read_v_num_moins () const;
double read_v_analog_plus () const;
int read_numero () const;
char * read_code() const;
void set_v_num_plus(double);
void set_v_num_moins(double);
void set_v_analog_plus(double);
void set_numero(int);
virtual char *affiche_type();
virtual double calcule_courant();
};
#endif CIRCUIT_FLAG_
//&% circuit_48_1.C
//Definitions des fonctions membres de la classe CIRCUIT
#include <iostream.h>
#include "circuit_48_1.h"
//Constructeur defaut
CIRCUIT::CIRCUIT () {
v_num_plus = 5.0;
v_num_moins = 0.0;
v_analog_plus = 12.0;
numero = 0;
}
CIRCUIT::CIRCUIT(char* inpt_str) {
code = new char[strlen(inpt_str) + 1];
strcpy(code, inpt_str);
}
//Copy constructor
CIRCUIT::CIRCUIT(const CIRCUIT & cr){
v_num_plus = cr.read_v_num_plus();
v_num_moins = cr.read_v_num_moins ();
v_analog_plus = cr.read_v_analog_plus ();
numero = cr.read_numero ();
code = new char[strlen(cr.code) + 1];
strcpy(code, cr.code);
}
//Destructeur
CIRCUIT::~CIRCUIT(){}
// Readers
}
double CIRCUIT::read_v_num_moins () const{
return v_num_moins;
}
double CIRCUIT::read_v_analog_plus () const{
return v_analog_plus;
}
int CIRCUIT::read_numero () const{
return numero;
}
// Writers
void CIRCUIT::set_v_num_plus(double v) {
v_num_plus = v;
}
void CIRCUIT::set_v_num_moins(double v) {
v_num_moins = v;
}
void CIRCUIT::set_v_analog_plus(double v) {
v_analog_plus = v;
}
void CIRCUIT::set_numero(int n) {
numero = n;
}
#ifndef ANALOGIQUE_FLAG_
#define ANALOGIQUE_FLAG_
int type;
public:
ANALOGIQUE ();
ANALOGIQUE (double, int);
ANALOGIQUE (char *);
ANALOGIQUE (const ANALOGIQUE & an);
virtual ~ANALOGIQUE();
double read_puissance_max() const;
int read_type() const;
void set_puissance_max(double);
void set_type(int);
virtual char *affiche_type();
virtual double calcule_courant();
};
#endif ANALOGIQUE_FLAG_
#include <iostream.h>
#include "circuit_48_1.h"
#include "analogique_48_1.h"
//Copy constructor
ANALOGIQUE::ANALOGIQUE(const ANALOGIQUE & an){
set_v_num_plus(an.read_v_num_plus());
set_v_num_moins(an.read_v_num_moins());
set_v_analog_plus(an.read_v_analog_plus());
set_numero(an.read_numero());
code = new char[strlen(an.code) + 1];
strcpy(code, an.code);
set_puissance_max(an.read_puissance_max());
set_type(an.read_type());
}
//Destructeur
ANALOGIQUE::~ANALOGIQUE() {}
//Readers
double ANALOGIQUE::read_puissance_max() const{
return puissance_max;
}
int ANALOGIQUE::read_type() const{
return type;
}
//Writers
void ANALOGIQUE::set_puissance_max(double p_max) {
puissance_max = p_max;
}
void ANALOGIQUE::set_type(int typ) {
type = typ;
}
//Calcul du courant
double ANALOGIQUE::calcule_courant() {return 0.0;}
#ifndef NUMERIQUE_FLAG_
#define NUMERIQUE_FLAG_
public:
NUMERIQUE ();
NUMERIQUE (int);
NUMERIQUE (char *);
NUMERIQUE (const NUMERIQUE &);
virtual ~NUMERIQUE();
int read_fan_out() const;
void set_fan_out(int);
virtual char *affiche_type();
};
#endif NUMERIQUE_FLAG_
//Copy constructor
NUMERIQUE::NUMERIQUE(const NUMERIQUE & nm) {
set_v_num_plus(nm.read_v_num_plus());
set_v_num_moins(nm.read_v_num_moins());
set_v_analog_plus(nm.read_v_analog_plus());
set_numero(nm.read_numero());
code = new char[strlen(nm.code) + 1];
strcpy(code, nm.code);
set_fan_out(nm.read_fan_out());
}
//Destructeur
NUMERIQUE::~NUMERIQUE(){}
// Readers
int NUMERIQUE::read_fan_out() const{
return fan_out;
}
// Writers
void NUMERIQUE::set_fan_out(int f_out) {
fan_out = f_out;
}
#include <iostream.h>
#include "circuit_48_1.h"
#include "numerique_48_1.h"
#ifndef NAND_FLAG_
#define NAND_FLAG_
private:
int out_1; /*sortie */
int set_out_1();
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
NAND_2();
NAND_2(int, int);
NAND_2(int, int, char*);
NAND_2(const NAND_2 &);
virtual ~NAND_2();
int lire_in_1() const;
int lire_in_2() const;
int lire_out_1() const;
void ecrire_in_1(int);
void ecrire_in_2(int);
int operator ~ ();
virtual char *affiche_type();
};
#endif NAND_FLAG_
#include <iostream.h>
#include "circuit_48_1.h"
#include "numerique_48_1.h"
#include "nand_2_48_1.h"
int NAND_2::set_out_1() {
return 1 - (in_1 * in_2);
}
//Copy constructor
NAND_2::NAND_2(const NAND_2 & nnd) {
set_v_num_plus(nnd.read_v_num_plus());
set_v_num_moins(nnd.read_v_num_moins());
set_v_analog_plus(nnd.read_v_analog_plus());
set_numero(nnd.read_numero());
code = new char[strlen(nnd.code) + 1];
strcpy(code, nnd.code);
set_fan_out(nnd.read_fan_out());
ecrire_in_1(nnd.in_1);
ecrire_in_2(nnd.in_2);
}
//Destructeur
NAND_2::~NAND_2(){
delete [] code;
}
//Readers
int NAND_2::lire_in_1() const{
return in_1;
}
//Writers
void NAND_2::ecrire_in_1(int val) {
in_1 = val;
out_1 = set_out_1();
}
#include <iostream.h>
#include "circuit_48_1.h"
#include "numerique_48_1.h"
#ifndef NOR_FLAG_
#define NOR_FLAG_
private:
int out_1; /*sortie */
int set_out_1();
public:
int in_1; /*entree 1*/
int in_2; /*entree 2*/
NOR_2();
NOR_2(int, int);
NOR_2(int, int, char*);
NOR_2(const NOR_2 &);
virtual ~NOR_2();
int lire_in_1() const;
int lire_in_2() const;
int lire_out_1() const;
void ecrire_in_1(int);
void ecrire_in_2(int);
int operator ~ ();
virtual char *affiche_type();
};
#endif NOR_FLAG_
#include <iostream.h>
#include "circuit_48_1.h"
#include "numerique_48_1.h"
#include "nor_2_48_1.h"
int NOR_2::set_out_1() {
return ((1-in_1) * (1-in_2));
}
//Copy constructor
NOR_2::NOR_2(const NOR_2 & nr) {
set_v_num_plus(nr.read_v_num_plus());
set_v_num_moins(nr.read_v_num_moins());
set_v_analog_plus(nr.read_v_analog_plus());
set_numero(nr.read_numero());
code = new char[strlen(nr.code) + 1];
strcpy(code, nr.code);
set_fan_out(nr.read_fan_out());
ecrire_in_1(nr.in_1);
ecrire_in_2(nr.in_2);
}
//Destructeur
NOR_2::~NOR_2(){
delete [] code;
}
//Readers
int NOR_2::lire_in_1() const{
return in_1;
}
int NOR_2::lire_in_2() const{
return in_2;
}
int NOR_2::lire_out_1() const{
return out_1;
}
//Writers
void NOR_2::ecrire_in_1(int val) {
in_1 = val;
out_1 = set_out_1();
}
void NOR_2::ecrire_in_2(int val) {
in_2 = val;
out_1 = set_out_1();
}
#include <iostream.h>
#include "circuit_48_1.h"
#include "analogique_48_1.h"
#ifndef RESISTANCE_FLAG_
#define RESISTANCE_FLAG_
private:
double res;
double tolerance;
public:
RESISTANCE();
RESISTANCE(double, double);
RESISTANCE(double, double, char*);
RESISTANCE(const RESISTANCE &);
virtual ~RESISTANCE();
double lire_resistance() const;
double lire_tolerance() const;
void ecrire_resistance(double);
void ecrire_tolerance(double);
const RESISTANCE& operator= (const RESISTANCE&);
RESISTANCE operator+ (const RESISTANCE&);
RESISTANCE operator|| (const RESISTANCE&);
virtual char *affiche_type();
virtual double calcule_courant();
};
#endif RESISTANCE_FLAG_
#include <iostream.h>
#include "circuit_48_1.h"
#include "analogique_48_1.h"
#include "resis_2_48_1.h"
//Copy constructor
RESISTANCE::RESISTANCE(const RESISTANCE & rs) {
set_v_num_plus(rs.read_v_num_plus());
set_v_num_moins(rs.read_v_num_moins());
set_v_analog_plus(rs.read_v_analog_plus());
set_numero(rs.read_numero());
code = new char[strlen(rs.code) + 1];
strcpy(code, rs.code);
set_puissance_max(rs.read_puissance_max());
set_type(rs.read_type());
ecrire_resistance(rs.lire_resistance());
ecrire_tolerance(rs.lire_tolerance());
}
//Destructeur
RESISTANCE::~RESISTANCE(){
delete [] code;
}
//Readers
double RESISTANCE::lire_resistance() const {
return res;
}
//Writers
void RESISTANCE::ecrire_resistance(double val) {
res = val;
}
res_temp.ecrire_resistance(res +
right_res.lire_resistance());
res_temp.ecrire_tolerance(right_res.lire_tolerance());
return res_temp;
}
res_temp.ecrire_resistance((res *
right_res.lire_resistance()) /
(res + right_res.lire_resistance()));
res_temp.ecrire_tolerance(right_res.lire_tolerance());
return res_temp;
}
//Calcul du courant
double RESISTANCE::calcule_courant() {
return read_v_analog_plus() / res;
}
#include <iostream.h>
#include "circuit_48_1.h"
#include "analogique_48_1.h"
#ifndef CONDENSATEUR_FLAG_
#define CONDENSATEUR_FLAG_
private:
double capacite;
double v_claquage;
public:
CONDENSATEUR();
CONDENSATEUR(char *);
CONDENSATEUR(double, double);
CONDENSATEUR(double, double, char*);
CONDENSATEUR(const CONDENSATEUR &);
virtual ~CONDENSATEUR();
double lire_capacite() const;
double lire_v_claquage() const;
void ecrire_capacite(double);
void ecrire_v_claquage(double);
virtual char *affiche_type();
virtual double calcule_courant();
};
#endif CONDENSATEUR_FLAG_
#include <iostream.h>
#include "circuit_48_1.h"
#include "analogique_48_1.h"
#include "condens_2_48_1.h"
//Copy constructor)
CONDENSATEUR::CONDENSATEUR(const CONDENSATEUR & cnd){
set_v_num_plus(cnd.read_v_num_plus());
set_v_num_moins(cnd.read_v_num_moins());
set_v_analog_plus(cnd.read_v_analog_plus());
set_numero(cnd.read_numero());
code = new char[strlen(cnd.code) + 1];
strcpy(code, cnd.code);
set_puissance_max(cnd.read_puissance_max());
set_type(cnd.read_type());
ecrire_capacite(cnd.lire_capacite());
ecrire_v_claquage(cnd.lire_v_claquage());
}
//Destructeur
CONDENSATEUR::~CONDENSATEUR(){
delete [] code;
}
//Readers
double CONDENSATEUR::lire_capacite() const{
return capacite;
}
double CONDENSATEUR::lire_v_claquage() const{
return v_claquage;
}
//Writers
void CONDENSATEUR::ecrire_capacite(double val) {
capacite = val;
}
void CONDENSATEUR::ecrire_v_claquage(double val) {
v_claquage = val;
}
//Calcul du courant
double CONDENSATEUR::calcule_courant(){return 0.0;}
1. friend en anglais
2. extérieures à la classe
class B {
private:
int xb;
public:
B(){}
int f_membre_b();
};
};
class B {
private:
int xb;
public:
b(){}
int f_membre_b();
friend int funct(A &a, B &b);
};
577 Si toutes les fonctions membres d’une classe A sont amies d’une
autre classe B, il suffit alors de spécifier l’amitié en adoptant la
syntaxe suivante:
class B {
private:
int xb;
public:
b(){}
int f_membre_b();
friend int funct(A &a, B &b);
friend class A;
};
#include <iostream.h>
#include "circuit_48_1.h"
#include "analogique_48_1.h"
#ifndef RESISTANCE_FLAG_
#define RESISTANCE_FLAG_
private:
double res;
double tolerance;
public:
RESISTANCE();
RESISTANCE(double, double);
RESISTANCE(double, double, char*);
RESISTANCE(const RESISTANCE &);
virtual ~RESISTANCE();
double lire_resistance() const;
double lire_tolerance() const;
void ecrire_resistance(double);
void ecrire_tolerance(double);
const RESISTANCE& operator= (const RESISTANCE&);
friend RESISTANCE
operator+ (const RESISTANCE&, const RESISTANCE&);
RESISTANCE operator|| (const RESISTANCE&);
virtual char *affiche_type();
virtual double calcule_courant();
};
#endif RESISTANCE_FLAG_
Résumé
580 ✔ Les variables membres private d’une classe ne sont pas
directement accessibles à des fonctions indépendantes de la
classe.
✔ Un moyen d’accéder aux variables membres private d’une
classe est d’utiliser les fonctions membres public d’écriture-
lecture.
✔ Un autre moyen moins sécuritaire mais souvent plus
pratique est de déclarer la fonction comme étant amie de la
classe. Dans ce cas, la fonction amie a accès aux variables
membres private (et évidemment public) de la classe.
✔ La surdéfinition d’opérateurs peut être implantée via les
fonctions amies.
#include <iostream.h>
int i1 = 2;
int i2 = 5;
float f1 = 3.4;
float f2 = 7.9;
void main() {
Résultat
Valeurs entieres: 2
Valeurs reelles : 3.4
585 Le patron de la fonction min peut être utilisé pour tout type de
données standard (char, char *, int, float, double, long,
etc) ou pour tout type défini par l’usager. Par exemple, on
pourrait utiliser la fonction min pour des objets de type
RESISTANCE à condition que l’opérateur < (plus petit que) ait
été surdéfini pour cette classe.
Notion avancée
586 Un patron de fonction pourra s’appliquer à un patron de
classe.
Résumé
592 ✔ Lorsqu’une fonction doit être utilisée de la même façon pour
divers types de données standards ou définis par l’utilisateur, il
est intéressant de définir un patron pour cette fonction.
✔ La syntaxe de définition d’un patron est
template <class T, class U,..., class P>
retour nom_fonction(paramètres) {...}
✔ On peut surdéfinir un patron de fonction.
✔ On peut spécialiser un patron de fonction.
#include <iostream.h>
class complexe {
private:
int reelle;
int imaginaire;
public:
complexe(){
reelle = 0;
imaginaire = 0;
}
int lit_reelle() {
return reelle;
}
int lit_imaginaire() {
return imaginaire;
}
void affiche_nombre(){
cout << "Partie reelle: " << lit_reelle() << endl;
cout << "Partie imaginaire: "
<< lit_imaginaire() << endl;
}
};
Cette classe est intéressante mais ne peut que servir à décrire
des objets qui sont des nombres complexes avec des
composantes entières (int). Pour décrire des nombres
complexes ayant des composantes réelles, il faudrait
normalement définir une autre classe où les variables membres
sont de type double.
#include <iostream.h>
T lit_reelle() {
return reelle;
}
T lit_imaginaire() {
return imaginaire;
}
void affiche_nombre(){
cout << "Partie reelle: " << lit_reelle() << endl;
cout << "Partie imaginaire: " << lit_imaginaire() <<
endl;
}
};
On remarque que le mot réservé int a été remplacé par le
paramètre de type T.
Pour définir un patron de classe, il faut spécifier au
compilateur qu’il s’agit bien d’un patron et donner ensuite la
#include <iostream.h>
T lit_reelle() {
return reelle;
}
T lit_imaginaire() {
return imaginaire;
}
imaginaire = im;
}
void affiche_nombre();
};
#include <iostream.h>
T lit_reelle() {
return reelle;
}
T lit_imaginaire() {
return imaginaire;
1. Le deuxième <T> semble redondant mais est nécessaire dans la syntaxe du C++.
void affiche_nombre();
};
void main(){
c_i.affiche_nombre();
c_d.affiche_nombre();
Résultat
Partie reelle: 2
Partie imaginaire: 3
Partie reelle: 3.7
Partie imaginaire: 7.9
Dans ce dernier exemple, qui est loin d’être évident, toutes les
instances du patron point sont amies de n’importe quelle
instance du patron classe_c...
607 Les patrons peuvent aussi être soumis aux règles de l’héritage.
Cependant, ces notions sont très avancées. Les références [7],
[3] et [4] traitent plus en détails ces aspects “obscurs” du C++.
Résumé
608 ✔ On peut réutiliser des classes en créant des patrons de classe
aussi appelés templates.
Bibliographie
Symbols
A
abstaction des données 89
abstraction des données 6
abstraction des procédures 6
addition 23
adresse 175
affectation 18
arguments 36
associativité 24
astérisque 177
automatique 50
B
barres obliques 21
binaire 25
break 212
C
C++ 5
case 212
casting 26, 129
catégories 55
cerr 213
chaîne de caractères 12, 245
char 19
cin 31
classe 56
classe ami 394
classe de bas 116
classe dérivée 116
classes 55
classes extrinsèques 56
classes intrinsèques 56
code exécutable 10
code objet 9
code source 9
commandes de construction 317
commentaires 21
compilateur 9
compiler 9
const 52
constructeur par défaut 73
constructeur par recopie 369, 370, 375
constructeurs 73
consultation directe 124
contenu 17
copy constructor 369
cout 12
D
décalage à gauche 28
déclaration 18, 49, 57, 297
default 212
définition 49, 57, 297
delete 186, 351, 358
dérivation publique 120
dérivations privée 120
dérivations protégées 120
destructeur 370
destructeurs 361
disque dur 9
division 23
données internes 5
double 19
drapeaux 102
dynamiques 51
E
éditeur 9
éditeur de liens 10, 298
effets secondaires 11
élément 161
else 132
endl 12
énoncé associé 131, 147, 149
énoncés 11
entiers 5, 17
enum 216
ET 139
expression booléenne 131, 149
expression d’entrée 149
F
fichier cible 317
fichier objet 9
fichier source 9
fichiers prérequis 317
float 19
flot 169
fonction 36
fonction amie 279, 394
fonction générique 399, 400
fonction membre 65
fonction membre d’écriture 83
fonction membre virtuelle 200
fonction virtuelle pure 209
fonctions 6, 11
fonctions d’accès 89
FORTRAN 6
freestore 178
H
héritage 107
hiérarchie 6
I
identificateur 17, 49
if 131, 132
if-else 132
indice 161
initialiser 18
inline 408
instructions 9
int 17, 19
interface publique 97
L
langage procédural 6
long 19
long double 19
M
macros 323
macros prédéfinies 326
main 11
make 317
makefile 317
masque 50
modularité 124
modulo 24
mot reservé 18
multiplication 23
N
négation 128
new 178
non-divulgation 124
non-duplication 123
nul 245, 336
O
objet 5
objet de transit 271
objets 55
objets atomiques 56
objets de données intrinsèques 56
opérateur conditionnel 28, 135, 136
opérateur d’accès aux membres 58
opérateur d’évaluation de portée 70, 71, 297
opérateur d’extraction 31
opérateur d’insertion 12
opérateur de pointeurs aux classes 179
opérateur de sortie 12
opération d’affectation étendue 150
opérations d’itération 147
operator 277
origine zéro 161
OU 139
P
paramètre de type 401, 407
paramètres 37
Pascal 6
passage par valeur 48
patron de classe 401
patron de la fonction 400
patrons de classe 405
patrons de classes 406
pointeur 175
portée locale 51
portée universelle 51
prédicats 127
préfixe 151
principe de visibilité locale 297
priorité 24
private 229
procédure 11
procédures 6
programmation objet 5
programme source 9
protected 229
protégée 232
prototype 69
R
redirection 32
réels 5
référence 265
référence à une constante 372
règles suffixes 323
relations de dépendance 317
représentation explicite 123
réutilisation 45
run-time 175
S
short 19
simplicité 124
sizeof 20, 186
sous-classes 110
soustractio 23
statiques 51
superclasse 109
surdéfinir un opérateur 255
surdéfinition de fonctions 41
switch 211, 215
symboles ponctuation 11
T
tableau 161
template 399, 400, 405
ternaire 136
this 226, 280, 398
touch 319
type 5, 17, 49
types de données d’énumération 218
U
unaire 25
V
variable 17
variable globale 50
variable locale 50
variables 11
variables d’énumération 218
variables membres 57
virtual 200, 311
visibilité 315
visibilité locale 124
void 39
vrai 127
W
while 147