Cours C++.livre(Opérateurs)
10 pages
Français

Cours C++.livre(Opérateurs)

-

Le téléchargement nécessite un accès à la bibliothèque YouScribe
Tout savoir sur nos offres
10 pages
Français
Le téléchargement nécessite un accès à la bibliothèque YouScribe
Tout savoir sur nos offres

Description

CHAPITRE 11 Opérateurs C++Ce chapitre introduit les nouveaux opérateurs définis dans le cadre de C++uniquement. De plus, il décrit les nouvelles possibilités offertes au développeurpour la définition (redéfinition, ou surcharge) d’opérateurs.Le langage C++ 157einev Télécommunications mjn 11.1 Opérateurs définis en C++ uniquement11.1.1 Opérateur de contexte ::L’opérateur de contexte :: permet de spécifier le contexte de visibilité d’un identifica-teur. Cet opérateur est utilisé en particulier en conjonction avec la définition et l’implémenta-tion de classes, mais est également utilisable pour accéder à des variables masquées par uneredéfinition.int anIntegerValue = 10; // Variable globalevoid affiche(){int anIntegerValue = 5; // Variable localecout<

Informations

Publié par
Nombre de lectures 76
Langue Français

Extrait

CHAPITRE 11
Opérateurs C++
Ce chapitre introduit les nouveaux opérateurs définis dans le cadre de C++ uniquement. De plus, il décrit les nouvelles possibilités offertes au développeur pour la définition (redéfinition, ou surcharge) d’opérateurs.
Le langage C++
157
einev
11.1
11.1.1
Télécommunications
Opérateurs définis en C++ uniquement
Opérateur de contexte ::
mjn
L’opérateur de contexte :: permet de spécifier le contexte de visibilité d’un identifica teur. Cet opérateur est utilisé en particulier en conjonction avec la définition et l’implémenta tion de classes, mais est également utilisable pour accéder à des variables masquées par une redéfinition.
int
void
{ int
anIntegerValue = 10;
affiche()
anIntegerValue = 5;
cout<<anIntegerValue<<endl;
cout<<::anIntegerValue<<endl;
}
11.1.2
Opérateur new
// Variable globale
// Variable locale
// affiche 5, car la variable locale // masque la variable globale // utilisation de l’opérateur de // contexte pour explicitement // accéder à la variable globale
L'opérateur new remplace l'appel de librairie C malloc, calloc et autres. Il permet, tout comme malloc, de réserver de la mémoire pour un ou plusieurs éléments d'un type donné (qui peut être un type défini par l'utilisateur). new a l'avantage de retourner un pointeur de type cor rect. Le type du pointeur doit être spécifié avec l'opérateur new, ce qui permet à new de cal culer la place mémoire qu'il doit réserver. Ainsi :
char *
p1 = new int;
entraîne le message d'erreur suivant :
file myProg.C, line XXX : error : bad initializer type int * for p1 (char* expected)// HPC++
int
la syntaxe correcte est bien sûr :
*p1 = new int;
De la même manière, on peut réserver de la place pour une table, en spécifiant le nombre d'éléments que l'on désire réserver, comme dans l'exemple suivant qui réserve un tableau de 25 entiers :
158
Le langage C++
einev
const int
Télécommunications
int arraySize = 25; *arrayPointer = new int [arraySize];
mjn
La dimension du tableau passée à new ne doit pas forcément être une constante. Comme pour malloc ou calloc, la réservation de mémoire est une opération dynamique, qui est entiè rement effectuée lors de l'éxécution, contrairement à la réservation de place pour des tableaux statiques.
Le principal avantage de new par rapport à malloc, c'est qu'il induit une initialisation de la zone mémoire réservée. Cette initialisation est réalisée au moyen d'un constructeur qui est lié au type de données pour lesquelles on réserve de la place. Nous discuterons plus tard de la manière d'utiliser les constructeurs en relation avec les classes et les types de données dé finis par l'utilisateur. Bornonsnous à dire ici que tout type de données, abstrait ou non, pos sède un constructeur, éventuellement défini par défaut par le compilateur, ou défini par un segment de code utilisateur. C'est ce constructeur qui sera appelé par new pour initialiser cor rectement la place mémoire réservée.
Un autre avantage de new est qu'il contrôle les types de pointeurs. Ainsi l'instruction :
int *ptr = new char;
produit une erreur à la compilation. L'instruction correcte est, bien sûr :
int *ptr = new int;
11.1.3
Opérateur delete
L'opérateur delete est le correspondant de new, et remplace la fonction standard C free. Elle permet de marquer comme libre la place occupée par un opérateur new. Exemples :
delete p1; // libère la place mémoire occupée par le pointeur p1 delete [] arrayPointer; // libère la place occupée par le tableau d'entiers // pointé par arrayPointer.
On remarque dans cet exemple qu'il est nécessaire de spécifier que l'on libère la place nécessitée par un tableau par une syntaxe appropriée. Cette contrainte est assez gênante, et est source de pas mal d'erreurs parfois difficiles à trouver. La bible du programmeur C++, l'An notated Reference Manual for C++ de Ellis et Stroustrup justifie cet inconvénient par des ar guments de performance et d'overhead nécessaires à la détection automatiques de la destruction d'un array par rapport à la destruction de types simples. Cette justification n'est que moyennement convaincante.
L'exemple suivant compile correctement, et fonctionnera aussi convenablement, dans
Le langage C++
159
einev
Télécommunications
un premier temps; néanmoins il ne libère pas la place occupée :
const int
int arraySize = 25; *arrayPointer = new int [arraySize];
delete arrayPointer;
// //
libère la place occupée par l'élément numéro 0 (premier élément) du tableau.
mjn
D'autre part, cette erreur introduit des inconsistances dans la table d'allocation mémoire de certaines librairies. Ainsi, il peut arriver que cette erreur se traduise par un "crash" du pro gramme beaucoup plus tard, dans des circonstances n'ayant strictement rien à voir avec l'er reur de programmation ellemême, ce qui rend l'erreur proprement dite pratiquement impossible à localiser par les moyens usuels (débogueurs, etc...)
Similairement à new, delete s'assure que la libération de la place mémoire se fait correc tement en appelant le destructeur de la classe our laquelle il libère de la mémoire. Souvent, ce destructeur est vide (ne fait rien). C'est le cas pour le destructeur du type entier (int) de l'exem ple çidessus, par exemple. Nous reviendrons plus tard sur les destructeurs, en conjonction avec les constructeurs.
160
Le langage C++
einev
11.2
Télécommunications
Quelques commentaires sur les opérateurs et new delete
mjn
Ces deux opérateurs sont très importants en C++, c'est pourquoi il est important de bien comprendre leurs interactions. D'une manière générale, on peut affirmer que presque toutes les implémentations de new et delete appellent à un moment donné les fonctions librairie mal loc et free. Ceci ne signifie pas qu'ils demandent à malloc de réserver la même place que celle que l'on a demandé à new. En effet, new va ajouter de l'information à l'usage de la librairie C++, donc la place mémoire demandée à malloc sera en règle générale plus grande que celle demandée à new.
Ceci implique, entre autres, que l'on ne peut libérer la place reservée par new à l'aide de free, ou inversément, libérer par delete la place réservée par malloc. Il serait d'ailleurs dange reux de le faire, indépendemment de critères de compatibilité. Le constructeur associé à un type (par exemple à un type défini par l'utilisateur) est un morceau de code comme n'importe quel autre bout de code C++. Ce constructeur peut donc lui aussi réserver de la place mémoi re, laissant au destructeur le soin de la libérer. Mais si on appelait free en lieu et place de de lete  qui est seul censé appeler le destructeur, cette place mémoire ne serait jamais libérée.
Enfin, une remarque tout à fait générale sur l'allocation mémoire en C ou C++. Le lan gage ne supporte pas de réorganisation automatique de la mémoire (garbage collector). Au cours de la durée de vie du programme, il va donc se constituer des trous dans la mémoire allouée au programme, au gré des appels à new et delete pas forcément tous symétriques. Le fait de libérer de la mémoire n'a pas pour effet de rendre cette mémoire disponible aux autres programmes, mais seulement à soimême. Il n'est en effet pas possible de libérer totalement de la mémoire sans réorganisation, et cette réorganisation est impossible à réaliser de manière pratique dans un langage comme C. Le résultat de ces constatations est qu'un programme ayant une durée de vie longue et faisant fréquemment des allocationsdéallocations mémoire de taille variable et de manière non symétrique peut voir sa taille croître même si, globale ment, il libère autant de place qu'il n'en réserve.
Le langage C++
161
einev
11.3
Télécommunications
Surcharge d'opérateurs
mjn
C++ permet de redéfinir les opérateurs standard pour des types de données quelconques. Ainsi, considérons le cas suivant : un utilisateur est amené à définir un type complexe pour une certaine application, qui demande entre autres de pouvoir additionner des nombres com plexes. Il implémente pour ce faire le code suivant :
typedef struct cp_typ { float real, imag; } Complex;
void addComplex(const Complex& c1, const Complex& c2, Complex& result) { result.real = c1.real + c2.real; result.imag = c1.imag + c2.imag; }
Si ce code est parfaitement correct, il est néanmoins lourd et difficile à manipuler pour l'utilisateur. Il serait bien plus simple et élégant de pouvoir écrire directement :
result = c1 + c2;
Ceci est exactement ce que la surcharge d'opérateurs de C++ permet d'écrire. Il est pos sible de définir une nouvelle signification de l'opérateur + de la manière suivante :
Complex operator+(const Complex& c1, const Complex&c2) { Complex result; result.real = c1.real + c2.real; result.imag = c1.imag + c2.imag; return result; }
On a ainsi redéfini l'opérateur + pour des nombres complexes. La syntaxe pour la redé finition est la suivante :
<type_retour> operator<operateur_redefini>(argument)
162
pour des opérateurs unaires (!, ++, etc...) et
Le langage C++
einev
<type_retour> opérande_de_droite)
Télécommunications
mjn
operator<operateur_redefini>(opérande_de_gauche,
pour des opérateurs binaires (+, *, /, etc...).
Redéfinir un opérateur signifie que l'on donne à un opérateur connu une nouvelle por tée, que l'on étend sa signification, non pas que l'on en modifie la signification. Bien que légal, modifier la signification d'un opérateur ne peut qu'apporter de la confusion: Ainsi, on peut en principe décider que l'opérateur soustraction (a  b) appliqué à des chaînes de caractère a et b, retournera une chaîne de caractères contenant tous les caractères de a ne figurant pas dans b. Ou retournetelle plutôt une chaîne dont est retranchée toute occurence de b dans a? Dans ce dernier cas, estce que seule la première occurence de b dans a est ôtée, ou plutôt toutes les occurences? Visiblement, l'opérateur soustraction, pour des chaînes de caractères, n'est pas aussi simple d'emploi que son homologue pour des entiers ou des réels. Dans ce cas, il est préférable de s'abstenir de définir un opérateur. D'autre part, si l'on définit un opérateur pour une classe donnée, il est indispensable (bien que non prescrit par le langage) que cet opérateur ait un comportement correspondant à sa définition pour un type de base. Ainsi, pour des en tiers ou des réels, on a int a = 10, b = 20, c; c = a * b; // c == 200 c = b * a; // c == 200
Soit la règle normale, et probablement acceptée implicitement par tout un chacun, de commutativité. Si il vous arrive de redéfinir l'opérateur multiplication pour des types diffé rents, il faut veiller à conserver les propriétés que l'utilisateur potentiel considère comme al lant de soi du fait de toute son expérience passée avec cet opérateur.
Notons, par parenthèse, que les règles que nous énonçons çidessus ont été d'emblée violées par les créateurs de C++, lors de l'écriture de iostream, la bibliothèque d'entréessor ties de C++. Ils ont défini l'opérateur de sortie comme <<, et l'opérateur d'entrée comme >>. Soit une redéfinition des opérateurs standard de décalage (shift left, shift right) de C ! Cette redéfinition a permis à certains, comme Mr. Cargill, d'écrire des livres sur certaines erreurs potentielles que l'on peut commettre en C++. La redéfinition d'opérateurs peut être un moyen extrêmement efficace d'écrire des programmes compréhensibles: malheureusement, elle est parfois source de confusions supplémentaires, en raison d'une réflexion insuffisante de la part de l'implémentateur.
Nous entrerons plus en détail dans la discussion de la surcharge des opérateurs dans le cadre de la classe C++, car la majorité des avantages que l'on peut tirer de la possibilité de surcharge d'opérateurs se rencontre en conjonction avec l'utilisation des classes C++. Dans le cadre de la discussion présente, il nous suffit de savoir que l'on peut surcharger des opérateurs exactement comme on peut le faire pour des fonctions. La définition d'un opérateur, comme pour toute fonction en C++, comprend non seulement l'identificateur de cet opérateur luimê me, mais aussi les arguments qui lui sont passés (notion de signature, de manière identique à la définition de fonctions).
Il n'est pas possible de définir de nouveaux opérateurs en C++. La raison en est que cette définition serait lourde et compliquée : il ne suffit pas de définir l'opérateur luimême, il faut
Le langage C++
163
einev
Télécommunications
mjn
également définir sa précédence et son associativité par rapport aux autres opérateurs. Cette définition serait prohibitive, et résulterait très vraisemblablement en un code peu clair, néces sitant une abondante documentation annexe pour être compréhensible.
164
Le langage C++
einev
11.4
Test
Télécommunications
mjn
1.Comment réserver de la place en cours d’exécution pour un tableau de 20 entiers, puis libérer cette place ayant été réservée ? Peuton s’assurer que la réservation a effective ment eu lieu ? 2., qui implémente une chaîne de caractères de longueur variable (sansSoit le type String que les détails d’implémentation nous préoccupent pour l’instant). Quels opérateurs pour raiton définir logiquement avec un tel type ? 3.Soit le type , qui représente un vecteur. On définit le produit par l’opérateur Vecteur suivant :
Vecteur& operator*(const Vecteur& v1, const Vecteur& v2); // Produit vectoriel Vecteur& operator*(const Vecteur& v, double multiplicateur); // Produit scalaire Ces déclarations sontelles suffisantes ? 4.Quelles sont les règles à respecter absolument lors de la surcharge (redéfinition) d’opéra teurs pour de nouveaux types de données ?
Le langage C++
165
einev
166
Le langage C++
Télécommunications
mjn
  • Univers Univers
  • Ebooks Ebooks
  • Livres audio Livres audio
  • Presse Presse
  • Podcasts Podcasts
  • BD BD
  • Documents Documents