Langage C++/Version imprimable

Image logo
Ceci est la version imprimable de Langage C++.
  • Si vous imprimez cette page, choisissez « Aperçu avant impression » dans votre navigateur, ou cliquez sur le lien Version imprimable dans la boîte à outils, vous verrez cette page sans ce message, ni éléments de navigation sur la gauche ou en haut.
  • Cliquez sur Rafraîchir cette page pour obtenir la dernière version du cours.
  • Pour plus d'informations sur les version imprimables, y compris la manière d'obtenir une version PDF, vous pouvez lire l’article Versions imprimables.


Langage C++

Une version à jour et éditable de ce livre est disponible sur la Wikiversité,
une bibliothèque de livres pédagogiques, à l'URL :
http://fr.wikiversity.org/wiki/Langage_C%2B%2B

Vous avez la permission de copier, distribuer et/ou modifier ce document selon les termes de la Licence de documentation libre GNU, version 1.2 ou plus récente publiée par la Free Software Foundation ; sans sections inaltérables, sans texte de première page de couverture et sans Texte de dernière page de couverture. Une copie de cette licence est inclue dans l'annexe nommée « Licence de documentation libre GNU ».

Introduction

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 1
Leçon : Langage C++
Retour auSommaire
Chap. suiv. :Mots clés
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.

Préambule :

modifier

Ce cours est pour le moment destiné à donner les clefs permettant l'apprentissage des bases du langage C++. Ce cours n'a pour le moment pas vocation à faire de vous des développeurs de haut vol mais simplement vous apprendre à utiliser le langage C++ de manière efficace à des fins d'aide de travail avec l'outil informatique.

Je m'efforcerai dans ce cours d’être précis, concis et pédagogique. Si d'aventure vous ne compreniez pas un point du cours, je m'efforcerai de répondre à vos questions.

Avant de commencer vous pouvez, si vous le désirez, consulter ce lien qui vous permettra d’en apprendre un peu plus sur l'histoire du C++

Introduction :

modifier

Des Chiffres et des BITs

modifier

Dans un ordinateur les données sont représentées par des ensembles de cases mémoires. Ces cases mémoires, appelées "BIT" (de l'anglais Binary-digIT), ne peuvent prendre que 2 valeurs. Soit la valeur 0, soit la valeur 1. Pour représenter des nombres plus grands que 0 ou 1 nous devons rajouter des rangs de bits.

Voici, pour exemple, la représentation binaire des nombres décimaux de 0 à 9 :

Nombre décimal Représentation binaire
0 0000
1 0001
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001

Il existe une formule mathématique qui permet de connaître le nombre de valeurs possibles qu'un ensemble de n bit(s) peut prendre :

nombre_de_valeurs_possibles =  

Il existe une formule mathématique permettant de connaître le nombre maximal que l’on peut représenter avec un ensemble de n bit(s) :

nombre_maximal_representable =  

Il existe une formule qui permet de convertir n’importe quel nombre binaire en nombre décimal :

Nombre_décimal =    est le bit de rang   commençant à zéro en partant de la droite et en lisant vers la gauche.

Si nous appliquons ces formules à notre exemple ci-dessus cela donne :

Pour un ensemble de 4 bits, aussi appelé quartet, nous avons :

  • nous pouvons représenter   valeurs,
  • le plus grand nombre que l’on peut représenter avec cet ensemble sera  .

Pour le nombre binaire 1010 nous avons :

Conversion Binaire → Décimal
1         0         1         0
 
8    +    0   +    2   +   0
10

Vous pourriez vous demander d'où vient cet écart de 1 entre le nombre de valeurs et la valeur maximale accessible, cela vient du fait que le 0 (en binaire sur 1 quartet : 0000) est comptabilisé dans le nombre de valeurs.

En informatique on appelle le bit le plus à droite d’un nombre binaire, le bit de poids faible car il représente la valeur  . Par opposition au bit de poids faible, on appelle le bit le plus à gauche du nombre binaire le bit de poids fort car il représente la valeur la plus forte du nombre, soit pour notre quartet  

→ Consultez ce lien pour plus d'informations à propos du système binaire

En regroupant des bits en quartets on s'aperçoit que l’on peut représenter toutes les valeurs par un seul chiffre hexadécimal.

Voici un tableau de conversions binaire <→ hexadécimal:

Binaire Hexadécimal
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
1010 A(10)
1011 B(11)
1100 C(12)
1101 D(13)
1110 E(14)
1111 F(15)

→ Consultez ce lien pour plus d'informations à propos du système hexadécimal

À partir de maintenant et dans un souci de clarté, nous représenterons les valeurs exprimées dans le système hexadécimal en les préfixant par "0x" (les lettres seront en majuscules). Je n'utiliserai plus le système binaire qu’à des fins de démonstration en le préfixant par "0b".

ex:

Nombre décimal Nombre hexadécimal Nombre binaire
26 0x1A 0b00011010

Les Octets :

modifier

En informatique il est courant (surtout dans les architectures PC et Mac) que les bits soient regroupés en ensembles de 8 bits (soit 2 quartets) que l’on appelle octets. Ce regroupement apporte 2 avantages :

  • Toutes ses valeurs peuvent être représentées par seulement 2 chiffres hexadécimaux (de 0x00 à 0xFF).
  • Il a permis la création d’un jeu de caractères ASCII étendu.

Des Octets et des Lettres :

modifier

Puisque l'informatique n'est capable de gérer que des 0 et/ou des 1, comment faire en sorte d'obtenir des lettres ? Tout simplement en donnant une valeur numérique à chaque lettre. Ainsi dans le jeu de caractère ASCII étendu, nous avons une représentation numérique de chaque caractère avec une différenciation minuscule/majuscule. En fait pour un ordinateur une lettre n'est ni plus ni moins qu'un simple nombre.

→ Consultez ce lien pour plus d'informations à propos du jeu de caractères ASCII

Entiers, Relatifs, Naturels :

modifier

Nous avons vu que les octets peuvent représenter les nombres de 0 à 255 soit 256 valeurs. Mais comment l'ordinateur peut-il représenter des valeurs négatives ? En fait l'ordinateur ne sait pas faire la différence entre un nombre négatif et un nombre positif si on ne lui dit pas explicitement de la faire en lui indiquant qu’il doit travailler en mode signé.

Comment l'ordinateur représente-t-il des nombres entiers signés en nombres binaires ?

En mode signé, le bit de poids fort est interprété comme le signe du nombre (0 pour +, 1 pour -) on parle alors de bit de signe. Prenons l'exemple de l'octet :

Modes\ Valeurs 0b00000000 0b00000001 ... 0b01111110 0b01111111 0b10000000 0b1000000 0b10000010 ... 0b11111110 0b11111111
Interprétation Non-Signé 0 1 ... 126 127 128 129 130 ... 254 255
Interprétation Signé 0 1 ... 126 127 -128 -127 -126 ... -2 -1

On peut remarquer que les valeurs absolues sont dans un ordre inverse pour les nombres négatifs. Ceci est du à l’utilisation du complément à deux pour leur construction. Cet encodage, simple à mettre en œuvre, facilite ensuite énormément les opérations arithmétiques. (cf. lien)

Réels Entiers :

modifier

Nous avons vu comment un ordinateur pouvait représenter des entiers, signés ou non. Comment alors, à partir de nombres entiers relatifs composés de bits, représenter des nombres réels ?

En fait, l'organisme IEEE (Institute of Electrical and Electronics Engineers), a défini une norme permettant de coder en binaire, sur 2 octets et plus, un nombre réel. Cette norme a été reprise par les fondeurs de processeurs et a donc été admise comme standard pour la représentation de nombres dits « à virgule flottante ».

Voici une schématisation de la norme :

Signe du nombre Valeur exposant signé Valeur mantisse
1 bit Taille variable selon encodage Taille variable selon encodage

La partie qui représente les données du nombre, la mantisse, est entière. On est donc obligé de déplacer la virgule jusqu'à atteindre la bonne valeur, ce qui est fait grâce à l'exposant. La virgule se déplaçant, grâce à l'exposant, aléatoirement d’un nombre à l'autre on dit qu'elle flotte, d'où l’expression « nombre à virgule flottante ».

→ Consultez ce lien pour plus d'informations à propos des nombres à virgules flottantes

→ Consultez ce lien pour plus d'informations à propos de l’IEEE

Opérations Logiques Binaire :

modifier

Pour pouvoir faire des opérations sur des valeurs logiques on doit savoir utiliser les opérateurs booléens de base.

Voici les différents opérateurs logiques booleens :

Opérateur YES (OUI) :

modifier

L'opérateur YES effectue une comparaison sur une valeur logique. Si la valeur est vraie alors la sortie est vraie, sinon elle est fausse.

Voici la table de vérité de l'opérateur YES pour l'opération (YES x):

YES x = Faux x = Vrai
Résultat Faux Vrai

Opérateur NOT (NON) :

modifier

L'opérateur NOT effectue une comparaison sur une valeur logique. Si la valeur est vraie alors la sortie est fausse, sinon elle est vraie.

Voici la table de vérité de l'opérateur NOT pour l'opération (NOT x):

NOT x = Faux x = Vrai
Résultat Vrai Faux

Opérateur AND (ET) :

modifier

L'opérateur AND effectue une comparaison entre 2 valeurs logiques. Si les 2 valeurs sont vraies alors la sortie est vraie, sinon elle est fausse.

Voici la table de vérité de l'opérateur AND pour l'opération (x AND y):

AND y = Faux y = Vrai
x = Faux Faux Faux
x = Vrai Faux Vrai

Opérateur NAND (NON-ET) :

modifier

L'opérateur NAND effectue une comparaison entre 2 valeurs logiques. Si les 2 valeurs sont vrai alors la sortie est fausse, sinon elle est vrai.

Voici la table de vérité de l'opérateur NAND pour l'opération (x NAND y):

NAND y = Faux y = Vrai
x = Faux Vrai Vrai
x = Vrai Vrai Faux

Opérateur OR (OU Inclusif) :

modifier

L'opérateur OR effectue une comparaison entre 2 valeurs logiques. Si au moins 1 des 2 valeurs est vraie alors la sortie est vraie, sinon elle est fausse.

Voici la table de vérité de l'opérateur OR pour l'opération (x OR y):

OR y = Faux y = Vrai
x = Faux Faux Vrai
x = Vrai Vrai Vrai

Opérateur NOR (NON-OU) :

modifier

L'opérateur NOR effectue une comparaison entre 2 valeurs logiques. Si les 2 valeurs sont fausses alors la sortie est vraie, sinon elle est fausse.

Voici la table de vérité de l'opérateur NOR pour l'opération (x NOR y):

NOR y = Faux y = Vrai
x = Faux Vrai Faux
x = Vrai Faux Faux

Opérateur XOR (OU Exclusif) :

modifier

L'opérateur XOR effectue une comparaison entre 2 valeurs logiques. Si exactement 1 des 2 valeurs est vraie alors la sortie est vraie, sinon elle est fausse.

Voici la table de vérité de l'opérateur XOR pour l'opération (x XOR y):

XOR y = Faux y = Vrai
x = Faux Faux Vrai
x = Vrai Vrai Faux

En fait l'opérateur XOR peut être construit à partir d'autres opérateurs, mais, pour des raisons pratiques, il a directement été mis à disposition du langage.

XOR = ( ( (NOT x) AND (y) ) OR ( (x) AND (NOT y) ) )

À partir de tous ces opérateurs on peut réaliser n’importe quel modèle logique.



Mots clés

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 2
Leçon : Langage C++
Chap. préc. :Introduction
Chap. suiv. :Types
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.

Les Mots Clés

modifier

En C++, comme en C d'ailleurs, il existe une série de mots qui ne peuvent et ne doivent être, en aucun cas, utilisés autrement que pour ce à quoi ils sont destinés. Ces mots sont dit "mots clés" et, dans certains cas, appelés "instructions", ou encore opérateurs, ou mots réservés et ont une signification particulière pour le compilateur.

Voici une liste des mots clés/mot réservés du C++ :

Mots clés Utilisation
+, -, /, *, =, ., <, <=, ==, >=, > &&, ||, &, |, !, ~, != , ()(parenthèses), [] (Crochets)… (Triples Points), , (Virgule) Opérateurs
&, * Opérateur de référencement/déréferencement (référence/pointeur)
# Préfixe de directives préprocesseur
0x (Zéro-x) Préfixe de nombre hexadécimal
0 (Chiffre zéro) Préfixe de nombre octal
{} (Accolades) Délimitation de portée
:: (double deux points) Opérateur de déréférencement de portée
Mot Clé Utilisation
asm Déclarateur de code assembleur !
auto Déclarateur de variable à désallocation automatique "Pile"(stack).
Mot Clé Utilisation
break Instruction de branchement dans une boucle ou un traitement de cas.
bool Type de donnée logique dit "booléen". Prend la valeur vrai (true) ou faux (false).
Mot Clé Utilisation
case Déclarateur de cas dans une instruction switch.
catch Récupérateur d'erreur.
char Type de donnée entier dit "caractère". En programmation structurée ce type est déconseillé à l’utilisation mais il permet de rendre certains services.
class Déclarateur de définition de classe.
const Déclarateur de constantes.
continue Instruction de branchement dans une boucle imbriquée.
Mot Clé Utilisation
default Déclarateur de cas par défaut dans une instruction switch.
delete Désallocateur de mémoire dynamique "Tas"(heap).
do Déclarateur de boucle. Ne peut être utilisé qu'en association avec while.
double Type de donnée nombre flottant à double précision.
Mot Clé Utilisation
else Déclarateur de cas par défaut dans une instruction if.
enum Structure de donnée énuméré.
extern Déclarateur d’une variable déclarée dans un autre fichier.
explicit Interdit les constructeurs pour casts implicites.
Mot Clé Utilisation
false Valeur logique (Faux)
float Type de donnée nombre flottant à simple précision.
for Déclarateur de boucle paramétrée.
friend Déclarateur de classe ou de fonction ayant accès aux données privées.
Mot Clé Utilisation
goto Instruction de branchement, en développement structuré son utilisation est interdite car elle rend la compréhension du code plus difficile.
Mot Clé Utilisation
if Déclarateur de traitement conditionnel.
inline Déclarateur de MACRO
int Type de donnée entier. En programmation structurée ce type est déconseillé à l’utilisation mais il permet de rendre certains services.
Mot Clé Utilisation
long Modificateur de longueur de type.
Mot Clé Utilisation
main Méthode point d'entrée du programme.
mutable Rend une partie d’un objet constant modifiable.
Mot Clé Utilisation
new Allocateur de mémoire dynamique "Tas"(heap).
Mot Clé Utilisation
operator Déclarateur de surcharge d'opérateur.
Mot Clé Utilisation
private Déclarateur de membre privé.
protected Déclarateur de membre protégé.
public Déclarateur de membre public.
Mot Clé Utilisation
register Déclarateur de variable registre.
return Instruction de branchement.
Mot Clé Utilisation
short Modificateur de longueur de type.
signed Modificateur d'interprétation de signe de type entier.
sizeof Opérateur spécial permettant de renvoyer la taille d’une variable stockée en pile(stack).
static Déclarateur de variable statique dite "de classe". En programmation structurée, il est déconseillé à l’utilisation mais permet de rendre certains services.
struct Déclarateur de structure. En programmation structurée il est déconseillé à l’utilisation il est préférable d’utiliser des classes à la place.
switch Déclarateur de traitement conditionnel cardinal.
Mot Clé Utilisation
template Declarateur de paramétrage.
this Pointeur spécial désignant l'instance en cours de l'objet. En programmation structurée il est systématiquement et presque obligatoirement utilisé car il améliore la lisibilité.
throw Déclencheur d'exceptions.
try Déclarateur de section à déclenchement d'exceptions.
true Valeur logique (Vrai)
typedef Déclarateur de type.
Mot Clé Utilisation
unsigned Modificateur d'interprétation de signe de type entier.
union Déclarateur d'union. En programmation structurée, il est très fortement déconseillé à l’utilisation il n'est utilisé que dans des cas très rares et pour des applications très spécifiques.
Mot Clé Utilisation
virtual Déclarateur de méthode virtuelle.
void Indicateur d’une absence de type (là où un type serait attendu).
volatile Déclarateur de membre critique nécessitant un traitement d'actualisation particulier notamment lors de l’utilisation de threads.
Mot Clé Utilisation
while Déclarateur de boucle conditionnelle.


Instructions du langage C

modifier
Mot Clé Utilisation
include, define, ifdef, ifndef, pragma, error Directives préprocesseur
malloc, realloc, calloc, free Opérateurs C d'allocation/désallocation de mémoire dynamique.




Types

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 3
Leçon : Langage C++
Chap. préc. :Mots clés
Chap. suiv. :Opérateurs
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.

Les Types

modifier

En C++ il existe plusieurs types organisés dans des catégories. Ces types ont différentes longueurs et différentes interprétations possibles dépendant de la plage de valeurs sur laquelle on veut travailler : signés ou non, à virgule flottante ou non.

  Selon l'architecture de votre ordinateur les tailles des types peuvent varier ! Ce cours se base sur une architecture PC. D'autres machines ont potentiellement des types de longueurs différentes de celles exprimées ici.

Types Intrinsèques :

modifier

Il existe plusieurs catégories de types en C++ dont certaines sont incompatibles avec le langage C. Pour représenter les nombres non-signés on utilise le mot clé "unsigned". Pour représenter les nombres signés on utilise le mot clé "signed". Cela permet à l'ordinateur de savoir comment il doit interpréter les valeurs. Pour des raisons de simplification du codage, les types représentent par défaut des nombres signés, ce qui rend l’utilisation du mot clé "signed" facultative.


Catégories de types

modifier

Le Type Vide

modifier

Cette catégorie, compatible avec le langage C, représente l'absence de type.

Le type vide est surtout utilisé pour l'écriture de méthodes (les méthodes ne renvoyant aucune valeur ont un type de retour vide), mais il arrive que l’on s'en serve pour accéder à une adresse mémoire sans essayer d’avoir une interprétation de la valeur se trouvant à cette adresse.

Type de Base Taille du type en octets Nombres de valeurs possibles Plage de valeurs possibles
void (inaccessible) (inaccessible) (inaccessible)

Le Type Char, un type lettré !

modifier

Le type char est un type un peu particulier : il se comporte comme un entier mais il est destiné à mémoriser des caractères (ou parfois des morceaux de caractères). Dans certains système d'exploitation il existe une table de 256 dessins représentant graphiquement, entre autres, les 26 lettres minuscules et majuscules (52 au total) ainsi que les 10 chiffres (0 à 9). Le reste des 256 codes permet de représenter d'autres caractères tels que le caractère null, l'espace, la tabulation, la ponctuation ou des caractères propres à des langues spéciales, parfois même des smileys. Le dessin de ces caractères ainsi que leur codage numérique sont définis par une norme de codage de caractères. Par exemple, la norme ASCII est une norme relativement ancienne, alors que la norme Unicode est plus universelle. L'ordinateur lui ne manipulera que les valeurs numérique de ce type, par contre les logiciels qui affichent la valeur de ce type à l'écran le font sous forme graphique, c'est-à-dire en affichant le dessin du caractère correspondant à la valeur affectée.

Commun aux processeurs 32 et 64 bit :

Mode Type de Base Taille du type en octets Nombres de valeurs possibles Plage de valeurs possible dans le mode
non-signé unsigned char 1 256 [0 ; 255]
signé (signed) char 1 256 [ -128 ; +127 ]
caractère (non-signé) (signed|unsigned) char 1 256 Caractères de la table ASCII

Les Entiers

modifier

Cette catégorie, compatible avec le langage C, regroupe les types représentant les nombres entiers. À partir du type short, tous les types entiers se réfèrent au type int. Là encore pour simplifier le codage, l'écriture du mot clé "int" est facultative excepté pour le type int signé lui-même.

Commun aux processeurs 32 et 64 bit :

Mode Type de Base Taille du type en octets Nombres de valeurs possibles Plage de valeurs possible dans le mode
non-signé unsigned char 1 256 [ 0 ; 255 ]
signé (signed) char 1 256 [ -128 ; +127 ]
non-signé unsigned short (int) 2 65 536 [ 0 ; 65 535 ]
signé (signed) short (int) 2 65 536 [ −32 768 ; 32 767 ]
non-signé unsigned (int) 4 4 294 967 296 [0; 4 294 967 295 ]
signé (signed) int 4 4 294 967 296 [ −2 147 483 648 ; 2 147 483 647 ]


Spécifiques aux processeurs 32 bits :

Mode Type de Base Taille du type en octets Nombres de valeurs possibles Plage de valeurs possible dans le mode
non-signé unsigned long (int) 4 4 294 967 296 [0; 4 294 967 295 ]
signé signed long (int) 4 4 294 967 296 [ −2 147 489 648 ; 2 147 489 647 ]
non-signé unsigned long long (int) 8 18 446 744 073 509 552 000 [0; 18 446 744 073 509 552 000 ]
signé (signed) long long (int) 8 18 446 744 073 509 552 000 [ −9 223 372 036 854 776 000 ; 9 223 372 036 854 776 000 ]


Spécifiques aux processeurs 64 bits :

Mode Type de Base Taille du type en octets Nombres de valeurs possibles Plage de valeurs possible dans le mode
non-signé unsigned long (int) 8 18 446 744 073 509 552 000 [ 0 ; 18 446 744 073 509 552 000 ]
signé (signed) long (int) 8 18 446 744 073 509 552 000 [ −9 223 372 036 854 776 000 ; 9 223 372 036 854 776 000 ]


En effet, selon que l’on soit en présence d’un processeur 32 ou 64 bits, certains types ont des tailles et des plages de valeurs variables. C’est pour cette raison que normalement on doit utiliser les types dit "Entiers Fixes" dont nous parlerons plus loin.


Une règle détermine la validité des tailles des types entiers :

[signed|unsigned], char <= short <= int <= long [<= long long]

Les Flottants

modifier

Cette catégorie, compatible avec le langage C, regroupe les types représentant les nombres à virgule flottante.


Commun aux processeurs 32 et 64 bit :

Type de Base Taille du type en octets Taille de l'exposant Taille de la mantisse Nombres de valeurs possibles Plage de valeurs possible
float 4 8 bits 23 bits 4 294 967 296 (indisponible actuellement)
double 8 11 bits 52 bits 18 446 744 073 509 552 000 (indisponible actuellement)
long double 12 15 bits 64 bits 79 008 162 513 705 380 000 000 000 000 (indisponible actuellement)


Le Type Logique

modifier

Ce type est un apport par rapport au langage C, il n'est donc pas compatible avec celui ci.

Le type bool est un type un peu particulier. Il est codé sur 4 octets et représente les données logiques vrai et faux par 2 mots clés respectivement "true" et "false". Ce type gère sa valeur de manière assez singulière :

true vaut 1, false vaut 0, mais d’une manière générale tout ce qui n’est pas false (égal à 0) vaut true (différent de 0). En effet comme ce type est codé sur 4 octets il possède la même plage de valeurs qu'un signed int et nous pouvons lui affecter n’importe quelle valeur de cette plage.


Commun aux processeurs 32 et 64 bit :

Type de Base Taille du type en octets Nombres de valeurs possibles Plage de valeurs possible Valeur = 0 Valeur = [ −2 147 489 648 ; -1 ] U [ +1 ; 2 147 489 647 ]
bool 4 4 294 967 296 [ −2 147 489 648 ; 2 147 489 647 ] false true

Les Méthodes

modifier

Nous parlerons en détail de ce type dans le chapitre Méthodes.

Les Pointeurs, Tableaux et Références

modifier

Le type pointeur n'est qu'un raccourci vers la mémoire. Le type pointeur est en fait une adresse mémoire soit un entier non signé de la taille du bus d'adresse dans le contexte logiciel. Sa taille est donc variable suivant le matériel et le système d'exploitation utilisé.

Sa valeur représente l'adresse mémoire vers laquelle il pointe. Son type est le type dans lequel on lui demande de traduire la valeur existant à l'adresse où il pointe.

Il peut s'agir dans des cas peu fréquents de pointeurs sur méthodes mais nous verrons cela dans le chapitre Pointeur, Tableaux et Références.

Les Énumerations

modifier

Nous parlerons en détail de ce type dans le chapitre Énumérations.

Les Structures

modifier

Nous parlerons en détail de ce type dans le chapitre Structures, unions et champs de bits.

Les Champs de Bits

modifier

Nous parlerons en détail de ce type dans le chapitre Structures, unions et champs de bits.

Les Classes

modifier

Les classes sont le summum de l'architecture logicielle. Elles permettent la programmation-objet et la construction d'applications au code robuste réutilisable et relativement facile à debugger. Nous en reparlerons plus loin.

Structures vs Classes

modifier
  • La visibilité d’une classe est par défaut privée, celle d’une structure, publique.
  • L’héritage d’une classe est par défaut privé, celle d’une structure, publique.
  • Template de classe, impossible en structure.

Types Étendus

modifier

Les types étendus sont des types définis par les utilisateurs et sont le plus souvent des agglomérations de types intrinsèques et/ou types étendus. Ainsi il est possible de créer des types étendus agglomérant n’importe quel type intrinsèque en n’importe quelle quantité (dans les limites de la mémoire disponible) et dans n’importe quel ordre.

Le langage nous permet de définir de nouveaux types étendus grâce au mot clé "typedef".

La syntaxe de cette instruction est :

typedef NomDuTypeEtendu NomDuTypeDeBase;

Nous verrons des exemples concrets de cette syntaxe dans les chapitres suivants.



Opérateurs

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 4
Leçon : Langage C++
Chap. préc. :Types
Chap. suiv. :Syntaxe C++
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.

Les Opérateurs C++

modifier

Les opérateurs sont des symboles permettant d'effectuer des opérations sur des valeurs. Ils sont classés dans trois catégories.


Opérateurs d'affectation

modifier

L'opérateur d'affectation simple "="

modifier

L'opérateur d'affectation simple est "=" il signifie "affecter à".

Syntaxe :

<Destination> = <Source>; Où <Source> et <Destination> doivent être de même nature et où <Source> est une expression et <Destination> une variable ;

Début de l'exemple
Fin de l'exemple


L'opérateur d'affectation additionneur "+="

modifier

L'opérateur d'affectation additionneur est "+=" il signifie "affecter en additionnant à".

Syntaxe :

<Destination> += <Source>; Où <Source> et <Destination> doivent être de même nature et où <Source> est une expression et <Destination> une variable ;

Début de l'exemple
Fin de l'exemple


L'opérateur d'affectation soustracteur "-="

modifier

L'opérateur d'affectation soustracteur est "-=" il signifie "affecter en soustrayant à".

Syntaxe :

<Destination> -= <Source>; Où <Source> et <Destination> doivent être de même nature et où <Source> est une expression et <Destination> une variable ;

Début de l'exemple
Fin de l'exemple


L'opérateur d'affectation multiplicateur "*="

modifier

L'opérateur d'affectation multiplicateur est "*=" il signifie "affecter en multipliant à".

Syntaxe :

<Destination> *= <Source>; Où <Destination> et le résultat de l'opération doivent être de même nature et où <Source> est une expression et <Destination> une variable ;

Début de l'exemple
Fin de l'exemple


L'opérateur d'affectation diviseur "/="

modifier

L'opérateur d'affectation diviseur est "/=" il signifie "affecter en divisant à".

Syntaxe :

<Destination> /= <Source>; Où <Destination> et le résultat de l'opération doivent être de même nature et où <Source> est une expression et <Destination> une variable ;

Début de l'exemple
Fin de l'exemple


L'opérateur d'affectation modulo "%="

modifier

L'opérateur d'affectation modulo est "%=" il signifie "affecter le modulo de, à".

Syntaxe :

<Destination> %= <Source>; Où <Destination> et le résultat de l'opération doivent être de même nature et où <Source> est une expression et <Destination> une variable ;

Début de l'exemple
Fin de l'exemple


Les opérateurs mathématiques

modifier

Opérateur d'addition "+"

modifier

L'opérateur permettant l'addition est "+" il permet d'additionner un nombre ou le résultat numérique d’une expression à un autre.

Début de l'exemple
Fin de l'exemple


Opérateur de soustraction "-"

modifier

L'opérateur permettant la soustraction est "-" il permet de soustraire un nombre ou le résultat numérique d’une expression à un autre.

Début de l'exemple
Fin de l'exemple


Opérateur de multiplication "*"

modifier

L'opérateur permettant la multiplication est "*" il permet de multiplier un nombre ou le résultat numérique d’une expression à un autre.

Début de l'exemple
Fin de l'exemple


Opérateur de division "/"

modifier

L'opérateur permettant la division est "/" il permet de diviser un nombre ou le résultat numérique d’une expression à un autre.

Début de l'exemple
Fin de l'exemple


Opérateur de modulo "%"

modifier

L'opérateur permettant de récupérer le reste de la division entière d’un nombre ou du résultat numérique d’une expression par un autre, est "%".

Début de l'exemple
Fin de l'exemple


Les opérateurs comparatifs

modifier

Ils servent essentiellement à effectuer des tests logiques entre plusieurs valeurs numériques.


Comparateur d'égalité "=="

modifier

Le comparateur C++ d'égalité == permet de vérifier si la valeur de gauche est strictement égale à la valeur de droite.

Cela revient à faire une inversion de l'opérateur logique XOR (soit NOT XOR). Voici la table de vérité du comparateur == pour l'opération (x == y) :

== (NOT XOR) y = false y = true
x = false true false
x = true false true


Comparateur "!=" (différent de)

modifier

Le comparateur C++ différentiel "!=" permet de vérifier si la valeur de gauche est strictement différente de la valeur de droite. Cela revient à appliquer l'opérateur logique XOR.


Comparateur "<" (strictement inférieur à)

modifier

Le comparateur "<" (strictement inférieur à) permet de vérifier si la valeur numérique de gauche est strictement inférieure à la valeur numérique de droite. Cet opérateur renvoie true uniquement si la valeur numérique de gauche est strictement inférieure à la valeur numérique de droite.


Comparateur "<=" (inférieur à)

modifier

Le comparateur "<=" (inférieurs à) permet de vérifier si la valeur numérique de gauche est inférieure ou égale à la valeur numérique de droite. Cet opérateur renvoie true uniquement si la valeur numérique de gauche est inférieure ou égale à la valeur numérique de droite.


Comparateur ">=" (supérieur à)

modifier

Le comparateur ">=" (supérieur à) permet de vérifier si la valeur numérique de gauche est supérieure ou égale à la valeur numérique de droite. Cet opérateur renvoie true uniquement si la valeur numérique de gauche est supérieure ou égale à la valeur numérique de droite.

Comparateur ">" (strictement supérieur à)

modifier

Le comparateur ">" (strictement supérieur à) permet de vérifier si la valeur numérique de gauche est strictement supérieure à la valeur numérique de droite. Cet opérateur renvoie true uniquement si la valeur numérique de gauche est strictement supérieure à la valeur numérique de droite.


Opérateurs logiques

modifier

Les opérateurs logiques représentent les manipulations et comparaisons possibles que l’on peut faire sur des données de types booléen et/ou binaire.

  Attention : les opérateurs &, | et ^ sont des opérateurs bit à bit ! Les opérateurs logiques correspondant au & et au | sont respectivement && et ||.


Les opérateurs logiques

modifier

Cette sous-catégorie représente les opérateurs logiques que nous avons vu en introduction en leur spécifiant une syntaxe. Il est à noter que l'opérateur logique YES n'a pas de représentation en C++. En effet le C++ utilise le postulat que les opérations doivent retourner true pour être vraies. Partant de ce principe, il n'est plus nécessaire d’avoir un opérateur YES.


Opérateur "!" (NOT)
modifier

L'opérateur logique inverseur NOT a pour syntaxe en C++ le symbole "!" (point d'exclamation) ou l'expression "not". Pour inverser une valeur on écrira donc en C++

Début de l'exemple
Fin de l'exemple


Opérateur "&&" (AND)
modifier

Le comparateur C++ AND, représenté par "&&" (double "et commercial"), permet de vérifier si les 2 valeurs soumises sont vraies. Ce comparateur est souvent utilisé dans les tests car il permet une optimisation du code. Contrairement à l'opérateur logique "&" le comparateur "&&" teste la première valeur et si elle est false il renvoie directement false sans tester la deuxième valeur. Ce comportement permet un gain de temps sur les procédures de comparaison et permet d’éviter l'exécution d’un code qui serait invalide si le test de la première valeur renvoyait false.

Début de l'exemple
Fin de l'exemple


Opérateur "||" (OR)
modifier

Le comparateur C++ OR "||" permet de vérifier si au moins une des valeurs soumises est vraie. Ce comparateur est souvent utilisé dans les tests car il permet une optimisation du code. Contrairement à l'opérateur logique "|", le comparateur "||" teste la première valeur, et si elle est à vrai, il renvoie directement true sans tester la deuxième valeur. Ce comportement permet un gain de temps sur les procédures de comparaison et permet d’éviter l'exécution d’un code qui serait invalide si le test de la première valeur renvoyait vrai.

Opérateurs bit à bit

modifier

Les opérateurs bit à bit travaillent sur les variables en s'appliquant bit à bit[1].

Opérateur "&" (AND)
modifier

L'opérateur AND a pour syntaxe en C++ le symbole "&" (l’esperluette ou et commercial). Pour effectuer un AND sur des valeurs on écrira donc en C++

Début de l'exemple
Fin de l'exemple


Opérateur "|" (OR)
modifier

L'opérateur OR a pour syntaxe en C++ le symbole "|" (la barre verticale). Pour effectuer un OR sur des valeurs on écrira donc en C++ :

Début de l'exemple
Fin de l'exemple


Opérateur "^" (XOR)
modifier

L'opérateur XOR a pour syntaxe en C++ le symbole "^" (Accent circonflexe). Pour effectuer un XOR sur des valeurs on écrira donc en C++ :

Début de l'exemple
Fin de l'exemple


Opérateur "~" (NOT)
modifier

L'opérateur NOT a pour syntaxe en C++ le symbole "~" (tilde). Pour effectuer un NOT sur des valeurs on écrira donc en C++ :

Début de l'exemple
Fin de l'exemple



Opérateurs unaires

modifier

En C++, il existe des opérateurs qui ne nécessitent qu'un opérande. Ils sont appelés opérateurs unaires.


Opérateurs "++" (incrémentation)

modifier

L'opérateur "++" est un peu particulier car il possède 2 formes et des effets indésirables. Je n'en parle que pour vous permettre d’éviter les cas problématiques.


Forme préfixée
modifier

Dans sa forme préfixée la théorie veut que l'opérande associé soit d’abord incrémenté puis renvoyé.

operande est la valeur à incrémenter puis à renvoyer.

Forme suffixée
modifier

Dans sa forme suffixée la théorie veut que l'opérande associé soit d’abord renvoyé puis incrémenté.

operande est la valeur à renvoyer puis à incrémenter.

Opérateurs "--" (décrémentation)

modifier

L'opérateur "--" est un peu particulier car il possède 2 formes et des effets indésirables. Je n'en parle que pour vous permettre d’éviter les cas problématiques.

Forme préfixée
modifier

Dans sa forme préfixée la théorie veut que l'opérande associé soit d’abord décrémenté puis renvoyé.

operande est la valeur à décrémenter puis à renvoyer.

Forme suffixée
modifier

Dans sa forme suffixée la théorie veut que l'opérande associé soit d’abord renvoyé puis décrémenté.

operande est la valeur à renvoyer puis à décrémenter.

Effets secondaires
modifier

En C++ il existe un effet secondaire avec certains opérateurs. Si l’on utilise plusieurs opérateurs unaires ou d'affectation dans la même ligne de commande, le résultat est indéterminé par les spécifications du langage[2]. Ce qui veut dire que le compilateur fait ce qu’il veut. En fait ce problème survient car l'opérateur lit et modifie la valeur de ses opérandes.

Début de l'exemple
Fin de l'exemple

Dans ce cas, "a" peut valoir 1 ou 2 selon comment le compilateur a traité les opérateurs. Autre exemple

Début de l'exemple
Fin de l'exemple


Dans ce cas, "a" peut valoir 0, 1 ou 3 selon comment le compilateur a traité les opérateurs.

autre exemple :

Début de l'exemple
Fin de l'exemple


Ici l’affichage peut avoir bien des valeurs.

Il est donc recommandé d’utiliser ces opérateurs avec précaution et d’éviter toute expression trop complexe(comme ci-dessus). D'ailleurs, de telles expressions ont plutôt tendance à rendre le code moins lisible - ce qui est l'inverse du but initial - et cache souvent une mauvaise conception.


Néanmoins, une utilisation raisonnable de ces opérateurs permet de rendre un code plus concis, sans perdre en lisibilité.

Début de l'exemple
Fin de l'exemple

\n, signifie une le line-feed.

\t, signifie la tabulation horizontale.

Et leurs origines viennent des ordinateurs qui existaient avant la démocratisation des écrans. Et ils étaient destinés aux imprimantes.

Le \n est un caractère spéciale que les imprimantes interprétaient comme instruction de faire avancer le papier d’une ligne verticalement.

Le \t est un caractère spéciale qui donnait l’instruction d’avancer la tête d’impression de 1 à 8 espaces horizontalement. Soit la position divisible par huit sans reste la plus près de sa position actuelle.

Références

modifier
  1. https://www.miniwebtool.com/bitwise-calculator/?data_type=10&number1=6&number2=12&operator=OR
  2. Programmer en langage C++, Claude Delannoy, 2011

Voir aussi

modifier



Syntaxe C++

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 5
Leçon : Langage C++
Chap. préc. :Opérateurs
Chap. suiv. :Constantes et Variables
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.

Syntaxe C++

modifier

Avant de passer à la suite il est temps de voir comment on structure les instructions en C++.

Sens de Lecture

modifier

En C++ les instructions se lisent de droite à gauche, (non ce n'est ni de l'arabe, ni de l'hébreu, ni du japonais). Cela est dû au fait que les langages de programmation sont issus de l'anglais. Les opérateurs transfèrent le plus souvent les valeurs situées à leur droite vers les valeurs situées à leur gauche et beaucoup d'opérateurs spécifient l'état des identificateurs situés à leur gauche.

Voici un exemple concret que vous verrez rapidement. Nous allons parler de la méthode principale de tout programme C++ aussi appelé « point d'entrée » du programme, il s'agit de la méthode « main » et plus particulièrement de ses paramètres.

  Attention : Pour ceux qui connaissent des langages comme le Pascal et qui s'attendraient à ce que je parle de fonction, en C++ il n'existe pas de distinctions clairement établie entre procédure et fonction comme en Pascal. Une méthode est un compromis entre la fonction et la procédure. La seule chose qui différencie la fonction de la procédure en C++ est le type de retour qui est 'void' pour une procédure et différent de 'void' pour les fonctions. On ne peut donc pas faire de différence entre une procédure et une fonction en C++. On regroupe donc les deux formes sous l'appellation de méthodes.


Début de l'exemple
Fin de l'exemple


Dans ce morceau de code nous voyons donc sur la première ligne :

Début de l'exemple
Fin de l'exemple


La syntaxe :


signifie que nous avons affaire à une méthode.

Dans les parenthèses nous trouvons, séparés par une virgule, les paramètres (aussi appelés arguments) suivants :

Début de l'exemple
Fin de l'exemple


Nous pouvons facilement comprendre le premier qui est un entier comptant les paramètres.
Le second est plus délicat car c’est un piège qui fait chuter plus d'un débutant.

Un débutant qui a des notions de syntaxe lira

Début de l'exemple
Fin de l'exemple

puis

Début de l'exemple
Fin de l'exemple


et en déduira qu’il s'agit d'un pointeur (*), appelé argv, de tableau de char. En gros, une (seule) chaîne de caractères ce qui est totalement faux.
Ne vous alarmez pas si vous ne comprenez pas le concept de tableau il sera expliqué en temps et en heure. Essayez de simplement comprendre le sens de lecture.

En fait il faut le lire dans l'autre sens :

Début de l'exemple
Fin de l'exemple


puis

Début de l'exemple
Fin de l'exemple


déclare un tableau, appelé argv, contenant des pointeurs (*) de char. Ce qui correspond à plusieurs pointeurs sur des caractères regroupés dans un tableau. Vous pourriez me dire que cela revient au même puisque l’on a un tableau de pointeurs sur des caractères. C’est vrai que l’on peut le voir sous cet angle, cependant un pointeur de caractère peut avoir 2 significations :

  • un pointeur sur un et un seul caractère,
  • un pointeur sur un caractère qui correspond au premier caractère d'une chaine de caractères à zéro terminal.

Il faut donc interpréter

Début de l'exemple
Fin de l'exemple


comme un tableau de pointeurs sur des chaînes de caractères, soit un tableau contenant plusieurs chaînes de caractères. Cela vous paraîtra plus logique lorsque nous étudierons en détail le paramétrage d’applications en ligne de commande.

Une instruction se termine par un point-virgule

modifier

En C++ la grande majorité des instructions (hormis les définitions, les inclusions, les méthodes, les structures et certaines directives) se terminent par un point-virgule « ; »


Début de l'exemple
Fin de l'exemple




Constantes et Variables

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 6
Leçon : Langage C++
Chap. préc. :Syntaxe C++
Chap. suiv. :Boucles & Structures Conditionnelles
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.

Constantes et Variables

modifier

En C++ nous avons besoin, pour pouvoir travailler, d'enregistrer des valeurs en mémoire.

La Mémoire

modifier

La mémoire dans un ordinateur est une succession d'octets (soit 8 bits), organisés les uns à la suite des autres et directement accessibles par une adresse. Ces adresses sont linéaires, et commencent à l'adresse zéro. Certaines de ces adresses sont affectées au matériel (carte graphique, disques durs, BIOS, etc.), les autres servent à enregistrer des données.

Organisation de la mémoire en C++

modifier

En C++ la mémoire est organisée en deux catégories, la pile (de l'anglais Stack) et le Tas (de l'anglais Heap). Cette formalisation vient de l'architecture même des microprocesseurs compatibles i80x86.

La Pile
modifier

La pile est un espace mémoire réservé au stockage des variables désallouées automatiquement en mémoire. Sa taille est limitée mais on peut choisir sa taille de manière à ce que l’on ne la remplisse jamais. Les compilateurs C++ gèrent ce paramétrage et nous n'avons plus à le configurer mais il est important de savoir où sont stockées les variables en mémoire et comment ce processus fonctionne.

La pile est bâtie sur le modèle LIFO (Last In First Out) ce qui signifie littéralement "Dernier Entré Premier Sorti" (DEPS). Il faut voir cet espace mémoire comme une pile d'assiettes où on a le droit d'empiler/dépiler qu'une seule assiette à la fois. Par contre on a le droit d'empiler des assiettes de taille différente. Lorsque l’on ajoute des assiettes on les empile par le haut, les unes au dessus des autres. Quand on les "dépile" on le fait en commençant aussi par le haut, soit par la dernière posée. Lorsqu'une valeur est dépilée elle est effacée de la mémoire.

Description Adresse no Élément Type
Espace vide de la pile (vide) (vide) (vide)
Pointeur de pile → Pile Base + x n char
... ... ...
Pile Base + 7 4 int (LSB2)
Pile Base + 6 4 int (LSB1)
Pile Base + 5 4 int (MSB2)
Pile Base + 4 4 int (MSB1)
Pile Base + 3 3 short(LSB*)
Pile Base + 2 3 short(MSB**)
Pile Base + 1 2 char
Base de la pile → Pile Base 1 char

* LSB = Least Significant Byte (Octet de Poids Faible)

** MSB = Most Significant Byte (Octet de Poids Fort)

Le tas est l'espace mémoire qui n'est ni alloué au système d'exploitation, ni alloué à la pile du programme. Il est lui aussi limité mais il est aussi beaucoup plus grand que la pile. On lui alloue des valeurs que l’on veut pouvoir transmettre hors des portée des méthodes où elles sont définies. Ne fonctionnant pas comme une pile, la mémoire ainsi allouée doit être désallouée explicitement.

Allocation en Pile et en Tas

modifier
Allocation en Pile
modifier

Une allocation de valeur en pile se fait grâce au mot clé "auto".

Début de l'exemple
Fin de l'exemple


Depuis la normalisation ANSI, il n'est plus nécessaire de préciser le mot clé "auto". Le compilateur génère automatiquement des variables à désallocation automatique.

Ce code est donc équivalent au précédent

Début de l'exemple
Fin de l'exemple


Allocation en Tas
modifier

Pour allouer en tas en C++, il faut utiliser le mot clé "new" qui retourne un pointeur (nous verrons les pointeurs dans un autre chapitre). Après allocation il faut impérativement désallouer le tas sous peine de créer une fuite de mémoire. Pour désallouer une variable en tas, il faut utiliser le mot clé "delete" qui attend comme paramètre un pointeur.

Début de l'exemple
Fin de l'exemple


  Attention : L'allocation en tas ne permet pas la désallocation automatique. Chaque allocation de variable créée avec "new" doit impérativement être détruite avec "delete" sous peine de créer une fuite de mémoire. La fuite de mémoire est une zone mémoire qui a été allouée en tas par un programme qui a omis de la désallouer avant de se terminer. Cela rend la zone inaccessible à toute application (y compris le système d'exploitation) jusqu'au redémarrage du système. Si ce phénomène se produit trop fréquemment la mémoire se remplit de fuites et le système finit par tomber faute de mémoire.

Variables et Constantes

modifier

En C++ il existe deux catégories de valeurs : Les Variables et les Constantes. On peut donc créer en mémoire soit un espace qui contient une valeur variable (ou modifiable), soit un espace qui contient une valeur constante (ou non-modifiable).

Dans le premier cas nous appelons ces espaces mémoire des "variables", dans le second cas nous les appelons des "constantes".

Les Variables

modifier

Ces espaces mémoire permettent d'enregistrer des valeurs modifiables à volonté. Il n'existe pas de mot clé spécialisé dans la création des variables.

  Attention : Une variable non initialisée contient une valeur indéterminée, il est impératif d'initialiser une variable avant toute utilisation afin d’éviter toute erreur de logique. Si vous parvenez à en faire un réflexe vous gagnerez beaucoup de temps sur le débogage.


Où <Type> correspond au type (de base ou étendu) qui contiendra la valeur constante ou variable <Valeur> et <AutreValeur>, désignée par le nom <NomVariable>. <Type> et <Valeur> doivent être de même nature. <NomVariable> est un identifiant unique répondant aux mêmes critères que les identifiants de constantes.


Début de l'exemple
Fin de l'exemple


  Attention : Certains identifiants sont interdits. Ils sont utilisés par l'environnement de développement et les utiliser provoquera au mieux des erreurs de logique et au pire un plantage général de l'application, pouvant entraîner des instabilités du système. Non seulement la liste est très longue mais elle change en fonction de l'environnement de développement utilisé. Heureusement la plupart des compilateurs avertissent de la mauvaise utilisation de ces identifiants. Si, d'aventure, ils ne le faisaient pas et que vous constatiez des erreurs inexpliquées à l’utilisation d’un identifiant, cela viendrait peut-être d’un problème de conflit de noms d'identifiant.

Le tableau ci-dessous montre les différentes possibilités pour un nom d'identifiant

Noms d'identifiant Validité
a Valide
R2d2_c6PO_Starwars Valide
_a Valide
_2 Valide
_ Valide (à éviter)
-a Non valide (le "-" est considéré comme l'opérateur mathématique moins)
248sp542 Non valide (commence par un chiffre)
R 2 d 2 Non valide (il ne doit pas y avoir d'espace)
Métronome Non valide (une lettre accentuée n’est pas un caractère autorisé dans un nom d'identifiant)

Les Constantes

modifier

Contrairement aux variables les constantes ne peuvent pas être initialisées après leur déclaration et leur valeur ne peut pas être modifiée après initialisation.

Comme cité précédemment, ces espaces mémoire ne peuvent plus être modifiés une fois qu’ils ont été initialisés. Ces constantes doivent être déclarées avec le mot clé "const" et obligatoirement initialisées sur la même ligne.


Où <type> correspond au type (de base ou étendu) qui contiendra la valeur constante <valeur>, désignée par le nom <NomConstante>. <type> et <valeur> doivent être de même nature. <NomConstante> est un identifiant unique composé de caractères alphanumériques sans espace, sans accents et ne commençant pas par un chiffre.

Début de l'exemple
Fin de l'exemple


  Attention : A l'instar des variables, les constantes ne peuvent pas porter n’importe quel nom d'identifiant.


Les Constantes Non Nommées
modifier

Une constante non nommée est en C++, une catégorie de constante qui n'a pas de nom. En pratique, ce sont des valeurs brutes non modifiables qui ne peuvent pas être représentées autrement.

Début de l'exemple
Fin de l'exemple


Exemples :

modifier

Ces exemples sont très bien pour appréhender le concept de "constante" mais n'ont pas d'autre intérêt fonctionnel.

Début de l'exemple
Fin de l'exemple

Ces exemples sont très bien pour appréhender le concept de "variable" mais n'ont pas d'autre intérêt fonctionnel.

Début de l'exemple
Fin de l'exemple




Boucles & Structures Conditionnelles

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 7
Leçon : Langage C++
Chap. préc. :Constantes et Variables
Chap. suiv. :Méthodes
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.

Boucles & Structures Conditionnelles

modifier

En C++ on a très fréquemment besoin de contrôler les chemins d'exécutions, ce afin de réagir à certaines conditions ou pour répéter plusieurs fois un même morceau de code.

Structures conditionnelles

modifier

Les structures conditionnelles servent à effectuer des vérifications avant d'exécuter du code spécifique à la condition.

IF, ELSE

modifier

Il faut se représenter l'instruction "if" comme un test logique binaire : "si cela est vrai, alors je fais ceci, sinon, je fais cela."


Où <Conditions> sont les conditions à remplir pour que le bloc de <ActionsSiConditionsVrai> de la clause "if"(Si) soit exécuté, et fausses pour que <ActionsSiConditionsFausses> de la clause "else" (Sinon) soit exécuté. Il est à noter que la clause "else" est facultative.

Début de l'exemple
Fin de l'exemple


Le fait d'écrire true == vBascule n’est pas une erreur de conception :

  • Cela améliore la lisibilité du test.
  • Cela permet d'assurer le plantage à la compilation du test si on fait l'erreur de ne mettre qu'un seul égal =.
  • Cela permet de signaler aux développeur qui reliront le code que l’on veut bien la valeur true (dans le cas contraire on aurait mis false)
  Attention: Il est dangereux et très fortement déconseillé d'enlever les accolades des clauses "if" et "else". Tout comme d’utiliser l'implémentation "else if" pour imbriquer des tests conditionnels.
  Attention:Exemples à ne pas faire :
Début de l'exemple
Fin de l'exemple


Ceci est à prohiber pour 3 raisons :

  1. D'abord, c’est débile "a = b" et "a = 5" auraient été mieux placés dans les premiers "if"
  2. Ensuite, c’est illisible et seul un initié sait comment va être exécuté "FaitAutreChose"
  3. Enfin, c’est dangereux car on ne voit pas l'organisation des chemins d'exécutions.
  Attention: Quant au groupement "else if" cela revient à faire :
Début de l'exemple
Fin de l'exemple

soit :

Début de l'exemple
Fin de l'exemple

et donc :

Début de l'exemple
Fin de l'exemple

Ce qui est plus lisible.


Le code de l'exemple à ne pas faire avec les accolades donnerait :

Début de l'exemple
Fin de l'exemple


Ce qui permet tout de suite de voir que "FaitAutreChose" ne fait pas partie des tests. En effet sans accolades les clauses "if" et "else" n'exécutent que l'instruction située juste derrière elles. De plus on voit les chemins de codes et les optimisations possibles.

Voici donc le même code optimisé

Début de l'exemple
Fin de l'exemple


On peut voir ici que le nouveau code fait non seulement la même chose que le code original mais en plus on effectue un test de moins grâce à l'optimisation.

Malheureusement il existe encore beaucoup de code industriel écrit de manière compacte (sans accolades) et possédant des "else if" imbriqués.

Switch, case, default

modifier

Il faut se représenter l'instruction "switch" comme un test logique multi-états : "Selon la variable, si elle est à telle valeur alors faire ceci, si elle est à telle autre valeur faire cela, si elle est à encore une autre valeur faire autre chose, ..., sinon : faire le traitement par défaut.".

  Attention: Cette structure de test conditionnel ne permet de tester que des valeurs entières ou des chars, pas des chaines ni des flottants !



Où <Variable> est la variable à tester <Valeur1> à <ValeurN> sont les différentes valeurs que <Variable> peut prendre et qui nécessitent un traitement particulier, <ActionsSiValeur1> à <ActionsSiValeurN> sont les différents traitements pour chaque valeur nécessitant leur propre traitement, <ActionsSiValeur3Ou4> est un cas où plusieurs valeurs (ici <Valeur3> et <Valeur4>) nécessitent le même traitement (remarquez l'absence de break; entre les deux cas, autrement obligatoire), <TraitementParDéfaut> signalé par la clause default est le traitement réservé à toute autre valeur que celles renseignées par les clauses case. Il est à noter que la clause default n’est pas obligatoire. Par contre il est impératif de mettre une clause break après chaque clause case suivie de code.

Dans un souci d'optimisation de code et de compréhension les valeurs renseignées dans les clauses case doivent décrire les cas uniques nécessitant des traitements personnalisés. La clause default traitant tous les autres cas.

Début de l'exemple
Fin de l'exemple


  Attention: Il est fortement conseillé de mettre entre accolades les traitements des clauses case, en particulier si les traitements en question sont longs ou utilisent des boucles ou des structures conditionnelles.

Boucles

modifier

En C++ on peut demander à un programme de répéter plusieurs fois l'exécution du même code. Cela s’appelle itérer le code, ou plus simplement faire une boucle.

Boucles FOR

modifier

La boucle "for" est une itération bornée elle est généralement associée à une variable de contrôle et à une condition afin de ne pas exécuter le code indéfiniment. En pratique, on ne l'utilise que si l’on sait ou s'il y a moyen de savoir combien de fois on doit itérer.


Où <Initialisation> est la séquence d'initialisation de la boucle qui est en général utilisée pour initialiser le compteur de contrôle, <Condition> est la condition à réaliser pour que la boucle for continue d'itérer le code <ItérationCode>, et <ItérationContrôle> est la séquence d'itération du contrôle de la boucle.


Concrètement :

Début de l'exemple
Fin de l'exemple


La boucle "for" est à traduire littéralement par : "pour". Par analogie avec d'autres langages comme le Pascal on peut la comprendre ainsi : "Pour, De, À, Faire, Puis", soit en ce qui concerne notre exemple :

"Pour" l'entier 'vIndex', "De" la valeur '0', "À" la valeur 'vLimite', "Faire" : 'std:cout << (vIndex + 1) << " / " << vLimite;' "Puis" 'vIndex++'.

  Attention: Il est à noter que si la condition est fausse à l'entrée de la boucle (ex: 1 < 0) alors la boucle n'effectue aucune itération. De même si la condition est toujours vraie (ex: 0 > 1) alors on ne sort jamais de la boucle. Ces deux comportements sont la plupart du temps les conséquences d'erreurs de conception.

Boucles WHILE

modifier

La boucle "while" est une forme d'itération non bornée qui permet de répéter un code tant qu'une condition est vraie. La plupart du temps cette boucle est utilisée si l’on ne connait pas l'état initial de la variable qui est utilisée dans la condition, ou que l’on ne connait pas et que l’on ne peut pas calculer le nombre d'itérations nécessaire pour que la variable atteigne son état final.


Où <Condition> est la condition pour que la boucle fasse une nouvelle itération et <ItérationCode> le code à itérer.

Début de l'exemple
Fin de l'exemple


Boucles DO WHILE
modifier

La différence entre une boucle while et une boucle do while est que dans la boucle "do while" le code inclus dans la boucle est exécuté au moins une fois.


Voici un exemple d'implémentation qui montre que les instructions de la boucle "do while" sont bien executé au moins 1 fois.

Début de l'exemple
Fin de l'exemple




Méthodes

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 8
Leçon : Langage C++
Chap. préc. :Boucles & Structures Conditionnelles
Chap. suiv. :Pointeur, Tableaux et références
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.

Les Méthodes :

modifier

La méthode en C++ est l'un des éléments les plus basiques mais essentiels du langage. De nos jours, certains programmes informatiques tels les systèmes d'exploitation contiennent plusieurs millions de méthodes. Comme nous l'avons dit précédemment, la méthode permet de définir une suite d'opérations à exécuter dans un ordre séquentiel donné.


Où <TypeRetour> est le type de retour de la méthode <NomMethode>, <Portee> est le nom de la portée à laquelle est rattachée la méthode, s'il y a lieu, <TypeParametre> est le type du paramètre, <NomParametre> est le nom du paramètre, <ValeurParDefaut> est la valeur éventuellement souhaitée pour le paramètre, <...> sont des paramètres additionnels et <Instructions> sont les instructions contenues dans la méthode.

En C++ l’application la plus basique en C++ est le point d'entrée programme qui n'est autre que la méthode "main".

Début d'une démonstration
Fin de la démonstration

Ceci est le minimum de code requis pour la création d'une application en C++.

Décortiquons un peu cela :

int main(int argc, char* argv[])

Nous avons donc ici le point d'entrée du programme. C'est une méthode qui a comme type de retour une valeur entière. Cette valeur permet au système de savoir dans quelle circonstance s'est terminé le programme. Si le programme s'est fermé normalement, la valeur de retour sera égale à zéro. Par contre si une erreur s'est produite, alors la valeur de retour sera différente de zéro. En général, en cas d'erreur, main retourne -1, mais il peut y avoir d'autres valeurs retournées dans certains cas particuliers.

Le paramètre argc est en fait un compteur sur le nombre de chaines de caractères contenus dans le tableau argv[].

Premières applications :

modifier
Début d’un principe
Fin du principe


Voici un programme d'exemple qui affiche à l'écran une lettre choisie par le programmeur.

Début de l'exemple
Fin de l'exemple


Autre exemple :

Début de l'exemple
Fin de l'exemple


Les 2 applications produisent le même résultat mais n'ont pas le même code. Cela confirme le fait que la représentation graphique des caractères se fait par le biais de la représentation non signée du codage du caractère.

Bonjour Monde !

modifier
Début d’un principe
Fin du principe
Début de l'exemple
Fin de l'exemple


Bonjour M. X !

modifier
Début d’un principe
Fin du principe
Début d'une démonstration
Fin de la démonstration

Voyons ce qui se passe si on exécute la commande DOS: "[PATH]\BonjourMX.exe X". Le programme affiche "Bonjour M. X !".

Si nous exécutons "[PATH]\BonjourMX.exe Dupont", le programme affiche "Bonjour M. Dupont !".

Si nous exécutons "[PATH]\BonjourMX.exe Dupont Dupond" le programme affiche "Bonjour M. Dupond !".

Maintenant que se passerait-il si nous exécutions "[PATH]\BonjourMX.exe" ?

En fait, le programme est prévu pour afficher le dernier paramètre de la chaîne d'appel de la ligne de commande. Comme le système d'exploitation passe au programme le chemin de ce dernier comme premier paramètre, la commande "[PATH]\BonjourMX.exe" affichera "Bonjour M. [PATH]\BonjourMX.exe !".


Appels de Méthodes :

modifier
Début d’un principe
Fin du principe

Voici une petite application qui permet de se familiariser avec l'utilité des méthodes. Imaginons que nous voulons rendre le calclul : y = a . x + b, accessible à tout le programme sans avoir à le réécrire partout dans le code. Nous devrons alors écrire la méthode "CalculeAffine" suivante :

Début de l'exemple
Fin de l'exemple


Ce code n'est pas trop mal, il fonctionne bien. Seulement, il a un problème, il comporte des doublons de code.

 

Un vrai développeur n'aurait jamais fait ce programme ainsi. Le copier/coller du code tel que je vous l'ai présenté est consommateur en lignes de code. De plus, il ne facilite pas la lecture du code et peut même introduire des erreurs. Il n'est jamais conseillé de copier/coller du code. Dans un cas sur deux, cela génère des erreurs.

Voyons comment on pourrait arranger cela de manière plus lisible et moins gourmande en code :

Début de l'exemple
Fin de l'exemple


Récursivité :

modifier
Début d’un principe
Fin du principe


Récursivité Directe :

modifier
Début d’un principe
Fin du principe
Début de l'exemple
Fin de l'exemple


Récursivité Indirecte :

modifier
Début d’un principe
Fin du principe
Début de l'exemple
Fin de l'exemple



Alternative à la Récursivité :

modifier

En C++, il est toujours possible d'écrire un algorithme qui effectue les tache de la récursion de manière itérative.

Pour la factorielle :

Début d'une démonstration
Fin de la démonstration

Pour le pair/impair :

Début d'une démonstration
Fin de la démonstration


Polymorphisme de Méthode

modifier

Le polymorphisme de méthode est la faculté qu'ont les méthodes de même nom mais de signatures différentes à pouvoir sélectionner la bonne méthode pour la bonne signature.

Début de l'exemple
Fin de l'exemple




Pointeur, Tableaux et références

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 9
Leçon : Langage C++
Chap. préc. :Méthodes
Chap. suiv. :Portée du code
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.

Les Pointeurs

modifier

Les pointeurs servent à accéder aux données en mémoire par le biais de leur adresse. Le type pointeur est un entier non signé dont la taille dépend du type de processeur et du mode en cours. En gros, et pour simplifier, il utilise le format du "unsigned long int" actuellement en cours, où il enregistre l'adresse mémoire de la donnée à pointer.

Adresse Valeur Nom de variable
... ... ...
0x635 'P' x
... ... ...
0x7B8 0x00000635 p
... ... ...

Voici la syntaxe :


Où <Type> est le type de la donnée en mémoire, "*" signifie que l’on a affaire à un pointeur, <NomPointeur> est l'identifiant du pointeur et <Valeur> l'adresse mémoire (unsigned long int) où le pointeur doit aller chercher la donnée.


Affectation d'adresse

modifier

En C++, il est utile de récupérer l'adresse d'une variable ou d'une constante en utilisant l'opérateur "&" qui signifie "adresse de" et se place à gauche de l'identifiant.

Début de l'exemple
Fin de l'exemple


Comprendre : création d'une variable nommée "p" de type pointeur sur char "char*" prenant pour valeur "=" l'adresse de "&" la variable "x"

Mécanique d'Adressage

modifier

En C++, il peut être utile de copier l'adresse d'une variable pointée par un pointeur sur un autre pointeur. Cela se fait ainsi :

Début de l'exemple
Fin de l'exemple


On n'utilise pas l'opérateur "adresse de" dans ce cas là car "p1" renvoie l'adresse de "x"

Déréférencement

modifier

En C++, il peut être utile de récupérer la valeur d'une variable pointée par un pointeur. Cela se fait ainsi :

Début de l'exemple
Fin de l'exemple


Arithmétique des pointeurs

modifier

En C++, les pointeurs ont leur propre algorithmique. L'incrémentation et la décrémentation est particulière pour ce type. En fait, l'incrémentation (ainsi que la décrémentation) se fait en multipliant la taille du type du pointeur (obtenue avec sizeof()) par la valeur que l’on veut lui ajouter/retrancher.

L'arithmétique de pointeur est un concept particulier qu’il est possible d’éviter avec les notations liées au tableau. En particulier en C++, il est intéressant de s'interdire de recourir à l’arithmétique de pointeurs.

Début de l'exemple
Fin de l'exemple


Ce comportement permet la création et l’utilisation des tableaux. Nous verrons cela un peu plus bas.

Lorsque les pointeurs sont créés, ils prennent la valeur d'adresse qu’il y avait à l'emplacement qu’ils occupent. C'est pour cela qu’il faut impérativement les initialiser avant de s'en servir. Dans le cas contraire on peut assister, parfois, à des comportements des plus étranges.

Le pire des cas est celui du pointeur non initialisé à qui l’on affecte une valeur. Dans certains cas rares, l’application fonctionne sur l'ordinateur de développement car la donnée que le pointeur va prendre comme adresse, correspond à une adresse valide et donc le programme se déroule normalement.

Mais après installation sur le poste client, l’application plante car la donnée en mémoire n'est plus la même et ne correspond plus à une adresse valide.

 

Il est inacceptable d’utiliser la forme calculée des pointeurs pour accéder à des tableaux. Seule la forme entre crochets est valide. Le jour où un développeur instanciera un tableau dynamique sur un pointeur utilisé sous forme calculée, le programme plantera car le compilateur sera incapable de faire les déréférencements.

Préférez donc la forme entre crochets

Début de l'exemple
Fin de l'exemple

à la forme calculée

 
Début de l'exemple
Fin de l'exemple


Les Tableaux

modifier

En C++, les tableaux permettent de mémoriser des ensembles de données de même type. Il en existe 2 sortes, les tableaux statiques et les tableaux dynamiques.

La différence entre tableaux statiques et dynamiques est que les tableaux statiques sont dans tous les cas des groupes d'octets contigus monolithiques dans la pile alors que les tableaux dynamiques multidimensionnels sont, le plus souvent, des groupes d'octets contigus disséminés en plusieurs paquets dans le tas.


Tableaux "Statiques"

modifier

Le fait qu’ils soient enregistrés en pile fait que leur taille doit être connue dès la compilation et donc être constante. En effet, pour le processeur, il serait mal vu de pouvoir redimensionner le tableau après la création d'autres variables en pile. Dans le cas de l'agrandissement, on écraserait les nouvelles variables et dans le cas de la diminution, on ferait des "TROUS" dans la pile, qui alors s'effondrerait.(Rappelez vous qu’il faut considérer la pile comme une pile d'assiettes)


On peut calculer le volume que le tableau statique occupe en mémoire, en multipliant la taille du type utilisé par le nombre d'éléments.

Ainsi un tableau tridimensionnel de 4 * 3 * 2 valeurs int, fera :

4(grilles) * 3(lignes) * 2(colonnes) * 4(Octets du type int) = 24(éléments)* 4(Octets du type int) = 96 Octets contigus en pile.


Voici la syntaxe :


Où <Type> est le type des éléments du tableau, <NomTableau> est le nom de la variable tableau, [](...)[] indique que c’est un tableau à N dimensions, <ConstanteN> est le nombre d'éléments, constant, de la dimension N du tableau (facultatif si on a défini une liste de valeurs), = assigne des valeurs au tableau (facultatif si <ConstanteN> est présent), {{},(...),{}} est une liste de valeurs des dimensions (il y en a autant que <ConstanteN>, facultatif si <ConstanteN> est présent), et <VariableN1,...VariableNM> sont les différentes valeurs que le tableau doit affecter aux éléments de la ligne N (il doit y en avoir autant que <ConstanteN>, facultatif si <ConstanteN> est présent).


Concrètement, voyons ce que ça donne si on reprend notre exemple :

Début de l'exemple
Fin de l'exemple


Les tableaux statiques, sont organisés par lignes. Cela signifie que lorsque l’on incrémente l'indice du tableau, on change de colonne plus rapidement que ce que l’on change de ligne.

Pour reprendre notre exemple, cela donnerait (en supposant que l'adresse de base du tableau t1 soit 0x02C0) :

Adresse Index no Élément Grille Ligne Colonne Valeur
0x02C0 t1[0][0][0] 0 0 0 0 1
0x02C4 t1[0][0][1] 1 0 0 1 2
0x02C8 t1[0][1][0] 2 0 1 0 3
0x02CC t1[0][1][1] 3 0 1 1 4
0x02D0 t1[0][2][0] 4 0 2 0 5
0x02D4 t1[0][2][1] 5 0 2 1 6
0x02D8 t1[1][0][0] 6 1 0 0 7
... ... ... ... ... ... ...
0x03A8 t1[2][2][1] 17 2 2 1 18
0x03AC t1[3][0][0] 18 3 0 0 19
0x03B0 t1[3][0][1] 19 3 0 1 20
0x03B4 t1[3][1][0] 20 3 1 0 21
0x03B8 t1[3][1][1] 21 3 1 1 22
0x03BC t1[3][2][0] 22 3 2 0 23
0x03C0 t1[3][2][1] 23 3 2 1 24

Pointeurs et Tableaux Statiques

modifier

Nous avons pu voir tout à l’heure que l'arithmétique des pointeurs permet de travailler sur plus d'une valeur voyons ce que cela donne :

Début de l'exemple
Fin de l'exemple


Tableaux "Dynamiques"

modifier

Pour un tableau dynamique, il existe deux méthodes.

Tableaux Compacts
modifier

On peut utiliser le format des tableaux statiques multidimensionnels que l’on reproduit en tas.

Voici la syntaxe de déclaration :


Où <Type> est le type du tableau, <NomVariable> est le nom identifiant du tableau, <Variable> est une variable entière non signée définissant la première dimension du tableau, <ConstanteN> sont des constantes entières non signées définissant les autres dimensions du tableau.

Aucun tableau dynamique n'est assignable à la déclaration. On ne peut pas en C++ assigner des valeurs à un tableau dynamique lors de sa création.

Début de l'exemple
Fin de l'exemple


Cela n’est pas très judicieux pour trois raisons :

  • La première c’est que les tailles des dimensions du tableau sont fixées définitivement (à moins de recréer le tableau complet).
  • La deuxième c’est que les différents éléments ne sont pas désolidarisables du tableau.
  • La troisième pour redimensionner le tableau il faut le recopier intégralement.

Techniquement, on peut faire mieux.

La seule raison valable qui peut amener à utiliser ces types de tableaux dynamiques est leur forme compacte. Ceci dit ils ne font réellement gagner de l'espace que sur les tableaux dont on connaît à l'avance la longueur. En effet, la lourdeur du traitement pour ajouter ou soustraire des éléments est souvent rédhibitoire.

Il ne reste donc qu'une solution techniquement valable en environnement PC : L'indirection de pointeurs multiples.

Indirection de Pointeurs
modifier

Le but du jeu est de créer un tableau de pointeurs où chaque pointeur pointe vers un autre tableau de pointeurs... (ce récursivement pour chaque dimension)... où chaque pointeur pointe vers un élément.


Où <Type> est le type du tableau, <NomTableau> est le nom du tableau et <Variable> une variable entière non signée.

Voyons comment cela est représenté en mémoire :


Voici un exemple concret qui montre comment créer/détruire et manipuler un tableau utilisant l'indirection de pointeurs.

Début de l'exemple
Fin de l'exemple


Pointeurs sur Méthodes

modifier

En C++, il est possible de créer des pointeurs sur des méthodes. Cela est surtout utile pour lier les fonctions d'une bibliothèque de liens dynamiques ou, plus intéressant, pour appeler des méthodes que l’on ne connaît pas à l'avance mais qui auront un prototype que l’on a défini. Cela permet, par exemple, de créer une liste de tâches à accomplir suite à un évènement donné.


Où "<TypeRetour>" est le type de retour de la méthode à pointer, "<NomPointeur>" est le nom attribué au pointeur, "<TypeParametre>" et "<NomParametres>" sont respectivement les types et noms des paramètres de la méthode

Début de l'exemple
Fin de l'exemple

Remarquez bien les parenthèses autour de "*LaMethode". Si on ne les avait pas mis comme dans cet exemple :

Début de l'exemple
Fin de l'exemple


Le compilateur aurait compris "LaMethode" une méthode qui accepte un paramètre "Params" de type "int" et retourne un pointeur de type "bool";

Pointeurs sur Méthodes Encapsulées

modifier

Il est possible de créer des pointeurs sur des méthodes qui sont encapsulées dans des classes à deux conditions :

  • la méthode doit être publique,
  • la méthode doit être statique.
Début de l'exemple
Fin de l'exemple


Les Références

modifier

Devant la difficulté qu'avaient la plupart des programmeurs de l'époque, à gérer convenablement la mémoire avec les pointeurs, le C++ a introduit la notion de référence. Une référence est un ALIAS, un autre nom, pour une variable DÉJÀ EXISTANTE. À l'inverse du pointeur, une référence n'occupe pas de place en mémoire. Le compilateur la remplace par le nom de la variable actuellement assignée dans le code après avoir déroulé les traitements mais avant de traduire ce code en binaire.



Où "<Type>" est le type de la référence "<NomRéférence>" est l'identifiant de la référence et <NomVariable> est l'identifiant de la variable que la référence doit désigner. Contrairement aux pointeurs, la référence doit impérativement être assignée dès la déclaration et il n’est pas possible de réassigner la référence à une autre variable après initialisation.


Ou plus concrètement :

Début de l'exemple
Fin de l'exemple


Références sur Méthodes

modifier

À l'instar du pointeur, il est possible de référencer une méthode.



Où "<Type>" est le type de retour de la méthode à référencer, "<NomRéférence>" est l'identifiant de la référence, <TypeParam> et <NomParam> sont respectivement le type et le nom de l'image des paramètres de la méthode à référencer et <NomMéthode> est l'identifiant de la méthode que la référence doit désigner. Contrairement aux pointeurs, la référence doit impérativement être assignée dès la déclaration et il n’est pas possible de réassigner la référence à une autre méthode après initialisation.

Début de l'exemple
Fin de l'exemple


Le Casting :

modifier

L'une des fonctionnalités les plus appréciées dans la manipulation des variables est l'opérateur de casting, ou de refonte, que sont les parenthèses "()".

Syntaxe:
(<Type>)[<Constante>|<Variable>]

Où <Type> est le type dans lequel la valeur <Constante> ou <Variable> doit être refondue.

En fait, on peut refondre des valeurs de types différents de manière sûre si au moins l'une de ces conditions est remplie :

  • La valeur source peut être transcodée dans le type de destination.(ex. : (char)'A'; est équivalent à (char)41; "41" est un const int)
  • Le type de la valeur source peut être approximée ou être un sous ensemble du type de la variable de destination (numérique, ex. : (double)int ou (int)double).
  • Le type de la valeur source hérite du type de la variable de destination (upcasting).
  • Le type de la valeur source est parente du type de la variable de destination (downcasting).
  Attention : L'utilisation de l'opérateur de refonte est à utiliser avec précautions. Le compilateur ne vous avertira pas si un dépassement de capacité a lieu ou si vous faites des downcasting (refontes descendantes) avec des types de nature différente

Exemples :

modifier

Exemple de cast pour une valeur constante

Début de l'exemple
Fin de l'exemple


Autre exemple pour une variable :

Début de l'exemple
Fin de l'exemple




Portée du code

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 10
Leçon : Langage C++
Chap. préc. :Pointeur, Tableaux et références
Chap. suiv. :Énumérations
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.


Portée du code

modifier

En C++, la notion de "portée du code" est très importante car elle permet de décrire ce qui se passe, dans quel cadre, et dans une certaine mesure ce qui existe et ce qui n'existe pas encore au travers de contextes d'exécutions.

Portée Globale

modifier

La portée globale fournit un espace de partage pour toute l'application.

Début d'une démonstration
Fin de la démonstration

La portée Globale englobe tout le programme.

Bien qu’il soit facile de s'en servir de fourre-tout, il est déconseillé de le faire. En fait, si on le faisait, on se retrouverait avec une myriade de types et de noms de variables que l’on ne pourrait pas distinguer des types et variables que l’on crée soit même[pas clair].

C'est pour cela qu’il existe les autres portées et notamment la portée nommée.

Portée Nommée "Espace de nom"

modifier

La portée nommée (aussi appelée "Espace de nom" du fait du mot clé qui les déclare "namespace") est la portée la plus simple à la disposition du développeur. Elle permet de fournir des noms pour encapsuler des constantes, des variables des types et des méthodes ceci afin de restreindre le champ d'application de ces objets ou de fournir une architecture pour leur utilisation. Ce type de portée est surtout utilisé dans le but de diviser pour mieux régner et délivre toute sa puissance dans l'implémentation des énumérations au sein d'un IDE graphique.

Début de l'exemple
Fin de l'exemple


Il peut être tentant de fournir des noms identiques à des types/variables/méthodes pour des d'espaces de noms différents. Mais cela est à éviter pour trois raisons :

  • Cela porte à confusion,
  • Ne sachant pas comment les utilisateurs vont les utiliser, il vaut mieux éviter les homonymes (risque d'inversion de variables),
  • Cela provoque des erreurs de compilations si l'utilisateur met des using sur les deux namespaces simultanément et l'obligera à taper le nom du namespace à chaque fois.

Portée Locale

modifier

La portée locale et la plus petite portée qui existe. C'est la portée disponible dans les méthodes, les boucles et les structures de tests conditionnels. Elle est généralement déterminée par les accolades. La portée locale est la portée pour laquelle les variables déclarées dans cette même portée sont accessibles. C'est pour cette raison que les portées locales doivent être clairement identifiées et identifiables. Pour cela, il est impératif de les écrire toutes explicitement et de bonne pratique de tabuler chaque niveau de portées afin que l’on puisse facilement les dissocier. La tabulation standard des éditeurs de textes des IDE du marché tourne entre 4 et 6 espaces. Dans le projet LINUX, les recommandations de tabulation sont de 8 espaces. La plupart des développeurs modernes vous maudiront si vous tabulez en dessous de 4 espaces.

Début de l'exemple
Fin de l'exemple


Portée "STATIQUE"

modifier

La portée statique est la portée des membres statiques du programme. Elle se distingue des autres types de portées par le fait qu'une variable statique existe au chargement du programme dans la mémoire mais n'est accessible que dans la portée locale de sa déclaration.

Début de l'exemple
Fin de l'exemple




Énumérations

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 11
Leçon : Langage C++
Chap. préc. :Portée du code
Chap. suiv. :Structures, unions et champs de bits
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.

Énumérations

modifier

En C++ les énumérations servent à créer des listes de constantes entières. Cela permet de regrouper des constantes d'un même contexte dans un même type. Pour des raisons de clarté il est préférable de définir chaque énumération dans un fichier d'en-tête propre. Pour des raisons de conception objet il est nécessaire de typer l'énumération à l'aide du mot clé "typedef". Enfin pour des raisons d'interférences il est conseillé de cloisonner l'énumération à un namespace.


Où <NomNamespaceEnum> est le nom du namespace qui encapsulera la portée des constantes énumérées, <NomConstante0> et <NomConstanteN-1> sont les identifiants des constantes, <ValeurConstante0> et <ValeurConstanteN-1> sont les valeurs respectivement de <NomConstante0> et <NomConstanteN-1> et <NomTypeEnum> est le type de l'énumération.

Il est à noter que les valeurs des constantes sont facultatives. Sans aucune précision, la valeur d'une constante est la valeur de la constante précédente augmenté de 1, la première valeur étant par défaut "0". Il faut aussi savoir qu’il ne peut y avoir qu'un identifiant pour une valeur donnée.

Début de l'exemple
Fin de l'exemple


Début de l'exemple
Fin de l'exemple




Structures, unions et champs de bits

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 12
Leçon : Langage C++
Chap. préc. :Énumérations
Chap. suiv. :Objet
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.

Structures, unions et champs de bits

modifier

Les structures existent en C++ uniquement pour la compatibilité ascendante et ne servent plus qu’à quelques cas spécifique comme les unions, les champs de bits et quelques applications matérielles mais même dans ces circonstances, il est possible de trouver des alternatives plus "Objet".

Le but principal de la structure était de regrouper et de mieux gérer les données qui avaient des affinités communes dans un langage qui ne disposait pas d'autres moyens (hormis l'énumération) pour organiser les données d'un programme.

Usuellement, les structures sont utilisées conjointement avec le mot clé "typedef". Cette écriture permet d’utiliser le casting et l'auto-référencement en est facilité.

En C++ la structure a été remplacée avantageusement par la classe mais nous verrons cela un peu plus tard.

Il existe trois catégories de structures :

  • Les structures simples,
  • Les unions,
  • Les champs de bits.

Les Structures Simples

modifier

En C++ la structure simple a évolué pour suivre un modèle plus "orienté objet", désormais, il est possible et conseillé d'inclure les méthodes qui manipulent les données de la structure dans la structure elle-même. Chose qui était très lourd à faire en C. On pouvait bien créer un pointeur sur méthode mais cela n'était pas aussi trivial que ce que permet le C++.


Où <NomStructure> (optionnel) est le nom interne de la structure, <StructureParente> (optionel) est le nom de la structure parente, <Visibilite> (optionel) déclare la visibilité des membres (parmi private, protected et public, par défaut : public), <TypeChamp1> est le type du premier champ ayant pour nom <NomChamp1>, <TypeChampN> est le type du dernier champ ayant pour nom <NomChampN>, <TypeMethod1> est le type de la première méthode <NomMethod1> ayant pour paramétrés <ParametresMethod1>, <TypeMethodN> est le type de la dernières méthode <NomMethodN> ayant pour paramètres <ParametresMethodN> et <NomTypeStructure> est le type de la structure

Début de l'exemple
Fin de l'exemple


Alternative à la structure

modifier

En fait, je l'ai déjà dit, la structure a été avantageusement remplacée par la classe, déclarée par le mot clé class.

Les Unions

modifier

Les unions sont une forme de typage faiblement typé. Les unions ont été conservées du C pour compatibilité et ne correspondent pas à la philosophie d'un langage orienté objet.

Les unions permettent de créer des espaces mémoire où l’on peut interpréter une même donnée de différentes manières ou de diviser une même donnée en sous ensembles. En fait, une union déclare la disposition de tous ses membres en partant de la même adresse contrairement à la structure qui dispose ses membres les uns à la suite des autres.

Les unions ne sont pas à proprement parler des structures. Utilisées telles quelles, elles ne permettent pas l'héritage mais il existe un moyen de contourner cet obstacle. Il est possible de créer des unions non nommées, cela permet de déléguer la manipulation des membres directement à la portée supérieure. Sachant cela il suffit d'encapsuler une union dans une structure typée pour rendre l'héritage possible. Cela implique cependant qu’il sera impossible de créer un constructeur ou des méthodes non inlinées au niveau de l'union. Pour des raisons de lecture il sera donc préférable de déléguer la gestion des méthodes de l'union à la portée supérieure (soit la structure dans notre cas). Cela exige aussi que tous les attributs de l'union soient déclarés comme public.


Où <NomStructure>(optionnel) est le nom interne de la structure d'accueil, <TypeChamp1> et <TypeChampN> sont les types des champs respectivement <NomChamp1> et <TypeChampN>, <NomTypeStructure> est le nom du type de la structure.

Début de l'exemple
Fin de l'exemple


Début de l'exemple
Fin de l'exemple


Alternative à l'union

modifier

Comme je l'ai dit plus haut, une classe et ses méthodes qui encapsulent les castings sera plus claire et propre que de manipuler la même variable avec plusieurs noms différents.

Champs de bits

modifier

Les champs de bits (ou "Drapeaux" de l'anglais "Flags"), qui ont leur principale application en industrie, sont des structures qui ont la possibilité de regrouper au plus juste, dans un nombre d'octets moindre, plusieurs valeurs. Cela vient directement du monde de l'électronique. Il existe nombres d'exemples de ce fonctionnement dans l’industrie.

Cela dit, les champs de bits sont très fortement déconseillés car aucun standard C, C++ ne fixe les règles d'implémentations.

La norme ISO C++ 14 (ISO/IEC JTC1 SC22 WG21 N 3690) énonce ceci :

"Allocation of bit-fields within a class object is implementation-defined. Alignment of bit-fields is implementation-defined."

Cela signifie qu'aucune garantie n'est fournie en ce qui concerne l'alignement mémoire. certains compilateurs peuvent générer un exécutable demandant l'accès à deux cases mémoire simultanément. Si cela ne pose que des problèmes de performances sur Intel, d'autres processeurs peuvent générer une erreur d'accès non alignée à la mémoire (Cas des processeur Spark par exemple).

Cela signifie également que chaque compilateur peut implémenter le champ de bit à sa manière dans une classe (et donc par extension dans une structure). Deux compilateurs peuvent alors générer deux exécutables aux comportement différents à partir du même code source.

Cela est vrai surtout en cas de cast ou d'union permettant de d'accéder à un champ de bit comme un seul entier ou champ par champ. Dans ce cas, rien ne garantit l'ordre dans lequel seront accédés les champs de bit.

La tentation est trop forte pour ne pas faire une petite parenthèse sur le standard C (ISO/IEC 9899:201x) qui est encore plus explicitement permissif :

"An implementation may allocate any addressable storage unit large enough to hold a bit- field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified."

Cela veut dire la même chose mais sans sous entendus.

Bref, voici un champ de bit :

Où <NomChampsBits> est le nom interne des champs de bits(facultatif), <TypeChamp> est le type (ou taille totale) du champs de bit, [<NomChamp1>] et [<NomChampN>] sont les nom des sous-champs(facultatif), <NombresBits1> et <NombresBitsN> sont les taille des sous-champs, respectivement, [<NomChamp1>] et [<NomChampN>], <NomTypeChampsBits> est le nom du type donné au champs de bits

Début de l'exemple
Fin de l'exemple


Alternative aux champs de bits

modifier

À l'instar de l'union et de la structure, une classe et ses méthodes qui encapsulent les castings seront plus claires et propres que de manipuler plusieurs sous-noms différents d'une même variable.



Objet

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 13
Leçon : Langage C++
Chap. préc. :Structures, unions et champs de bits
Chap. suiv. :Classe
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.


Programmation orientée objet

modifier

Historique

modifier

Préhistoire : la programmation monolithique séquentielle

modifier

Au temps des prémices de l'informatique, à l'époque des dinosaures électromécaniques qui faisaient le volume d'un bâtiment de quatre étages, où les bandes magnétiques n'existaient pas encore, où la programmation se faisait en connectant des câbles et des trous ou, pour les plus sophistiqués, par cartes perforées, où le moindre calcul scientifique prenait plus de six mois à la main (et à la règle à calcul) et seulement une semaine une fois informatisé, la programmation était monolithique. C'est-à-dire que les développeurs de l'époque faisaient des programmes (la plupart du temps en binaire) de manière séquentielle et s'adressaient directement au système physique de traitement qui sera nommé plus tard et après miniaturisation : processeur. De fait le programme en question était le seul programme que la machine connaissait. Il n'y avait donc pas de traitements parallèles ni de problèmes d'accès concurrentiel aux ressources. Le programme était purement séquentiel et on recréait un nouveau programme pour chaque nouvelle application (qui à l'époque n'étaient que des calculs). Les programmes étaient courts (de quelques centaines à quelques milliers d'ordres machines simples). Le terme de "BUG" (insecte en anglais) est introduit à cause des cafards qui venaient se réchauffer un peu trop près de l'ancêtre des transistors, des lampes à tubes électrostatiques, et finissaient par griller entre les pâtes du composant, ce qui entrainait des pannes longues et coûteuses à réparer.

Antiquité : la programmation procédurale

modifier

À cette époque le transistor en silicium a déjà été intégré aux ordinateurs qui n'occupent plus qu'une salle de réunion entière. Les bandes magnétiques sont devenues monnaie courante et les programmeurs développent maintenant sur un clavier et un écran. Les premiers compilateurs pour langages extensibles lui permettent grâce au concept de procédures et de fonctions d'enrichir le langage de base dont il dispose en composant des suites d'instructions qu’il peut réutiliser. On développe alors les programmes de manière fonctionnelle afin de pouvoir décomposer un traitement en sous-traitements de plus en plus simples.

Moyen Âge : La programmation orientée donnée

modifier

À ce moment le processeur vient de voir le jour. Les ordinateurs occupent le volume d'une armoire. Les premiers systèmes d'exploitations mono-tâches voient le jour. Le programmeur définit toutes les variables dont il aura besoin et les regroupe par fonctionnalités dans des fichiers distincts avec les procédures et fonctions qui les traitent.

Renaissance : La multiprogrammation

modifier

À ce stade les processeurs/micro-contrôleurs sont courants. Les ordinateurs ont la taille de valises. Les premiers systèmes d'exploitation en ligne de commandes permettent une utilisation facilitée de l'ordinateur qui intègre désormais un disque dur et un lecteur de disquettes. Les programmeurs de l'époque commencent à utiliser le PIC (Programmable Interrupt Controller) au maximum de ses ressources. Les programmes résidents voient le jour. Le programme ne travaille plus seul, il doit cohabiter avec les autres programmes résidents dans la machine. Les concepts de processus et de threads voient le jour et les problèmes de concurrences d'accès aux ressources aussi. Les premiers réseaux d'entreprises voient le jour.

Temps Modernes : La programmation orienté objet

modifier

À l'époque, les industries du logiciel se heurtent de plus en plus à la difficulté de développer des projet de taille toujours croissante. Pire : plus il y a de monde sur le projet, moins le projet devient gérable avec les outils de l'époque, et ce, même pour des projets simples. La programmation orienté objet est un concept récupéré de l'ingénierie du bâtiment qui permet de décrire les différents aspects du logiciel de manière formelle et ainsi maitriser les couts, les délais, les risques et assurer la qualité du logiciel développé.

Pourquoi la programmation orienté objet
modifier

En 1995, le Standish Group édita un rapport édifiant intitulé "CHAOS" sur l'état des échecs de projets informatiques dans plus de 360 sociétés américaines. Ce rapport fait référence et fait état que les entreprises américaines et organismes gouvernementaux perdent des centaines de milliards de dollars par ans, de ne pas appliquer les mêmes règles de conception et de gestion du risque que dans les domaines de génie civil. En effet, ce rapport dénonce une renonciation des cadres techniques à appliquer les mêmes méthodes que leurs homologues du bâtiment, pour la plupart du temps pour des raisons politiques de rond de jambes et, pour les autre rares cas, d'incompétences de capture des besoins ou d'omission de diagnostic techniques.

  • 16% de projets conformes aux prévisions initiales en temps et couts (mais souvent diminués en fonctionnalités),
  • 53% de projets dépassements en coûts et délais d'un facteur 2 à 3,
  • 31% de projets abandonnés.

Depuis, le Standish Group édite des mises à jours annuelle de ces chiffres, et malheureusement les chiffres restent très alarmants et surtout très réels.

En effet le logiciel étant un produit qui n'a de consistance qu'au travers de l'ordinateur qui l'exécute, les ingénieurs de l'époque n'arrivent pas à concevoir qu’il est aussi difficile d'abattre un mur porteur dans un immeuble que de redévelopper un ensemble de méthodes imbriquées les unes aux autres dans du code métier.

Qu'est-ce que la programmation orienté objet
modifier

Le paradigme de la programmation orientée-objet repose entièrement sur la "classe" à laquelle on délègue la gestion des données qui lui sont encapsulées et sont accessibles via l'interface représenté par ses méthodes appelées aussi accesseurs/mutateurs/propriétés. Les concepts d'abstraction, de généralisation, d'héritage, et de polymorphisme sont aussi cruciaux.

De nos jours : La programmation évènementielle

modifier

Grâce aux avancées technologiques en graphisme, l'entreprise Xerox développe une interface graphique pour ses imprimantes qui sera ensuite reprise par Steeve Jobs pour le compte du nouvel ordinateur d'Apple le Maccintosh. Bill Gates, voyant une menace en ce concurrent pour son MS DOS, débauche des ingénieurs d'Apple et crée son Windows 3.1x. La technologie basée sur les Interruptions du PIC permet de recevoir, de propager et de traiter en temps réel les évènements utilisateur. Les jeux informatiques voient le jour, les interfaces graphiques facilitent grandement l'accès à l'ordinateur qui se démocratise. Un enfant est capable d’utiliser un ordinateur. Internet voit le jour et se répand. Les "Design patterns" du GoF et du GRASP rendent possibles des réutilisations de développements en exposant des problèmes récurrents de programmation et leurs solutions-types.

Demain : La programmation orienté agent

modifier

La programmation orienté agent est une évolution de la programmation orienté objet. Au lieux d’utiliser des objets classiques uniquement réactifs, on utilise des objets composites comme les automates et/ou les réseaux neuronaux afin de rendre les objets, du moins en partie, pro-actifs en fonction de leurs états internes, de l'état de leur environnement et des taches qu’ils ont à réaliser. Le but du jeu est de rendre les agents autonomes et indépendants de l'interface de leurs voisins. Ainsi cela permet une plus grande souplesse de dialogue entre les agents. Cela pose toutefois l'éternel problème de la construction des moteurs de tels agents. En effet, plus un comportement logiciel est sophistiqué, plus le développement est difficile, couteux et instable. De nombreux frameworks voient le jour et comme tous les frameworks standards actuels ils sont incompatibles entre eux, un comble pour une architecture qui aspire à une compatibilité massive. Peut-être qu'un jour les interfaces s'uniformiseront mais, en attendant, vu la complexité de la mise en œuvre de ces agents, il me semble (et cela n'engage que moi) prématuré de les utiliser dans des solutions industrielles de grande envergure hormis éventuellement pour le domaine de la robotique et des domaines associés qui font encore partie de la recherche et qui ne sont pas encore entrés dans le grand public.

L'Objet

modifier

Dans le domaine de l'informatique, un objet est quelque-chose de concret au sens mathématique du terme que l’on peut manipuler ou dont on peut récupérer des informations.

  • Un cercle de rayon 8 cm,
  • Un triangle équilatéral de 3 cm de côté ou encore
  • Un cube de 5 cm d'arêtes.

Les moules ou modèles qui ont permis d'obtenir ces figures (respectivement)

  • (pi*(R*R)),
  • (a*a)=(b*b)+(c*c)-(2*B*C*Cos(A)) où A=B=C=60°, et
  • a*a*a

correspondent en C++ à ce que l’on appelle des Classes.

En développement on parle plus souvent d'instance de classe pour désigner la représentation informatisée d'un objet.

Pour faire une analogie avec le moulage, on pourrait dire que l'instance correspond à la figurine de plâtre peinte et vernie, et que la classe correspond au moule en caoutchouc qui lui a donné forme.



Classe

Début de la boite de navigation du chapitre
Version imprimable
 
Chapitre no 14
Leçon : Langage C++
Chap. préc. :Objet
Chap. suiv. :Sommaire
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Version imprimable
Langage C++/Version imprimable
 », n'a pu être restituée correctement ci-dessus.

La classe

modifier

En programmation orientée objet, tout est basé sur le concept de la classe. La classe est une entité autonome capable de maintenir, une fois instanciée (définie), la cohérence des données qu'elle est chargée d'entretenir. (nous mettrons en évidence la syntaxe dans la partie implémentation)


Les visibilités : niveaux de visibilité dans une classe

modifier

Il existe trois niveaux de visibilité dans une classe :

  • Privé (Niveau par défaut) : Permet de masquer complètement les données et méthodes à des classes tierces et même aux classes dérivées. On parle d'encapsulation (peut être utilisé par les méthodes mais est surtout destiné aux variables d'une classe).
  • Protégé : permet de partager des données uniquement aux classes dérivées. (Normalement uniquement utilisé pour les méthodes)
  • Public : permet de partager les données avec toutes les classes tierces. (Normalement uniquement utilisé pour les méthodes)

Les attributs : variable de classes

modifier

La classe est une entité qui peut être composée de variables internes que l’on nomme attributs membres.

La théorie veut que les attributs d'une classe ne soient accessibles directement qu’à cette classe. Bien qu'en C++ il soit possible de définir des attributs membres de visibilité publique ou protégée, il est normalement indispensable de rendre les attributs membres privés (je n'ai jamais eu à rendre publiques ou même protégés des attributs).

En effet le paradigme-objet étant de rendre la classe responsable de la gestion de ses attributs pour assurer la gestion correcte et la cohésion des données, il serait mal vu qu'une autre classe accède malencontreusement à ces attributs et vienne bouleverser l'organisation que la classe permet de maintenir entre eux. Quand une classe privatise un attribut on dit qu'elle l'encapsule.

Les méthodes : méthodes de classes

modifier

Nous avons déjà vu les méthodes auparavant.

Les méthodes déterminent le comportement qu'une classe est capable de réaliser. La classe possède au moins trois méthodes spéciales :

  • Un constructeur sans paramètres appelé aussi constructeur par défaut ou, selon le cas, un constructeur paramétré qui oblige à fournir des paramètres pour créer la classe.
  • Un constructeur par copie qui prend en paramètre une référence sur la même classe (dans la majorité des cas elle est fournie par défaut par le compilateur mais il peut être fréquemment nécessaire de la définir manuellement).
  • Un destructeur qui est chargé d'appliquer un traitement pour éventuellement nettoyer la mémoire. Bien qu’il soit souvent vide, l'implémenter explicitement et systématiquement permet de faire fonctionner le mécanisme d'héritage polymorphe.

Les méthodes donnant accès aux attributs sont appelés suivant le sens d'accès :

  • Des accesseurs pour les méthodes accédant en lecture seule.
  • Des mutateurs pour les méthodes accédant en écriture seule.
  • Des propriétés pour les méthodes accédant en écriture et en lecture.

La théorie veux que tout attribut qui aurait besoin d’être transmis ou modifié, le soit par l'une de ces méthodes d'accès. (Je n'ai jamais eu à faire autrement.) Les méthodes définissent aussi l'interface d'une classe.


Généralisation de classes

modifier

En programmation orientée objet, on peut structurer une hiérarchie de classes en arborescence. Les classes du sommet de l'arbre sont les classes les plus abstraites et générales. Les classes les plus profondes sont les plus concrètes et les plus spécialisés.

Pour illustrer ces propos supposons les figures suivantes :

  • Carré
  • Rectangle
  • Parallélogramme
  • Losange
  • Quadrilatère
  • Cercle
  • Ovale

Imaginons maintenant que nous devions réaliser des classes basées sur ces figures. Supposons que chaque classe doit être capable de dessiner. Supposons aussi que nous devions gérer chacune de ces classes avec le même (et unique) pointeur et appeler la même méthode pour le dessin de chaque classe.

Dans les figures imposées on peut voir que "Carré" est un cas particulier de (ou "une sorte de") "Rectangle" qui est lui-même un cas particulier du "Parallélogramme" qui est lui-même un "Quadrilatère". Quant à "Losange" c’est un "Quadrilatère".

On peut voir aussi que "Cercle" est un cas particulier de "Ovale".

Cependant rien ne lie Ovale et Quadrilatère.

Nous allons donc devoir généraliser Ovale et Quadrilatère en "Figure"

Ainsi Quadrilatère et Ovale sont des Figures, ainsi un pointeur sur Figure est capable de manipuler n’importe quelle classe sous-jacente, puisque, après tout, un carré est une figure tout comme un cercle.

Traitons maintenant le problème de la méthode unique. En fait depuis que l’on a créé la classe Figure ce n'est plus un problème.

En effet il suffit de définir la méthode "Dessine()" dans la classe Figure pour que toutes les autres classes en héritent.

Abstraction de classes

modifier

Lors de généralisations successives il est courant de se rendre compte qu'une classe est trop "abstraite" pour avoir suffisamment de données exploitables pour pouvoir en générer une instance. C'est le cas pour notre classe "Figure". En effet elle est trop générique et rien d'intéressant ne peut en sortir, cependant elle fournit une interface que toutes les autres classes devront reproduire fidèlement. La méthode "Dessiner" ne représente pas grand chose pour une Figure. Nous ne pouvons pas décrire de comportement pour cette méthode dans cette classe. Nous ne pouvons donc pas instancier la classe car cela n'aurait pas de sens. Nous allons donc devoir rendre cette classe abstraite. Cela signifie que la classe n’est pas instanciable en l'état mais qu'une classe héritée non abstraite peut être interprétée comme cette classe (via l'utilisations de pointeurs ou de références).

Héritage de classes

modifier

L'héritage est la faculté qu’à une classe de pouvoir transmettre ses attributs et méthodes à ses classes dérivées et, sous certaines conditions, permettre à ses classes dérivées de redéfinir ses méthodes pour pouvoir les améliorer et les spécialiser.

Tout d’abord il faut savoir qu'en C++ une classe dérivée reçoit toujours une copie de l'intégralité des attributs et des méthodes de sa classe ancêtre. En C++, bien que l’on puisse choisir la façon dont sont copiés les membres de la classe ancêtre, la théorie-objet veux que l’on hérite toujours des classes ancêtres de manière publique afin que tous les membres publics de la classe ancêtre soient aussi disponibles de manière publique dans la classe dérivée. L'héritage permet de ne pas réécrire éternellement les mêmes codes, de réutiliser les objets, de pouvoir les spécialiser et mettre en œuvre le polymorphisme de classe.

Polymorphisme de classes

modifier

Le polymorphisme en informatique se traduit par la capacité qu'a un pointeur de classe ancêtre présentant une interface donnée, à appeler la méthode de l'instance de la classe dérivée correspondant à la méthode de la classe ancêtre. En C++ la mise en œuvre du polymorphisme se fait à l'aide du mot clé "virtual". Dans la pratique et pour reprendre l'exemple vu précédemment :

Si l’on créé un pointeur sur Figure que l’on lui assigne l'adresse de l'instance d'un carré et que l’on demande au pointeur-figure de se dessiner alors le pointeur va appeler la méthode virtuelle et par le biais de l'héritage virtuel appeler la méthode implémentée dans la classe Carré.

Implémentation

modifier

Les classes sont donc la représentation logique du concept d'objet. Voici en C++ comment implémenter ces classes conformément à la théorie de l'objet.


Où <NomNouvelleClasse> est le nom de la classe, <ClasseAncêtre> est une classe ancêtre tout comme <AutreClasseAncêtre> et sont facultatives si la classe n'a pas à avoir d'ancêtre, <TypeAttribut1> et <TypeAttributN> sont les types des attributs, <NomAttribut1> et <NomAttributN> sont les noms des attributs, les attributs sont facultatifs, <TypeParamettre> et <NomParamettre> sont les paramètres des méthodes de la classe, <TypeMethode1> et <TypeMethodeN> sont les types de retours des méthodes de la classe, <NomMethode1> et <NomMethodeN> sont les noms des méthodes de la classe, , <Visibilitée> peut prendre trois valeurs: public, protected ou private (par défaut). Un membre déclaré public peut être manipulé par n’importe quelle classe, un membre protected ne peut être manipulé que par la classe et ses dérivées tandis qu'un membre private ne peut être manipulé que par les méthodes de la classe.

Les méthodes sont soit définies directement dans la déclaration de classe (auquel cas ce sont des Macros), soit définies en dehors de la déclaration dans un fichier source séparé de la manière suivante :


Voici un exemple de la classe la plus simple à réaliser. Il faut dire aussi qu'elle ne fait strictement rien.

Début de l'exemple
Fin de l'exemple


Exemples:

modifier

Maintenant reprenons nos exemples de figures de tout à l’heure.

Début de l'exemple
Fin de l'exemple


  GFDL Vous avez la permission de copier, distribuer et/ou modifier ce document selon les termes de la licence de documentation libre GNU, version 1.2 ou plus récente publiée par la Free Software Foundation ; sans sections inaltérables, sans texte de première page de couverture et sans texte de dernière page de couverture.