web-dev-qa-db-fra.com

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

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

Quand devrais-je utiliser & et *?

256
Pieter

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 utiliserez * pour obtenir les valeurs qu'ils contiennent, comme expliqué ci-dessus, mais il existe également un autre moyen plus commun d'utilisation de 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

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

a[i] == *(a + i);  // these two statements are the same thing
560
Dan Olson

Il y a une tendance 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 les points suivants: lorsqu’une expression de tableau apparaît dans la plupart des contextes, le type de l’expression est converti implicitement de "tableau de N-éléments de T" en "pointeur sur T" et sa valeur est définie. pour 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 utilisé en tant qu'initialiseur dans une déclaration.

Ainsi, lorsque vous appelez une fonction avec une expression de tableau en tant qu'argument, la fonction recevra un pointeur, et non un tableau:

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

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

C'est pourquoi vous ne pas utilisez 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 au début du tableau str. Ceci est vrai pour toute fonction appelée avec une expression de tableau en tant qu'argument (à peu près n'importe laquelle des fonctions str*, *scanf et *printf, etc.).

En pratique, vous n’appelerez 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 les pointeurs sur les tableaux de tailles spécifiques (un pointeur sur un tableau à 10 éléments de T est d'un type différent d'un pointeur sur un tableau à 11 éléments de T).

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

Lors de l'utilisation de fonctions et de 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 d'appel, vous devez passer un pointeur sur l'élément que vous souhaitez modifier. Encore une fois, les tableaux jettent un peu une clé à molette dans les travaux, mais nous allons d'abord traiter les cas normaux.

Rappelez-vous que C passe tous 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 apportées au paramètre formel ne sont pas reflétées dans le paramètre actuel. L’exemple le plus 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 le résultat suivant:

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

Les paramètres formels x et y sont des objets distincts de a et b. Par conséquent, les modifications apportées à 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 passer 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 changeons pas les valeurs de x et y, mais les valeurs de ce que x et ypointez sur. L'écriture dans *x diffère de l'écriture dans x; nous ne mettons pas à jour la valeur dans x elle-même, nous obtenons un emplacement de x et mettons à jour la valeur à cet emplacement.

Ceci 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, et non ce que streampointe vers, donc changer stream n'a aucun effet sur la 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 une clé à molette dans les travaux. Lorsque vous transmettez une expression de tableau à une fonction, la fonction reçoit 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 le utilisez 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 peuvent ne pas être assigné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 sur des tableaux. quelque chose comme

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

ne fonctionnera pas.

24
John Bode

Mettre tout simplement

  • & signifie le address-of, vous verrez que dans les espaces réservés pour les fonctions permettant de modifier la variable de paramètre comme en C, les variables de paramètre sont passées par valeur, en utilisant l'esperluette et les moyens de 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 principal (int argc, char ** argv) {
 int y = 5; 
 foo (& y); // Maintenant y est incrémenté et a la portée ici 
 Printf ("valeur de y =% d\n", y); // le résultat est 6 
/* ... */
} 

L'exemple ci-dessus montre comment appeler une fonction foo à l'aide de la méthode de référence par comparaison.

 int foo (int x) {
 x ++; 
} 
 
 int principal (int argc, char ** argv) {
 int y = 5; 
 foo (y); // Maintenant y est toujours 5 
 Printf ("valeur de y =% d\n", y); // le résultat est 5 
/* ... */
} 

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

 int principal (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 illustre comment nous avons obtenu le address-ofy et l'avons affecté à la variable de pointeur p. Ensuite, nous dereferencep en attachant le * à l'avant de celui-ci pour obtenir la valeur de p, c'est-à-dire *p.

11
t0mm13b

Oui, cela peut être assez compliqué puisque le * est utilisé à différentes fins 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 s'il est surchargé par l'opérateur *).
  • b) * a la signification de l'opérateur multiply; dans ce cas, il doit y avoir une autre variable à gauche du *

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, il retourne l'adresse de cette variable.

De plus, vous devez savoir que lorsque vous passez un tableau à une fonction, vous devez toujours transmettre la taille de tableau de ce tableau ainsi, sauf lorsque le tableau ressemble à une chaîne de caractères à terminaison 0 (tableau de caractères).

9
smerlin

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

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 ne sont généralement traités que 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 réalité 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 *.

4
Jay Conrod

Je parcourais toutes les explications, alors je me suis tourné vers une vidéo de sauvetage de l'université de New South Wales. Voici l'explication simple: si nous avons une cellule qui a l'adresse x et la valeur 7, le moyen indirect de demander l'adresse de valeur 7 est &7 et le moyen indirect de demander une valeur à l'adresse x est *x. Donc (cell: x , value: 7) == (cell: &7 , value: *x) .Autre façon de regarder: John est assis à 7th seat. Le *7th seat pointera sur John et &John donnera address/emplacement du 7th seat. Cette explication simple m'a aidé et espère que cela 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();
}

Add-on: Toujours initialiser le pointeur avant de les utiliser.Si ce n'est pas le cas, le pointeur pointera sur tout ce qui pourrait provoquer un crash 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 au pointeur un emplacement spécifique.

3
user6288471

En fait, vous l'avez tout simplement, il n'y a plus rien à 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 son contenu).
  • les tableaux "se dégradent" en pointeurs lorsque vous les passez à des fonctions.
  • vous pouvez réellement avoir plusieurs niveaux sur l'indirection (char **p signifie que p est un pointeur sur un pointeur sur un char.

Pour que les choses fonctionnent différemment, pas vraiment:

  • comme mentionné précédemment, les tableaux 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, mais juste des tableaux de caractères qui, par convention, représentent une chaîne de caractères terminée par un caractère nul (\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 passées par valeur (sauf pour les tableaux)).
3
paxdiablo

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 commencer (explique clairement ce que & et * sont). Et oui, n'oubliez pas de lire le livre de 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;
}
3
Prasoon Saurav

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.

1
wheaties