(SD & AV & POO)
(Selon le cours du Prof. Dr. A. BENHSAIN)
:
* Il faut répondre aux questions comme on a l'habitude de le faire en classe chez Dr. BENHSAIN, et donc si le Professeur a mentionné qu'il faut éviter ou suivre qu'elle que étapes ou méthodes pour résoudre un problème donné c'est qu'il faut nécessairement le faire ; * Il faut bien lire l'énoncé du problème avant de procéder à la résolution !!!! * Pour procéder à la résolution il faut procéder à la résolution Algorithmique puis juste après et seulement après qu’on accède au codage (Programmation) ;
-
Les abréviations écrites dans ce Fiche de Notes de Cours sont des Acronymes ou des notations abrégés :
- RQ : Remarque ; - P.S : post-scriptum, qui signifie « écrit après » ou « annexe » ; - N.B : Nota Bene, qui signifie on noter bien que ..., ou à noter que … ; - LP : Langage de Programmation - LP C : Langage de Programmation C - LP C++ : Langage de Programmation C++ C+ + - LP C/C++ : Langage de Programmation C & C++ - MC : Mémoire, Mémoire Centrale, Mémoire Principale ou la RAM ; - CPU : Processeur ou MicroProcesseur ; - Disque : Disque Magnétique, Disque Dur ou Mémoire Externe ; - FCT : fonction ; - PRG : Programme (Informatique) ; - Lvalue : référence de la variable (nom, étiquette ...) ; // Exp : int a ; → a est une lvalue !! - Rvalue : Expression calculable ou une valeur affectable à une Lvalue ; - LIFO : Last In, Firs Firs t Out - FIFO : First In, First Out - ISO: International Organization for Standardization
- ASCII : (Code américain normalisé pour l'échange d'information) ;
- INFORMATIQUE : INFORMATION AUTOMATIQUE !! - EMSI : École Marocaine Marocaine des Sciences d'Ingénieur ; - IIR : Ingénierie Informatique et Réseaux ;
→
** →
-
Le LP C ne connais que la transmission par valeur, et quand un paramètre est transmis par valeur c'est une nouvelle copie (une autre variable de même valeur) se crée dans la fonction et si on modifie cette copie alors la variable originale ne subira aucun changement, et dans ce cas la si on veut modifier la variable transmit donc il faut passer l'adresse comme valeur et grâce au formalisme de pointeur que supporte le LP C la variable sera modifier dans la fonction : fonction(type1 var1 , type2 var2) ; // passage par valeur des paramètres type fonction(type1 * var1 , type2 * var2) ; // Passage des adresses des paramètres par va eur type
- LP C++ a ajouté la transmission des paramètres
par référence, à l'instar de la majorité des LP évolués : Les modifications du paramètre formel dans la fonction sont représentées directement par le paramètre réel correspondant et qui doit être nécessairement une lvalue ;
- En LP C++ ou LP C/C++, il suffit de mettre "&" devant la déclaration du paramètre que Nous voulions recevoir par référence ; • Exemple :
// La procédure qui permet de permuter 2 variables En LP C/C++ : // Les paramètres sont transmis par référence ! void permuter(typeElem &x, typeElem &y) { typeElem Save = x; x = y; y = Save; }
// Cette fonction est équivalente en LP C à : // La procédure qui permet de permuter 2 variables En LP C : // Les adresses des paramètres sont passés par valeur ! void permuter_En_C(typeElem* x, typeElem* y) { typeElem Save = *x; *x = *y; *y = Save; }
→
Le LP C/C++ permet de d éfinir plusieurs versions d'une m ême fonction. Chacune de ces fonctions ou surcharges doivent avoir une signature diff érente des autres. Une signature est constituée du nom de la fonction, le nombre de param ètres qu’elle reçoit et leurs types. C’est ce qui permet de reconnaitre la surcharge de la fonction sollicitée à partir de son invocation. → Exemple : Soit à réaliser la fct polymorphe inc() , qui incrémente un objet selon sa nature : // Définition de la fct polymorphe Incrémenter (inc()) : // surcharges integer :
int inc(int &a) { return a += 1; } int inc(int &a, int pas) { return a += pas; } //
surcharges char :
char inc(char &c) { return c += 1; } char inc(char &c, int pas) { return c += pas; }
// surcharge string : string inc(string &chaine, string augmentation) { return chaine += augmentation; }
→ RmQ - surcharge des strings :
- Si on utilise le type char* pour définir la surcharge des chaînes de caractère il faut réserver l'espace pour un état future de la chaîne de caractères. - LP C++ fournit la Bibliothèque standard string qui support le type string dans le "namespace std".
- Le type string, à l'instar du type du même nom des autres langages de programmation fournit une meilleure gestion des chaînes de caractères. Pour utiliser ce type, il faut mettre au début du PRG : #include using namespace std;
- Nous pouvons utiliser ce type à la place de ➢
char* pour mieux gérer les strings.
RmQ :
→ La signature d'une fct se compose des éléments qui identifient la fct :
- Nom ; - Le nombre de paramètres ; - Les Types de ces paramètres ; Or, le type de la fct (de sa valeur de retour) ne fait pas partie de la signature. → LP C/C++ permet de surcharger une fct plusieurs fois, en en définissant plusieurs
versions, à condition que chaque version ait une signature différente des autres versions.
→ Exemple & RmQ :
Si on définit 2 surcharges d’une fct inc() dont les prototypes sont : int inc(int &, int); int inc(int, int);
Ces 2 surcharges ont presque une signature similaire, la différence entre les 2 prototypes est dans le type de passage du 1 er paramètre à la fct. Or les IDEs (MS VisualStudio, …) acceptent les 2 surcharges en même temps. Dans le cas où on appelle la fct inc() avec des constantes alors le compilateur ne connaît pas d’ambiguïté car il sait qu’il doit appeler la 2 surcharge , mais si on appelle la fct inc() avec au 1 paramètre un lvalue (Left Value), alors là, le compilateur va sortir ème
er
une ERREUR car il est dans l’ambigüité du choix entre les 2 surcharges . Alors IL FAUT EVITER D’UTLISER COMME CES 2 SURCHARGES DANS
UN MEME PROJET !!!!!!!
→
LP C/C++ permet de fusionner plusieurs versions correspondantes par une seule surcharge, dont des param ètres é tant par dé faut é gale à une valeur d éfinit. ▪
Exemple : // Exemple de prototype : int inc(int&, int = 1);
// Exemple de définition int inc(int &x, int pas) { return x += pas; }
* Une fct peut recevoir plusieurs param ètres par d éfaut, qui doivent nécessairement être les derniers de la liste de param ètres. Un param ètre par d éfaut ne peut pas être suivis par un param ètre obligatoire. - exemple de prototype : int f(int &, int, int = 2, int = 0, int = 0);
* Lors d'une invocation, les param ètres non-fournis seront les derniers de la liste de param ètres formels par d éfaut. * Une fct qui reçoit param ètres par d éfaut fournit + 1 signatures
diff érentes et ne permet donc pas des surcharges suppl émentaires avec un prototype qu'elle offre d éjà. * Le compilateur traduit l’appel à une fonction qui ne fournit pas la valeur du param ètre optionnel en ajoutant la valeur par d éfaut comme param ètre réel. ATTENTION :
→
o
Exemple : Définissons la fct mini() qui retourne la variable la plus petite parmi les 2 variables paramètres qu'elle reçoit , et qui permettrait par exemple mini(a, b) += 30;
En LP C il faudra transmettre à la fct les adresses des variables et elle retournera l’adresse de la plus petite, elle sera invoquée par *(mini(&a, &b)) += 30;
En LP C/C++, on peut la définir comme suite :
int & mini( int &a , int &b ) { return (a < b) ? a : b ; }
const
→
Le modificateur const placer devant la d éclaration d'un param ètre formel (const a) interdit la modification, dans la fct de la valeur du param ètre. • Exemple :
int
char* strcat (char* const S1, const char* S2) { char* pS1 = S1; while (*pS1++); pS1--; while (*pS1++ = *S2++); return S1; }
La déclaration " char* const S1 " interdit la modification de la valeur du pointeur 1 (pour que nous ne le modifions pas dans la fct), parce qu'elle se termine par return S1 qui doit retourner la valeur initiale de 1 . " const char* S2 " interdit la modification de la chaine de caractère de la cha ne 2 c'est un rappel, si nous î oublions le fait que la 2eme cha ne î ne doit pas être modifier (par exemple par une modification du code). Le modificateur const appliqué à un param ètre formel est utilis é souvent pour prot éger un param ètre volumineux, que nous pr éf érons recevoir par r éf érence et pour le prot éger contre une modification par inadvertance au moment d'une modification du PRG. :
-
interdit la modification de la valeur du pointeur pointeur1 , const type* pointeur2 interdit la modification de la valeur de la variable sur laquelle pointe pointeur2 ; type* const pointeur1
- Si a est un param ètre non-pointeur par exemple alors : - " const int a " et " int const a " sont deux déclarations équivalentes.
- La valeur retourner par une fct peut aussi être déclarer " const ", pour emp êcher sa modification. C’est un modificateur utile lorsqu ’une fct retourne un pointeur ou un r ésultat par r éf érence. - Si le prototype de
strcat()
devient :
const char* strcat(char* const S1, const char* S2);
-
Le compilateur interdira l’invocation suivante : strcat(strcat(X1, X2), "Samira");
→
inline
Le compilateur traduit une fct d éclarée inline de façon que chaque invocation de la fct soit remplac ée par le code m ême de la fct, et pas par une instruction call. // Exemple de déclarations : inline int inc(int &a, int b = 1) { return a += b ; } int dec(int &a, int b = 1) { return a -= b ; }
// Exemple d'invocation : inc(x); // sera traduite par le code de fct en langage dec(y); // sera traduite par call dec()
d’Assemblage
** →
• Le
extern
modificateur
extern
déclare que la fct ou la variable est
d éfinie à l'extérieur de la fct ou du fichier-PRG. • Quand
une variable est d éclarée
extern au
niveau global, cela
veut dire qu'elle est d éfinie et créée dans un autre fichier PRG qui sera linker avec le fichier courant pour produire l'exécutable. • Si
la variable est d éclarée
extern dans
une fct, elle doit
être d éfinie soit au niveau global dans le m ême fichier soit dans un autre fichier. •
Une fct déclarée, et non d éfinie dans un fichier-PRG est implicitement .
static
→
Une variable déclarée static dans une fct est créée en un seul exemplaire partagé par toutes les invocations de la fct. Chaque invocation de la fct trouve dans la variable la valeur qui y a été laissée par l’invocation précédente. La variable static est créée et initialisée au d ébut du PRG avec les variables globales (segment DS). Une variable locale normale est cr éée et initialisée chaque fois que la fct est activée puis d étruite à la fin de l’activation. Elle est enregistrée dans la PILE (segment SS). Une variable locale static est une variable locale du point de vue de la visibilité (la portée). Elle est une variable globale du point de vue de sa durée de vie et de son emplacement dans la MC et de son initialisation. Une fct ou une variable globale déclarée static dans un fichier n'est visible que dans le fichier-PRG dans lequel elle est d éfinie. o
Exemple : ▪
Soient les 3 fichier- PRG F1.cpp, F2.cpp et F3.cpp, les fichiers d’un même projet C/C++ :
double K[400]; .
. . static int f() { . . .
}
//… extern double K[200]; //…
int f() { //.... } //…
/* Le vecteur K[] utilisée dans ce fichier sera celui défini et crée dans F1.cpp */
static double K[400]; int f();
int main() { f(); f(); f(); f(); return 0; } /* Le vecteur K[] ici sera celui défini et créé par F3.cpp et f() ici est celle définie dans F2.cpp */
→
Nous parlons souvent d’un ADT (Abstract Data Type) ou TDA (Type de Données Abstrait), ce qui consiste en une extension aux types de données disponibles dans le langage de programmation. Un type de donn ées consiste en un ensemble de valeurs et un ensemble d ’opérations qui peuvent être appliquées à ces valeurs. Plusieurs LPs connaissent le type de données nombre réel "double" : nous pouvons dé finir des variables (de ce type) et utiliser des constantes (de ce type). Nous pouvons aussi manipuler ses valeurs par un ensembles de fcts et d’opérations ( + , * , / , (int) , pow(double, int) , sqrt() ). Et pratiquement tous les langages de programmations connaissent le type de nombre "int" : nous pouvons d éfinir et utiliser des constantes de ce type. Nous pouvons aussi manipuler ces valeurs par un ensemble de fonctions et d’opérateurs (+, *, / , % , (double), (char),pow(), sqrt(),floor(),ceil(),fabs()).
L’ADT : La d éfinition d’un ADT consistera ainsi à d éfinir ses valeurs et les opérations que ces valeurs (du ADT) peuvent subir. La manière la plus él égante pour d éfinir un ADT consiste à le d éfinir sous la forme d’une classe (class).
Par Exemple, le type des nombres complexes n ’existe pas en LP C/C++, mais C/C++ nous permet de d éfinir ce type sous la forme d ’une classe, avec ses valeurs possibles et un ensemble d ’opérations correspondantes, se sera un ADT complex, qui r ésumera (abstract) le type math ématique des nombres complexes. →
mplantation en LP C/C++ de la class complex :
class complex { public: // Explicite en C/C++ pour les classes (class) , implicite en C/ C++ pour Struct // Le constructeur de la class complex: complex(double ia = 0, double ib = 0) { a = ia; b = ib; }
double Module() { return sqrt(pow(a, 2)+ pow(b, 2)); } complex operator+(complex rhs) { return complex(a + rhs.a, b + rhs.b); } complex& operator+=(complex rhs) { a += rhs.a; b += rhs.b; return *this; } complex operator*(complex); private: // Implicite en C/C++ pour les classes (class) , Explicite en C/ C++ pour Struct double a, b; };
complex complex::operator*(complex rhs) { return complex( (a * rhs.a - b * rhs.b) , (a*rhs.b + b*rhs.a) ); }
// Exemple d'invocation : complex X, Y = 5.6, W(5.2), Z(12.5, 5.3); double mod = X.Module() + Y.Module(); Z += W; // équivalente à : Z.operator+=(W); (Y += W) = Z; // Comme avec les type standard Y*W ; // équivalente à : Y.operator*(W);
→ RmQ - ‘ sur les classes (class), avec l’exemple de class complex ’ : -
La phrase class permet de créer la classe complex. Cette class ce compose d'un ensemble de champs (a et b), et un ensemble de méthode (fonctions). Nous disons que ces champs et ces méthodes constitues les membres ou attributs de la class. Nous disons qu'ils sont encapsulés pour constituer une class. c'est le phénomène de l’encapsulation.
-
La description (déclaration) de la class est similaire à un manuel d'utilisation des objets qui appartiennent à la class cette description est appliqué à chaque objet qui appartient à la class, ou instance de la class . La class elle-même n'existera que par ces objets qui auront chacun sa propre copie a et b, et se partageront la même copie de méthodes.
-
Les champs dans cet exemple sont déclarés dans une section de visibilité private: ils ne sont accessibles (visibles) que par les méthodes de la class. Cela pour les protéger des mauvaises manipulations et pour faciliter les modifications de la structure interne de la class. Ces membres privés de la class restent accessibles via les méthodes publiques de la class (exemple : le constructeur() et Module() ) .
-
Les méthodes sont ici déclarées dans une section public : elles sont visibles aussi de l'extérieur des méthodes de la class. Elles constituent l'interface de la classe, c.à.d. la partie mise à disposition de l'utilisateur de la classe pour manipuler ses objets.
-
La méthode complex() porte le nom de sa class. Elle est alors un constructeur de la class.. Elle construit les instances de la class et ne retourne aucune valeur. Un constructeur sera invoqué chaque fois qu'une instance de class est créée pour la construire.
-
Les méthodes Module(), operator+=() et le constructeur sont définis à l'intérieur de la déclaration de la class . Ce sont alors implicitement des fonctions inline.
-
La méthode operator*() est définie à l'extérieur de la classe, c'est une méthode à appel classique (non inline). Pour indiquer son appartenance à la class complex, au moment de sa définition, nous utilisons l'opérateur de résolution de portée ‘ :: ’ .
-
Dans la programmation classique la fonction Module() serait invoqué pa la syntaxe Module(X) , où X est un paramètre qui subit la fct. Cette syntaxe accorde le rôle le plus important à la fonction.
-
L'invocation dans la POO : X.Module(), donne à l'objet X l 'importance qu’il mérite. C'est X qui exécute Module(). X est "responsable" de l'exécution de la méthode.
-
Lorsque l'appel est traduit dans le langage machine, X redevient un paramètre, dans le sens classique du terme.
-
Dans l'appel C1.Module(), nous disons que C1 est le paramètre implicite de la méthode Module(), de C1+= C2 , et de toutes les méthodes de la class invoquée à partir de C1. Tous les champs de la class manipulés par les méthodes de la class, sont implicitement ceux du paramètre implicite, sauf si le propriétaire du champ est explicitement fourni (Exemple : a += rhs.a) . -
Le compilateur permet l'accès au paramètre implicite, soit automatiquement, soit via le
this fourni par le compilateur, qui POINTE vers le paramètre implicite. C’est comme si la méthode operator+=() était définie par : complex operator+=(complex *this, complex rhs){this->a += rhs.s ;... return *this;}, et
invoquée par Module(&C1,C2);
Le pointeur this est utilisé lorsque nous sommes obligés de l ’utilisés, et quelques fois pour r endre le code plus clair. Exemple : La différence entre les codes : if (X.a < a + Y.a)
et if(X.a < this->a + Y.a)
-
Une méthode de la class ne peut être utilisé que par l'objet de la class. Alors les champs de la class manipulés par une méthode sont ceux de l'instance qui appelle la méthode ( X.PartieReelle() retournerait X.a et Y.PartieImaginaire() retournerait Y.b ).
-
Un attribut (champ ou membre) est déclaré static dans une class est créée en un seul exemplaire partagé par toutes les instanciations des objets de la class, cet attribut static est le même (aura la même valeur) pour tous les objets de cette class.
>
Définir la class complex, le types mathématiques des nombres complexes, ainsi que ses opérations .
Définir la class fraction , qui permet de manipuler les nombres fractionnels représenté par un numérateur et un dénominateur, il faut définir le constructeur et ses opérations.
→
→
Le constructeur d'une class est une m éthode dont la tâche consiste "construire" des instances de la classe et normalement les initialiser. Un constructeur porte le m ême nom que sa classe et ne retourne aucune valeur. Le compilateur g énère un appel au constructeur, apr ès avoir g énéré la r éservation de l'espace m émoire statique. Un constructeur peut être surchargé, par exemple :
à
complex() ; complex(double); complex(double , double ) ;
Un constructeur qui ne reçoit aucun param ètre explicite est . Plus exactement c'est un constructeur qui peut tre invoqué par ê é 0 param ètre. Par exemple le constructeur que nous avons (exemple de class complex) d éfini fusionne 3 versions dont le constructeur par d éfaut, exemple : complex(double ia = 0, double ib = 0)
Exemple :
D éfinissonsString qui fournira l'ADT cha ne de caractères mieux géré î que le type char*. R ésolution : class String { public: String(char* ival = "") { val = new char[strlen(ival) + 1]; strcpy(val, ival); } private: char* val;
};
RmQ : Pour cette
nous sommes oblig és de d éfinir le vecteur dynamique pour
class String
î de caractère. r éserver le vecteur qui contient la valeur de la cha ne
Quand on ne d éfinit aucun constructeur pour une class, le compilateur fournit son constructeur, qui ne reçoit aucun param ètre. C'est donc un constructeur par d éfaut (parce qu’il reçoit 0 param ètres). Lorsque nous d éfinissons au moins un constructeur pour une class, le compilateur ne fournis pas son constructeur.
Si nous d éfissions au moins un constructeur pour une class, et si aucun de ces constructeurs n'est par d éfaut, alors la class n'aura pas de constructeur par d éfaut. →
Une Liste d’initialisation, dans un constructeur, permet d'initialiser les valeurs des membres de la class.
Par Exemple : complex(double ia=0,double ib=0):a(ia),b(ib) {}
Si Par exemple nous associons à chaque objet String une constante MAXLEN qui indiquera la taille maximale permise à la chaine valeur de l'objet, dans ce cas les champs de la class devient : private: const int MAXLEN; char* val;
Et le constructeur devient : String(int iMAXLEN = 100, char* ival = "") : MAXLEN(iMAXLEN) , val(new char[MAXLEN + 1]) { strcpy(val, ival); }
L'ordre dans lequel les champs membre de la class sont initialisées par la liste d'initialisation est l'ordre dans lequel ils sont déclarés, l'ordre de la liste d'initialisation n'est pas pris en compte. Dans notre exemple MAXLEN doit être déclaré avant val. La liste d'initialisation est ainsi le seul endroit où on peut initialiser la constante est d'invoqué le constructeur de la class mère (*important ). (class mère ???)
Exemple :
Surchargeons l'opérateur de l'affectation pour →
Résolution : String operator=(const char* ival) { delete[] val; val = new char[strlen(ival) + 1]; strcpy(val, ival) ; return *this; }
S1 = "Samira Said"
→
Le destructeur est une m éthode qui porte le nom de la class précédé par et ne reçoit aucun param ètre et ne retourne aucune valeur. Il et invoqu é implicitement chaque fois qu'un objet doit être d étruit. Il est fourni par le syst ème quand non d éfini par le programmeur. On le d éfini explicitement normalement pour r écupérer la valeur d'un objet avant sa destruction et pour lib érer l'espace dynamique, exemple : ~
~String() { delete[] val; }
→
Le constructeur de copie est une m éthode fournie par le compilateur lorsqu'elle n'est pas d éfinis par le programmeur. Elle construit un nouvel objet comme copie d'un objet qui existe d éjà. Elle est invoqu ée dans les 3 cas suivant : •
•
L'initialisation d'un objet par la valeur d'un autre, exemple : String S1 = "Bonjour mon amie" ; String S2 = S1 ; transmission à une fct d'un objet
La copie du param ètre réel, exemple
par valeur, le param è tre formel sera construit comme
:
bool precede(String X) { return strcmp(val, X.val) < 0; }
Dans ce cas, l'invocation S1.precede(S2) provoque la construction du param ètre X , comme copie du param ètre réel S2; •
La construction de la valeur retourn ée par une fct, par valeur, par exemple l' operator=() ci-dessus retournera par valeur une copie de *this .
Le constructeur de copie fourni par le compilateur ne connait de l'objet que les champs statiques. Il construit des copies des champs statiques mais pas des champs dynamiques (une copie de val mais pas du vecteur point é par val). Alors val et sa copie pointeront vers le m ême vecteur. Le constructeur de copie de la class CL à normalement le prototype : CL(const CL&); (Avec & est obligatoire , const est recommandée)
Pour la
class String : // Constructeur de copie de la class String : String(const String &Orig) :val(new char[strlen(Orig.val) + 1 ]) { strcpy(val, Orig.val); }
→
→
Définissons la surcharge de strlen() qui reçoit un string de laquelle il retourne la langueur : int strlen(String S) { return strlen(S.val); }
Cette fct est définit au niveau global elle n'a pas le droit d'accéder à val, de visibilité private. Pour résoudre ce problème nous pouvons déclarer cette fct amie (friend ) de la
: class String { ... ... friend int strlen(String); ... ... };
Une fct amie d'une class a tous les privilèges des méthodes membre de la class qui consiste à accéder au champs private et protected de la class.
>
•
Enoncé de l’exercice :
Soit à réaliser un ADT String qui définit le type de données chaîne de caractères, et qui saura mieux gérer les chaînes de caractères. (L’ADT String n’est pas la class string prédéfinit dans la bibliothèque )
•
Développons la class String qui permet : String S1, S2 = "Samira Said", S3("Hatim") ; S2 += " Bonjour"; S1 = S2 + S3; int n = strlen(S1); n = S1.strlen();
→
cout
**→ →
Surchargeons l’opérateur << (operator<<()) pour la class ostream afin qu’on
puisse afficher un objet de class String :
ostream &operator<<(ostream &cout , const String &S) { return cout << S.val; } // Puis Il faut indiquer à l'intérieur de la class que la surcharge est une fonction amie : friend ostream &operator<<(ostream&, const String &);
→
o
Soit : int a = 20;
Et soit : cout <<
a << endl;
cout est un
<< est un
objet de class
opérateur, opérant
ostream
sur la class
a une variable de type int
ostream
(cout << a) retourne par référence cout (l’objet du flux de sortie) décalé à gauche par la valeur de a (pour qu’il soit pris en compte par le flux de sortie) , qui sera elle-même décalé à gauche par endl (retour chariot), pour enfin afficher
le tous dans la console. ATTENTION : "l’operator <<"
ou Décalage à gauche ou "Shift Left" dans ce cas est une
surcharge de << , et qui opère sur la class ostream et qui insert la valeur de la variable placer à droite dans l’output pour qu’elle soit affichée ;
→
Les flux de sortie utilisent l'opérateur insert (<<) pour les types standard. Vous pouvez également de surcharger l'opérateur d'<< pour vos propres classes.
Exempl e : L'exemple de fonction d'write indiquée l'utilisation d'une structure d'Date. Une date est un candidat idéal pour la classe actuelle C++ dans laquelle les membres de données (mois, jour, et année) sont masqués de la vue. Un flux de sortie est la destination logique pour afficher une telle structure. Ce code affiche une date à l'objet d' cout : Date dt( 1, 2, 92 ); cout << dt;
Pour obtenir cout de recevoir un objet d'Date après l'opérateur insert, surchargez l'opérateur d'insertion pour identifier un objet d'ostream à gauche et un Date à droite. La fonction surchargée d'opérateur << doit être déclarée comme une fonction friend de la classe Date ce qui peut accéder à des données privées dans un objet d' Date. // overload_date.cpp // compile with: /EHsc #include using namespace std; class Date { private: int mo, da, yr; public: Date(int m, int d, int y) { mo = m; da = d; yr = y; } friend ostream& operator<<(ostream& os, const Date& dt); }; ostream& operator<<(ostream& os, const Date& dt) { os << dt.mo << '/' << dt.da << '/' << dt.yr; return os; } int main() { Date dt(5, 6, 92); cout << dt; }
L'opérateur surchargé retourne une référence à l'objet d'origine des pouvez combiner des insertions : cout << "The date is" << dt << flush;
ostream ,
ce qui signifie que vous
cin
**→ →
o
Soit : Et soit :
int a , b;
cin
cin est un objet de class istream
>>
a
>> b ; a une variable de type int, et c’est l’opérande
>> est un opérateur, opérant sur la class istream
dont valeur sera lue
(cin >> a) retourne par référence cin (l’objet du flux d’entrée) décalé à droite par la valeur de a (pour qu’il soit pris en compte par le flux d’entrée ,c’ est -à-dire que ce qui sera tapé dans le clavier sera affecté à a ) , qui sera elle-même décalé à droite par b, pour enfin lire deux valeurs du clavier et les affecter successivement à a puis b .
ATTENTION : "l’operator >>" ou Décalage à droite ou "Shift Right" dans ce cas est une surcharge de << , et qui opère sur la class istream et qui insert la valeur lue du clavier à la variable placer à droite ;
→
Surchargeons l’opérateur >> (operator>>()) pour la class istream afin qu’on puisse lire du clavier un objet
de la class String : istream &operator>>(istream &cin, String &S) { char Txt[1000]; cin >> Txt; S = Txt; return cin; }
// Puis Il faut indiquer à l'intérieur de la class que la surcharge est une fonction amie : friend istream &operator>>(istream&, String &);
Les flux d'entrée utilisent l'opérateur d'extraction (>>) pour les types standard. Vous pouvez écrire des opérateurs d'extraction similaires pour vos propres types ; votre succès dépend de l'utilisation de l'espace blanc avec précision. Voici un exemple d'un opérateur d'extraction pour la classe Date présentée précédemment : istream& operator >> (istream& is, Date& dt) { is >> dt.mo >> dt.da >> dt.yr; return is; }
**→
Même si une fct que nous définissons au niveau global n'a pas besoin d'accéder aux membres privés d'une class, il est recommandé de la déclarer amie de la class, si elle "travail" pour la class. Cela pour montrer sa relation étroite avec la class et pour qu'elle soit visible dans la déclaration (manuel) de la class.
→
**→
-
Nous pouvons surcharger un opérateur déjà utilisé pour les types primitifs, mais nous ne pouvons pas créer de nouveaux opérateurs (par exemple <>).
-
Nous ne pouvons modifier ni la priorité d'un opérateur, ni son reçoit), ni son associativité.
-
On ne peut surcharger un opérateur que si au moins l'un de ses opérandes est membre d'une classe ADT (on ne peut pas définir par exemple : int operator/(int, int);).
-
Plusieurs Opérateur sont commutatifs pour les types primitifs (+,*) , mais ils ne sont pas automatiquement sur les ADT. Si nous avons besoin de la commutativité nous devons la déclarer, exemple :
(le nombre de paramètre qu'il
String operator*(String S, unsigned n) { String Res = ""; while (n--) Res = Res + S; return Res; }
Exemple d’invocation : String Y, Trt = "-"; Y = Trt * 10; Y = 10 * Trt;
Le compilateur accepte Y = Trt * 10 et pas Y = 10 * Trt, Si nous voulons que le compilateur l’accepte, nous définissons : String operator*(unsigned n , String S) { return S * n; }
-
-
De façon similaire au cas de la commutativité, la majorité des opérateurs binaire (+,-,/,* , ...) peuvent être fusionner à l'affectation (+=,/=,...) , et ainsi A = A + B <=> A += B . Cela n'est pas automatique pour les ADT. Par exemple, nous ne pouvions pas écrire dans le dernier exemple Res += S. pour pouvoir le faire il faut définir String operator+=(String rhs); Les opérateurs surcharger ne peuvent pas avoir un paramètre par défaut.
La surcharge d'un opérateur peut lui donner un sens qui n'a rien à voir avec l'interprétation qui lui est communément associé lorsqu’il est appliqué aux types primitifs. Mais, par déontologie, il est
fortement recommandé de respecter l'interprétation reconnue de l'opérateur.
→
[]
**→
Normalement cet opérateur est surchargé sur les classes desquelles chaque membre se compose d'un ensemble de valeurs plus simples d'un type T ;
Le prototype sera normalement :
T &
(article MSDN VisualC++ sur "enum") Une énumération est un type défini par l'utilisateur (programmeur) qui se compose d'un jeu de constantes intégrales nommées, appelées énumérateurs.
Syntaxe : enum NomEnum{enum-list};
Exemple1 : enum Jour { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche }; // dans ce cas : lundi représente 0 , ... , dimanche représente 6
Exemple2 : enum ExempleEnum { enum1 = val1, enum2 = val2, … };
Exemple de définition de l‘ operator[]() dans la class String : char &operator[](int i) { return val[i]; }
Exercice (Application) : définir une meilleure version de cette méthode (operator[]()) ;
Résolution : char & operator[](unsigned i) { static char Poubelle; if (i < ::strlen(val)) return val[i]; else { Poubelle = '!'; cerr << "!!! Erreur d'accès en dehors d'un String" << endl; return Poubelle; } }
RmQ : Les opérateurs [] et () ne peuvent être surchargés que comme des méthodes membre de la class et
non pas comme des fonctions friend, et cela car ces deux opérateurs sont très proches de la class où ils sont surchargés ;
**→
Tout constructeur d’une class CL qui reçoit un paramètre unique de type T, est
aussi un opérateur de conversion du type T vers la class CL : il peut convertir une valeur de type T en un objet de la class CL. Il peut être invoqué explicitement ou implicitement par le Compilateur. • Exemple : String S1 = "Ali", S2 ; // Après avoir surcharger l’affectation entre deux String :
S2 = String("Baba") ; S2 = (String)"Baba" ; // Appels explicites à l'opérateur de conversion(constructeur) S2 ="Baba" ; // Appel implicite à l'opérateur de conversion(constructeur)
Un constructeur -operateur de conversion- peut être déclaré avec le modificateur explicit (explicit String(char* ival = "")), alors le compilateur ne le reconnais plus comme convertisseur implicite il ne peut être invoqué que explicitement pour la conversion et avec les parenthèse comme constructeur (String S2("Samira Said")) Le compilateur génère la conversion implicite dans le cas S2 = S2 + "Sissi et" mais pas dans le cas S2 = "Sissi et" + S2 ; Le Compilateur sait convertir implicitement les paramètres explicites mais pas les paramètres implicites, c’est l’une des raisons pour laquelle il est préférable de définir
les opérateurs binaires tel que + et * par une surcharge amie plutôt que membre, cela pour permettre la conversion implicite sur les 2 opérandes, ainsi, si + est surchargé amie nous pouvons écrire S2 = "Sissi et" + S2 que le compilateur la traduira par S2 = (String)"Sissi et" + S2. L’opérateur de conversion dans le sens inverse (de CL vers T) en ce qui nous concerne (dans la class String) ce sera une conversion de String vers char*, et qui peut être définit membre de la class String, sous la forme : operator char*() { return val; }
Ainsi le compilateur saura traduire strcat(S, "Samir"); par strcat(S.val, "Samir"); Nous pouvons aussi déclarer un tel opérateur de conversion explicite pour ne pas permettre son invocation implicite.
=
**→
Exemple de définition de l’opérateur = : String &String::operator=(const String &rhs) { if (&rhs != this) { delete[] val; val = new char[::strlen(rhs.val) + 1]; ::strcpy(val, rhs.val); } return *this; }
Lorsque les objets d'une class sont volumineux (occupent beaucoup d'espace), nous préférons recevoir l'objet original comme paramètre par référence (pour économiser l'espace nécessaire à la copie du paramètre par valeur et les temps de la création de la copie et destruction). Alors nous protégeons ce paramètre par const. Nous avons alors un problème si nous avons une opération du type : *p = *q et p et q point vers le même objet (càd S=S). Le test if(&rhs != this)évite la suppression de l'objet par delete[] val.
++
**→
--
• Problème : Essayons de Surcharger l’opérateur ++ dans notre class ;
• RmQ :
Les Surcharge des deux versions de ++ (S++, ++S) ne reçoivent aucun paramètre explicite ; • Solution : - Certains compilateurs ne permettent que de surcharger la version préfixée. - D'autres compilateurs permettent l'invocation de cette même surcharge par les deux syntaxes ; - La plupart des compilateurs actuels ont adapté la convention suivant : La version préfixée ne reçoit aucun paramètre explicite alors que la post-fixée reçoit un paramètre fictif de type int, et qui n'apparaitra pas dans l'appel et ne doit pas être utilisé à
-
-
l’intérieur de la surcharge.
Exemple de Surcharge ++ préfixée dans la class string : String &String::operator++() { char* pVal = val; while (*pVal = toupper(*pVal)) pVal++; return *this; }
Exemple de Surcharge ++ post-fixée dans la class string : String String::operator++(int) { String result = *this; ++(*this); return result; }
→
A partir d'une class M appelée class Mère, nous pouvons d éfinir (dériver) une nouvelle class F appelée Fille, par la syntaxe suivante : class f :public M { ... };
Alors, tous ce qui a dit pour la class M ( toute la description) reste valable pour la class F, sauf, éventuellement la visibilit é de certains membre (champs ou fcts). Nous disonsque la class F hérite de la class M. Nous pouvons ensuite ajouter à (enrichir) la class fille de nouveaux attributs. Nous pouvons aussi modifier des m éthodes héritées. Exemple, définissions la class IntFract qui représente les nombres sous la forme d’un nombre entier associé à une fraction (exp 153 2/3).
class IntFraction :public Fraction { ... private: int PInt; // est la partie entière associé à IntFract
};
Quand on ne définit aucun constructeur pour une class, le compilateur fournit son constructeur, qui ne reçoit aucun paramètre. C'est donc un constructeur par défaut (parce qu’il reçoit 0 paramètres).
Lorsque nous définissons au moins un constructeur pour une class, le compilateur ne fournis pas son constructeur. Si nous défissions au moins un constructeur pour une class, et si aucun de ces constructeurs n'est par défaut, alors la class n'aura pas de constructeur par défaut.
**→
La class fille n'hérite pas les constructeurs ni le destructeur de la class mère. Mais tous les constructeurs de la class fille invoquent nécessairement un constructeur de la class mère, au d ébut de la construction. Ainsi si la class mère n'a pas de constructeur par d éfaut, alors nous devons au moins un constructeur pour la class fille, qui précise la manière avec laquelle le constructeur de la class mère est invoqué (le compilateur ne peut pas g énérer d'appel au constructeur de la class mère). Nous devons aussi, dans ce cas dans tous les constructeurs de la class fille fournir explicitement l'appel au constructeur de la class mère, nécessairement dans la liste d'initialisation du constructeur de la class fille. Exemple de d éfinition des constructeurs de la class IntFract : IntFract(int iPint, int iNum, int iden) : Fraction(iNum, iden), PInt(iPint + num / den) { num %= den; } IntFract(int iNum=0, int iden=1) : IntFract(0,iNum,iden) { }
**→
Exemple
:
IntFract K(28, 10), L(33, 6, 8); L += K;
L'opération L += K sera effectuer par Fraction::operator+=() le r ésultat obtenu sera 33 31/20. Ce r ésultat est faux parce que la m éthode +=() ne connait de chacun des objets que la partie fraction, et elle fait abstraction de la partie entière PInt. Pour Obtenir le r ésultat correct nous devons substituer la m éthode héritée en en d éfinissant une nouvelle version. Nous d éfinissons comme membre de la class IntFract : IntFract & IntFract::operator+=(IntFract rhs) { this->Fraction ::operator+=(rhs); PInt += rhs.PInt + num / den; num %= den; return *this; }
Ou la définition : IntFract &operator+=(IntFract rhs) { Fraction X( Fraction (PInt * den + num, den) += Fraction(rhs.PInt * rhs.den + rhs.num, rhs.den)
); return *this = IntFract (X.num,X.den); }
**→
Exemple
:
const double PI = 3.14; enum Color { blanc, jaune, bleu, vert, noir, rouge }; class FigureGeo { public: FigureGeo( Color icoul = blanc) : coul(icoul) {} virtual double surface() { return 0; } protected : Color coul; }; class rectangle : public FigureGeo { public: rectangle( double ilong, double ilarg, Color icoul = blanc) : FigureGeo (icoul), longueur(ilong), largeur(ilarg) { if (longueur > largeur) { double sauv = longueur; longueur = largeur; largeur = sauv;
} } double surface() { return longueur*largeur; } protected : double longueur, largeur; }; class carre : public rectangle { public: carre(double cote, Color icoul = blanc) : rectangle(cote, cote, icoul)
{ } private: }; class cercle : public FigureGeo { public: cercle(double iray, Color icoul = blanc) : FigureGeo(icoul), rayon(iray)
{ } double surface() { return (rayon*rayon)*PI; } protected: double rayon;
};
Exemples d’invocations
:
int main() { FigureGeo FG; rectangle Rec(28, 120, noir); cercle C(85); double SF = FG.surface(); double SC = C.surface(); double SR = Rec.surface(); FigureGeo* pF[5];
int a; for (int i = 0; i < 5; i++) { cin >> a; if (a < 10)
pF[i] = new FigureGeo (bleu); else if (a < 20) pF[i] = new rectangle(25,10,blanc); else pF[i] = new cercle(25, blanc); } cout << "Les surface sont :" << endl; for (int i = 0; i < 5; i++) { cout << pF[i]->surface() << ", "; } cout << endl; return 0; }
Le compilateur traduit, dans cet exemple l'invocation FG.surface() par un appel à FigureGeo::surface() , et C.surface() par cercle::surface() . C’est à dire un appel à la class de l'objet qui invoque la m éthode. L'invocation pF[i]->surface() est normalement traduite par un appel vers FigureGeo::surface() , c’est à dire la m éthode de la class du pointeur (de type FigureGeo*). Ainsi la boucle va afficher 0.0.0.0.0 quels que soient les objets point és par le vecteur pF. L'objet *pF[i] (l’objet pointé ) peut être soit une FigureGeo pure, rectangle ou un cercle. Nous disons que c'est un objet polymorphe. Nous voudrions que l'invocation pF[i]->surface() m ène vers la m éthode de la class de l'objet point é. Pour cela, nous pouvons déclarer la m éthode FigureGeo::surface(), comme étant virtuelle : virtual double surface() { return 0; } ***→
Puisqu’une
pure ne peut pas exister, nous n'aurons pas besoin d'ex écuter FigureGeo::surface(), nous pouvons alors la d éclarer abstraite : FigureGeo
Soit : virtual double surface() = 0; Ou : virtual double surface() abstract; ***→
Toutes les substitutions d'une m éthode virtuelle doivent avoir exactement le m ême prototype ;
***→
Le compilateur traduit les invocations des m éthodes non-virtuelles et des m éthodes virtuelles qui proviennent d'objet statiques, par un CALL de la m éthode de la class de l'objet static ou du pointeur quand il s'agit d'un objet dynamique. Nous obtenons un appel compil é (traduit à 100%). Lorsque l'appel à une m éthode virtuelle provient d'un pointeur (notamment this) par un CALL qui m ènera vers la classe de l'objet pointé, dont l'adresse est obtenue par un calcul. C'est un appel calculé. ***→
LP C/C++, est l’un des rares langages POO qui supporte h l’ éritage multiple. Exemple : Nous voulons d éfinir les classes : cylindre, parall élépipède, combiné. Pour cela, nous allons d éfinir la classe figureAVolume :
→ Source : MSDN Libraby Visual Studio 2015, Visual C++ Documentation (Microsoft)
(https://msdn.microsoft.com/fr-fr/library/2bxt6kc4.aspx) (https://msdn.microsoft.com/fr-fr/library/126fe14k(v=vs.110).aspx )
Symbole1 [ ] ( ) . –> suffixe
Type d’Opération Priorité la plus élevé ++
et suffixe ––
préfixe ++ et préfixe –– sizeof
Expression-conditionnelle
De gauche à droite De droite à gauche De droite à gauche De gauche à droite De gauche à droite De gauche à droite De gauche à droite De gauche à droite De gauche à droite De gauche à droite De gauche à droite De gauche à droite De gauche à droite De droite à gauche
Assignation² simple et composée
De droite à gauche
Expression
& * + – ~ !
Unaire
casts de type
Unaire
*/%
Multiplication
+ –
Addition
<< >>
Décalage au niveau du bit
< > <= >=
Relation
== !=
Égalité
&
Opération de bits AND Opération de bits OR exclusive Opération de bits OR inclusive
^ | && ||
?:
Associativité
AND logique OR logique
= *= /= %= += –= <<= >>= &= ^= |=
Priorité la plus basse 1. Les opérateurs sont répertoriés par ordre de priorité descendant. Si plusieurs opérateurs apparaissent sur la même ligne ou dans un groupe, ils ont la même priorité. 2. Tous les opérateurs d'assignation simple et composée ont une même priorité.