Je me bats avec le signe de pointeur *, je trouve cela très déroutant dans son utilisation dans les déclarations et les expressions.
Par exemple:
int *i; // i is a pointer to an int
Mais quelle est la logique derrière la syntaxe? Que veut dire * juste avant le je? Prenons l'exemple suivant. S'il te plaît, corrige-moi là où je me trompe:
char **s;
char *(*s); // added parentheses to highlight precedence
Et c'est là que je perds le fil. Le * s entre parenthèses signifie: s est un pointeur? Mais un pointeur vers quoi? Et que signifie * hors des parenthèses: un pointeur sur ce qui pointe?
Donc, la signification de ceci est: Le pointeur indiquant ce que s indique est un pointeur sur un caractère?
Je suis à perte. Le signe * est-il interprété différemment dans les déclarations et les expressions? Si oui, comment est-il interprété différemment? Où vais-je mal?
La règle de déclaration en c est la suivante: vous le déclarez tel que vous l’utilisez.
char *p
signifie que vous avez besoin de *p
pour obtenir le caractère,
char **p
signifie que vous avez besoin de **p
pour obtenir le caractère.
Prends ça comme ça:
int *i
signifie que la valeur sur laquelle je pointe est un entier.
char **p
signifie que p est un pointeur qui est lui-même un pointeur sur un caractère.
int i; //i is an int.
int *i; //i is a pointer to an int
int **i;//i is a pointer to a pointer to an int.
Le signe * est-il interprété différemment dans les déclarations et les expressions?
Oui. Ils sont complètement différents. dans une déclaration * est utilisé pour déclarer des pointeurs. Dans une expression, unary * est utilisé pour déréférencer un pointeur (ou en tant qu'opérateur de multiplication binaire)
Quelques exemples:
int i = 10; //i is an int, it has allocated storage to store an int.
int *k; // k is an uninitialized pointer to an int.
//It does not store an int, but a pointer to one.
k = &i; // make k point to i. We take the address of i and store it in k
int j = *k; //here we dereference the k pointer to get at the int value it points
//to. As it points to i, *k will get the value 10 and store it in j
Les déclarations en C sont centrées sur l'expression, ce qui signifie que la forme de la déclaration doit correspondre à la forme de l'expression dans le code exécutable.
Par exemple, supposons que nous ayons un pointeur sur un entier nommé p
. Nous voulons accéder à la valeur entière désignée par p
, donc nous dereference le pointeur, comme suit:
x = *p;
Le type de expression*p
est int
; par conséquent, la déclaration de p
prend la forme
int *p;
Dans cette déclaration, int
est le spécificateur de type type et *p
est le déclarateur. Le déclarant introduit le nom de l'objet en cours de déclaration (p
), ainsi que des informations de type supplémentaires non fournies par le spécificateur de type. Dans ce cas, l'information de type supplémentaire est que p
est un type de pointeur. La déclaration peut être lue comme suit: "p
est de type pointeur sur int
" ou "p
est un pointeur sur type int
". Je préfère utiliser le deuxième formulaire, d'autres préfèrent le premier.
C'est un accident de la syntaxe C et C++ que vous pouvez écrire cette déclaration sous la forme int *p;
ou int* p;
. Dans les deux cas, il est analysé comme suit: int (*p);
- en d'autres termes, le *
est toujours associé au nom de la variable et non au spécificateur de type.
Supposons maintenant que nous avons un tableau de pointeurs sur int
et que nous voulons accéder à la valeur pointée par le ième élément du tableau. Nous inscrivons dans le tableau et déréférencons le résultat, comme suit:
x = *ap[i]; // parsed as *(ap[i]), since subscript has higher precedence
// than dereference.
De nouveau, le type de expression*ap[i]
est int
, la déclaration de ap
est donc
int *ap[N];
où le déclarant *ap[N]
signifie que ap
est un tableau de pointeurs sur int
.
Et juste pour faire passer le message, supposons maintenant que nous avons un pointeur sur un pointeur sur int
et que nous voulons accéder à cette valeur. De nouveau, nous déférenceons le pointeur, puis nous déréférencons ce résultat pour obtenir la valeur entière:
x = **pp; // *pp deferences pp, then **pp dereferences the result of *pp
Puisque le type de l'expression **pp
est int
, la déclaration est
int **pp;
Le déclarant **pp
indique que pp
est un pointeur sur un autre pointeur sur int
.
La double indirection apparaît souvent, généralement lorsque vous souhaitez modifier une valeur de pointeur que vous transmettez à une fonction, telle que:
void openAndInit(FILE **p)
{
*p = fopen("AFile.txt", "r");
// do other stuff
}
int main(void)
{
FILE *f = NULL;
...
openAndInit(&f);
...
}
Dans ce cas, nous voulons que la fonction mette à jour la valeur de f
; pour ce faire, nous devons passer un pointeur sur f
. Puisque f
est déjà un type de pointeur (FILE *
), cela signifie que nous passons un pointeur sur un FILE *
, d'où la déclaration de p
en tant que FILE **p
. N'oubliez pas que expression*p
dans openAndInit
fait référence au même objet que l'expression f
dans main
.
Dans les déclarations et les expressions, []
et ()
ont une priorité supérieure à celle de *
unaire. Par exemple, *ap[i]
est interprété comme *(ap[i])
; l'expression ap[i]
est un type de pointeur et le *
déréférence ce pointeur. Ainsi, ap
est un tableau de pointeurs. Si vous voulez déclarer un pointeur dans un tableau, vous devez explicitement regrouper le *
avec le nom du tableau, comme suit:
int (*pa)[N]; // pa is a pointer to an N-element array of int
et lorsque vous souhaitez accéder à une valeur du tableau, vous devez respecter pa
avant d'appliquer l'indice:
x = (*pa)[i];
De même avec des fonctions:
int *f(); // f is a function that returns a pointer to int
...
x = *f(); // we must dereference the result of f() to get the int value
int (*f)(); // f is a pointer to a function that returns an int
...
x = (*f)(); // we must dereference f and execute the result to get the int value
Ma méthode préférée pour analyser les déclarateurs compliqués est la règle clockwise-spiral .
Fondamentalement, vous commencez à partir de l'identifiant et suivez une spirale dans le sens des aiguilles d'une montre. Voir le lien pour savoir exactement comment il est utilisé.
L'article ne mentionne pas deux choses:
1- Vous devez séparer le spécificateur de type (int, char, etc.) du déclarant, analyser le déclarateur, puis ajouter le spécificateur de type.
2- Si vous rencontrez des crochets qui désignent un tableau, veillez à lire également les crochets suivants (le cas échéant).
int * i
signifie que i est un pointeur sur int (lecture arrière, * en tant que pointeur) .char **p
et char *(*p)
signifient tous deux un pointeur sur un pointeur sur char.
Voici quelques autres exemples
int* a[3]
// a est un tableau de 3 pointeurs sur int
int (*a)[3]
// a est un pointeur sur un tableau de 3 ints
Vous avez la réponse dans vos questions.
En effet, une étoile double est utilisée pour indiquer un pointeur à l'autre.
La déclaration * in signifie que la variable est un pointeur sur une autre variable/constante. ce qui signifie qu'il peut contenir l'adresse de la variable du type. Par exemple: char *c;
signifie que c peut contenir l'adresse à un caractère, alors que int *b
signifie que b peut contenir l'adresse d'un int, le type de la référence est important car, dans l'arithmétique des pointeurs, pointer + 1
est en réalité pointer + (1 * sizeof(*pointer))
.
Le * dans l'expression signifie "la valeur stockée dans l'adresse" donc si c
est un pointeur sur un caractère, alors *c
est le caractère spécifique.
char *(*s);
signifie que s est un pointeur sur un pointeur sur char. s ne contient donc pas l'adresse d'un caractère, mais l'adresse de la variable qui contient l'adresse d'un caractère.
Déclarer &a
signifie qu'il pointe sur *i
. Après tout, il s’agit d’un pointeur sur *int
. Un entier est à pointer *i
. Mais si consider j = *k
est le pointeur sur le pointeur this, cela signifie que &k
sera la valeur de k
et k
aura un pointeur sur *int
.
voici un peu d'information
variable pointer
declaring &a p
reading/ a *p
processing