1. PRESENTATION GENERALE ... 3 1.1 Historique: ... 3 1.2 Caractéristiques : ... 3 1.3 Objectifs atteints : ... 3 1.4 Remarque ... 3 1.5 Bibliographie ... 4
2. LES EXTENSIONS DE C (NON OBJETS) ... 5
2.1 Les commentaires ... 5
2.2 Conversions explicites ... 5
2.3 Utilisation des types enum, struct et union ... 5
2.4 Déclaration des variables ... 5
2.5 Fonction inline ... 6
2.6 Paramètres optionnels avec valeur par défaut ... 6
2.7 Surcharge des noms de fonctions ... 6
2.8 Opérateurs (Objets) de sortie et d’entrée: << et >> ... 7
2.9 Opérateurs : new et delete ... 7
2.10 fonction de gestion d’erreur d’allocation... 8
2.11 Adressage par réference & ... 8
2.12 Les fonctions génériques (template) ... 9
3. INCOMPATIBILITES ENTRE C ET C++ ... 10
3.1 Entête de fonctions ... 10
3.2 Prototypes de fonctions ... 10
3.3 fonction sans argument ... 10
3.4 fonction sans valeur de retour ... 10
3.5 le type void * ... 10
3.6 Symbole de type const. ... 10
1. Présentation générale
1.1
Historique:
C++ a été développé vers 1980 par Bjarne Stroustup (Bell Labs, AT & T )
Améliorer le langage C
1.2
Caractéristiques :
Le langage C++ se présente presque comme un sur-ensemble du langage C ANSI.
Quelques incompatibilités entre C et C++.
Le langage C++ contient des (améliorations) caractéristiques nouvelles par rapport au C qui ne sont pas spécifiquement objet.
Le langage C++ offre la possibilité de programmation par objets.
Rien n’impose au programmeur de programmer en utilisant les concepts de la POO.
1.3
Objectifs atteints :
C++ conserve les aspects positifs de C:
Portabilité, Concision, Efficacité, Bas niveau de langage
Corrige les mauvais côtés de C : trop grande permissivité.
Permettre la programmation par OBJETS
1.4
Remarque
1.5
Bibliographie
Delannoy Claude Programmer en langage C++ (248 F.) Ed. Eyrolles
Delannoy Claude Apprendre le C++ sous Turbo/Borland C++ (250 F.) Ed. Eyrolles
Delannoy Claude Exercices en langage C++. Programmation orientée objets Ed. Eyrolles
Leblanc Gérard Turbo/Borland C++ (236 F.) (technique BIOS .... ) Ed. Eyrolles
O'Reilley Cd, Oualline Steve La programmation C++ par la pratique (255 F.) (exemples) Meyer Jean Jacques Borland C++ TurboC++ 3.0/3.1 pour Windows Ed. Dunod Tech Weiskamp, Heiney K. L., Flamig B. Object Oriented Programming with Turbo C++
Ed. Wiley
Borland C++ 3.0 Guide du programmeur Ed Borland
Petzold Charles Programmer sous Windows 3.1 Ed. Microsoft Press.
B Stroustup. Le langage C++ 2ième édition Ed. Addison-Wesley
Lipman S.B L’essentiel du C++ 2ième édition Ed. Addison-Wesley
Meyer B. Conception et programmation par objets pour du logiciel de qualité
Ed. InterEditions
Charbonnel Jacquelin Le langage C, les finesses d'un langage redoutable
Charbonnel Jacquelin Langage C++, les spécifications du standard ANSI/ISO expliquées (260 F.)
InterEditions (2ième édition).
Clavel G., Mirouze N., Pichon E., Soukal M. Java la synthèse Ed. InterEditions
2. Les extensions de C (non objets)
2.1
Les commentaires
C C ++
/* commentaire en C */ // commentaire en C++ et C ANSI
/* com. toujours acceptable en C++ */
Un programme est beaucoup plus souvent lu qu’il n’est écrit
2.2
Conversions explicites
Nouvelle syntaxe : (le cast est toujours valable)
C C ++ int i = 1; float x = 3.2; i = (int) x; x = (float) i; int i = 1; float x = 3.2; i = int(x); x = float(i);
Point p = Point(1.0, 2.0) ; (initialisation par
l'appel d'une fonction constructure)
i = (int) x; // toujours valable x = (float) i;
2.3
Utilisation des types enum, struct et union
Le nom d’une énumération , d’une structure ou d’une union est un type.
C C ++ enum E { ... }; struct S { ... }; union U { ... }; enum E e; struct S s, *ptr; Union U u;
Ptr = malloc( sizeof( struct S) );
enum E { ... }; struct S { ... }; union U { ... }; E e;
S s, *ptr;
struct S s1; // toujours valable U u;
Ptr = malloc( sizeof( S ) );
C C ++
typedef enum { False, True } BOOL; BOOL ouvert ;
enum BOOL { False, True }; BOOL ouvert;
2.4
Déclaration des variables
On peut déclarer des variables en tout endroit dans une bloc et non plus uniquement avant la première instruction du bloc.
{
int i; // déclaration de variables i = 2; // instruction
int j; // autre déclaration de variable j = i;
...
for( int k = 0; k < 5; k++ ) { ...
}
// valeur de k ici ? (dépend des compilateurs !) }
2.5
Fonction inline
Fonction expansée (développée) à chaque appel
inline int carre(int x) { return x*x; } int j = carre( 2 ); // j vaut 4
Ressemble à une macro C mais faire très attention dans la macro (effet de bord) !
C C ++
#define ABS(x) (x)>0 ? (x) : (-x)
int i, k = -1;
i = ABS( k ) ; // i vaut 1;
inline int abs( int x ) { return x>0 ? x : -x ;} int i, k = -1;
I = abs( k ) ; // i vaut 1;
En C on utilise des macros expansés par le préprocesseur.
Inconvénient : parfois difficile à écrire, pas de contrôle de type, mise au point difficile.
2.6
Paramètres optionnels avec valeur par défaut
void f ( float, int = 2, char * = " ") ; int g ( int *ptr = null , char ); // illégal
f ( 1.23, 10, " bonjour "); // appel classique f ( 1.23, 10 ); // => f ( 1.23, 10, " "); f ( 1.23 ); // => f ( 1.23, 2, " ");
f ( ) // illégal
f ( 1.23, , " bonjour "); // => illégal f ( 1.23, " bonjour " ); // => illégal
La définition des valeurs par défaut peut se faire soit dans le prototype de la fonction, soit dans l’entête de sa définition, mais pas dans les 2 à la fois. Mettre de préférence dans le prototype.
Les valeurs par défaut sont placées à partir de la fin de la liste des paramètres.
2.7
Surcharge des noms de fonctions
Surcharge ou encore surdéfinition : plusieurs fonctions ont le même nom.
int max(int, int );
int max( const int * list );
// mais erreur car même signature et valeur retournée différente char * cherche( char *);
int cherche( char * );
2.8
Opérateurs (Objets) de sortie et d’entrée: << et >>
Ces opérateurs sont en réalité des opérateurs relevant des possibilités OBJET du langage.
Ils sont introduits ici pour pouvoir les utiliser tout de suite sans attendre tous les développements nécessaires (et longs) à leur explication.
#include <stdio.h> #include <conio.h>
#include <iostream.h> // pour pouvoir utiliser les flots cin et cout int main()
{
int n; float x;
// méthode traditionnelle avec printf et scanf printf( "\n\nentrez un entier et un flotant : ");
scanf( "%d %f", &n, &x );
printf( " le produit de %5d par %10.2f est : %10.2f\n", n, x, n * x ); // méthode C++ avec les flots cin et cout
cout << "\n\nentrez un entier et un flotant : " ; cin >> n >> x ;
cout << " le produit de " << n << " par " << x << " est " << n * x ; return 0;
}
<< est appelé output on insère dans le flot de sortie (opérateur d'insertion ou injection) >> est appelé input on extrait du flot d'entrée (opérateur d'extraction)
(comme un entonnoir !)
<<endl; // équivaux à /n mais plus performant en objet 4 flots prédéfinis :
cin (équivalent stdin en C) cout (équivalent stdout en C) cerr (équivalent stderr en C) clog
2.9
Opérateurs : new et delete
Utiliser les opérateurs new et delete de préférence à malloc et free. syntaxe :
pointeur = new type ; delete pointeur ;
C C ++ int *ptr, *tab;
ptr = (int *)malloc( sizeof( int ) ); tab = (int *)malloc( n * sizeof( int ) ); ...
free( tab ); free( ptr );
int *ptr, *tab; ptr = new int; tab = new int[ n ]; ...
delete ptr;
delete [ ] tab ; // ou delete tab;
2.10
fonction de gestion d’erreur d’allocation
On peut définir une fonction qui sera appelée automatiquement si l’opérateur new échoue.
typedef void (*HANDLER)() ;
HANDLER HandlerPrecedent = set_new_handler( memoireSaturee ); void memoireSaturee()
{
cout << " memoire saturee " ; exit( 1 );
} void f( ) {
HandlerPrecedent = set_new_handler( memoireSaturee ); ....
set_new_handler(HandlerPrecedent); // restauration }
2.11 Adressage par réference &
int val = 10
int *pval = &val // initialisation non obligatoire
*pval = 5 ; *pval += 2;
int val = 10;
int &refVal = val;// pas int &refVal = &val;
// initialisation obligatoire
refVal = 5; // maintenant val vaut 5 refVal += 2; // maintenant val vaut 7 // identique à val += 2 ;
Nouveau type.
Si T est un type, T& est le type référence vers T
Toutes les manipulations de la référence s’effectuent sur l’objet référencé.
Une référence doit obligatoirement être initialisée lors de sa déclaration.
Une référence est un alias (un autre nom) pour la variable référencée.
On ne peut pas définir un pointeur sur une référence.
5 100 7 100 val 100 refVal val refVal
refVal += 2;
15 104 7 104 j 106 102 104 refj j refjrefj = refVal;
2.11.1 Utilisation dans les passages de paramètres
Equivalent au VAR du langage PASCAL
Syntaxe beaucoup plus lisible et naturelle.
C C ++
void echange( int *x, int *y) { int temp;
temp = *x; *x = *y; *y = temp; }
void main() { nt a = 1, b = 2; change( &a, &b ); }
void echange( int &x, int &y) { int temp; temp = x; x = y; y = temp; } void main() { int a = 1, b = 2; echange( a, b ); }
2.11.2 Retour d’une fonction par reference
C C ++
int x, y ...
int *f( int b ) { return b ? &x : &y; } ....
... Possible mais trop complexe, à éviter !
int x, y ...
int &f( int b ) { return b ? x : y ; } ....
int i = f( 1 ); // range x dans i f ( 0 ) = 5; // range 5 dans y
on peut utiliser ainsi une fonction dans la partie gauche d’une expression
2.12 Les fonctions génériques (template)
création d'un modèle (d'un moule, d'un exemple) de fonction. Le compilateur créera la fonction réelle quand ce sera utile. Voir plus loin.3. Incompatibilités entre C et C++
3.1
Entête de fonctions
La syntaxe Kernigham & Ritchie n’est plus acceptée
void echange( x, y) // erreur de compilation
int &x, &y; { ... }
3.2
Prototypes de fonctions
Le nombre et le type des paramètres de fonctions sont contrôlés à chaque appel.
Tout appel de fonction doit donc obligatoirement être précédé d’un prototype ou de la définition de la fonction. (Comme en C ANSI)
3.3
fonction sans argument
float f1 ( ) ; // fonction sans argument et pas float f ( void);
3.4
fonction sans valeur de retour
void f2 ( ) ; // ni f2 ( ) ni void f2( void ) ;
3.5
le type void *
la conversion implicite n’est possible que dans le sens void * Type *
void *generic; int *ptr;
generic = ptr; // ok
ptr = generic; // erreur à la compilation
ptr = ( int *)generic; // ok
3.6
Symbole de type const.
Le mot clé const, placé devant un symbole, est un modificateur qui signifie que la valeur du symbole ne doit pas être modifiée.
En C++ un symbole défini avec const est local au fichier : on peut redéfinir le même symbole dans un autre fichier (avec une valeur différente)
en C il y a une erreur à l'édition de liens.
en C++, const est utilisé en remplacement de la directive #define du préprocesseur.
En C en C++
#define MAX 100 char tab[ MAX + 1]
const int max = 100; char tab[ MAX + 1]
En C++ une constante doit obligatoirement être initialisée lors de sa déclaration.
4. CONCEPTS DE LA PROGRAMMATION ORIENTEE OBJET : LES CLASSES ET LES
4.1 Programmation traditionnelle en C... 13
4.2 Programmation objet : l'encapsulation. ... 15
4.3 Définitions. ... 15
4.4 Améliorations 1 : private, accesseur. ... 17
4.5 Amélioration 2 : Fonctions membres constructeur et destructeur ... 19
4.6 Autres éléments d'une classe : ... 21
4.7 Définition et utilisation des objets ... 22
4.8 Organisation pratique des fichiers ... 23
4.9 exercice : ... 24
5. CONSTRUCTION DES OBJETS ... 25
5.1 Classe de mémorisation des objets. ... 25
5.2 Constructeur par défaut. ... 25
5.3 Constructeur de recopie. ... 27
5.4 Cas des objets avec des objets membres. ... 29
5.5 initialisation des objets ( <> affectation ). ... 29
5.6 Affectation d’objets. ... 29
5.7 Tableaux d’objets. ... 30
6. S AMIS. ... 31
6.1 Fonction amie. ... 31
4. Concepts de la programmation orientée objet : les classes et
les objets
4.1
Programmation traditionnelle en C
soit le programme suivant :
#include <iostream.h>
struct Personne { // une personne est décrite par son nom et sa societe char nom[ 20 ] ;
char societe[ 30 ]; } ;
void presente( struct Personne p )
//--- // affiche la description d'une personne
{
cout << "je m'appelle " << p.nom << endl ; cout << "je travaille à " << p.societe << endl ; }
int main() {
// on definit une variable individu qui décrit une personne struct Personne individu ; // struct inutile en C++ // on initialise cette variable
strcpy( individu.nom, "Durand" ); strcpy( individu.societe, "Thomson" );
// on affiche la description de la variable individu presente( individu );
return 0 ; }
Cet exemple montre l'implémentation et l'utilisation d'un type Personne.
5. séparation des données (nom et société) et des
traitements (presente() ) Le type abstrait Personne est
aussi bien caractérisé par ses données et ses
traitements.
Les contrôles sont difficiles à assurer : on peut afficher une personne sans avoir donné une valeur à ses données (variable non initialisée), on peut changer le nom d'une personne (toutes les données sont accessibles à l'utilisateur de la structure) !...
6. Programmation objet : l'encapsulation.
Cahier des charges d'un type Personne :
Une personne est décrite par 2 informations :
son nom
la société où elle travaille. Une personne est capable de se présenter en affichant ses données.
class Personne { // une personne est décrite par son nom, sa societe, son comportement
public :
char nom[ 20 ] ; char societe[ 30 ]; void presenteToi( ) {
cout << "je m'appelle " << nom << endl ; cout << "je travaille à " << societe << endl ; }
} ;
int main() {
// on definit une variable individu qui décrit une personne Personne individu ;
// on initialise cette variable strcpy( individu.nom, "Durand" ); strcpy( individu.societe, "Thomson" ); // on demande à individu de se présenter individu.presenteToi( );
return 0 ; }
Les changements :
Les données et les comportements sont rassemblés - encapsulés - dans une même entité. L'objet est défini par ses données et par son comportement.
La fonction presenteToi() n'a pas besoin de paramètres. Les champs nom et societe sont directement accessibles à l'intérieur de la classe.
L'instruction : individu.presenteToi( ); représente l'envoi d'un message à l'objet individu. Cet envoi de message déclenche le traitement demandé. Ce traitement est connu de l'objet.
6.1
Définitions.
Une classe est analogue à un type (extension du type struct du C).
Un objet est une instance (une entité identifiable, un exemplaire) d’une classe. Un objet est analogue à une variable. La création d'un objet est appelée une instanciation.
Une classe comprend
des données (les données membres, des champs). Les variables sont des variables d'instances 1. des fonctions membres pour manipuler les données membres. Les fonctions membres décrivent le
comportement des objets. (Les méthodes).
l’encapsulation:
Regroupement dans une même structure des données et du comportement (fonctions qui manipulent ces données).
class NomClasse // nom de la classe {
public : // spécificateur d'accès
int x, y ; // variables membres
char s[ 10 ] ;
void f( ) ; // fonctions membres
int g( ) ; } ;
Une classe se définie comme une structure.
A partir d'une classe on peut créer autant d'objets que nécessaire. Chaque objet contient ses données membres (variables d'instances), mais le comportement n'est défini qu'une seule fois dans la classe pour tous les objets membres de la classe.
Pour déclencher un traitement, un comportement, il faut envoyer un message à un objet.
Les membres d'un objet ont un spécificateur d'accès : ici public. Public veut dire que tous les membres sont accessibles de l'extérieur de la classe.
p1, un objet Personne La classe Personne deux instanciations societe nom cout << ... ; cout << ....; presenteToi() Durand nom Thomson societe
p2, un autre objet Personne
Dupond
nom
ISAIP
6.2
Améliorations 1 : private, accesseur.
Mais Les contrôles sont toujours difficiles à assurer : on peut afficher une personne sans avoir donné une valeur à ses données (variable non initialisée), on peut changer le nom d'une personne (toutes les données sont accessibles à l'utilisateur de la structure) !...
Nouveau cahier des charges d'un type Personne :
1. Une personne est décrite par 2 informations : son nom et sa société
2. Une personne a toujours un nom, ce nom ne peut pas changer, tous les caractères sont en majuscule, il comporte 20 caractères max.
3. Une personne peut ne pas être associée à une société (non salariée ).
4. S'il existe, le nom de la société comporte 30 caractères max et est en majuscule. 5. Une personne peut changer de société.
6. Une personne est capable de se présenter en affichant ses données.
Nouveau spécificateur d'accès : private :
Les membres d'accès privés sont inaccessibles en dehors de la classe. Il ne sont accessibles que par les membres de la classe.
Pour pouvoir accéder aux membres privés il faut ajouter des fonctions membres publics.
class Personne { // une personne est décrite par son nom et sa societe private :
char nom[ 20 + 1 ] ; // membres privés
char societe[ 30 + 1 ] ; char *majuscule( char * ) ;
public : // membres publics
void initNom( char *nm )
{ strncpy( nom, majuscule( nm ), 20 ) ; } void initSociete( char *soc )
{ strncpy( societe, majuscule( soc ), 30 ) ; } void init( char *nm, char *soc = "?" ) ;
void presenteToi() ; } ;
#include <iostream.h> #include <ctype.h> #include <string.h>
char *Personne :: majuscule( char *texte ) //--- {
int longueur = strlen( texte ) ;
for( int i = 0; i < longueur; i++ ) texte[ i ] = toupper( texte[ i ] ) ; return texte ;
}
void Personne :: init( char *nm, char *soc ) //--- // initialise les membres de la personne
// mets les noms en majuscule
// initialise par défaut la société à ? si pas défini {
initNom( nm ); initSociete( soc ); }
void Personne :: presenteToi( )
//--- {
cout << "je m'appelle " << nom << endl ; if( strcmp( societe, "?" ) != 0 )
cout << "je travaille à " << societe << endl ; else
cout << "je ne suis pas dans une societe" << endl ; }
int main()
//======================================================== {
Personne individu ;
individu.init( "DuPond", "Renault" ); individu.presenteToi();
individu.init( "Durand" );
// strcpy( individu.nom, "Durand" ); est maintenant impossible individu.presenteToi();
return 0 ; }
6.2.1 remarques :
On peut trouver des variables membres et des fonctions membres privées.
Les membres d’une classe peuvent être des variables, des constantes, des fonctions. des éléments de type prédéfini ou défini par l’utilisateur, d’autres objets, des déclarations de type ...
Les membres d’une classe peuvent être privés ou publics. (private, public)
privés : ils ne sont connus et utilisables directement que par les objets de la classe publics : ils sont connus et utilisables par tout utilisateur
Par défaut les membres sont privés.
Les fonctions privées sont réservées à un usage interne à la classe.
Les fonctions peuvent être
soit déclarées dans la classe et définies en dehors de la classe, soit déclarées et définies en même temps dans la classe.
:: opérateur de (résolution de) portée. Pour préciser à quelle classe appartient la fonction.
Les fonctions définies dans la classe sont inline par défaut. Elles sont en général petites. En général on définit à l'intérieur les fonctions membres très petites ( une ligne ).
Toutes les fonctions membres d’un objet donné de la classe ont accès à tous les membres de cet objet (données ou fonctions)
initNom()
L'abstraction des données : La structure exacte d'une donnée est cachée. On peut utiliser un objet sans connaître sa structure interne.
Les données privées forment l'implémentation de la classe les données publiques constituent l'interface entre l'objet et les utilisateurs 6.2.2 Accesseur ( sélecteur, manipulateur ).
Accesseur : fonction membre publique qui permet d'accéder aux variables d'instances privées.
Une variable d'instance privée peut :
n'avoir aucun accesseur : c'est une variable d'implémentation dont l'existence n'est pas forcément connue par l'utilisateur qui ne peut ni la consulter ni la modifier.
Avoir un accesseur en consultation : l'utilisateur de la classe peut consulter la variable d'instance mais ne peut pas la modifier.
Avoir deux accesseurs, l'un en consultation, l'autre en modification : l'utilisateur de la classe peut consulter la variable d'instance et la modifier.
class Personne { private :
char nom[ 20 + 1 ] ; // membres privés
char societe[ 30 + 1 ] ; char *majuscule( char * ) ;
public : // membres publics
void initNom( char *nm ) { strncpy( nom, majuscule( nm ), 20 ) ; } void initSociete( char *soc ) { strncpy(societe, majuscule( soc ), 20 ) ; } void init( char *nm, char *soc = "?" ) ;
void presenteToi( ) ;
char *sonNom( char *nm) { return strcpy( nm, nom ) ; } } ;
class Complex {
double x, y; public :
void init( double xx, double yy ) ; // accesseur en modification
double &X( ) { return x; } // accesseur en consultation et modification double &Y( ) { return y ; }
double module() { return sqrt( x*x + y*y ) ; } } ;
6.3
Amélioration 2 : Fonctions membres constructeur et destructeur
6.3.1 fonctions membres constructeur.class Personne { private :
char nom[ 20 + 1 ] ; // membres privés
char societe[ 30 + 1 ] ; char *majuscule( char * ) ;
void initNom( char *nm ) { strncpy( nom, majuscule( nm ), 20 ) ; }
public : // membres publics
Personne( char *nm, char *soc = "?" ) ; // fonction constructeur
void initSociete( char *soc ) { strncpy( societe, majuscule( soc ), 20 ) ; } void presenteToi() ;
char *sonNom( char *nm) { return strcpy( nm, nom ) ; } } ;
Fonction membre appelée automatiquement juste après toute création d’un objet. (après l’allocation de l’espace mémoire destiné à l’objet)
Possède le même nom que la classe.
N’a pas de type de retour.
Le constructeur permet d'initialiser un nouvel objet.
Il peut servir à initialiser l’objet au moment de sa création. (c’est sa fonction principale pour le programmeur).
On peut définir plusieurs constructeurs. Les valeurs d’initialisation sont mentionnées :
1. entre parenthèses après le nom de l’objet lors de sa définition. 2. en utilisant le signe = suivi explicitement de l’appel du constructeur
3. en utilisant le signe = suivi d’une valeur d’initialisation si le constructeur ne peut accepter qu’un seul paramètre.
Personne p1 ("Durand", "Renault" );
Personne p2 = Personne("Durand", "Renault");
Personne p3( "Durand" ); // équivalent à p3("Durand", "?") Personne p4 = Personne("Durand" ); // équivalent à p4("Durand", "?")
Personne p5 ; // illégal pas de constructeur sans paramètre
Personne p6 = "Durand"; // équivalent à p6("Durand", "?") quand il y aun seul paramètre
Même principe pour des objets dynamiques:
Complex *z1 = new Complex; // illégal Complex *z2 = new Complex(1, 2);
On peut définir plusieurs constructeurs avec des paramètres différents.
6.3.2 Fonction membre destructeur
class Tableau {
int *ptr; public :
Tableau ( ) { ptr = new int [ 100 ] ; } ~Tableau ( ) { delete [ ] ptr ; }
} ;
Fonction membre appelée automatiquement juste avant toute destruction d’objet. (Avant la libération de la mémoire allouée).
Possède le même nom que la classe précédée de ~ (tilda) (alt 126).
N’a ni paramètre ni type de retour.
Une classe ne peut posséder qu’un seul destructeur.
Un destructeur sert principalement à nettoyer la mémoire quand l’objet possède des données allouées dynamiquement.
6.4
Autres éléments d'une classe :
6.4.1 Membres constantsclass Tableau {
const int taille; // pas d'initialisation ici int *ptr;
public :
Tableau ( int n ) : taille( n ) { ptr = new int [ taille ] ; } ~Tableau ( ) { delete [ ] ptr ; }
} ;
L’initialisation d’un membre constant se fait sur l’entête de chaque constructeur.
Il s'agit d'une constante pour chaque objet, pas pour la classe. 6.4.2 Fonctions constantes : fonctions de consultation
class Complex {
double x, y; public :
Complex( double xx, double yy ) ; double X( ) const { return x ; } double Y( ) const { return y ; } void annule( ) { x = y = 0 ; } } ;
Ce sont des fonctions qui ne modifient pas l’état d’un objet : fonctions de consultation ( ou accesseur de
consultation ).
on obtient
const Complex z( 1, 2 ); double x = z.X( );
z.annule( ) ; // erreur à la compilation.
sur un objet constant on ne peut appeler que des fonctions constantes. 6.4.3 Membres statiques
Class Point {
static int nbPoint; int x, y;
public :
Point( int xx, int yy ) { x = xx; y = yy; nbPoint++ ; } ~Point( ) { nbPoint-- ; }
... } ;
int Point :: nbPoint = 0; // déclaration obligatoire à l’extérieur de la classe // en global et initialisation
Membres partagés par tous les objets d’une même classe : variables globales à la classe. initialisation au niveau global, pas dans un contructeur pour un objet donné.
6.4.4 Fonctions statiques
Class Point {
static int nbPoint; int x, y;
public :
Point( int xx, int yy ) { x = xx; y = yy; nbPoint++ ; } ~Point( ) { nbPoint-- ; }
static int litNbPoint ( ) { return nbPoint ; } ...
} ;
De la même façon une fonction statique est indépendante de tout objet.
Une fonction statique est une fonction de classe.
Une fonction statique n’a accès qu’aux membres statiques ( de classe ).
Un objet a accès à ses membres propres et aux membres statiques.
int nb = Point :: litNbPoint ( ) ; // de préférence int nb = objetPoint.litNbPoint( ) ; // moins naturel
Les fonctions de classe (publiques) ne dépendant pas d’un objet particulier peuvent être appelées même si aucun objet n’a encore été créé.
6.4.5 Le pointeur this
Toute fonction membre (non statique) dispose implicitement d’un pointeur sur l’objet courant
manipulé par la fonction : ce pointeur est nommé this.
De type const C * const
class Complex {
int x, y; public :
int &X() { return x ; } // équivalent à { return this->x ; } int &Y() { return y ; } // équivalent à { return this->x ; } Complex( int x, int y ) { this->x = x; this->y = y ; }
void affiche() { cout << "x = " << x << "y = " << y
<< " adresse de la variable = " << this ; } } ;
ici this est de type Complex *
6.5
Définition et utilisation des objets
Les objets sont des variables du type de la classe, ce sont des instances de la classe. Les objets se définissent et se manipulent comme des variables de type structure. Definition = instanciation.
Complex z, *pz; // les membres de z sont définis par le constructeur double x, y, rho;
x = z.X( ) ; y = z.Y( ) ;
rho = pz->module( ); // ou (*pz ).module( ) ; à éviter
class Rectangle {
unsigned h, l; // membres privés
unsigned c; // couleur void modifie( int, int ); public :
Rectangle( unsigned haut = 0, unsigned larg = 0, unsigned col = NOIR ); unsigned hauteur( );
unsigned largeur ( ); unsigned couleur ( ); void dessine ( ); void aggrandit( int ) void deplace( int , int ) } ;
Rectangle r;
unsigned hauteur, largeur, couleur;
largeur = r.l ; // erreur à la compilation
largeur = r.largeur();
r.h = 10; // erreur à la compilation
r.modifie( 5, 8 ) ; // erreur à la compilation r.deplace( 5, 8 );
On peut faire des affectations entre objets comme entre des structures. Il y a alors recopie des valeurs de tous les champs de données.
Les fonctions ne sont pas copiées, elles sont définies une seule fois et appartiennent à tous les objets de la classe.
Complex z1, z2;
z1 = z2; ; z1 et z2 ont les mêmes valeurs pour x et y
6.6
Organisation pratique des fichiers
on aura en général : un fichier entête .h pour chaque définition de classe
un fichier source .cpp pour la définition des fonctions publiques de chaque classe.
Les fichiers d'application .cpp qui utilisent les classes.
un projet contenant les fichiers d'application .cpp et les fichiers .cpp des classes.
#ifndef RECTANGLE_H // fichier rectangle.h :
#define RECTANGLE_H class Rectangle
{
int x0, y0, x1, y1 ; // membres privés
unsigned c; // couleur
void modifie(int x0, int y0, int x1, int y1 ); public :
Rectangle( int x0, int y0, int x1, int y1, unsigned col = NOIR ); unsigned hauteur( ) { return x1 - x0 ; }
unsigned largeur ( ) { return y1 - y0 ; } unsigned couleur ( ) { return c ; } void dessine ( ) ;
void aggrandit( int dx, int dy ) ; void aggrandit( float taux ) ; } ;
#include "rectangle.h" // fichier rectangle.cpp (extrait)
void Rectangle::dessine( ) { ...
}
void Rectangle :: aggrandit( int dx, int dy ) { ...
modifie( .... ) ; }
#include "rectangle.h" // fichier appli.cpp int main()
{
Rectangle r( 10, 50, 20, 150 ); unsigned hauteur, largeur, couleur; longueur = r. largeur();
r.aggrandit( 2, 5 ); }
le projet contient les fichiers applic.cpp et rectangle.cpp
6.7
exercice :
réaliser une classe Point permettant de manipuler un point du plan.
Les coordonnées du point seront privées.
le constructeur recevra en argument les coordonnées du point (float)
une fonction membre déplace() permettra d'effectuer une translation du point.
Une fonction membre affiche() affichera les coordonnées du point.
Ecrire un programme d'essai qui déclare un point, l'affiche, le déplace et l'affiche à nouveau. Compléter la classe pour que la fonction affiche indique aussi le nombre d'objets de type point.
7. Construction des objets
7.1
Classe de mémorisation des objets.
Différentes classes de mémorisation des objets :
Objets automatiques (locaux): déclarés localement dans un bloc et recréés à chaque appel de la fonction Objets statiques (globaux) : déclarés en dehors de toute fonction : créés avant main(), détruits après. Objets dynamiques : créés par new et supprimés par delete.
Objets temporaires (ils sont sans nom): créés pour mémoriser un résultat intermédiaire dans le calcul d’une expression, passage de paramètres par valeur, retour de fonction par valeur.
Point p1( 2, 2 ); // objet statique void f ( Point p )
{ ...
Point p2( 0, 0 ); // objet automatique, constructeur de p2 appelé ici ...
point *ptr ; // pas d'appel de constructeur ici ...
ptr = new Point( 1, 1); // objet dynamique, constructeur appelé ici ...
delete ptr; // destructeur de l’objet dynamique appelé ici ...
} // destructeur de p2 appelé ici
// le point p1 est construit avant d’entrer dans main() int main ()
{ ...
f ( Point( 3, 3 ) ); // création d’un objet temporaire
// destruction de l’objet temporaire (au plus tôt) ...
}
// destruction du point p1
7.2
Constructeur par défaut.
S’il n’y a pas de constructeur, le compilateur génère un constructeur par défaut sans paramètre.
Le constructeur par défaut, sans paramètre, permet de créer des objets non initialisés.
De façon générale un constructeur par défaut est un constructeur qui peut être appelé sans paramètre. (Soit il n'existe aucun paramètre, soit tous les paramètres ont des valeurs par défaut).
Si un ou plusieurs constructeurs sont définis par la classe, le constructeur pas défaut n’est pas généré.
class Texte1 {
int taille; char *ptr; public :
... // un constructeur par défaut est généré } ;
Texte1 t; // ok class Texte2 {
char *ptr; public :
Texte2 ( char * ); // pas de constructeur par défaut généré } ;
Texte2 t; // illégal
Texte2 t ( " je suis le contenu du texte " ); class Texte3 { int taille; char *ptr; public : Texte3 ()
{ taille = 80; ptr = new char[ taille + 1 ] ; } // constructeur par défaut Texte3 ( char * );
7.3
Constructeur de recopie.
7.3.1 Problème à résoudreClass Tableau {
int *ptr;
int nb; // taille du tableau public :
Tableau ( int n ) { ptr = new int[ nb = n ]; } ~Tableau () { delete [] ptr ; } ... } ; int main() { Tableau t1( 3 ); Tableau t2 ( t1 ) ; ... return 0; }
Le tableau t2 est créé à partir du tableau t1. Le compilateur fait une copie automatique de tous les champs de t1 dans t2. Les champs sont recopiés mais pas les zones pointées.
On constate que le tableau d'entiers alloué en mémoire est référencé par deux objets différents.
A la destruction des objets t1 et t2, cette zone allouée unique est libérée deux fois par le destructeur de t1 et t2, ce qui provoque une erreur à l'exécution.
Il faudrait obtenir :
les deux tableaux t1 et t2 sont indépendants, la zone allouée pointée pour t2 est une copie de la zone allouée pour t1.
Il faut créer un constructeur de recopie.
7.3.2 définition.
Son prototype est de la forme :
Classe( Classe & ) ou mieux Classe( const Classe & )
sert à créer des clones, c’est à dire à dupliquer les objets.
class Complex {
t1
3
t2
3
t1
3
t2
3
copieint x, y ; public :
Complex( int x, int y ) ; // constructeur 'normal'
Complex ( const Complex &z ) // constructeur de recopie (inutile ici) { x = z.x; y = z.y; }
} ;
7.3.3 Utilisations :
Appel lors de l’initialisation d’un objet par un autre objet de la classe
Complex z1( 2, 4 );
Complex z2( z1 ); // appel du constructeur de recopie Complex z3 = z1 ; // appel du constructeur de recopie
Appel lors du passage d’objet en paramètres par valeur à une fonction pour recopier le paramètre effectif dans le paramètre formel.
int module( Complex z ); // prototype de fonction (pas une fonction membre) Complex z( 1, 2 );
int m = module( z ); // appel du constructeur de recopie // pour le passage de paramètre par valeur
Appel lors du renvoi d’un objet par une fonction pour recopier la valeur de retour dans un objet temporaire :
Complex racine( ... ); // prototype de fonction(pas une fonction membre) Complex z = racine( ...); // appel du constructeur de recopie
// pour le retour de la fonction
Class Tableau {
int *ptr;
int nb; // nombres d’éléments public :
Tableau (int n) { ptr = new int[ nb = n ]; } Tableau ( const Tableau &t );
~Tableau () { delete [] ptr ; } ...
} ;
Tableau :: Tableau ( const Tableau &t ) {
nb = t.nb;
ptr = new int [ nb ] ;
memcpy( ptr, t.ptr, nb*sizeof( int ) ) ; }
Tableau t1; Tableau t2 ( t1 ) ;
Point Point :: symetrique() {
Point p ; // création d'un point automatique (local) p.x = -x; p.y = -y ;
return p; // recopie dans un point temporaire // appel constructeur de recopie }
Point &Point :: symetrique() {
Point p ; // création d'un point automatique (local) p.x = -x; p.y = -y ;
return p; // erreur à la compilation : référence sur un objet temporaire }
Point *Point :: symetrique() {
Point p ; // création d'un point automatique (local) p.x = -x; p.y = -y ;
return &p; // pas d'erreur déclarée !!!! }
// faire Point *p = new Point ; ... return p ; est aussi une mauvaise solution
Point p1( 2, 5 ), p2 = p1.symetrique() ;
7.4
Cas des objets avec des objets membres.
Dans un constructeur, il y a d'abord construction des membres (donc appel des constructeur par défaut des objets membres) puis exécution des instructions du bloc du constructeur.
Les objets membres sont donc d'abord construit avant l’objet englobant (et récursivement). Donc :
Si les objets membres disposent d’un constructeur par défaut, celui-ci est appelé avant que le constructeur de l’objet englobant soit lui-même appelé.
Si les objets membres ne disposent pas d’un constructeur par défaut, il faut faire un appel explicite au
constructeur souhaité.
L'appel des constructeurs a lieu en dehors du bloc du constructeur.
Même raisonnement avec tous les constructeurs ( constructeur de recopie compris ).
class Point; class Segment {
Point a, b; public :
Segment( int, int, int, int ); ...
}
Segment :: Segment( int x1, int y1, int x2, int y2 ) : a( x1, y1 ), b( x2, y2 ) { ...
}
7.5
initialisation des objets ( <> affectation ).
L'initialisation d'un objet est toujours faite par un constructeur. Il y a initialisation d’un objet dans les 3 cas suivants:1 - Déclarations d’un objet avec intialisation : appel d'un constructeur.
Point a = 5 ; // ou a( 5 ) il doit exister un constructeur à un argument entier Point b = a ; // ou b( a ) il doit exister un constructeur à un argument Point
2 - Transmission d’un objet par valeur, en argument d’un appel d’une fonction : appel d'un constructeur de recopie.
3 - Transmission d’un objet par valeur, en valeur de retour d’une fonction : appel d'un constructeur de recopie.
7.6
Affectation d’objets.
L’opérateur d’affectation est le signe =.Exécuté avant les
instructions du
constructeur
Il recopie tous les membres de l’objet source dans ceux de l’objet destination.
Il faudra définir un opérateur d’affectation personnalisé dès que l’objet contient des pointeurs vers une zone dynamique.
Cas des objets membres : L’opérateur d’affectation par défaut utilise l’opérateur d’affectation pour chacun des objets membres.
Cas des pointeurs membres : Les membres de type pointeur sont copiés, mais pas les zones pointées.
7.7
Tableaux d’objets.
Il est possible de construire un tableau d’objets d’une classe donnée, à condition que la classe possède un constructeur par défaut (ou de les initialiser explicitement). Les éléments du tableau sont alors initialisés avec ce constructeur.
class Point { ....
public :
Point ( int, int ); }
Point tab[ 10 ] ; // illégal class Point
{ .... public :
Point ( int, int = 0 ); }
Point tab[ 5 ] = { Point( 1, 2), 3, Point( 4 ), 9, 6 } ;
// crée le tableau { ( 1, 2), ( 3, 0), ( 4, 0 ), ( 9, 0) ( 6, 0 ) } // mais il est impossible d'initialiser un tableau dynamique
8. s amis.
8.1
Fonction amie.
Une fonction amie d’une classe A est une fonction, n’appartenant pas à la classe A, autorisée à accéder aux membres non publiques de la classe A.
Le concepteur de la classe doit déclarer dans la classe la fonction amie avec le mot clé friend.
class Point {
int x, y; public :
friend void affiche( const Point &p );
Point( int abs = 0 ; ord = 0 ) { x = abs; y = ord ; } ...
} ;
void affiche ( const Point &p ) {
cout << " abscisse = " << p.x << " ordonnée = " << p.y ; }
Une fonction amie d’une classe A peut appartenir à une autre classe B.
class Fenetre ; class Point {
int x, y; public :
friend void Fenetre :: affiche( const Point &p ); Point( int abs = 0 ; ord = 0 ) { x = abs; y = ord ; } ...
} ;
8.2
Classe amie.
Une classe B est amie d’une classe A. Toutes les fonctions membres de la classe B sont amies de A.
classe Fenetre ; class Point { ...
friend class Fenetre ; ... } ;