extension
du constructeur de type struct
2 types de
membres
- les
membres données (attributs)
- les membres fonctions
(méthodes)
3 niveaux
d'accès
public :
valeur par défaut des struct
protected :
private :
valeur par défaut des class
la définition
d'une class crée un nouveau type :
class complexe { }
ou
struct complexe { }
complexe
C, *PC=& C;
float module (complexe &)
Class Ecran {short hauteur, largeur ; // nb lignes, colonnes
};
char * curseur ; // position courante
char * écran ; // stockage
class truc ; // déclaration préalable
|
|
|
|
|
|
|
Z |
- attention à laccès |
- toutes les fonctions membres doivent être prototypées dans la définition |
|
|
|||
|
|
|||
|
|
|
||
|
|
|
|
|
|
|
|
|
|
Toutes les fonctions membres définies
dans le corps de la classe sont automatiquement inline.
Inline
: Void Ecran :: haut( ){curseur
= écran ;}
Class A |
) |
private |
) |
protected |
) |
public |
|
Class A' : A |
|A' hérite |
|
|
|
|
||
Code extérieur |
|
|
|
Les membres dune class
sont tous dans la portée des fonctions membres de la classe.
Lencapsulation doit être matérialisée dès lécriture de linterface de la classe. |
Tous les membres données sont |
|
|
|
|
|
|
|
|
|
|
|
|
|
int max ;
class F {
public :
F( ){max = 0 ;}// F ::
max = 0 ;
G(
);H( );K( );
private:
int max ;
} ;
les membres données sont visibles avant leur définition et cachent les variables globales de même nom
F :: H( ){max = 3 ;}// F :: max = 3 ;
mais il est possible daccéder à la variable globale grâce à lopérateur de résolution de portée
|
|
|
|
|
|
|
|
Une classe peut être définie de façon globale ou locale.
class Arbre {
public :
class noeud {
}; // classe
imbriquée
void tri (
){
class
paire {
}; // classe locale
paire
p ;
noe
ud
* pn ;
}
private :
paire
q ; // erreur
noe
ud
* fg ; * fd ;
};
noe
ud *
racine ; // erreur
Arbre :: noe
ud * racine ;
// ok !
I. |
Dans le bloc |
pas de membre statique
pas de membre objet nécessitant
ou |
un constructeur |
Union token { int
ival ;
public :
long
lval ;
long
get_Lval( ) {return Lval ;}
};
class token {public :
} ;
int type;
union {int ival ;
char cval ;
long lval ;
} ;
token::get_ival( ){assert(type == INT)
return ival ;
}
Accès direct aux membres de lunion
fonctions de gestion
création, destruction, copie appelées implicitement
fonctions d'implémentation
le type abstrait
fonctions internes / d'aide
liées à l'implémentation non publique
fonctions d'accès
permettent l'encapsulation
limitent le nombre de fonctions à "effet de bord"
// maclass - h |
|
// maclass - c |
|
L'objectif de créer des types intégrés nous impose d'avoir des instances constantes.
Mais le compilateur ne peut présumer des fonctions membres utilisables ou non sur une instance constante (ou volatile).
|
|
|
|
|
|
|
|
|
|
Le compilateur refusera
|
|
|
|
Seuls constructeur et destructeur fonctionnent sur une instance constante (mais pas d'appel explicite).
Constructeur & destructeur
initialisation déallocation
Les opérateurs
affectation, copie, allocation, libération etc
Les opérateurs de coercition
L'appel de ces fonctions est en général transparent à l'utilisation, ce qui permet une utilisation naturelle et ainsi d'ajouter le type au langage.
classique en fonction des membres donnéesclass bi {int b ; char * t} e = {io,"txt"};
comme les struct du c
objet qui est basé sur la notion de constructeur et l'éventuelle surcharge de celui-ci.
|
|
Permet deux initialisations :
et |
|
class Mot {int occ
;
char*string
;
Public
:
Mot(
) {occ = 0 ; string = ""
;}
Mot(char
*, int = 1);
};
main ( ) {
Mot A ; //. Mot
:: Mot ( ) Û Mot :: Mot ("",
0);
Mot outil ("Marteau"); // Mot :: Mot
("Marteau", 1);
Mot véhicule ("806",
2); // Mot :: Mot ("806", 2);
Mot truc = "machin"
; // Mot :: Mot ("machin", 1);
Mot *
pmot ;
pmot = new Mot ("objective C");// Mot :: Mot
("objectiveC",1);
}
|
|
|
|
|
|
Le destructeur permet de définir les opérations à réaliser au moment de la libération de l'instance (déallocation).
Même nom que le constructeur précédé d'un ~ (tilde)
|
|
|
|
|
|
|
|
|
|
|
|
Un seul destructeur
A l'instar du constructeur qui ne réalise pas l'allocation, le destructeur ne réalise pas la déallocation
De la même façon qu'en C il n'y a pas de tableau en C++ mais la possibilité d'allouer des groupes d'objets de même type.
Const int taille = 16 ;
Point tp [taille]
;
string * tstr = new string [taille] ;
L'initialisation des différents éléments du tableau est réalisée par une application du constructeur par défaut. Si il n'existe pas de constructeur par défaut, il faut une liste d'initialisation complète :
string tab [ ] = {"arbre",
"vallée"} ;
string tab2 [3] = { string
(6), string (4), string ("toto")} ;
sinon si le constructeur par défaut existe
string tab3[6] = { 1024, string (12),
"toto" } ;
//
construction string :: string( )pour tab[3-5]
|
|
|
Z |
|
|
Pas d'initialisation de tableau du tas comme en C.
|
|
|
|
|
|
|
|
|
2 constructeurs sont appelés pour chaque Mot :
- Mot :: Mot (?)
- string :: string
(?)
Y a t'il un ordre d'appel ?
Comment fournir les arguments au constructeur de l'attribut ?
L'ordre d'appel est celui des déclarations des attributs dans le corps de class puis celui de la classe (ici string, mot)
La définition du constructeur peut contenir une "member initialization list"
Mot :: Mot(const char * s,int(e)
: nom
(s),occurrence (e)
{ } // ¬
rien !
L'initialisation des membres est faite pour tous les membres ayant un constructeur par défaut.
Remarque : Un constructeur qui se limite à une "member initialization list" nous assure que la classe ne posera pas de prolèmes d'allocation.
String voyelle ("a");String mot = voyelle
;
Un constructeur membre à membre est fourni par défaut par C++ mais il n'est pas toujours adapté, exemple pour String :
string :: string (const string &
s)
{longueur = s.longueur ;
str
= s.str ; // Danger !!!
}
Le constructeur membre à membre a de multiples utilisations :
Initialisation (cf. voyelle)
Argument
d'appel de fonction :extern int
notich (string s,char
c)
la
variable locale s'est initialisée
par
le paramètre
d'appel de type string
La
valeur de retour d'une fonctionextern
string substituer(string &,char,char);
main(
){string umot("invisibilité");
cont
<< substituer" (umot,'i','I')<< endl ;
}
Utiliser plutôt le passage par référence qui évite l'appel au constructeur membre à membre.
La définition d'un constructeur membre à membre non standard n'est nécessaire que dans le cas où une allocation et un pointeur sont utilisés dans la classe (c.a.d. quand les constructeurs sont vides).
Ainsi il n'est pas nécessaire de redéfinir le constructeur membre à membre dans le cas de membres objets.
Par exemple :
|
|
|
|
|
|
Le constructeur Index (const Index
&)
n'est pas défini car le
constructeur membre à membre de mot sera automatiquement
utilisé par le constructeur membre à membre par défaut
de Index.
Dans le cas où vous voulez définir un constructeur membre à membre pour Index, attention au mode d'appel du constructeur de string.
L'implémentation suivante est incorrecte
|
|
|
|
|
|
Ecriture correcte
Index :: Index (const Index & i): mot(i.mot)
Ok initialisation
{occurrence = 0 ;
// ou occurrence = i.occurrence ;
Ok car type int
Remarque : On appelle le constructeur membre à membre constructeur par recopie.
Le concepteur de la classe peut fournir un certain nombre d'opérateurs qui ne sont pas nécessairement des fonctions membres mais qui ont au moins un argument de type class.
Cette règle assure que l'on ne surcharge pas un opérateur d'un type prédéfini.
La définition d'un opérateur est
identique à celle d'une fonction à part le nom qui est
de la forme operator 0(
)
où
0 est un des opérateurs redéfinissables.
Exemple :
string &
string::operator + =
(const string & s)
{ len
+ = s.len ;
char * p = new char [len + 1]
;
assert (p)
strcpy (p,
str) ; delete str ;
strcat (p, s.str) ;
str
= p ;
return * this ;
}
+ * - / % ^ | & ~ ! && || ,
= = < = > = ! = >> << + + - - < >
+ = * = - = % = ^ = & = | = >> = << =
[ ] ( ) ( ) ® ® * new delete
cast
* & =
La précédence des opérateurs du C est conservée
L'arité est conservée
Les opérateurs + - * & ont 2 arités
Les arguments par défaut sont interdits
Deux possibilités
non membre |
|
membre |
|
Si le premier paramètre n'est pas du type de la classe, nous devons écrire une fonction non membre. Si de plus nous devons manipuler des membres privés, nous écrirons une fonction amie (friend).
Par exemple :string s1 ("les stream")
cout << s1 << endl ;
Le premier paramètre est un
stream (cout
) d'où la
fonction amie suivante :
|
|
|
|
|
|
|
|
|
|
|
|
Certains opérateurs sont
obligatoirement des fonctions membres : =
[ ] ( ) ®
[pour
être sûr de travailler sur un objet de la classe pas un
résultat de coersition]
Dans les autres cas le concepteur choisit de placer ou non l'opérateur dans les fonctions membres.
Par exemple :
quel ensemble d'opérateurs nous permettra d'écrire les trois expressions suivantes :
|
|
|
|
|
|
Si string a un constructeurstring(const char*)
;
l'opérateur suivant fonctionne sur e1 et e2.
string
:: operator + (const string &);
De même en utilisant encore le constructeur la fonction non membre suivante fonctionne pour e3.
string
operator + (const string &, const chachaineicirc;ne &); // V X
au lieu d'écrire
string
operator + (const char *, const string &);
Le défaut de VX vient du fait de l'appel implicite du constructeur qui prend du temps.
Remarque :
"cordon" + "bleu" n'appelle pas l'opérateur VX car on a 2 types prédéfinis.
(mais "a" "b" fonctionne comme en C)
operator [ ]
On veut que le code suivant fonctionne :
string
s1 ("Vive" C++");
string
s2 (s1.len( ))
;
for(int i = 0) L= L1 . L en c);i <
L ;i ++)
s2
[i] = s1[i];
Pour cela on définit l'opérateur suivant
|
|
|
|
|
|
Operator ( )
L'itérateur, cet opérateur parcourt l'instance, il permet d'écrire du code ayant le sens suivant :
"pour tous les caractères faire"
"pour tous les éléments faire"
ce qui s'écritWhile(string 1( )){faire ;}
Cette conception est dangereuse et peu commode
il faut stocker dans chaque string une variable qui stocke l'état de l'itération variable dont on ne sait que faire dans l'opération d'affectation
cela limite à une seule itération à la fois, on ne peut pas manipuler en même temps plusieurs itérations sur la même string
Attention au fait que l'itérateur doit retourner zéro en fin d'itération
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
il faut indiquer dans la class string
- soit friend class
string Iterator ;
pour
que toutes les fonctions membres de string Iterator soient
amies de string
- soit friend string
Iterator :: operator( )( );
pour
que seule cette fonction soit amie
L'opérateur d'affectation par défaut fonctionne comme le constructeur membre à membre sur les données non statiques de la classe.
Problèmes :
les pointeurs sont recopiés tels quels
les zones pointées ne sont pas libérées
|
|
|
|
que fait on pour les itérateurs internes
Þ |
Dans le cas où notre classe réalise une allocation "cachée" il faut redéfinir l'opérateur d'affectation. |
|
|
|
|
On a un petit problème dans le cas suivant :
s1 = s1 ;
d'où un test supplémentaire
if
(this = = & s) return * this ;
dans le cas d'une auto-affectation ne rien faire !
Dans le cas de membres de type class
|
|
|
|
Si l'on ne redéfinit pas
l'opérateur = de mot
l'opérateur par défaut va appeler
chachaineicirc;ne
& string :: operator = (const string &) ;
Si l'on veut éviter cet appel il faut redéfinir
Mot &
Mot :: operator = (const Mot &);
Attention au problème d'efficacité suivantclass X {
L'opérateur unaire de sélection est une fonction membre qui doit retourner un pointeur sur un objet de type class ou bien une référence sur un objet dont la classe redéfinit l'opérateur de sélection.
class X ; class Y ;
X * p ;
p ®
m ( ); // utilisation du ® standard (*
p).m ( )
Xx ; x ® m( );
début : |
si il n'y a pas d'opérateur ®
erreur |
Les opérateurs ++ et -- sont par défaut préfixes.
Pour avoir la possibilité d'avoir un opérateur postfixé, on ajoute un faux paramètre de type int pour indiquer que l'on veut la version postfixée.
class Ecran {public:
char operator ++( ) { return * ++curseur ;} // préfix
char operator ++(int) { return * curseur ++ ; } // postfix
};
Deux opérateurs prédéfinis par C++ qui sont l'opérateur d'affectation = et d'adresse &. Tous les autres doivent être explicitement écrits.
Le fait de fabriquer = et + ne fabrique pas + =
Ne pas fabriquer d'opérateurs ambigus !
Que fait ++ sur une string
?
- minuscule ou
majuscule
- itérateur
- autre
On travaille en deux étapes :
création des fonctions d'implémentation et d'accès
transformation de celles qui ont un comportement d'opérateur en opérateur
Les conversions permettent de minimiser le nombre d'opérateurs à définir.
Si l'on veut définir l'addition pour les int, les char et les short il nous faut dans opérateurs de conversion définir 6 opérateurs + supplémentaires ou 3 opérateurs de base.
D'autre part nous devons définir des opérateurs de conversion pour être plus conforme au comportement standard du C qui veut que les conversions standards implicites aient lieu vers un type plus grand.
Class short int {int valeur
;
public
:
friend
operator + (shortint &, int) ;
};
shortint
d(3) ;
d + 3.14159 // ® 6 délicat
non ?
Tous les constructeurs à un argument sont implicitement des opérateurs de coercition.
Ces conversions sont dites "standard".
Elles
sont utilisées dans les affectations dans le type de la classe
et pour la conversion dans les appels de fonctions ou autres qui
utilisent le type défini par la class.
Ambiguïté avec une conversion standard.
Si deux conversions sont possibles que l'une est standard et l'autre définie par l'utilisateur, c'est la conversion standard qui est effectuée.
Ex. : |
|
string :: operator char*( ){return
str ;}
Dangereux : |
|
D'où l'opérateur suivant qui nécessite une définition de type :
typedef const char * p cchar ;
inline string
:: operator p cchar ( ){return str ;}
Z |
|
|
|
|
|
|
|
|
Nous
devons redéfinir = = inline
string :: operator == (const char * s)
{return
strcomp (str,s) == 0 ;}
Ambiguïté entre deux résultats distincts
extern void f (int) ;
extern Int f (shortint)
;
Token Tok ("vanille", 34) ;
// Token ::
operator int ( )
// Token :: operator shortint ( )
// Token ::
operator floot ( )
f (tok) ; // erreur : ambiguïté
solution résoudre à la main
f(int(tok));ou
f((int)tok);
Si token :: operator int( )
n'était pas défini, du fait que short int
:: operator int( )
existe nous pouvons
imaginer une ambiguïté mais elle n'existe pas car il faut
une conversion de plus. Mais dans le cas suivant l'ambiguïté
existe car les 2 conversions sont au même niveau.
long Lval = tok ; // floot( )ou int( )error
2 chemins de conversion
A :: A(B); et B :: operator A( ){
}
B
x ; Void f(A);
f(x);
//ambiguïté
f(A(x));
//ambiguïté
Il faut écrire soit |
|
soit |
|