Pointeurs dans C: quand utiliser l’esperluette et l’astérisque?

Je commence juste avec des pointeurs, et je suis un peu confus. Je connais & signifie l’adresse d’une variable et celle-ci peut être utilisée devant une variable de pointeur pour obtenir la valeur de l’object 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 du pointeur d’une variable. Il est difficile de voir un schéma de logique dans tout cela.

Quand devrais-je utiliser & et * ?

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 un 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 utiliserez * pour obtenir les valeurs à l’intérieur d’eux comme expliqué ci-dessus, mais il y a aussi 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 

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 

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.

Lorsque vous traitez avec des tableaux, il est utile de vous rappeler ce qui suit: lorsqu’une expression de tableau apparaît dans la plupart des contextes, le type de l’expression est implicitement converti du “tableau N-element de T” en “pointeur sur T” pointer sur le premier élément du tableau. Les exceptions à cette règle sont lorsque l’expression de tableau apparaît en tant qu’opérande des opérateurs & ou sizeof , 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 et non 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 valeur char * qui pointe vers le début du tableau str . Cela est vrai pour toute fonction appelée avec une expression de tableau comme argument (à peu près toutes les fonctions str* fonctions *scanf et *printf , etc.).

En pratique, vous n’appellerez probablement jamais une fonction avec une expression de tableau utilisant 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 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 à l’opérateur & , le type de l’expression résultante est “pointeur vers un tableau de N-éléments de T” ou T (*)[N] , différent d’un tableau de pointeurs ( T *[N] ) et un pointeur sur le type de base ( T * ).

En ce qui concerne les fonctions et les pointeurs, la règle à retenir est la suivante: si vous souhaitez modifier la valeur d’un argument et le faire 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 de clé dans les œuvres, mais nous traiterons les cas normaux en premier.

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 les modifications apscopes au paramètre formel ne sont pas reflétées dans le paramètre réel. L’exemple commun est une fonction d’échange:

 void swap(int x, int y) { int tmp = x; x = y; y = x; } ... 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 échange: a = 1, b = 2
 après échange: a = 1, b = 2

Les parameters formels x et y sont des objects distincts de a et b , donc les modifications de x et y ne sont pas reflétées dans a et b . Puisque nous voulons modifier les valeurs de a et b , nous devons leur transmettre des pointeurs à 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 échange: a = 1, b = 2
 après échange: 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 indiquent . L’écriture dans *x est différente de l’écriture sur x ; nous ne mettons pas à jour la valeur dans x lui-même, nous obtenons un emplacement à partir de x et mettons à jour la valeur à cet emplacement.

Cela est également vrai si l’on veut modifier une valeur de pointeur; si on écrit

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

Nous modifions alors la valeur du stream parameters d’entrée, et non pas le stream vers lequel le stream dirigé . Le changement de stream n’a donc aucun effet sur la valeur de in ; pour que cela fonctionne, il faut 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é 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’indice 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 objects de 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 faire attention lorsque vous utilisez des pointeurs vers des tableaux; quelque chose comme

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

ne marchera pas

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 l’esperluette signifie passer par référence.
  • * désigne la déréférence d’une variable de pointeur, c’est-à-dire la valeur de cette variable de pointeur.
 int foo (int * x) {
    * x ++
 }

 int main (int argc, char ** argv) {
    int y = 5;
    foo (& y);  // Maintenant, y est incrémenté et dans la scope ici
    printf ("valeur de y =% d \ n", y);  // le résultat est 6
    / * ... * /
 }

L’exemple ci-dessus montre comment appeler une fonction foo en utilisant la référence par référence,

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

 int main (int argc, char ** argv) {
    int y = 5;
    foo (y);  // Maintenant, il en rest 5
    printf ("valeur de y =% d \ n", y);  // le résultat est 5
    / * ... * /
 }

Voici une illustration de l’utilisation d’un déréférencement

 int main (int argc, char ** argv) {
    int y = 5;
    int * p = NULL;
    p = & y;
    printf ("valeur de * p =% d \ n", * p);  // le résultat est 5
 }

Ce qui précède montre comment nous avons obtenu l’ adresse de y et l’a assigné à la variable de pointeur p . Ensuite, nous déréférons p en attachant le * à l’avant pour obtenir la valeur de p , c’est-à-dire *p .

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:

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

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 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, elle renvoie l’adresse de cette variable

De plus, vous devez savoir que lors du passage d’un tableau à une fonction, vous devrez toujours passer la taille du tableau de ce tableau, sauf lorsque le tableau ressemble à une chaîne de caractères terminée par 0 (tableau de caractères).

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 sa propre *.

Lorsque vous souhaitez 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 simplement 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 utiliser &, et vous pouvez appeler un pointeur de fonction sans utiliser *.

En fait, vous l’avez, il n’y a rien d’autre à savoir 🙂

Je voudrais juste append 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 transmettez à des fonctions.
  • vous pouvez réellement avoir plusieurs niveaux sur l’indirection ( char **p signifie que p est un pointeur sur un pointeur vers un caractère.

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 à des 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 transmettez l’adresse d’une variable à une fonction, vous pouvez dé-référencer le pointeur pour modifier la variable elle-même (normalement, les variables sont transmises par valeur (sauf pour les tableaux)).

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

Ce tutoriel est très bien pour les débutants (explique clairement ce que & 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  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 ie x */ printf("x is %d\n", x); (*p)++; /* increment what p points to ie x */ printf("x is %d\n", x); return 0; } 

Je regardais toutes les explications verbeuses et 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 avec l’adresse x et la valeur 7 , la valeur 7 est &7 et la manière indirecte de demander la valeur à l’adresse x est *x .So (cell: x , value: 7) == (cell: &7 , value: *x) . se trouve à la 7th seat . La *7th seat indiquera John et &John donnera l’ address / l’emplacement de la 7th seat . Cette simple explication m’a aidé et j’espère que cela aidera également les autres. Voici le lien pour l’excellente vidéo: cliquez ici.

Voici un autre exemple:

 #include  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 indiquera quelque chose, ce qui pourrait entraîner un blocage du programme car le système d’exploitation vous empêchera d’accéder à la mémoire dont vous n’êtes pas propriétaire. p = &x; , nous assignons un point spécifique au pointeur.

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.