Note CPP
Note CPP
Notions de base
par
Olivier Bernard
Thomas Grenier
[email protected]
[email protected]
i
2.8.1 Tableaux statiques . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.8.2 Tableaux et pointeurs . . . . . . . . . . . . . . . . . . . . . . . 26
2.8.3 Tableaux dynamiques . . . . . . . . . . . . . . . . . . . . . . . . 26
2.8.4 Tableaux multidimensionnels . . . . . . . . . . . . . . . . . . . . 28
2.9 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.9.1 Prototype d’une fonction . . . . . . . . . . . . . . . . . . . . . . 29
2.9.2 Définition d’une fonction . . . . . . . . . . . . . . . . . . . . . . 29
2.9.3 Appel de fonction . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.9.4 Passage d’arguments à une fonction . . . . . . . . . . . . . . . . 30
2.9.5 Surcharge de fonction . . . . . . . . . . . . . . . . . . . . . . . . 33
ii
1 Développement en C++
1.1 Rappels sur la compilation
Avant de donner plus de détails sur les notions de base en C++, il est important de
rappeler les différentes étapes intervenant lors de la création d’un exécutable (ou d’une
librairie) à partir de fichiers C++. Figure 1 donne un schéma simplifié de la création
d’un exécutable lors de la compilation en C++.
De façon simplifiée, trois étapes sont réalisées lors de la création d’un exécutable.
1.1.1 Pré-processeur
Le pré-processeur ou pré-compilateur est un utilitaire qui traite le fichier source avant
le compilateur. C’est un manipulateur de chaı̂nes de caractères. Il retire les parties de
commentaires, qui sont comprises entre /* et */. Il prend aussi en compte les lignes
1
du texte source ayant un # en première colonne pour créer le texte que le compilateur
analysera. Ses possibilités sont de trois ordres :
– inclusion de fichiers (mot clé #include). Lors de cette étape, le pré-compilateur
remplace les lignes #include par le fichier indiqué ;
– définition d’alias et de macro-expression (mots clés #define, macro) ;
– sélection de parties de texte (mot clé inline).
A la fin de cette première étape, le fichier .cpp (ou .c) est complet et contient tous les
prototypes des fonctions utilisées.
1.1.2 Compilateur
Cette étape consiste à transformer les fichiers sources en code binaire compréhensible
par l’ordinateur. Le compilateur compile chaque fichier .cpp (ou .c) un à un. Le com-
pilateur génère un fichier .o (ou .obj, selon le compilateur) par fichier .cpp compilé.
Ce sont des fichiers binaires temporaires. Ces fichiers sont supprimés ou gardé selon
les options utilisées pour la compilation. Enfin, il est à noter que ces fichiers peuvent
contenir des références insatisfaites qui seront résolues par l’éditeur de liens.
2
1.2.1 Environnement Visual Studio C++ 6
La figure 2 donne un aperçu de l’interface (bien évidemment, la disposition des outils
peut être différente).
Dans ce qui suit, nous décrivons par la suite les étapes clées afin d’être rapidemment
autonome sur cet IDE. Notons que les démarches et les terminologies pour les autres
IDE sont très proches de celles présentées ci-après.
3
1.2.3 Compilation et exécution
Il existe différentes échelles de compilation :
– compilation du fichier sélectionné. Ceci permet de corriger les erreurs de syntaxe
éventuelles. Cette étape s’effectue de la façon suivante : à partir du menu faire :
Build− >Compile ;
– compilation de l’ensemble du projet. Un projet est généralement constitué par un
ensemble de fichiers (.h, .cpp, .cxx). Cette compilation permet de tester la syntaxe
de l’ensemble des fichiers ainsi que leurs interractions. De plus, avant la compilation,
tous les fichiers sont enregistrés et les modifications récentes sont prises en compte
par le compilateur. Cette étape s’effectue de la façon suivante : à partir du menu
faire : Build− >Build ;
Lors de la compilation, les erreurs de syntaxe détectées sont signalées dans l’onglet
Build de l’outil messages (cf. figure 2). Il est important de noter que l’erreur est
systématiquement décrite et qu’un double clic sur cette description envoie le curseur à
la ligne correspondant à l’erreur détectée dans le fichier concerné. Ceci facilite grande-
ment la résolution d’erreur de syntaxe dans les fichiers de code. La figure 4 donne un
exemple de résultat de compilation avec un message d’erreur.
Une fois l’ensemble du projet compilé (sans erreur de compilation), il est possible
d’exécuter le programme de la façon suivante : à partir du menu faire : Build− >Execute ;
1.2.4 Debugage
Il existe plusieurs mode de compilation. Par défaut, Visual Studio C++ 6 utilise le
mode de compilation debug. Contrairement au mode release, le mode debug permet
d’effectuer le debogage d’un programme. Le debogage d’un projet permet d’interagir
avec le programme pendant son exécution. Cela permet notamment de surveiller les
valeurs des variables, contrôler les passages dans les tests, l’exécution de boucles, l’al-
location des objets, modifier les valeurs de variables ...). Le debugage d’un programme
s’effectue de la façon suivante :
– positionner dans le code un ou plusieurs breakpoint. Cette étape s’effectue de la
façon suivante : à partir de la ligne de code où l’on souhaite insérer un breakpoint
faire : Clic droit− >Insert/remove Breakpoint ;
– exécuter le code en mode debug. Cette étape s’effectue de la façon suivante : à partir
du menu faire : Build− >Start− >Go ou F5.
L’excution du programme sera alors mise en pause juste avant l’exécution de la ligne où
est positioné le breakpoint. Une fois le programme mis en pause, on peut interagir avec
les variables et la progression de l’exécution en utilisant les bouttons de la barre d’outil
Step Over (F10 ) et Step Into (F11 ). La figure 5 donne un exemple de debugage
d’un programme.
4
Figure 3 – fenêtre de dialogue de sélection du type de projet sous Visual Studio C++
6
5
1.2.5 Ajout de classe
Afin d’ajouter une classe à un projet, il suffit d’effectuer l’étape suivante : à partir du
menu faire Insert− >New Class. Deux fichiers sont ainsi créés dans le repertoire
du projet : NouvelleClasse.h et NouvelleClasse.cpp. Ces deux fichiers sont également
ajoutés au projet.
Il est à noter la présence de majuscules/minuscules aux noms de fichiers. Cependant
Windows ne différentie pas la casse pour les noms de fichier contrairement à Linux et
à Mac OS. Ainsi, afin d’être le plus portable possible, il est important de respecter la
casse pour les noms de fichiers. Dans l’exemple précédent, afin de pouvoir utiliser la
nouvelle classe, il faut donc ajouter la ligne suivante :
#include "NouvelleClasse.h"
1.2.6 Remarques
Lors de la première compilation, vous pouvez avoir des erreurs du type : Error cannot
find ... .pch ou unexpected end of file ... precompiled header . Ces erreurs sont liées
aux options de compilation de votre projet. Pour modifier l’option responsable de ces
erreurs, il est nécessaire de changer l’option de précompilation des entêtes. Pour cela, il
suffit de faire à partir du menu : Project− >Settings− >Onglet C/C++ , puis
dans la boite Category choisir Pre compiled headers . Ensuite choisir l’option
not using precompiled headers (cf. tableau 6). Finalement il faut supprimer du projet
#include "stdafx.h"
6
Figure 5 – Exemple de debugage d’un programme sous Visual Studio C++ 6
Figure 6 – Fenêtre d’option d’un projet afin de supprimer des entêtes précompilées
sous Visual Studio C++ 6
7
2 Mémento de syntaxe en C++
2.1 Syntaxe élémentaire
Dans cette section, nous donnons les principales règles d’écriture d’un programme en
C++. En effet, pour qu’un compilateur puisse interpréter un code en C++, il est
necessaire de respecter un certain nombre de règles résumées dans les trois paragraphes
suivants.
2.1.1 Instructions
Toutes les instructions en C++ se terminent par ; . Le saut de ligne entrée ne
permet pas de signaler la fin d’une instruction. De manière générale, le saut de ligne,
les espaces ainsi que la tabulation, peuvent être ajoutés n’importe où, sauf pour :
– l’écriture d’un chiffre (100000 et non 100 000) ;
– le nom des fonctions, opérateurs et variables ;
– les mots clés.
La définition d’un bloc d’instructions (plusieurs instructions) se fait avec des accolades :
{
instruction1;
instruction2;
...;
}
2.1.2 Commentaires
Deux styles de commentaire sont disponibles en C++ :
– // permet de mettre en commentaire tout ce qui est situé après ce caractère et
jusqu’au prochain saut de ligne ;
– /∗ et ∗/ permettent de délimiter un bloc de commentaires (plusieurs lignes en
général).
Voici un exemple d’utilisation de commentaires en C++ :
8
2.1.3 Casse et mots réservés
Le compilateur prend en compte la casse (majuscule ou minuscule) pour
– les noms des fichiers ;
– les noms des variables et des fonctions ;
– les mots clés du langage C++ et les directives du préprocesseur.
Il est à noter que les mots clés et les directives du préprocesseur ne peuvent pas être uti-
lisés par le programmeur pour nommer les variables, les objets et fonctions. Les tableaux
1 et 2 fournissent une liste complète des mots clés et directives du préprocesseur.
asm auto
break
case catch char class const continue
default delete do double
else enum extern
float for friend
goto
if inline int
long
new
operator
private protected public
register return
short signed sizeof static struct switch
template this throw try typedef
unsigned union
virtual void volatile
while
#define
#elif #else #endif #error
#if #ifdef #ifndef #include
#line
#pragma
#undef
9
// Ces 3 directives sont necessaire a tous fichiers ".h", la
// variable "_MaClasse_H_" doit etre differente pour chaque fichier
#ifndef _MaClasse_H_
#define _MaClasse_H_
// le reste du fichier .h
class MaClasse
{
...
};
#endif
10
2.2 Types
Dans cette section, nous donnons les principales notions à connaı̂tre sur les types et la
déclaration de variables.
11
type var1 = 0;
type var1;
var1 = 0;
float a=3.002;
unsigned short b=1,c=2;
double un_mille(1.609e3);
int a,b;
a=4;
b=a; // la valeur de a est copiée dans b: b vaut 4
ou entre des variables de type différent (dans ce cas, attention aux pertes lors de la
conversion) :
float a=3.1415;
char b;
double c;
b = a; // b vaut 3, un warning est généré à la compilation
c = a; // c vaut 3.1415, pas de warning ici car la précisison
// du type double est supérieure à la précision du
// type float (pas de perte de précision)
12
2.3 Opérateurs
Nous abordons dans cette partie les définitions et les différentes propriétés liées aux
opérateurs en C++.
2.3.2 Priorité
Les opérateurs en C++ sont répartis selon 17 niveaux de priorité. La priorité la plus
basse vaut 1 et la priorité la plus haute vaut 17. La règle de priorité des opétateurs
est la suivante : si dans une même instruction sont présents des opérateurs avec des
niveaux de priorité différents, les opérateurs ayant la plus haute priorité sont exécutés
en premier. Par exemple :
13
A op1 B op2 C;
A op1 ( B op2 C );
De manière générale, l’ajout de parenthèse dans les expressions enlève les ambiguı̈tés
de priorité.
2.3.3 Associativité
L’associativité désigne la direction dans laquelle sont exécutés les opérateurs d’un même
niveau de priorité. Par exemple, la multiplication et la division ont les mêmes niveaux
de priorité et leur associativité est de gauche à droite :
int a = 3 * 4 / 2 * 3; // a = 18
int b = 3 * 4 / ( 2 * 3); // b = 2
De manière générale, l’ajout de parenthèse dans les expressions enlève les ambiguı̈tés
d’associativité. Tableau 2.3.3 fournit une liste détaillée des opérateurs en C++ ainsi
que leur niveau de priorité et leur associativité.
14
Prio. Opér. Signification Associativité Exemple
Résolution de portée
17 :: droite à gauche
(unaire)
Résolution de portée
17 :: droite à gauche
(binaire)
16 () Parenthèse de gauche à
droite MaFonction(x,y)
16 () Construction d’objet gauche à
droite MaClass(x,y)
16 [] Indexation de tableau gauche à
droite Tab[i]
16 . Sélection de composant gauche à
droite objet.methode();
16 -> Sélection de composant gauche à
droite this->methode();
conversion de type (double) x;
15 () droite à gauche
explicite double(x);
15 sizeof Taille en octets droite à gauche sizeof(int);
Fournit l’adresse
15 & droite à gauche &a
mémoire
15 * Déférentiation droite à gauche
15 ~ Négation binaire droite à gauche ~a;
15 ! Négation logique droite à gauche !a;
15 + Addition (unaire) droite à gauche +a;
15 - Soustraction (unaire) droite à gauche -a;
15 ++ Incrément droite à gauche ++a;
15 -- Décrément droite à gauche --a;
15 new Allocation mémoire droite à gauche double *t=new;
15 delete Dé-allocation mémoire droite à gauche delete[] t;
14 .* Sélection de composant
Sélection de composant
14 ->* gauche à droite
(pointeur)
13 * Multiplication gauche à droite a*b;
13 / Division gauche à droite a/b;
13 % Modulo gauche à droite a%b;
12 + Addition (binaire) gauche à droite a+b;
12 - Soustraction (binaire) gauche à droite a-b;
15
Prio. Opér. Signification Associativité Exemple
11 >> Décalage des bits à droite gauche à droite a>>2;
11 << Décalage des bits à gauche gauche à droite a<<2;
10 > Test strictement supérieur gauche à droite a>2;
10 >= Test supérieur ou égal gauche à droite a>=2;
10 < Test strictement inférieur gauche à droite a<2;
10 <= Test inférieur ou égal gauche à droite a<=2;
9 == Test d’égalité gauche à droite if(choix==’o’)
9 != Test de non égalité gauche à droite if(pt!=NULL)
8 & ET binaire gauche à droite a=1&3; //1
7 ^ OU exclusif binaire gauche à droite a=1^3; //2
6 | OU binaire gauche à droite a=1|2; //3
5 && ET logique gauche à droite if(a=1&&b<3)
4 || OU logique gauche à droite if(a=1||b<3)
Opérateur de condition
3 ?: gauche à droite
(ternaire)
2 = Affectation simple | droite à gauche a=b=2;
2 += Affectation combinée + droite à gauche a+=2; //a=a+2
2 -= Affectation combinée − droite à gauche a-=2; //a=a-2
2 *= Affectation combinée ∗ droite à gauche a*=2; //a=a*2
2 /= Affectation combinée / droite à gauche a/=2; //a=a/2
2 %= Affectation combinée % droite à gauche a%=2; //a=a%2
2 <<= Affectation combinée << droite à gauche a<<=2; //a*=4
2 >>= Affectation combinée >> droite à gauche a>>=1; //a/=2
2 &= Affectation combinée & droite à gauche a&=1;
2 ^= Affectation combinée ^ droite à gauche a^=0xFF;
2 |= Affectation combinée | droite à gauche a|=0xFE;
1 , Séquence d’expressions gauche à droite int a,b,c;
16
2.4 Structures conditionnelles
Les conditions servent à comparer (ou évaluer) des variables ou des valeurs retournées
par des fonctions.
2.4.1 if / else
La syntaxe d’une condition if est la suivante (les crochets signalent des blocs non
nécessaires) :
La variable test est forcément de type booléen (bool). Après l’instruction if, les ac-
colades sont nécessaires si plusieurs instructions sont déclarées. Le bloc else n’est pas
obligatoire. Il est à noter que la syntaxe if(a) est équivalent à if( a!=0 ). Voici un
exemple d’utilisation de structure if en C++ :
bool a,b;
if( a==true ) // il s’agit bien de 2 signes "=="
{
a = false;
// std::cout permet d’afficher à l’ecran un message
std::cout << "Initialisation de la variable à false";
}
double moyenne;
if ( moyenne >= 16.0 )
std::cout << "Mention très bien";
else
if ( moyenne >= 14.0 )
std::cout << "Mention bien";
else
if ( moyenne >= 12.0 )
std::cout << "Mention assez bien";
else
if ( moyenne >= 10.0 )
17
std::cout << "Mention passable";
else
std::cout << "Non admis...";
switch ( variable )
{
case constante_1: // faire attention au ":"
[ instructions réalisées si variable == constante_1 ]
[ break ; ]
case constante_2:
[ instructions réalisés si variable == constante_2 ]
[ break ; ]
...
[ default:
instructions réalisées si aucun des
cas précédents n’a pas été réalisé]
}
Les blocs d’instructions n’ont pas besoin d’accolade. Généralement chaque bloc se ter-
mine par l’instruction break; qui saute à la fin de la structure switch (l’accolade
fermante). Si un bloc case ne se termine pas par une instruction break;, le compila-
teur passera au bloc case suivant. Voici un exemple d’utilisation de structure switch
case en C++ :
char choix;
// cout permet d’afficher à l’ecran un message
cout << "Etes vous d’accord ? O/N";
// cin permet de récupérer une valeur saisie au clavier
cin >> choix;
switch( choix )
{
case ’o’:
case ’0’:
cout << "Vous etes d’accord";
18
break;
case ’n’:
case ’N’:
cout << "Vous n’etes pas d’accord";
break;
default:
cout << "Répondre par o/n ou O/N";
}
2.5.1 for
La syntaxe d’une boucle for en C++ est la suivante :
19
{ //exemple plus compliqué...
titi = i + j;
cout << i << endl; // Afficher i à l’écran
cout << toto << endl; // Afficher toto à l’écran
}
2.5.2 while
La syntaxe d’une boucle while est la suivante :
while ( [condition] )
{
// Instructions de boucle répétées tant que la condition est vraie
}
Il est à noter que le test du bloc [condition] se fait en début de boucle. Voici deux
exemples d’utilisation d’une boucle while en C++ :
int i=0, Taille=3, j;
// Résultat identique à la boucle \verb$for$ ci-dessus
while ( i < Taille )
{
cout << i << endl; // Afficher i à l’écran
i++;
}
//--------------------------------
// Multi conditions
while ( ( i< Taille ) && ( j != 0 ) )
{
cout << "Ok" << endl;
j = 2 * i - i * ( Taille - i );
i++;
}
2.5.3 do / while
Contrairement aux boucles for et while, la boucle do while effectue les instructions de
boucle avant d’évaluer l’expression d’arrêt (le bloc condition). La syntaxe d’une boucle
do while est la suivante :
do
{
// Instructions de boucle répétées tant que la condition est vrai
}
while ( [condition] )
20
Voici un exemple d’utilisation d’une boucle do while en C++ :
char choix;
do
{
cout << "Quel est votre choix: (q pour quitter) ?";
cin >> choix;
(...)
}
while ( choix != ’q’ );
#include <iostream>
...
using namespace std; // pour ne pas écrire "<std::">
// devant chaque fonction/classe de la std
#include <iostream.h>
// pas besoin de using namespace
21
<< longeur
<< " metres
<< endl;
// Affichage en bases différentes
cout << dec << 16 << endl; // base décimale
cout << hex << 16 << endl; // base hexadécimale
cout << oct << 16 << endl; // base octale
cout << 24; // la dernière base utilisée reste active !!!
16
10
20
30
#include <iostream.h>
int main()
{
int age;
cout << "Entrer votre age ";
cin >> age;
cout << "Vous avez " << age << " ans" << endl;
return 0;
}
22
2.7 Pointeurs / Références
2.7.1 Pointeurs
En langage C et C++, chaque variable est stockée à une (et unique) adresse physique.
Un pointeur est une variable contenant l’adresse d’une autre variable. Un pointeur
est typé : il pointe vers des variables d’un certain type et ainsi permet de manipuler
correctement ces variables. Avant d’être utilisé, un pointeur doit obligatoirement être
initialisé à l’adresse d’une variable ou d’un espace mémoire. Il est possible d’obtenir
l’adresse d’une variable à partir du caractère & :
int a;
cout << &a; // affichage de l’adresse de a
type * Nom_du_pointeur;
Le type de variable pointée peut être aussi bien un type primaire (tel que int, char...)
qu’un type complexe (tel que struct ou une classe). La taille des pointeurs, quelque soit
le type pointé, est toujours la même. Elle est de 4 octets avec un OS 2 32 bits (et 8 en
64 bits). Un pointeur est typé. Il est toutefois possible de définir un pointeur sur void,
c’est-à-dire sur quelque chose qui n’a pas de type prédéfini (void * toto). Ce genre
de pointeur sert généralement de pointeur de transition, dans une fonction générique,
avant un transtypage qui permetra d’accéder aux données pointées. Le polymorphisme
(cf. cours) proposé en programmation orientée objet est souvent une alternative au
pointeur void *.
Pour initialiser un pointeur, il faut utiliser l’opérateur d’affectation = suivi de
l’opérateur d’adresse & auquel est accollé un nom de variable :
int a=2;
int *p1;
p1 = &a; // initialisation du pointeur!
2. Système d’Exploitation
23
Après (et seulement après) avoir déclaré et initialisé un pointeur, il est possible d’accéder
au contenu de l’adresse mémoire pointée par le pointeur grâce à l’opérateur *. La syntaxe
est la suivante :
int a=2;
int *p1;
p1=&a;
cout << *p1; // Affiche le contenu pointé par p1, c’est à dire 2
*p1=4; // Affecte au contenu pointé par p1 la valeur 4
// A la fin des ces instructions, a=4;
2.7.2 Références
Par rapport au C, le C++ introduit un nouveau concept : les références. Une référence
permet de faire référence à des variables. Le concept de référence a été introduit en
C++ pour faciliter le passage de paramètre à une fonction, on parle alors de passage par
référence. La déclaration d’une référence se fait simplement en intercalant le caractère
&, entre le type de la variable et son nom :
int A = 2;
int &refA = A; //initialisatoin obligatoire!
refA++; // maintenant A=3
24
Les références permettent d’alléger la syntaxe du langage C++ vis à vis des poin-
teurs. Voici une comparaison de 2 codes équivalents, utilisant soit des pointeurs soit des
références.
2.8 Tableaux
Un tableau est une variable composée de données de même type, stockées de manière
contiguë en mémoire (les unes à la suite des autres). Un tableau est donc une suite de
cases (espaces mémoires) de même taille. La taille de chacune des cases est conditionnée
par le type de donnée que le tableau contient. Les éléments du tableau peuvent être :
– des données de type simple : int, char, float, long, double... ;
– des pointeurs, des tableaux, des structures et des classes
Type variable_tableau[nb_éléments];
Type variable_tableau[] = {élément1, élément2, élément3...};
float notes[10];
int premier[] = {4,5,6}; // le nombre d’éléments est 3
25
Les tableaux statiques sont alloués et détruits de façon automatique. Les éléments d’un
tableau sont indicés à partir de 0. La lecture ou l’écriture du ième élément d’un tableau
se fait de la façon suivante :
A=variable_tableau[i]; // lecture du ième élément
variable_tableau[i]=A; // écriture du ième élément
Remarque : La norme ISO du C++ interdit l’usage des variable-size array.
26
– Allocation puis affectation
variable_tableau = new type[nb_élément];
//ou encore
type *variable_tableau = new type[nb_élément];
/* Allocation dynamique en C */
{ /* Allocation dynamique en C++ */
int Nb = 4; {
int *Array = NULL; int Nb = 4;
// Allocation de la mémoire int *Array = NULL;
Array = malloc(sizeof(int)*Nb); // Allocation de la mémoire
// libération de la mémoire Array = new int[Nb];
free(Array); // libération de la mémoire
// ou plus simple : delete[] Array;
// variable-size array }
}
se simplifie :
delete variable_tableau;
27
2.8.4 Tableaux multidimensionnels
Les tableaux multidimensionnels sont des tableaux de tableaux. Par exemple, en di-
mension 3, un tableau statique sera déclaré de la façon suivante :
type **matrice;
matrice = new type*[L];
for( int i=0; i<L; i++ )
matrice[i] = new type[C];
28
2.9 Fonctions
Une fonction est un sous-programme qui permet d’effectuer un ensemble d’instructions
par simple appel à cette fonction. Les fonctions permettent d’exécuter dans plusieurs
parties du programme une série d’instructions, ce qui permet de simplifier le code
et d’obtenir une taille de code minimale. Il est possible de passer des variables aux
fonctions. Une fonction peut aussi retourner une valeur (au contraire des procédures,
terminologie oblige...). Une fonction peut faire appel à une autre fonction, qui fait elle
meme appel à une autre, etc. Une fonction peut aussi faire appel à elle-même, on parle
alors de fonction récursive (il ne faut pas oublier de mettre une condition de sortie au
risque sinon de ne pas pouvoir arrêter le programme).
29
– type_retour représente le type de valeur que la fonction peut retourner (char, int,
float...), il est obligatoire même si la fonction ne renvoie rien 3 ;
– si la fonction ne renvoie aucune valeur, alors type_retour est void ;
– s’il n’y a pas d’arguments, les parenthèses doivent rester présentes.
De plus, le nom de la fonction suit les mêmes règles que les noms de variables :
– le nom doit commencer par une lettre ;
– un nom de fonction peut comporter des lettres, des chiffres et le caractère _ (les
espaces ne sont pas autorisés ) ;
– le nom de la fonction, comme celui des variables est sensible à la casse (différenciation
entre les minuscules et majuscules)
– Définition :
30
Voici un exemple d’utilisation de telles fonctions :
– Définition :
31
Voici un exemple d’utilisation :
– Définition :
32
{
// instructions de la fonction
// les variables passées par référence s’utilisent
// comme les autres variables
return variable_de_type_retour;
}
33
3 Programmation Orientée Objet en C++
Le langage C est un langage procédural, c’est-à-dire un langage permettant de définir des
données grâce à des variables, et des traitements grâce aux fonctions. L’apport principal
du langage C++ par rapport au langage C est l’intégration du concept d’objet, afin
d’en faire un langage orienté objet.
34
correspondant aux ajouts du C++) d’une même classe Point permettant de représenter
et de manipuler (addition) des points du plan.
Point Point
- X :double - X :double
- Y :double - Y :double
+ GetX() :double + GetX() :double
+ GetY() :double + GetY() :double
+ SetX(x :double) :void + SetX(x :double) :void
+ SetY(y :double) :void + SetY(y :double) :void
+ Add(pt : Point) :Point + Add(pt : Point) :Point
+ Afficher() :void + Afficher() :void
<<create>> + Point(pt :Point)
<<create>> + Point()
+ operator=(pt :Point) :Point
<<destroy>> + Point()
class Point
{
private:
double X;
double Y;
public:
Point Add(const Point &pt);
void Afficher();
double GetX();
void SetX(const double &x);
double GetY();
void SetY(const double &y);
};
35
3.2.1 Définition de classe
Avant de manipuler un objet, il faut définir la classe dont il provient, c’est-à-dire décrire
de quoi est composé la classe : fonctions et données (cf. paradigme d’encapsulation
du cours). La définition d’une classe s’effectue dans un fichier d’entête (header) dont
l’extension commence généralement par un h (.h, .hxx). Le nom de ce fichier est
souvent le même que celui de la classe qu’il défini. Cette définition en C++ se fait de
la manière suivante (les crochets signalent des blocs non nécessaires) :
class NomClasse [ : type_héritage NomClasseHéritée]
{
[ public:
// définition des champs et méthodes publics
]
[ NomClasse(); ] // constructeur
[ ~NomClasse(); ] // Destructeur
[ protected:
// définition des champs et méthodes protégés
]
[ private:
// définition des champs et méthodes privées
]
};
Le mot clé type_héritage permet de définir un type d’héritage. Il existe trois types
d’héritage possibles : public, protected ou private (cf. paragraphe 3.4). Le point
virgule situé à la fin du bloc de définition de la classe est obligatoire.
36
– protected : l’accès aux variables et fonctions est limité aux fonctions membres de la
classe et aux fonctions des classes filles,
– private : Restriction d’accès le plus élevé. L’accès aux données et fonctions membres
est limité aux méthodes de la classe elle-même.
class Exemple
{
double X; // par défaut un attribut est de champ private
double Y;
protected:
char *Txt;
public:
double Val;
private:
Exemple *A; // champs privé, pointeur sur Exemple
};
37
noté ::. A gauche de l’opérateur de portée figure le nom de la classe, à sa droite le nom
de la fonction. Cet opérateur sert à lever toute ambiguı̈té par exemple si deux classes
ont des fonctions membres portant le même nom. Voici un exemple de déclaration d’une
classe Valeur avec attribut et méthodes :
// fichier Valeur.cpp
//fichier Valeur.h
/* Définition des méthodes à
/* Déclaration des méthodes
l’extèrieur de la classe */
à l’intérieur de la classe */
void Valeur::SetX(double a)
class Valeur
{
{
X = a;
private:
}
double X;
public:
double Valeur::GetX()
void SetX(double);
{
double GetX();
return X;
};
}
3.2.5 Objets
En C++, il existe deux façons de créer des objets, c’est-à-dire d’instancier une classe :
– de façon statique,
– de façon dynamque.
Création statique
La création statique d’objets consiste à créer un objet en lui affectant un nom, de la
même façon qu’une variable :
NomClasse NomObjet;
Ainsi l’objet est accessible à partir de son nom. L’accès à partir d’un objet à un
attribut ou une méthode de la classe instantiée se fait grâce à l’opérateur . :
38
// Fichier Valeur.h
#include "Valeur.h"
class Valeur
#include <iostream.h>
{
private:
int main()
double X;
{
public:
Valeur a;
void SetX(double a)
a.SetX(3);
{ X = a; }
cout << "a.X=" << a.GetX();
double GetX()
return 0;
{ return X; }
}
};
Création dynamique
La création d’objet dynamique se fait de la façon suivante :
– définition d’un pointeur du type de la classe à pointer ;
– création de l’objet dynamique grâce au mot clé new, renvoyant l’adresse de l’objet
nouvellement créé ;
– affecter cette adresse au pointeur.
Voici la syntaxe à utiliser pour la création d’un objet dynamique en C++ :
NomClasse *NomObjet;
NomObjet = new NomClasse;
ou directement
Tout objet créé dynamiquement devra impérativement être détruit à la fin de son
utilisation grâce au mot clé delete. Dans le cas contraire, une partie de la mémoire
ne sera pas libérée à la fin de l’exécution du programme. L’accès à partir d’un objet
créé dynamquement à un attribut ou une méthode de la classe instantiée se fait grâce
à l’opérateur -> (il remplace (*pointeur).Methode() ) :
// Fichier Valeur.h
class Valeur #include "Valeur.h"
{
private: int main()
double X; {
public: Valeur *a = new Valeur;
void SetX(double a) a->SetX(3); // ou (*a).SetX(3);
{ X = a; } delete a;
double GetX() return 0;
{ return X; } }
};
39
3.2.6 Surcharge de méthodes
Toute méthode peut être surchargée 4 . On parle de surcharge de méthode lorsque l’on
déclare au sein d’une classe une méthode ayant le même nom qu’une autre méthode. La
différence entre les deux méthodes (ou plus) réside dans les arguments de ces méthodes :
chaque fonction doit avoir une particularité dans ces arguments (type, nombre ou pro-
tection constante) qui permettra au compilateur de choisir la méthode à exécuter. Le
type de retour d’une fonction ne constitue pas une différence suffisante pour que le
compilateur fasse la différence entre plusieurs fonctions. Voici un exemple de surcharge
de méthode :
class Point
{
private:
double X;
double Y;
public:
void SetX(int); // définition d’une fonction
void SetX(int,int); // bonne surcharge
void SetX(double); // bonne surcharge
double SetX(double); // mauvaise surcharge
void Set(Point &a); // bonne surcharge
void Set(const Point &a); // bonne surcharge
};
40
class Point
{
private:
double X;
double Y;
public:
Point(double a=0,double b=0); // valeurs par défaut {0,0}
};
Point::Point(double a, double b)
{
X = a;
Y = b;
}
Comme pour n’importe quelle fonction membre, il est possible de surcharger les construc-
teurs, c’est-à-dire définir plusieurs constructeurs avec un nombre/type d’arguments
différents. Ainsi, il sera possible d’initialiser différemment un même objet, selon la
méthode de construction utilisée.
41
class Valeur #include "Valeur.h"
{ #include <iostream.h>
private:
double X; int main()
public: {
// constructeur // constructeur
Valeur(double a); Valeur v1(2.3);
{ X = a; } // constructeur par copie
// constructeur de copie Valeur v2(v1);
Valeur(Point &pt) // constructeur par copie
{ X = pt.X; } Valeur v3 = v2;
}; return 0;
}
3.3.3 Destructeurs
Le destructeur est une fonction membre qui s’exécute automatiquement lors de la des-
truction d’un objet (pas besoin d’un appel explicite à cette fonction pour détruire un
objet). Cette méthode permet d’éxécuter des instructions nécessaire à la destruction de
l’objet. Cette méthode possède les propriétés suivantes :
– un destructeur porte le même nom que la classe dans laquelle il est défini et est
précédé d’un tilde ~ ;
– un destructeur n’a pas de type de retour (même pas void) ;
– un destructeur n’a pas d’argument ;
– la définition d’un destructeur n’est pas obligatoire lorsque celui-ci n’est pas nécessaire.
Le destructeur, comme dans le cas du constructeur, est appelé différemment selon que
l’objet auquel il appartient a été créé de façon statique ou dynamique.
– le destructeur d’un objet créé de façon statique est appelé de façon implicite dès que
le programme quitte la portée dans lequel l’objet existe ;
– le destructeur d’un objet créé de façon dynamique sera appelée via le mot clé delete.
Il est à noter qu’un destructeur ne peut être surchargé, ni avoir de valeur par défaut pour
ses arguments, puisqu’il n’en a tout simplement pas. Voici un exemple de destructeur :
class Point
{
private:
double X;
double Y;
double *tab; // champs dynamique !!!
public:
Point(double a=0,double b=0); // constructeur
~Point(); // destructeur
42
};
Point::Point(double a, double b)
{
X = a;
Y = b;
tab = new double[2];
}
Point::~Point()
{
delete[] tab;
}
3.3.4 Opérateurs
Par convention, un opérateur op reliant deux opérandes a et b est vu comme une
fonction prenant en argument un objet de type a et un objet de type b. Ainsi, l’écriture
a op b est traduite en langage C++ par un appel op(a,b). Par convention également,
un opérateur renvoie un objet du type de ses opérandes. Cela permet pour de nombreux
opérateurs de pouvoir intervenir dans une chaı̂ne d’opérations (par ex. a=b+c+d).
La fonction C++ correspondant à un opérateur est réprésentée par un nom composé
du mot operator suivi de l’opérateur (ex : + s’appelle en réalité operator+()).
Un opérateur peut être définit en langage C++ comme une fonction membre, ou comme
une fonction amie indépendante. Ici, nous nous limiterons au cas des fonctions membres.
Tableau (6) donne une liste de l’ensemble des opérateurs qui peuvent être surchargés
en C++ :
43
+ - * / % ^ &
| ~ ! = < > +=
-= *= /= %= ^= &= |=
<< >> >>= <<= == != <=
>= && || ++ -- ->*, ,
-> [] () new delete type()
44
Voici un exemple de surcharge de l’opérateur + :
class Valeur
{ #include "Valeur.h"
private:
double X; int main()
public: {
Valeur(double a=0) { X=a; } Valeur a(2);
Valeur operator+( Valeur b(3);
const Valeur &pt) // surcharge de l’opérateur
{ Valeur c;
Valeur result; c = a + b;
result.X = X + pt.X; return 0;
return result; }
}
};
45
3.4 Héritage
L’héritage est un principe propre à la programmation orientée objet, permettant de
créer une nouvelle classe à partir d’une classe existante. Le nom d’héritage (pouvant
parfois être appelé dérivation de classe) provient du fait que la classe dérivée (la classe
nouvellement créée) contient les attributs et les méthodes de sa classe mère (la classe
dont elle dérive). L’intérêt majeur de l’héritage est de pouvoir définir de nouveaux
attributs et de nouvelles méthodes pour la classe dérivée, qui viennent s’ajouter à ceux
et celles héritées.
Par ce moyen, une hiérarchie de classes de plus en plus spécialisées est créée. Cela a
comme avantage majeur de ne pas avoir à repartir de zéro lorsque l’on veut spécialiser
une classe existante. De cette manière il est possible de récupérer des librairies de classes,
qui constituent une base, pouvant être spécialisées à loisir.
Héritage public
L’héritage public se fait en C++ à partir du mot clé public. C’est la forme la plus
courante de dérivation. Les principales propriétés liées à ce type de dérivation sont les
suivantes :
– les membres (attributs et méthodes) publics de la classe de base sont accessibles par
tout le monde , c’est à dire à la fois aux fonctions membres de la classe dérivée et
46
d’une nouvelle classe à partir de la classe dérivée.
Héritage privé
L’héritage privé se fait en C++ à partir du mot clé private. Les principales propriétés
liées à ce type de dérivation sont les suivantes :
– les membres (attributs et méthodes) publics de la classe de base sont inaccessibles à
la fois aux fonctions membres de la classe dérivée et aux utilisateurs de cette classe
dérivée ;
– les membres protégés de la classe de base restent accessibles aux fonctions membres
de la classe dérivée mais pas aux utilisateurs de cette classe dérivée. Cependant, ils
seront considéré comme privés lors de dérivation ultérieures.
Il est cependant possible dans une dérivation privée de laisser public un membre de la
classe de base en le redéclarant explicitement public.
Héritage protégé
L’héritage protégé se fait en C++ à partir du mot clé protected. Cette dérivation est
intermédiaire entre la dérivation publique et la dérivation privée. Ce type de dérivation
possède la propriété suivante :
– les membres (attributs et méthodes) publics de la classe de base seront considérés
comme protégés lors de dérivation ultérieures.
Récapitulation
Le tableau 7 récapitule toutes les propriétés liées aux différents types de dérivation.
47
Voici un exemple d’utilisation d’un héritage public :
//fichier Point.h //fichier PointColor.h
#include "PointColor.h"
#include <iostream.h>
int main()
{
PointColor pt; // Objet instantié
return 0;
}
48
3.4.2 Appel des constructeurs et des destructeurs
Pour créer un objet de type B, il faut tout d’abord créer un objet de type A, donc faire
appel au constructeur de A, puis le compléter par ce qui est spécifique à B en faisant
appel au constructeur de B. Ce mécanisme est pris en charge par le C++ : il n’y aura
pas à prévoir dans le constructeur de B l’appel au constructeur de A.
La même démarche s’applique aux destructeurs : lors de la destruction d’un objet de
type B, il y aura automatiquement appel du destructeur de B, puis appel de celui de
A. Il est important de noter que les destructeurs sont appelés dans l’ordre inverse de
l’appel des constructeurs.
49