'>

les Pointeurs dans c plus plus

 Nous avons déjà vu comment les variables sont considérées comme des cellules de mémoire qui peuvent être accessibles en utilisant leurs identifiants. De cette façon, nous n'avons pas eu à se soucier de l'emplacement physique de nos données au sein de la mémoire, nous avons simplement utilisé son identifiant chaque fois que nous voulions faire référence à notre variable.

La mémoire de votre ordinateur peut être imaginé comme une succession de cellules de mémoire, chacun de la taille minimale que les ordinateurs à gérer (un octet). Ces cellules de mémoire d'un octet sont numérotés de façon consécutive, de sorte que, dans n'importe quel bloc de mémoire, chaque cellule a le même numéro que le précédent, plus un.

De cette façon, chaque cellule peut être facilement localisé dans la mémoire, car il possède une adresse unique et toutes les cellules de mémoire suive un motif successif. Par exemple, si nous cherchons des cellules 1776, nous savons que ça va être juste entre les cellules de 1775 et de 1777, exactement un mille cellules après 776 et exactement mille cellules avant cellule 2776.

opérateur de référence (&)
Dès que nous déclarons une variable, la quantité de mémoire nécessaire est attribué pour cela à un endroit précis dans la mémoire (son adresse mémoire). Nous ne décidons pas généralement activement l'emplacement exact de la variable dans le panneau de cellules que nous avons imaginé la mémoire soit - Heureusement, c'est une tâche effectuée automatiquement par le système d'exploitation lors de l'exécution. Toutefois, dans certains cas, nous pouvons être intéressés à connaître l'adresse où notre variable est stockée lors de l'exécution pour fonctionner avec des positions par rapport à elle.

L'adresse qui localise une variable dans la mémoire est ce que nous appelons une référence à cette variable. Cette référence à une variable peut être obtenue en faisant précéder l'identificateur d'une variable avec un signe esperluette (&), connu comme l'opérateur de référence, et qui peut être traduit littéralement par "adresse". Par exemple:



  

 ted = &andy;



Ce serait céder à Ted l'adresse de la variable andy, depuis quand précédant le nom de la variable andy avec l'opérateur de référence (&), nous ne parlons plus sur le contenu de la variable elle-même, mais sur sa référence (par exemple, son adresse dans la mémoire).

A partir de maintenant, nous allons supposer que Andy est placé lors de l'exécution dans l'adresse mémoire 1776. Ce nombre (1776) est juste une hypothèse arbitraire que nous inventons dès maintenant afin d'aider à clarifier certains concepts dans ce tutoriel, mais en réalité, nous ne pouvons pas savoir avant l'exécution de la valeur réelle de l'adresse d'une variable aura en mémoire.

Considérons le fragment de code suivant:

 1
 2
 3


  

 andy = 25; fred = andy; ted = &andy;



Les valeurs contenues dans chaque variable après l'exécution de celui-ci, sont présentés dans le graphique suivant:


Tout d'abord, nous avons attribué la valeur 25 à andy (une variable dont l'adresse en mémoire, nous avons supposé être 1776),.

La deuxième déclaration copié fred le contenu de andy variable (qui est de 25). Il s'agit d'une opération d'affectation standard, comme nous l'avons fait tant de fois auparavant.

Enfin, la troisième déclaration copie à Ted pas la valeur contenue dans andy mais une référence à celui-ci (c.-à son adresse, que nous avons supposé être 1776),. La raison en est que, dans cette troisième opération d'affectation, nous avons précédé l'identifiant andy avec l'opérateur de référence (&), donc nous avons été plus référence à la valeur de andy mais sa référence (son adresse en mémoire).

La variable qui stocke la référence à une autre variable (comme Ted dans l'exemple précédent) est ce que nous appelons un pointeur. Les pointeurs sont une fonctionnalité très puissante du langage C + + qui a de nombreuses utilisations dans la programmation avancée. Plus loin en avant, nous allons voir comment ce type de variable est utilisé et déclaré.

opérateur de déréférencement (*)

Nous venons de voir que la variable qui stocke une référence à une autre variable est appelé un pointeur. Les pointeurs sont dits "pointer vers" la variable dont la référence qu'ils stockent.

L'utilisation d'un pointeur, nous pouvons accéder directement à la valeur stockée dans la variable il pointe. Pour ce faire, nous devons simplement faire précéder l'identificateur du pointeur avec un astérisque (*), qui agit comme opérateur de déréférencement et qui peut être traduit littéralement "la valeur pointée par".

Par conséquent, à la suite avec les valeurs de l'exemple précédent, si nous écrivons:



  

 beth = *ted;



(Que l'on pourrait lire comme suit: «beth égale à la valeur pointée par Ted») beth prendrait la valeur 25, depuis TED est 1776, et la valeur pointée par 1776 est de 25.


Vous devez différencier clairement que l'expression de TED se réfère à la valeur 1776, tandis que * TED (avec un astérisque * précédant l'identifiant) se réfère à la valeur stockée à l'adresse 1776, qui dans ce cas est de 25. Notez la différence d'inclure ou non l'opérateur de déréférencement (j'ai inclus un commentaire explicatif de la façon dont chacun de ces deux expressions pouvait-on lire):

 1
 2


  

 beth = ted; // beth equal to ted ( 1776 ) beth = *ted; // beth equal to value pointed by ted ( 25 )



Notez la différence entre la référence et les opérateurs de déréférencement:

    & Est l'opérateur de référence et peut être lu comme "adresse de"
    * Est l'opérateur de déréférencement et peut être lu comme «valeur pointée par"

Ainsi, ils ont des significations complémentaires (ou contre). Une variable référencé avec et peut être déréférencé par *.

Précédemment, nous avons réalisé deux opérations d'affectation suivantes:

 1
 2


  

 andy = 25; ted = &andy;



Juste après ces deux déclarations, toutes les expressions suivantes donnerait vrai résultat:

 1
 2
 3
 4


  

 andy == 25 &andy == 1776 ted == 1776 *ted == 25



La première expression est tout à fait clair étant donné que l'opération de cession réalisée sur Andy était andy = 25. La seconde utilise l'opérateur de référence (&), qui renvoie l'adresse de la variable andy, qui nous avons supposé qu'il ait une valeur de 1776. Le troisième est un peu évident depuis la seconde expression est vraie et l'opération de cession réalisée sur Ted était ted = & Andy. La quatrième expression utilise l'opérateur de déréférencement (*) qui, comme nous venons de le voir, peut être lu comme «valeur pointée par", et la valeur pointée par Ted est en effet 25.

Alors, après tout cela, vous pouvez également déduire que, aussi longtemps que l'adresse pointée par Ted reste inchangée l'expression suivante sera également vrai:



  

 *ted == andy



Déclaration des variables de type pointeur
En raison de la capacité d'un pointeur de se référer directement à la valeur qu'il pointe vers, il devient nécessaire de préciser dans sa déclaration que le type de donnée d'un pointeur va pointer. Ce n'est pas la même chose pour pointer vers un char comme pour pointer vers un int ou un float.

La déclaration de pointeurs suit ce format:

Tapez le nom de *;

où type est le type de données de la valeur que le pointeur est destiné à pointer. Ce type n'est pas le type de pointeur lui-même! mais le type de données que le pointeur pointe. Par exemple:

 1
 2
 3


  

 int * number; char * character; float * greatnumber;



Il s'agit de trois déclarations de pointeurs. Chacun est destiné à pointer vers un type de données différent, mais en fait elles sont toutes pointeurs et chacun d'eux va occuper le même espace dans la mémoire (la taille en mémoire d'un pointeur dépend de la plate-forme où le code va à courir). Néanmoins, les données auxquelles ils pointent n'occupent pas la même quantité d'espace ni sont du même type: la première pointe vers un int, le second à un chevalier et le dernier à un flotteur. Par conséquent, bien que ces trois variables sont par exemple tous les pointeurs qui occupent la même taille en mémoire, on dit d'avoir différents types: int *, char * et char * respectivement, en fonction du type ils pointent.

Je tiens à souligner que le signe astérisque (*) que nous utilisons lors de la déclaration d'un pointeur signifie seulement qu'il est un pointeur (cela fait partie de son composé spécificateur de type), et ne doit pas être confondu avec l'opérateur de déréférencement que nous avons vu un peu plus tôt, mais qui est également l'auteur d'un astérisque (*). Ils sont tout simplement deux choses différentes sont représentées avec le même signe.

Jetez un œil à ce code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17


  

 // my first pointer #include <iostream> using namespace std; int main () { int firstvalue, secondvalue; int * mypointer; mypointer = &firstvalue; *mypointer = 10; mypointer = &secondvalue; *mypointer = 20; cout << "firstvalue is " << firstvalue << endl; cout << "secondvalue is " << secondvalue << endl; return 0; }

  

  FirstValue est de 10
  SecondValue est de 20



Notez que même si nous n'avons jamais mis directement une valeur à chaque FirstValue ou SecondValue, les deux finissent avec une valeur fixée indirectement par l'utilisation de mypointeur. Il s'agit de la procédure:

Tout d'abord, nous avons assigné comme valeur de mypointeur une référence à firstValue utilisant l'opérateur de référence (&). Et puis, nous avons attribué la valeur 10 à l'emplacement mémoire pointé par mypointeur, parce que en ce moment est orientée vers l'emplacement mémoire de FirstValue, ce en fait modifie la valeur de FirstValue.

Afin de démontrer que le pointeur peut prendre plusieurs valeurs différentes au cours de la même émission, j'ai répété le processus avec SecondValue et que même pointeur, mypointeur.

Voici un exemple un peu plus élaboré:

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20


  

 // more pointers #include <iostream> using namespace std; int main () { int firstvalue = 5, secondvalue = 15; int * p1, * p2; p1 = &firstvalue; // p1 = address of firstvalue p2 = &secondvalue; // p2 = address of secondvalue *p1 = 10; // value pointed by p1 = 10 *p2 = *p1; // value pointed by p2 = value pointed by p1 p1 = p2; // p1 = p2 (value of pointer is copied) *p1 = 20; // value pointed by p1 = 20 cout << "firstvalue is " << firstvalue << endl; cout << "secondvalue is " << secondvalue << endl; return 0; }

  

  FirstValue est de 10
  SecondValue est de 20



J'ai inclus aussi un commentaire sur chaque ligne la façon dont le code peut être lu: esperluette (&) comme "adresse de" et astérisque (*) comme «valeur pointée par".

Notez qu'il ya des expressions avec des pointeurs P1 et P2, avec ou sans opérateur de déréférencement (*). Le sens d'une expression en utilisant l'opérateur de déréférencement (*) est très différent de celui qui ne fait pas: Lorsque cet opérateur précède le nom du pointeur, l'expression se réfère à la valeur étant signalé, alors que lorsque le nom d'un pointeur apparaît sans cet opérateur, il se réfère à la valeur du pointeur lui-même (c'est à dire l'adresse de ce que le pointeur pointe vers).

Une autre chose qui peut attirer votre attention est la ligne:



  

 int * p1, * p2;



Ceci déclare les deux pointeurs utilisés dans l'exemple précédent. Mais remarquez qu'il ya un astérisque (*) pour chaque indicateur, pour à la fois pour avoir le type int * (pointeur vers int).

Dans le cas contraire, le type de la seconde variable déclarée en ce que la ligne aurait été int (et non int *) en raison de relations priorité. Si nous avions écrit:



  

 int * p1, p2;



p1 serait en effet avoir le type int *, mais p2 aurait type int (espaces ne comptent pas du tout à cette fin). Cela est dû aux règles de priorité des opérateurs. Mais de toute façon, il suffit de se rappeler que vous devez mettre un astérisque par pointeur est suffisant pour la plupart des utilisateurs de pointeur.

Pointeurs et tableaux
La notion de réseau est très lié à celui de pointeur. En fait, l'identifiant d'un tableau est équivalent à l'adresse de son premier élément, comme un pointeur est équivalent à l'adresse du premier élément vers lequel il pointe, alors qu'en fait ils sont le même concept. Par exemple, en supposant que ces deux déclarations:

 1
 2


  

 int numbers [20]; int * p;



L'opération d'affectation suivante serait valable:



  

 p = numbers;



Après cela, p et chiffres seraient équivalentes et auraient les mêmes propriétés. La seule différence est que nous pourrions changer la valeur du pointeur p par un autre, alors que le nombre toujours pointer vers le premier des 20 éléments de type int avec lequel il a été défini. Par conséquent, contrairement p, qui est un pointeur ordinaire, le nombre est un tableau et un tableau peut être considéré comme un pointeur constant. Par conséquent, la répartition suivante ne serait pas valable:



  

 numbers = p;



Parce que le nombre est un tableau, de sorte qu'il fonctionne comme un pointeur constant, et nous ne pouvons attribuer des valeurs aux constantes.

En raison des caractéristiques de variables, toutes les expressions qui incluent des pointeurs dans l'exemple suivant sont parfaitement valables:

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17


  

 // more pointers #include <iostream> using namespace std; int main () { int numbers[5]; int * p; p = numbers; *p = 10; p++; *p = 20; p = &numbers[2]; *p = 30; p = numbers + 3; *p = 40; p = numbers; *(p+4) = 50; for ( int n=0; n<5; n++) cout << numbers[n] << ", " ; return 0; }

  

  10, 20, 30, 40, 50,



Dans le chapitre sur les tableaux, nous avons utilisé des crochets ([]) à plusieurs reprises afin de spécifier l'index d'un élément du tableau à laquelle nous voulions faire référence. Eh bien, ces opérateurs signe du support [] sont également un opérateur de déréférencement connu comme opérateur de décalage. Ils déréférencer la variable ils suivent juste que * le fait, mais ils ajoutent également le nombre entre parenthèses à l'adresse étant déréférencé. Par exemple:

 1
 2


  

 a[5] = 0; // a [offset of 5] = 0 *(a+5) = 0; // pointed by (a+5) = 0



Ces deux expressions sont équivalentes et valable à la fois si a est un pointeur ou si a est un tableau.

initialisation du pointeur
Lors de la déclaration des pointeurs on peut vouloir spécifier explicitement la variable qui nous voulons qu'ils pointent vers:

 1
 2


  

 int number; int *tommy = &number;



Le comportement de ce code est équivalent à:

 1
 2
 3


  

 int number; int *tommy; tommy = &number;



Quand une initialisation du pointeur prend place, nous sommes toujours affecter la valeur de référence à l'endroit où le pointeur (Tommy), jamais la valeur pointées (* tommy). Vous devez considérer que, au moment de déclarer un pointeur, l'astérisque (*) indique seulement qu'il s'agit d'un pointeur, ce n'est pas l'opérateur de déréférencement (bien que les deux utilisent le même signe: *). Rappelez-vous, ils sont deux fonctions différentes d'un signe. Ainsi, nous devons prendre soin de ne pas confondre le code précédent par:

 1
 2
 3


  

 int number; int *tommy; *tommy = &number;



qui est incorrect, et de toute façon n'aurait pas beaucoup de sens dans ce cas, si vous pensez cela.

Comme dans le cas des tableaux, le compilateur permet le cas particulier que nous voulons pour initialiser le contenu au cours de laquelle le pointeur avec des constantes au même moment que le pointeur est déclarée:



  

 const char * terry = "hello" ;



Dans ce cas, l'espace de la mémoire est réservée pour contenir «bonjour» et ensuite un pointeur sur le premier caractère de ce bloc de mémoire est attribué à Terry. Si nous imaginons que «bonjour» est stocké dans les emplacements de mémoire qui commencent à des adresses 1702, nous pouvons représenter la précédente déclaration en tant que:


Il est important d'indiquer que Terry contient la valeur 1702, et non pas «h» ni «bonjour», même si 1702 est en effet l'adresse de ces deux.

Le pointeur éponge pointe vers une séquence de caractères et peut être lu comme s'il s'agissait d'un tableau (rappelez-vous qu'un tableau est comme un pointeur constant). Par exemple, nous pouvons accéder au cinquième élément du tableau avec l'une de ces deux expression:

 1
 2


  

 *(terry+4) terry[4]



Les deux expressions ont une valeur de 'o' (le cinquième élément du tableau).

arithmétique de pointeur

Pour effectuer des opérations arithmétiques sur les pointeurs est un peu différent que de les conduire sur les types de données entiers réguliers. Pour commencer, seulement l'addition et de soustraction sont autorisés à mener avec eux, les autres n'ont pas de sens dans le monde des pointeurs. Mais les deux additions et des soustractions ont un comportement différent des pointeurs selon la taille du type de données à laquelle ils pointent.

Quand nous avons vu les différents types de données fondamentales, nous avons vu que certains occupent un espace plus ou moins que d'autres dans la mémoire. Par exemple, supposons que dans un compilateur donné pour une machine spécifique, char prend 1 octet, prend court 2 octets et prend beaucoup de temps 4.

Supposons que nous définissons trois pointeurs dans cette compilation:

 1
 2
 3


  

 char *mychar; short *myshort; long *mylong;



et que nous savons qu'ils pointent respectivement aux emplacements de mémoire 1000, 2000 et 3000.

Donc, si nous écrivons:

 1
 2
 3


  

 mychar++; myshort++; mylong++;



MyChar, comme vous pouvez vous attendre, contiendrait la valeur 1001. Mais ce n'est pas si évident, myshort contiendrait la valeur de 2002, et Mylong contiendrait 3004, même s'ils ont chacun été augmenté qu'une seule fois. La raison en est que lorsque vous ajoutez un à un pointeur que nous faisons pour pointer vers l'élément suivant du même type avec lequel il a été défini, et donc la taille en octets du type indiqué est ajouté au pointeur.


Ceci est applicable à la fois lors de l'ajout et la soustraction n'importe quel nombre à un pointeur. Il se passerait exactement la même chose si nous écrivons:

 1
 2
 3


  

 mychar = mychar + 1; myshort = myshort + 1; mylong = mylong + 1;



Tant l'augmentation (+ +) ou de diminution (-) Les opérateurs ont une plus grande priorité des opérateurs de l'opérateur de déréférencement (*), mais les deux ont un comportement particulier lorsqu'ils sont utilisés comme suffixe (l'expression est évaluée à la valeur qu'il avait avant d'être augmenté) . Par conséquent, l'expression suivante peut prêter à confusion:



  

 *p++



Parce + + a une plus grande priorité que *, cette expression est équivalente à * (p + +). Par conséquent, ce qu'il fait est d'augmenter la valeur de p (il pointe maintenant à l'élément suivant), mais parce que + + est utilisé comme postfix toute l'expression est évaluée comme la valeur pointée par la référence originale (l'adresse du pointeur a à, avant d'être accrue).

Notez la différence avec:

(* P) + +
Ici, l'expression aurait été évaluée à la valeur pointée par p augmente de un. La valeur de p (le pointeur lui-même) ne sera pas modifié (ce qui est modifié est ce qu'il est pointé par ce pointeur).

Si nous écrivons:



  

 *p++ = *q++;



Parce + + a une priorité plus élevée que *, p et q sont augmentés, mais parce que les deux opérateurs augmentation (+ +) sont utilisés comme postfix et non préfixe, la valeur attribuée à * p est * q avant de p et q sont augmentés . Et puis les deux sont augmentés. Il serait à peu près équivalent à:

 1
 2
 3


  

 *p = *q; ++p; ++q;



Comme toujours, je vous recommande d'utiliser des parenthèses () afin d'éviter des résultats inattendus et de donner plus de lisibilité du code.

Pointeurs sur des pointeurs
C + + permet l'utilisation de pointeurs qui pointent vers des pointeurs, que ceux-ci, à son tour, le point de données (ou même à d'autres pointeurs). Pour ce faire, il suffit d'ajouter un astérisque (*) pour chaque niveau de référence dans leurs déclarations:

 1
 2
 3
 4
 5
 6


  

 char a; char * b; char ** c; a = 'z' ; b = &a; c = &b;



Ce, en supposant que les emplacements de mémoire choisis aléatoirement pour chaque variable de 7230, 8092 et 10502, peut être représentée comme suit:


La valeur de chaque variable est écrite à l'intérieur de chaque cellule; sous les cellules sont leurs adresses respectives dans la mémoire.

La nouvelle chose dans cet exemple est variable c, qui peut être utilisé dans trois différents niveaux d'imbrication, chacun d'entre eux correspond à une valeur différente:

    c est de type char ** et une valeur de 8092
    * C est de type char * et une valeur de 7230
    C ** a type char et une valeur de 'z'


pointeurs void
Le type de vide de pointeur est un type particulier de pointeur. En C + +, nulle représente l'absence du type, de sorte pointeurs void sont des pointeurs qui pointent vers une valeur qui n'a pas de type (et donc une durée indéterminée et les propriétés de déréférencer indéterminés).

Ceci permet pointeurs void pour pointer vers n'importe quel type de données, à partir d'une valeur de nombre entier ou un flotteur à une chaîne de caractères. Mais en échange, ils ont une grande limitation: les données pointée par eux ne peuvent pas être directement déréférencé (ce qui est logique, puisque nous n'avons pas le type de déréférencer d'), et pour cette raison nous aurons toujours de jeter l'adresse dans le pointeur void à un autre type de pointeur qui pointe vers un type de données en béton avant que le déréférençant.

Une de ses utilisations peut-être de passer des paramètres génériques à une fonction:

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21


  

 // increaser #include <iostream> using namespace std; void increase ( void * data, int psize) { if ( psize == sizeof ( char ) ) { char * pchar; pchar=( char *)data; ++(*pchar); } else if (psize == sizeof ( int ) ) { int * pint; pint=( int *)data; ++(*pint); } } int main () { char a = 'x' ; int b = 1602; increase (&a, sizeof (a)); increase (&b, sizeof (b)); cout << a << ", " << b << endl; return 0; }

  

  Y 1603



sizeof est un opérateur intégré dans le langage C + + qui renvoie la taille en octets de son paramètre. Pour les types de données non dynamiques, cette valeur est une constante. Ainsi, par exemple, sizeof (char) est 1, car le type char est un octet.

Pointeur null
Un pointeur est un pointeur null régulière de n'importe quel type de pointeur ayant une valeur spéciale qui indique qu'il ne pointe pas vers une référence valable ou l'adresse de la mémoire. Cette valeur est le résultat de type coulée de la valeur entière zéro à n'importe quel type de pointeur.

 1
 2


  

 int * p; p = 0; // p has a null pointer value



Ne confondez pas les pointeurs nuls avec des pointeurs void. Un pointeur NULL est une valeur que n'importe quel pointeur peut prendre de représenter ce qu'il pointe vers «nulle part», alors qu'un pointeur void est un type spécial de pointeur qui peut pointer vers quelque part sans un type spécifique. On se réfère à la valeur stockée dans le pointeur lui-même et l'autre pour le type de données il pointe.

Pointeurs de fonctions
C + + permet des opérations avec des pointeurs vers les fonctions. L'utilisation typique de cela est pour faire passer une fonction comme argument d'une autre fonction, puisque ceux-ci ne peuvent pas être passés déréférencé. Pour déclarer un pointeur vers une fonction que nous devons déclarer comme le prototype de la fonction, sauf que le nom de la fonction est enfermée entre parenthèses () et un astérisque (*) est inséré avant le nom:

 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


  

 // pointer to functions #include <iostream> using namespace std; int addition ( int a, int b) { return (a+b); } int subtraction ( int a, int b) { return (ab); } int operation ( int x, int y, int (*functocall)( int , int )) { int g; g = (*functocall)(x,y); return (g); } int main () { int m,n; int (*minus)( int , int ) = subtraction; m = operation (7, 5, addition); n = operation (20, m, minus); cout <<n; return 0; }

  

  8



Dans l'exemple, moins est un pointeur vers une fonction qui a deux paramètres de type int. Il est immédiatement affecté à pointer vers la fonction de soustraction, le tout en une seule ligne:



  

 int (* minus)( int , int ) = subtraction;
Publié par Drupal Study