begin process at 2008 07 06 12:54:08
1 205 544 membres
121 nouveaux aujourd'hui
14 119 membres club

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

Catégorie :.Net Niveau : Expert Date de création : 28/10/2002 Date de mise à jour : 21/01/2003 18:17:31 Vu : 3 070

Note :
9,25 / 10 - par 4 personnes
9,25 / 10

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

Commentaire sur cette source (5)
Ajouter un commentaire et/ou une note

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)
 
  • signaler à un administrateur
    Commentaire de mart1 le 29/10/2002 20:07:50

    Excellent article.

  • signaler à un administrateur
    Commentaire de GoldenEye le 30/10/2002 18:59:04

    Rien à redire. Je dirai juste que ça manque de couleurs (mais ce n'est pas de ta faute vieuxlion) afin de bien cibler ce qui est important. Pourquoi ne pas faire un tut en format Word par exemple ?

  • signaler à un administrateur
    Commentaire de davwart le 21/01/2003 17:43:19

    Tout tout bon!
    Merci pour ces explications.

    juste une remarque: juste avant ta conclusion 3 tu as:
       vlaSwap(&i, &j);
    ne serait-ce valswap(i,j)?

    merci encore!

  • signaler à un administrateur
    Commentaire de tibur le 14/02/2004 22:15:34

    Je pense qu'un bonne majoritée des codeurs "C++" devrait lire ca ...
    Beaucoup de gens n'utilisent ni & ni const !
    Mon chef se plaisait à me repeter : "Le C++ c'est comme les preservatifs : beaucoup de gens disent l'utiliser, mais peu savent s'en servir !"

  • signaler à un administrateur
    Commentaire de coyotte508 le 11/05/2007 17:31:22

    à noté que la fonction ptrswap(int *px, int *py) peut aussi s'écrire ainsi:

    # void ptrSwap(int* px, int* py)
    # { px += py; py = px - py; px = px - py; }

    On inverse les adresses, pas les valeurs, mais le résultat est le même.

Ajouter un commentaire

Pub



Appels d'offres

Plugin Dialer outlook
Budget : 2 000€
Travail graphique- ill...
Budget : 1 000€
creation de marque et ...
Budget : 1 000€

CalendriCode

Juillet 2008
LMMJVSD
 123456
78910111213
14151617181920
21222324252627
28293031   

Boutique

Boutique de goodies CodeS-SourceS