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

Je commence juste avec des pointeurs, et je suis légèrement confus. Je sais & signifie que l'adresse d'une variable et que * peut être utilisé devant un pointeur de variable 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 modèle de logique dans tout cela.

Quand dois-je utiliser & et *?

219
demandé sur Pieter 2010-01-19 18:41:06

9 réponses

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 valeur avec *:

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

Vous transformez une valeur en un pointeur avec &:

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

Modifier: Dans le cas des tableaux, ils sont traités comme des pointeurs. Si vous les considérez comme des pointeurs, vous 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 la seconde é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

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

a[i] == *(a + i);  // these two statements are the same thing
486
répondu Dan Olson 2015-11-21 11:53:13

Il y a un modèle lorsqu'il s'agit de tableaux et de fonctions; c'est juste un peu difficile à voir au début.

Lorsqu'il s'agit de 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 "n-element array of T" à "pointer to 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 de & ou sizeof opérateurs, ou lorsqu'il s'agit d'un littéral de chaîne utilisé comme 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'est pas utiliser le & 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 un char * valeur qui pointe vers le début de la str tableau. Cela est vrai pour toute fonction appelée avec une expression de tableau comme argument (à peu près toutes les fonctions str*, *scanf et *printf, etc.).

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

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

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

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

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

Lorsqu'il s'agit de fonctions et de pointeurs, la règle à retenir est: si vous voulez changer la valeur d'un argument et l'avoir traduit dans le code appelant, vous devez passer un pointeur vers la chose que vous voulez modifier. Encore une fois, les tableaux jettent un peu d'une clé de singe dans les travaux, mais nous traiterons d'abord des cas normaux.

Rappelez-vous que C passe tous les arguments de fonction par valeur; le paramètre formel reçoit une copie de la valeur dans le paramètre réel, et toute modification du paramètre formel ne sont pas reflétées dans le paramètre réel. L'exemple courant est un échange fonction:

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:

before swap: a = 1, b = 2
after swap: a = 1, b = 2

Les paramètres formels x et y sont des objets distincts de a et b, de sorte que les modifications de x et y ne sont pas reflétés dans a et b. Puisque nous voulons modifier les valeurs de a et b, nous devons passer à pointeurs de la 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);

Maintenant, votre sortie sera

before swap: a = 1, b = 2
after swap: a = 2, b = 1

Notez que, dans la fonction swap, nous ne modifions pas les valeurs de x et y, mais les valeurs de ce que x et y point de. L'écriture dans {[41] } est différente de l'écriture dans x; nous ne mettons pas à jour la valeur dans x elle-même, nous obtenons un emplacement à partir de x et mettons à jour la valeur dans cet emplacement.

Ceci est également vrai si nous voulons modifier une valeur de pointeur; si nous écrivons

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

Ensuite, nous modifions la valeur du paramètre d'entrée stream, pas quoi stream points à , donc changer stream n'a aucun effet sur le valeur de in; pour que cela fonctionne, nous devons passer un pointeur sur le pointeur:

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

Encore une fois, les tableaux jettent un peu de clé de singe 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'subscription 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 de tableau peuvent ne pas être affectés; c'est-à-dire que vous ne pouvez pas faire quelque chose comme

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

Donc, vous voulez être prudent lorsque vous avez affaire à des pointeurs vers des tableaux; quelque chose comme

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

Ça ne marchera pas.

22
répondu John Bode 2018-09-17 15:58:25

Mettez simplement

  • & signifie l'adresse de , vous verrez que dans les espaces réservés pour les fonctions de modifier la variable de paramètre comme dans C, Les variables de paramètre sont passées par valeur, en utilisant l'Esperluette signifie passer par référence.
  • * signifie le déréférence 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 foo en utilisant par référence, comparez 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'un déréférencement de

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'a assigné à la variable de pointeur p. Ensuite, nous déréférence p en attachant le * à l'avant de celui-ci pour obtenir la valeur de p, c'est-à-dire *p.

9
répondu t0mm13b 2015-08-03 10:08:31

Ouais cela peut être assez compliqué puisque 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 de pointeur, ou surchargé l'opérateur *).
  • b) *, a la signification de l'opérateur produit, dans ce cas, il y a 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 aswell 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 indentical

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

Si & apparaît dans une variable ou une déclaration 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, elle renvoie l'adresse de cette variable

De plus, vous devez savoir que lorsque vous passez un tableau à une fonction, vous doivent également passer la taille du tableau de ce tableau, sauf lorsque le tableau est quelque chose comme un CString terminé à 0 (tableau char).

9
répondu smerlin 2016-06-04 21:47:39

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

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

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

Lorsque vous voulez prendre l'adresse d'une valeur, utilisez &. Lorsque vous voulez 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 sur 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 l'aide de &, et vous pouvez appeler un pointeur de fonction sans utiliser *.

4
répondu Jay Conrod 2010-01-19 15:51:43

En Fait, vous l'avez compris, il n'y a rien de plus que vous devez savoir :-)

, je voudrais juste ajouter les morceaux 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 de contenu).
  • les tableaux "se dégradent" en pointeurs lorsque vous les transmettez aux fonctions.
  • vous pouvez en fait avoir plusieurs niveaux d'indirection (char **p signifie que p est un pointeur vers une pointeur sur 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 transmis 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 le pointeur pour modifier la variable elle-même (normalement les variables sont passées par valeur (sauf pour les tableaux)).
3
répondu paxdiablo 2010-01-19 15:52:09

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 & et * sont). Et oui n'oubliez pas de lire le livre Pointeurs en C, Kenneth Lattes.

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

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;
}
3
répondu Prasoon Saurav 2010-01-19 15:55:28

Je regardais à travers toutes les explications verbeuses donc à la place 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 pour adresse x et la valeur 7, la manière détournée de demander l'adresse de la valeur de 7 est &7 et la manière détournée de demander la valeur à l'adresse x est *x.Donc (cell: x , value: 7) == (cell: &7 , value: *x) .Une autre façon de regarder dedans: John se trouve à 7th seat.Le {[10] } pointera sur John et &John donnera address / Emplacement du 7th seat. Cette simple explication m'a aidé et j'espère que ça aidera d'autres personnes. 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();
}

Add-on: Toujours initialiser le pointeur avant de les utiliser.Sinon, le pointeur pointera vers n'importe quoi, 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.Mais en mettant simplement p = &x;, nous assignons le pointeur a emplacement spécifique.

2
répondu 2016-07-14 14:34:34

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

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

Voir comment vous pouvez utiliser le & pour obtenir l'adresse du début de la structure du tableau? Ce qui suit

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.

1
répondu wheaties 2010-01-19 15:52:06