|
begin process at 2008 07 06 12:54:08
Derniers logiciels
|
Trouver une ressource (Nouvelle version du moteur, plus rapide & pertinent, essayez le !)
Vous ne trouvez pas de réponse à votre problème ? Alors posez la question dans le forum.
Souvenez-vous qu'il n'y a jamais de question bête, mais rester dans l'ignorance parce que l'on n'ose pas poser une question, ça c'est une erreur !
POINTEURS ET RÉFÉRENCES
Information sur la source
Description
// les références et les pointeurs sont à priori semblables // les pointeurs ont la réputation d'être ardus à manipuler // les références ont été inventées pour simplifier les notations des passages d'arguments de fonction. // et pourtant, nombreux sont les programmeurs qui méconnaissent les références
Source
- // Etape 1:
- //----------
- // un petit rappel :
- // déclaration d'un pointeur sur un type T :
-
- T* pt ; //mauvais, mais compile.
- // Un pointeur non initialisé pointe (pique!) sur une zone de mémoire aléatoire
- T* pt = null; // mieux, un pointeur null ne pointe plus sur n'importe quoi.
- T t;
- T* pt = &t; // encore mieux, il est vraiment initialisé sur un objet valide.
-
- // déclaration d'une référence sur le type T :
- T& rt ;//ne compile pas. La référence est mieux contrôlée par le compilateur que le pointeur
- // Il est NECESSAIRE d'indiquer vers quelle zone la référence pointe.
- T& rt = t; //OK
-
- // l'utilité du pointeur et de la référence n'est pas évidente sur cet exemple, mais nous en verrons une application
- // dans le développement d'une fonction de swap.
- // Disons simplement que ce sont deux autres moyens d'accéder à l'objet t :
- // on peut appeler 't' , '*pt' ou bien 'rt'
- // Conclusion 1 : une référence est une sorte de pointeur toujours initialisé
-
- // Etape 2:
- //----------
- //Pour simplifier, on prendra le type int pour le test suivant
-
- int main()
- {
- int i = 1, j=2;
- //déclaration et initialisation d'un pointeur sur i
- int * pi = &i;
- //passage du pointeur sur i en pointeur sur j
- pi = &j;
- //traitons alors de la "même manière" les références
- int & ri = i; // (1) la notation est plus simple
- //que se passe t'il sur la ligne suivante ?
- ri = j; // (2)
- //beaucoup se trompent:
- // certains disent que la référence est alors posée sur j
- // d'autres disent que ce n'est pas possible car une référence est toujours constante
- // en fait c'est la VALEUR de j qui est AFFECTEE à ri
- // on dit aussi que ri est UN ALIAS POUR i
-
- // dans la ligne (1) de déclaration+initialisation il s'agit bien d'INITIALISATION
- // alors que dans la ligne (2) il s'agit d'une affectation
- // Il est crucial de bien faire la différence entre ces deux signes '='
- // Ceci revient à différencier 'Constructeur de Copie' et 'Opérateur d'affectation'
- // (mais ceci est un autre thème : les classes canoniques)
-
- //Conclusion 2: Une référence est un pointeur constant
- // Une référence est un pointeur qui ne peut changer d'objet pointé
- // de ce point de vue, elle est moins souple que le pointeur qui peut changer ainsi :
- int * pi2 = new int(1); // déclaration et initialisation de pi sur une zone de tas elle-même initialisée à 1
- delete pi2; //libération de la zone de tas qui n'est donc plus allouée à notre process
- //ici il est TRES IMPORTANT de "ranger le pointeur" car il pointe toujours
- pi2 = 0; // avec les pointeurs, un oubli du développeur peut provoquer un trap
- //habituellement, on vérifie toujours qu'un pointeur est non null avant de l'utiliser.
-
-
- return 0;
- }
- // Etape 3:
- //----------
- // utilisation dans l'appel de fonction
- //soit l'exemple très classique d'une fonction de swap
- // son but est d'inverser deux variables passées en paramètres
- // on va essayer trois solutions
-
- //"solution" 1 : le passage par valeur
- void valSwap(int x, int y)
- { x += y; y = x - y; x = x - y; } //code inversant x et y sans variable temporaire
- // sans doute avez vous vu que cette essai n'est pas une solution ...
-
- //solution 2 : le passage par addresse (pointeur)
- void ptrSwap(int* px, int* py)
- { *px += *py; *py = *px - *py; *px = *px - *py; } // ici ça marche
- // on peut remarquer la forte concentration de '*' ce qui nuit à la lisibilité
- // et est générateur de bogues
-
- //solution 3 : le passage par référence
- void refSwap(int& x, int& y)
- { x += y; y = x - y; x = x - y; } //ça marche aussi
- // le code utilisé est nettement plus simple que par adresse pour un résultat identique
- // on notera que l'appel de la fonction refSwap est aussi plus simple que celui de ptrSwap
- // refSwap(i, j) contre ptrSwap(&i, &j) comme suit :
- int i=0, j=1;
- cout << i << " " << j << endl; // 0 1
- valSwap(i,j); //corrigé de l'erreur vlaSwap(&i, &j);
- cout << i << " " << j << endl; // 0 1 (pas d'inversion)
- ptrSwap(&i, &j);
- cout << i << " " << j << endl; // 1 0 (inversion)
- refSwap(i, j);
- cout << i << " " << j << endl; // 0 1 (réinversion)
-
- //Conclusion 3: Une référence est un pointeur toujours déréférencé
- // Une référence remplace avantageusement un pointeur dans les passages de paramètres
-
- // Attention tout de même à sa sémantique de passage par addresse qui est dissimulée à l'appel
- // car identique à un appel par valeur (mais un comportement différent)
-
- // Etape 4:
- //----------
- // Revenons aux types plus complexes (les structures et classes) que les types de base.
- // Il est bon de passer les paramètres par référence plutôt que par valeur pour des raisons
- // de performances (on évite l'appel du Ctor de copie et d'un destructeur).
- // Dans ce cas la sémantique de passage par référence donne le droit à la fonction appelée
- // de modifier la vraie variable passée : on la déclare donc const pour des raisons de sécurité
- class Elephant{... ... ...};//monumentale
- //que se passe t-il lorsque l'on exécute la fonction suivante :
- float enerve(Elephant e){float deslitres = 100; return deslitres;}
- // par la ligne suivante par exemple :
- Elephant a(); enerve(a);
- // à part le risque d'être arrosé ... remarquez le passage par VALEUR
- // A l'entrée de la fonction, (après le '{') un éléphant est créé (par son Constructeur de copie)
- // et passé sur la pile (écrasée de tout son poids)
- // A la sortie de la fonction, (après le '}') l'éléphant est détruit (appel au Destructeur)
- // et la pile est libérée
- float enerve(const Elephant& e){float deslitres = 100; return deslitres;}
-
- //Conclusion 4: Une référence (comme un pointeur) est plus efficace qu'une valeur pour les classes
- // Elle doit la plupart du temps être protégée par le modificateur const (autre thème)
-
-
- // Un petit exercice de Conception (de Design) pour finir :
- // lorsque vous voulez lier deux objets (un Propriétaire à sa Maison)
- // que choisissez vous parmi les trois solutions ci dessous ?
- class Maison {...};
- class Proprietaire { Maison propriete;...}; // (1)
- class Proprietaire { Maison& propriete;...}; // (2)
- class Proprietaire { Maison* propriete;...}; // (3)
-
- // ???
-
- //(1) l'objet propriété est inclu dans le propriétaire... elle se crée lorsque le propriétaire se crée !
- // et le détruit dans sa destruction !! => Est ce vraiment ce que l'on veut
- //(2) une référence doit toujours être initialisée (la liste d'initialisation est un autre thème)
- // et ne peut pas changer d'objet pointé! => Pas de déménagement possible !!
- //(3) le pointeur est le plus souple des couplages.
- // le futur propriétaire peut ne pas encore avoir de maison (pointeur null)
- // la maison peut exister dans un cycle de vie séparé des propriétaires
- // le propriétaire peut "facilement" acheter la maison , c'est pas cher :
- // une seule ligne de code (pointeur = &maison)
- // et il peut déménager : (pointeur = maison2)
-
// Etape 1:
//----------
// un petit rappel :
// déclaration d'un pointeur sur un type T :
T* pt ; //mauvais, mais compile.
// Un pointeur non initialisé pointe (pique!) sur une zone de mémoire aléatoire
T* pt = null; // mieux, un pointeur null ne pointe plus sur n'importe quoi.
T t;
T* pt = &t; // encore mieux, il est vraiment initialisé sur un objet valide.
// déclaration d'une référence sur le type T :
T& rt ;//ne compile pas. La référence est mieux contrôlée par le compilateur que le pointeur
// Il est NECESSAIRE d'indiquer vers quelle zone la référence pointe.
T& rt = t; //OK
// l'utilité du pointeur et de la référence n'est pas évidente sur cet exemple, mais nous en verrons une application
// dans le développement d'une fonction de swap.
// Disons simplement que ce sont deux autres moyens d'accéder à l'objet t :
// on peut appeler 't' , '*pt' ou bien 'rt'
// Conclusion 1 : une référence est une sorte de pointeur toujours initialisé
// Etape 2:
//----------
//Pour simplifier, on prendra le type int pour le test suivant
int main()
{
int i = 1, j=2;
//déclaration et initialisation d'un pointeur sur i
int * pi = &i;
//passage du pointeur sur i en pointeur sur j
pi = &j;
//traitons alors de la "même manière" les références
int & ri = i; // (1) la notation est plus simple
//que se passe t'il sur la ligne suivante ?
ri = j; // (2)
//beaucoup se trompent:
// certains disent que la référence est alors posée sur j
// d'autres disent que ce n'est pas possible car une référence est toujours constante
// en fait c'est la VALEUR de j qui est AFFECTEE à ri
// on dit aussi que ri est UN ALIAS POUR i
// dans la ligne (1) de déclaration+initialisation il s'agit bien d'INITIALISATION
// alors que dans la ligne (2) il s'agit d'une affectation
// Il est crucial de bien faire la différence entre ces deux signes '='
// Ceci revient à différencier 'Constructeur de Copie' et 'Opérateur d'affectation'
// (mais ceci est un autre thème : les classes canoniques)
//Conclusion 2: Une référence est un pointeur constant
// Une référence est un pointeur qui ne peut changer d'objet pointé
// de ce point de vue, elle est moins souple que le pointeur qui peut changer ainsi :
int * pi2 = new int(1); // déclaration et initialisation de pi sur une zone de tas elle-même initialisée à 1
delete pi2; //libération de la zone de tas qui n'est donc plus allouée à notre process
//ici il est TRES IMPORTANT de "ranger le pointeur" car il pointe toujours
pi2 = 0; // avec les pointeurs, un oubli du développeur peut provoquer un trap
//habituellement, on vérifie toujours qu'un pointeur est non null avant de l'utiliser.
return 0;
}
// Etape 3:
//----------
// utilisation dans l'appel de fonction
//soit l'exemple très classique d'une fonction de swap
// son but est d'inverser deux variables passées en paramètres
// on va essayer trois solutions
//"solution" 1 : le passage par valeur
void valSwap(int x, int y)
{ x += y; y = x - y; x = x - y; } //code inversant x et y sans variable temporaire
// sans doute avez vous vu que cette essai n'est pas une solution ...
//solution 2 : le passage par addresse (pointeur)
void ptrSwap(int* px, int* py)
{ *px += *py; *py = *px - *py; *px = *px - *py; } // ici ça marche
// on peut remarquer la forte concentration de '*' ce qui nuit à la lisibilité
// et est générateur de bogues
//solution 3 : le passage par référence
void refSwap(int& x, int& y)
{ x += y; y = x - y; x = x - y; } //ça marche aussi
// le code utilisé est nettement plus simple que par adresse pour un résultat identique
// on notera que l'appel de la fonction refSwap est aussi plus simple que celui de ptrSwap
// refSwap(i, j) contre ptrSwap(&i, &j) comme suit :
int i=0, j=1;
cout << i << " " << j << endl; // 0 1
valSwap(i,j); //corrigé de l'erreur vlaSwap(&i, &j);
cout << i << " " << j << endl; // 0 1 (pas d'inversion)
ptrSwap(&i, &j);
cout << i << " " << j << endl; // 1 0 (inversion)
refSwap(i, j);
cout << i << " " << j << endl; // 0 1 (réinversion)
//Conclusion 3: Une référence est un pointeur toujours déréférencé
// Une référence remplace avantageusement un pointeur dans les passages de paramètres
// Attention tout de même à sa sémantique de passage par addresse qui est dissimulée à l'appel
// car identique à un appel par valeur (mais un comportement différent)
// Etape 4:
//----------
// Revenons aux types plus complexes (les structures et classes) que les types de base.
// Il est bon de passer les paramètres par référence plutôt que par valeur pour des raisons
// de performances (on évite l'appel du Ctor de copie et d'un destructeur).
// Dans ce cas la sémantique de passage par référence donne le droit à la fonction appelée
// de modifier la vraie variable passée : on la déclare donc const pour des raisons de sécurité
class Elephant{... ... ...};//monumentale
//que se passe t-il lorsque l'on exécute la fonction suivante :
float enerve(Elephant e){float deslitres = 100; return deslitres;}
// par la ligne suivante par exemple :
Elephant a(); enerve(a);
// à part le risque d'être arrosé ... remarquez le passage par VALEUR
// A l'entrée de la fonction, (après le '{') un éléphant est créé (par son Constructeur de copie)
// et passé sur la pile (écrasée de tout son poids)
// A la sortie de la fonction, (après le '}') l'éléphant est détruit (appel au Destructeur)
// et la pile est libérée
float enerve(const Elephant& e){float deslitres = 100; return deslitres;}
//Conclusion 4: Une référence (comme un pointeur) est plus efficace qu'une valeur pour les classes
// Elle doit la plupart du temps être protégée par le modificateur const (autre thème)
// Un petit exercice de Conception (de Design) pour finir :
// lorsque vous voulez lier deux objets (un Propriétaire à sa Maison)
// que choisissez vous parmi les trois solutions ci dessous ?
class Maison {...};
class Proprietaire { Maison propriete;...}; // (1)
class Proprietaire { Maison& propriete;...}; // (2)
class Proprietaire { Maison* propriete;...}; // (3)
// ???
//(1) l'objet propriété est inclu dans le propriétaire... elle se crée lorsque le propriétaire se crée !
// et le détruit dans sa destruction !! => Est ce vraiment ce que l'on veut
//(2) une référence doit toujours être initialisée (la liste d'initialisation est un autre thème)
// et ne peut pas changer d'objet pointé! => Pas de déménagement possible !!
//(3) le pointeur est le plus souple des couplages.
// le futur propriétaire peut ne pas encore avoir de maison (pointeur null)
// la maison peut exister dans un cycle de vie séparé des propriétaires
// le propriétaire peut "facilement" acheter la maison , c'est pas cher :
// une seule ligne de code (pointeur = &maison)
// et il peut déménager : (pointeur = maison2)
Sources de la même categorie
Commentaires
|
CalendriCode
| | | L | M | M | J | V | S | D |
| | 1 | 2 | 3 | 4 | 5 | 6 |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 | | | |
|
|
|