Vous êtes sur la page 1sur 47

EMSI RABAT – Ingénierie Informatique & Réseaux

EMSI 3 – 3°IIR 3 – INFORMATIQUE

Programmation Orientée Objet & LP C/C++


*===> Études Avancée en Langage de Programmation C/C++

Professeur : Dr. Azzedine BenHsain

Étudiants : Youssef MAHTAT


Ibrahim MANNANE

Programmation Avancée
(AV&SD)

&

Programmation Orientée Objet


_

Étude de
la Programmation en

Langage C/C++
Notes de
Cours en
Informatique :

Révision du cours
(SD & AV & POO)

Programmation Avancée
&
Programmation
Orientée Objet
&
LP C/C++
(Selon le cours du Prof. Dr. A. BENHSAIN)
Nota Bene :

* 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++
- 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, First Out
- FIFO : First In, First Out
- ISO: International Organization for Standardization
- ASCII : American Standard Code for Information Interchange
(Code américain normalisé pour l'échange d'information) ;

- INFORMATIQUE : INFORMATION AUTOMATIQUE !!

- EMSI : École Marocaine des Sciences d'Ingénieur ;


- IIR : Ingénierie Informatique et Réseaux ;
CHAPITRE 1 :

Notes et Révisions du Cours sur :

« Les Compléments Fonctions C++ »


→ Notes et Révisions du Cours sur « Les Compléments Fonctions C++ » :

** → Transmission des paramètres aux fonctions :

- 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 :

type fonction(type1 var1 , type2 var2) ; // passage par valeur des paramètres
type fonction(type1 * var1 , type2 * var2) ; // Passage des adresses des paramètres par valeur

- 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;
}
** → Polymorphismes - Fonctions Polymorphe :

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 <string>
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 1er 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
ème

inc() avec au 1 paramètre un lvalue (Left Value), alors là, le compilateur va sortir
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 !!!!!!!
** → Paramètres par Défaut :

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 :
La valeur par défaut ne peut être fournie qu’une seule fois, soit
dans le Prototype, soit dans la définition.
** → Les Fonctions Références :

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 ;
}
** → Les Paramètres const :

Le modificateur const placer devant la déclaration d'un paramètre formel (const int
a) interdit la modification, dans la fct de la valeur du paramètre.
• Exemple :

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.

Nota Bene :
- type* const pointeur1 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 ;

- 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");


** → Les Fonctions 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 d’Assemblage


dec(y); // sera traduite par call dec()
** → Variables et Fonctions extern :

• Le 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 .
** → Variables et Fonctions 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++ :

F1.cpp F2.cpp F3.cpp

//… static double K[400];


double K[400]; extern double K[200];
. //… int f();

. int f()
. { int main()
//.... {
static int f() } f(); f(); f();
{ f(); return 0;
. //… }
.
. /* /*
Le vecteur K[] utilisée Le vecteur K[] ici
dans ce fichier sera sera celui défini et
} celui défini et crée créé par F3.cpp et f()
dans F1.cpp ici est celle définie
*/ dans F2.cpp
*/
CHAPITRE 2 :

Notes et Révisions du Cours sur :

« Les Classes & Objets en LP C/C++ »


→ Notes et Révisions du Cours sur « Les Classes & Objets en LP C/C++ » :

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.

→ Implantation 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 pointeur 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 rendre 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.
> Exercices du Chapitre 2 (Classes & Objets) :

Exercice 1 :
Définir la class complex, le types mathématiques des nombres complexes, ainsi
que ses opérations .

Exercice 2 :
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.
CHAPITRE 3 :

Notes et Révisions du Cours sur :

CONSTRUCTEURs & DESTRUCTEURs


(en LP C/C++)
→ Notes et Révisions du Cours sur « CONSTRUCTEURs & DESTRUCTEURs » :

** → Les Constructeurs :

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 un constructeur


par défaut. 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 class String nous sommes obligés de définir le vecteur dynamique pour

réserver le vecteur qui contient la valeur de la chaîne de caractère.


RmQ Importante :

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.

** → Listes d'initialisation :
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);
}

ATTENTION :
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 S1 = "Samira Said"

→ Résolution :
String operator=(const char* ival)
{
delete[] val;
val = new char[strlen(ival) +
1]; strcpy(val, ival) ;
return *this;
}
** → Le Destructeurs :
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 :

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 ;
• La transmission à une fct d'un objet par valeur, le paramètre formel sera construit comme
copie du paramètre réel, exemple :
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);
}
CHAPITRE 4 :

Notes et Révisions du Cours sur :

Les FONCTIONS AMIES


(en LP C/C++)
→ Notes et Révisions du Cours sur « Les FONCTIONS AMIES » :

**→ Exemple :

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
: c
l
a
s
class String
{
s
S
... t
... r
i
friend nint strlen(String);
... g
...

};

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.
> Exercices du Chapitre 3,4 (Classes&Objets, Constructeurs, Destructeurs, Opérateurs et FCTs Amies) :

 

 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 <string>)

• 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();
CHAPITRE 5 :

Notes et Révisions du Cours sur :

Les Operateurs de Lecture et d’Ecriture


(en LP C/C++)
→ Notes et Révisions du Cours sur « Les Opérateurs de Lecture et d’Ecriture » :

**→ Opérateur d’Ecriture – Output Stream (cout) :

***→ Exemple de Surcharge :

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 &);

***→ L’objet cout :

o Exemple :

Soit :
int a = 20;

Et soit :

cout << a << endl;

cout est un << est un a une variable


objet de class opérateur, opérant de type int
sur la class
ostream 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 ;
***→ Surcharge de l'opérateur << : (Source : MSDN C/C++ - << pour OSTREAM)

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.

Exemple :
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 <iostream>
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 ostream, ce qui signifie que vous
pouvez combiner des insertions :

cout << "The date is" << dt << flush;


**→ Opérateur de Lecture – Input Stream (cin) :

***→ L’Objet cin :


o Exemple :

Soit :
int a , b;
Et soit :

cin >> a >> b ;


a une variable de type
int, et c’est l’opérande
cin est un objet de >> est un opérateur, dont valeur sera lue
class istream opérant sur la class
istream

(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 ;

***→ Surcharge de l'opérateur >> : (Source : MSDN C/C++ - >> pour ISTREAM)

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;
}
**→ Remarque sur les opérateurs d’Entrée/Sortie :

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.
CHAPITRE 6 :

Notes et Révisions du Cours sur :

Surcharges des Opérateurs


(en LP C/C++)
→ Notes et Révisions du Cours sur « Surcharges des Opérateurs » :

**→ Remarques sur les Surcharges des Opérateurs en LP C/C++ :

- 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 arité (le nombre de paramètre qu'il
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 :
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.
CHAPITRE 7 :

Notes et Révisions du Cours sur :

Surcharge d'Opérateurs Intéressants


(en LP C/C++)
→ Notes et Révisions du Cours sur « Surcharges d’Opérateurs intéressants » :

**→ Surcharge de l'opérateur [] en LP C/C++ :

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 &
operator[](
type
énuméré);
Note & Remarque sur les types énumérés :
(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 ;
**→ Les Opérateur de Conversion en LP C/C++ :

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.
**→ Surcharge de l'opérateur = en LP C/C++ :

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.

**→ Surcharge des opérateurs ++ et -- en LP C/C++ :


• 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;
}
CHAPITRE 8 :

Notes et Révisions du Cours sur :

HERITAGE
(en LP C/C++)
→ Notes et Révisions du Cours sur « l’Héritage » :
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
};

/!\ Rappel & RmQ Importante :


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.
**→ Héritage et Constructeur :

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)
{ }
**→ Substitution de méthodes :

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);
}
**→ Méthodes Virtuelles et Polymorphisme :
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; }

***→ Méthode abstraite ou Virtuelle pures :


Puisqu’une FigureGeo pure ne peut pas exister, nous n'aurons pas besoin d'exécuter
FigureGeo::surface(), nous pouvons alors la déclarer abstraite :
Soit : virtual double surface() = 0;
Ou : virtual double surface() abstract;

***→ Important :
Toutes les substitutions d'une méthode virtuelle doivent avoir exactement le même
prototype ;
***→ Appel compilé et appel calculé :

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é.

***→ Héritage Multiple :

LP C/C++, est l’un des rares langages POO qui supporte l’héritage multiple.
Exemple :
Nous voulons définir les classes : cylindre, parallélépipède, combiné. Pour cela,
nous allons définir la classe figureAVolume :
Annexe 1 :

Priorité et ordre d'évaluation


en LP C/C++
→ Priorité et ordre d'évaluation :
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 Type d’Opération Associativité
Priorité la plus élevé
De gauche
[ ] ( ) . –> suffixe ++ et suffixe ––
Expression à droite
préfixe ++ et préfixe –– sizeof De droite
&*+–~! Unaire à gauche
De droite
casts de type
Unaire à gauche
De gauche
*/%
Multiplication à droite
De gauche
+–
Addition à droite
De gauche
<< >>
Décalage au niveau du bit à droite
De gauche
< > <= >=
Relation à droite
De gauche
== !=
Égalité à droite
De gauche
&
Opération de bits AND à droite
Opération de bits De gauche
^
OR exclusive à droite
Opération de bits De gauche
|
OR inclusive à droite
De gauche
&&
AND logique à droite
|| De gauche
OR logique à droite
De droite
?:
Expression-conditionnelle à gauche
= *= /= %= += –=
Assignation² simple De droite
<<= >>= &= ^= |= et composée à gauche

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é.
Une expression peut contenir plusieurs opérateurs avec une même priorité. Lorsque plusieurs
opérateurs de ce type apparaissent au même niveau dans une expression, l'évaluation se poursuit selon
l'associativité de l'opérateur, de droite à gauche ou de gauche à droite. Le sens de l'évaluation
n'affecte pas les résultats des expressions comprenant plus qu'un seul opérateur de multiplication (*),
d'addition (+) ou binaire au niveau du bit (& | ^), au même niveau. L'ordre des opérations n'est pas
défini par le langage. Le compilateur est libre d'évaluer de telles expressions dans n'importe quel
ordre, s'il peut garantir un résultat cohérent.

Seuls les opérateurs d'évaluation-séquentielle (,), AND logique (&&), OR logique (||), d'expression-
conditionnelle (? :) et d'appel de fonction constituent des points de séquence et garantissent par
conséquent un ordre particulier d'évaluation pour leurs opérandes. L'opérateur d'appel de fonction
correspond au jeu de parenthèses suivant l'identificateur de fonction. L'opérateur d'évaluation-
séquentielle (,) est assuré d'évaluer ses opérandes de gauche à droite. (Notez que l'opérateur virgule
dans un appel de fonction n'est pas le même que l'opérateur d'évaluation-séquentielle et ne fournit pas
de garantie.) Pour plus d'informations, consultez Points de séquence.

Les opérateurs logiques garantissent également l'évaluation de leurs opérandes de gauche à droite.
Toutefois, ils évaluent le plus petit nombre d'opérandes nécessaires pour déterminer le résultat de
l'expression. Cela est appelé « évaluation de court-circuit ». Ainsi, certains opérandes de
l'expression ne peuvent pas être évalués. Par exemple, dans l'expression :

x && y++

Le second opérande, y++, est évaluée uniquement si x est vrai (valeur différente de zéro). Par
conséquent, y n'est pas incrémenté si x a la valeur False (0).