Pointeurs en C: quand utiliser l'esperluette et l'astérisque?

298

Je commence juste avec des pointeurs et je suis un peu confus. Je sais &signifie l'adresse d'une variable et qui *peut être utilisée devant une variable de pointeur pour obtenir la valeur de l'objet pointé par le pointeur. Mais les choses fonctionnent différemment lorsque vous travaillez avec des tableaux, des chaînes ou lorsque vous appelez des fonctions avec une copie de pointeur d'une variable. Il est difficile de voir un schéma logique à l'intérieur de tout cela.

Quand dois-je utiliser &et *?

Pieter
la source
5
Veuillez illustrer comment vous voyez les choses fonctionner parfois différemment. Sinon, nous devons deviner ce qui vous embrouille.
Drew Dormann
1
D'accord avec Neil Butterworth. Je vais probablement obtenir beaucoup plus d'informations pour l'obtenir directement dans un livre, et l'explication de K&R est assez claire.
Tom
145
Je ne suis pas d'accord avec vous tous qui dites que ce n'est pas une bonne idée de poser ce type de questions sur SO. SO est devenu la ressource numéro 1 lors de la recherche sur Google. Vous n'accordez pas assez de crédit à ces réponses. Lisez la réponse de Dan Olson. Cette réponse est vraiment perspicace et incroyablement utile pour les débutants. RTFMest inutile, et franchement très grossier. Si vous n'avez pas le temps de répondre, soyez respectueux envers ceux qui prennent le temps de répondre à ces questions. J'aimerais pouvoir @ ceci à "anon" mais évidemment il / elle n'a pas le temps de contribuer de manière significative.
SSH ce
18
Ce que SSH a dit est absolument vrai. Certaines personnes crient "Just Google it", mais de nos jours c'est l'inverse: "Regardez simplement StackOverflow." Cette question est utile pour de nombreuses personnes. (D'où les votes positifs et aucun vote négatif.)
MC Emperor

Réponses:

610

Vous avez des pointeurs et des valeurs:

int* p; // variable p is pointer to integer type
int i; // integer value

Vous transformez un pointeur en une valeur avec *:

int i2 = *p; // integer i2 is assigned with integer value that pointer p is pointing to

Vous transformez une valeur en pointeur avec &:

int* p2 = &i; // pointer p2 will point to the address of integer i

Edit: dans le cas des tableaux, ils sont traités comme des pointeurs. Si vous les considérez comme des pointeurs, vous les utiliserez *pour obtenir les valeurs à l'intérieur d'eux comme expliqué ci-dessus, mais il existe également une autre manière plus courante d'utiliser l' []opérateur:

int a[2];  // array of integers
int i = *a; // the value of the first element of a
int i2 = a[0]; // another way to get the first element

Pour obtenir le deuxième élément:

int a[2]; // array
int i = *(a + 1); // the value of the second element
int i2 = a[1]; // the value of the second element

L' []opérateur d'indexation est donc une forme spéciale de l' *opérateur, et il fonctionne comme ceci:

a[i] == *(a + i);  // these two statements are the same thing
Dan Olson
la source
2
Pourquoi cela ne fonctionne-t-il pas? int aX[] = {3, 4}; int *bX = &aX;
Pieter
5
Les tableaux sont spéciaux et peuvent être convertis en pointeurs de manière transparente. Cela met en évidence une autre façon de passer d'un pointeur à une valeur, je vais l'ajouter à l'explication ci-dessus.
Dan Olson
4
Si je comprends bien ... l'exemple int *bX = &aX;ne fonctionne pas car le aXretourne déjà l'adresse de aX[0](c'est-à-dire &aX[0]), donc &aXobtiendrait l'adresse d'une adresse qui n'a aucun sens. Est-ce correct?
Pieter
6
Vous avez raison, bien qu'il y ait des cas où vous voudrez peut-être réellement l'adresse de l'adresse. Dans ce cas, vous le déclareriez comme int ** bX = & aX, mais c'est un sujet plus avancé.
Dan Olson
3
@Dan, étant donné int aX[] = {3,4};, int **bX = &aX;est une erreur. &aXest de type "pointeur vers le tableau [2] de int", et non "pointeur vers le pointeur vers int". Plus précisément, le nom d'un tableau n'est pas traité comme un pointeur sur son premier élément pour unaire &. Vous pouvez faire:int (*bX)[2] = &aX;
Alok Singhal
28

Il y a un modèle en traitant des tableaux et des fonctions; c'est juste un peu difficile à voir au début.

Lorsque vous traitez des tableaux, il est utile de se rappeler ce qui suit: lorsqu'une expression de tableau apparaît dans la plupart des contextes, le type de l'expression est implicitement converti de "tableau à N éléments de T" en "pointeur vers T", et sa valeur est définie pour pointer vers le premier élément du tableau. Les exceptions à cette règle sont lorsque l'expression de tableau apparaît comme un opérande soit le &ou les sizeofopérateurs, ou lorsqu'elle est une chaîne littérale utilisé comme un initialiseur dans une déclaration.

Ainsi, lorsque vous appelez une fonction avec une expression de tableau comme argument, la fonction recevra un pointeur, pas un tableau:

int arr[10];
...
foo(arr);
...

void foo(int *arr) { ... }

C'est pourquoi vous n'utilisez pas l' &opérateur pour les arguments correspondant à "% s" dans scanf():

char str[STRING_LENGTH];
...
scanf("%s", str);

En raison de la conversion implicite, scanf()reçoit une char *valeur qui pointe vers le début du strtableau. Cela est vrai pour toute fonction appelée avec une expression de tableau comme argument (à peu près n'importe laquelle des str*fonctions *scanfet des *printffonctions, etc.).

En pratique, vous n'appellerez probablement jamais une fonction avec une expression de tableau à l'aide de l' &opérateur, comme dans:

int arr[N];
...
foo(&arr);

void foo(int (*p)[N]) {...}

Un tel code n'est pas très courant; vous devez connaître la taille du tableau dans la déclaration de fonction, et la fonction ne fonctionne qu'avec des pointeurs vers des tableaux de tailles spécifiques (un pointeur vers un tableau de 10 éléments de T est d'un type différent d'un pointeur vers un tableau de 11 éléments de T).

Lorsqu'une expression de tableau apparaît en tant qu'opérande de l' &opérateur, le type de l'expression résultante est "pointeur vers un tableau à N éléments de T", ou T (*)[N], qui est différent d'un tableau de pointeurs ( T *[N]) et un pointeur vers le type de base ( T *).

Lorsque vous traitez des fonctions et des pointeurs, la règle à retenir est la suivante: si vous souhaitez modifier la valeur d'un argument et la refléter dans le code appelant, vous devez passer un pointeur sur la chose que vous souhaitez modifier. Encore une fois, les tableaux jettent un peu d'une clé à molette dans les travaux, mais nous traiterons d'abord les cas normaux.

N'oubliez pas que C transmet tous les arguments de fonction par valeur; le paramètre formel reçoit une copie de la valeur du paramètre réel et toute modification du paramètre formel n'est pas reflétée dans le paramètre réel. L'exemple courant est une fonction d'échange:

void swap(int x, int y) { int tmp = x; x = y; y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(a, b);
printf("after swap: a = %d, b = %d\n", a, b);

Vous obtiendrez la sortie suivante:

avant l'échange: a = 1, b = 2
après l'échange: a = 1, b = 2

Les paramètres formels xet ysont des objets distincts de aet b, de sorte que les modifications apportées à xet yne sont pas reflétées dans aet b. Puisque nous voulons modifier les valeurs de aet b, nous devons leur passer des pointeurs vers la fonction swap:

void swap(int *x, int *y) {int tmp = *x; *x = *y; *y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("after swap: a = %d, b = %d\n", a, b);

Maintenant, votre sortie sera

avant l'échange: a = 1, b = 2
après l'échange: a = 2, b = 1

Notez que, dans la fonction de swap, nous ne changeons pas les valeurs de xet y, mais les valeurs de quoi xet y pointer vers . Écrire dans *xest différent de l'écrire dans x; nous ne mettons pas à jour la valeur en xsoi, nous obtenons un emplacement xet mettons à jour la valeur à cet emplacement.

Cela est également vrai si nous voulons modifier une valeur de pointeur; si on écrit

int myFopen(FILE *stream) {stream = fopen("myfile.dat", "r"); }
...
FILE *in;
myFopen(in);

alors nous modifions la valeur du paramètre d'entrée stream, pas ce stream vers quoi pointe , donc changer streamn'a aucun effet sur la valeur de in; pour que cela fonctionne, nous devons passer un pointeur au pointeur:

int myFopen(FILE **stream) {*stream = fopen("myFile.dat", "r"); }
...
FILE *in;
myFopen(&in);

Encore une fois, les tableaux jettent un peu d'une clé à molette dans les œuvres. Lorsque vous passez une expression de tableau à une fonction, ce que la fonction reçoit est un pointeur. En raison de la définition de l'indexation de tableau, vous pouvez utiliser un opérateur d'indice sur un pointeur de la même manière que vous pouvez l'utiliser sur un tableau:

int arr[N];
init(arr, N);
...
void init(int *arr, int N) {size_t i; for (i = 0; i < N; i++) arr[i] = i*i;}

Notez que les objets du tableau ne peuvent pas être affectés; c'est-à-dire que vous ne pouvez pas faire quelque chose comme

int a[10], b[10];
...
a = b;

vous devez donc être prudent lorsque vous traitez avec des pointeurs vers des tableaux; quelque chose comme

void (int (*foo)[N])
{
  ...
  *foo = ...;
}

ne fonctionnera pas.

John Bode
la source
16

Mettre tout simplement

  • &signifie l' adresse de , vous verrez que dans les espaces réservés aux fonctions pour modifier la variable de paramètre comme dans C, les variables de paramètre sont passées par valeur, en utilisant les moyens esperluette pour passer par référence.
  • *signifie le déréférencement d'une variable de pointeur, ce qui signifie obtenir la valeur de cette variable de pointeur.
int foo(int *x){
   *x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(&y);  // Now y is incremented and in scope here
   printf("value of y = %d\n", y); // output is 6
   /* ... */
}

L'exemple ci-dessus montre comment appeler une fonction fooen utilisant le passage par référence, comparer avec ceci

int foo(int x){
   x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(y);  // Now y is still 5
   printf("value of y = %d\n", y); // output is 5
   /* ... */
}

Voici une illustration de l'utilisation d'une déréférence

int main(int argc, char **argv){
   int y = 5;
   int *p = NULL;
   p = &y;
   printf("value of *p = %d\n", *p); // output is 5
}

Ce qui précède illustre comment nous avons obtenu l' adresse de y et l' avons attribuée à la variable pointeur p. Ensuite, nous déréférençons p en attachant le *à l'avant de celui-ci pour obtenir la valeur de p, c'est-à-dire *p.

t0mm13b
la source
10

Oui, cela peut être assez compliqué car le *est utilisé à de nombreuses fins différentes en C / C ++.

Si *apparaît devant une variable / fonction déjà déclarée, cela signifie soit que:

  • a) *donne accès à la valeur de cette variable (si le type de cette variable est un type pointeur, ou surchargé l' *opérateur).
  • b) *a la signification de l'opérateur multiplier, dans ce cas, il doit y avoir une autre variable à gauche de la*

Si *apparaît dans une déclaration de variable ou de fonction, cela signifie que cette variable est un pointeur:

int int_value = 1;
int * int_ptr; //can point to another int variable
int   int_array1[10]; //can contain up to 10 int values, basically int_array1 is an pointer as well which points to the first int of the array
//int   int_array2[]; //illegal, without initializer list..
int int_array3[] = {1,2,3,4,5};  // these two
int int_array4[5] = {1,2,3,4,5}; // are identical

void func_takes_int_ptr1(int *int_ptr){} // these two are identical
void func_takes int_ptr2(int int_ptr[]){}// and legal

Si &apparaît dans une déclaration de variable ou de fonction, cela signifie généralement que cette variable est une référence à une variable de ce type.

Si &apparaît devant une variable déjà déclarée, il renvoie l'adresse de cette variable

De plus, vous devez savoir que lorsque vous passez un tableau à une fonction, vous devrez toujours également transmettre la taille du tableau de ce tableau, sauf lorsque le tableau est quelque chose comme une chaîne de caractères terminée par 0 (tableau char).

smerlin
la source
1
@akmozo s / func_takes int_ptr2 / func_takes_int_ptr2 / (espace invalide)
PixnBits
4

Lorsque vous déclarez une variable de pointeur ou un paramètre de fonction, utilisez *:

int *x = NULL;
int *y = malloc(sizeof(int)), *z = NULL;
int* f(int *x) {
    ...
}

NB: chaque variable déclarée a besoin de sa propre *.

Lorsque vous souhaitez prendre l'adresse d'une valeur, utilisez &. Lorsque vous souhaitez lire ou écrire la valeur dans un pointeur, utilisez *.

int a;
int *b;
b = f(&a);
a = *b;

a = *f(&a);

Les tableaux sont généralement traités comme des pointeurs. Lorsque vous déclarez un paramètre de tableau dans une fonction, vous pouvez tout aussi facilement déclarer qu'il s'agit d'un pointeur (cela signifie la même chose). Lorsque vous passez un tableau à une fonction, vous passez en fait un pointeur vers le premier élément.

Les pointeurs de fonction sont les seules choses qui ne suivent pas tout à fait les règles. Vous pouvez prendre l'adresse d'une fonction sans utiliser &, et vous pouvez appeler un pointeur de fonction sans utiliser *.

Jay Conrod
la source
4

Je regardais toutes les explications verbeuses, alors je me suis tourné vers une vidéo de l'Université de Nouvelle-Galles du Sud pour le sauvetage.Voici l'explication simple: si nous avons une cellule qui a une adresse xet une valeur 7, la façon indirecte de demander l'adresse de la valeur 7est &7et la façon indirecte de demander la valeur à l' adresse xest *x.Donc (cell: x , value: 7) == (cell: &7 , value: *x)façon à examiner .Une autre il: est Johnassis à 7th seat.La *7th seatindiqueraient Johnet &Johndonnera address/ emplacement du 7th seat. Cette explication simple m'a aidé et j'espère qu'elle aidera également les autres. Voici le lien pour l'excellente vidéo: cliquez ici.

Voici un autre exemple:

#include <stdio.h>

int main()
{ 
    int x;            /* A normal integer*/
    int *p;           /* A pointer to an integer ("*p" is an integer, so p
                       must be a pointer to an integer) */

    p = &x;           /* Read it, "assign the address of x to p" */
    scanf( "%d", &x );          /* Put a value in x, we could also use p here */
    printf( "%d\n", *p ); /* Note the use of the * to get the value */
    getchar();
}

Module complémentaire : initialisez toujours le pointeur avant de les utiliser.Si ce n'est pas le cas, le pointeur pointera vers quoi que ce soit, ce qui pourrait entraîner un plantage du programme car le système d'exploitation vous empêchera d'accéder à la mémoire qu'il sait que vous ne possédez pas. p = &x;, nous attribuons au pointeur un emplacement spécifique.


la source
3

En fait, vous avez raison, il n'y a rien de plus à savoir :-)

Je voudrais juste ajouter les bits suivants:

  • les deux opérations sont des extrémités opposées du spectre. &prend une variable et vous donne l'adresse, *prend une adresse et vous donne la variable (ou le contenu).
  • les tableaux "se dégradent" en pointeurs lorsque vous les passez aux fonctions.
  • vous pouvez réellement avoir plusieurs niveaux sur l'indirection ( char **psignifie que pc'est un pointeur vers un pointeur vers un char.

Quant aux choses qui fonctionnent différemment, pas vraiment:

  • les tableaux, comme déjà mentionné, se dégradent en pointeurs (vers le premier élément du tableau) lorsqu'ils sont passés aux fonctions; ils ne conservent pas les informations de taille.
  • il n'y a pas de chaînes en C, juste des tableaux de caractères qui, par convention, représentent une chaîne de caractères terminée par un caractère zéro ( \0).
  • Lorsque vous passez l'adresse d'une variable à une fonction, vous pouvez déréférencer le pointeur pour changer la variable elle-même (normalement les variables sont passées par valeur (sauf pour les tableaux)).
paxdiablo
la source
3

Je pense que vous êtes un peu confus. Vous devriez lire un bon tutoriel / livre sur les pointeurs.

Ce tutoriel est très bon pour les débutants (explique clairement ce &que *sont et sont). Et oui, n'oubliez pas de lire le livre Pointers in C de Kenneth Reek.

La différence entre &et *est très claire.

Exemple:

#include <stdio.h>

int main(){
  int x, *p;

  p = &x;         /* initialise pointer(take the address of x) */
  *p = 0;         /* set x to zero */
  printf("x is %d\n", x);
  printf("*p is %d\n", *p);

  *p += 1;        /* increment what p points to i.e x */
  printf("x is %d\n", x);

  (*p)++;         /* increment what p points to i.e x */
  printf("x is %d\n", x);

  return 0;
}
Prasoon Saurav
la source
1

Ok, on dirait que votre message a été édité ...

double foo[4];
double *bar_1 = &foo[0];

Voyez comment vous pouvez utiliser le &pour obtenir l'adresse du début de la structure du tableau? Le suivant

Foo_1(double *bar, int size){ return bar[size-1]; }
Foo_2(double bar[], int size){ return bar[size-1]; }

fera la même chose.

blés
la source
La question a été étiquetée C et non C ++.
Prasoon Saurav
1
Et j'ai supprimé le cout incriminé <<
Wheaties