web-dev-qa-db-fra.com

Pour boucle avec pointeur en C

Je ne comprends pas ce que fait un pointeur dans la boucle for. Que fait le *p faire dans la boucle suivante?

char str[128] = "Some Text";
char *p;

for (p = str; *p /*what does this mean?*/; p++)
{
    // Code
}

Je comprends le reste, mais pourquoi n'est-ce pas *p comme p > 3 ou quelque chose comme ça?
Pourquoi est-il seul?
Pourquoi est-il écrit de cette façon?

40
MeChris

Dans un contexte booléen tel que la condition d'une boucle for, chaque expression en C est évaluée à vrai (non nul) ou faux (zéro).

Vous voulez que la boucle for se termine lorsqu'elle atteint la fin de la chaîne.

En C, chaque chaîne se termine par le caractère '\0', ce qui est pratiquement 0. Ainsi, lorsque la boucle for atteint la fin de la chaîne, *p correspond à '\0', lequel est 0, qui prend la valeur false, ce qui termine la boucle for.

53
errikos

La boucle for se terminera si ce qui se trouve entre les deux ; dans l'instruction est zéro (faux). *p déréférence p et renvoie les points char, p vers. Selon Dennis Ritchie "C traite les chaînes comme des tableaux de caractères conventionnellement terminés par un marqueur" . Ce marqueur est le caractère nul avec une valeur (ASCII) de zéro. Donc, ceci pour la boucle:

for (p = str; *p; p++)

est équivalent à ceux-ci

for (p = str; *p != '\0'; p++)
for (p = str; *p != 0; p++)
for (p = str; p[0] != '\0'; p++)

Un autre nom pour le caractère de fin nul est sentinelle ou selon Donald Knuth "valeur fictive" (Art de la programmation informatique, volume 1). Voici un diagramme de la chaîne str, les index (décalages depuis le début) de chaque caractère et les valeurs de chaque index:

enter image description here

Pour être complet et après une demande dans les commentaires, voici ce que le débogueur voit dans le bloc de mémoire que str occupe:

0x00007fffffffe6a0:
  0x53 0x6f 0x6d 0x65 0x20 0x54 0x65 0x78 0x74 0x00 0x00 0x00 0x00 0x00 0x00 0x00
     S    o    m    e         T    e    x    t
  1. La valeur hexadécimale à la première ligne est l'adresse (64 bits) de ce bloc de mémoire. C'est là que p pointe au début de la boucle for.
  2. Sur la 2ème ligne, vous voyez les valeurs hexadécimales des lettres dans votre chaîne. Vous pouvez voir une table ASCII ici . Le dernier caractère de votre chaîne est t avec une valeur hexadécimale de 0x74. Après cela, vous avez le caractère nul de la chaîne 0x00. Ensuite, vous voyez quelques caractères nuls supplémentaires car j'ai construit en mode débogage et le compilateur initialisé à zéro. Normalement, vous verriez des ordures (valeurs apparemment aléatoires)
  3. Sur la 3ème ligne, j'ai ajouté les caractères de votre chaîne pour référence

Je comprends que vous êtes sur une courbe d'apprentissage abrupte en ce moment avec des pointeurs en C, mais finalement vous pourrez dire "Je C le point"

31
Manos Nikolaidis

Cela pourrait être réécrit comme ceci

for (p = str; *p != '\0'; p++)
{
    // Code
}

En C, une chaîne doit toujours se terminer par un caractère nul, qui est le même que '\ 0' ou 0.

19
KeylorSanchez

Avant de plonger, je voudrais énoncer une règle simple en C concernant une expression

Lorsque C requiert la valeur booléenne d'une expression, une valeur de false est déduite lorsque l'expression se compare à zéro , et une valeur true sinon. Autrement dit, chaque fois que l'on écrit

if(expr)

expr est n'importe quelle expression, le compilateur agit essentiellement comme s'il avait été écrit comme

if((expr) != 0)  

Venons-en maintenant à votre question:

Qu'est-ce que le *p faire dans la boucle suivante?

En C, les chaînes se terminent par un caractère nul '\0'.

enter image description here

Chaque caractère a un équivalent décimal. Cette '\0' est un caractère d'échappement ASCII . L'équivalent décimal de '\0' est 0.

Ainsi, l'expression *p en boucle vérifie simplement que l'équivalent décimal du caractère à l'adresse mémoire pointée par p est soit un zéro soit un non-zéro. Lorsque p atteint la fin de la chaîne et trouve le premier '\0' caractère, l'expression *p résultats 1  une valeur nulle. Un zéro signifie false en C. Cela équivaut à tester *p != '\0' ou *p != 0 Comme indiqué ci-dessus.

Voilà comment cela fonctionne:

enter image description here


1 Lorsque *p évalue alors la valeur de *p est récupéré de la mémoire. Cette valeur est la valeur de l'expression *p.

13
haccks

Permet de l'analyser à sec mais en profondeur!

Ou comme dirait D. Ritchie: Faisons-le avec la puissance du langage d'assemblage et la commodité du ... langage d'assemblage.


J'essaierai d'expliquer tous les aspects nécessaires en référençant la norme ISO/IEC: 9899 (c'est moi qui souligne) - la norme C99. (Le style du message est motivé par la phrase de Donald Knuth "La science est ce que nous comprenons assez bien pour expliquer à un ordinateur. L'art est tout ce que nous faisons.")

Tout d'abord, examinons ce que la boucle for est censée faire exactement!

Se référant à ISO/CEI: 9899 6.8.5 "Déclarations d'itération"

Sémantique

4 Une instruction d'itération entraîne l'exécution répétée d'une instruction appelée corps de boucle jusqu'à ce que l'expression de contrôle soit égale à 0 .

Jusqu'à présent, rien de nouveau, je suppose, alors allons-y:

6.8.5.3 L'instruction for

1 L'instruction for ( clause-1 ; expression-2 ; expression-3 ) statement

se comporte comme suit: L'expression expression-2 est l'expression de contrôle qui est évaluée avant chaque exécution du corps de boucle. ...

Nous savons donc maintenant que le corps (dans votre cas // Code) Sera exécuté tant que la valeur préalablement évaluée de votre *p N'est pas nulle.

... L'expression expression-3 est évaluée comme une expression vide après chaque exécution du corps de boucle. [...]

Alors maintenant, nous savons, (je suppose que déterrer la définition de p++ N'est pas nécessaire?!) Que pour chaque itération, des incréments de p, donc il peut y avoir un changement dans *p .

Le point suivant n'est pas lié, mais je l'ajoute car cela rend la partie sémantique de for complète et c'est bien à savoir puisque c'est la raison pour laquelle for(;;) est une boucle inf .

2 (---) La clause-1 et l'expression-3 peuvent être omises. Une expression-2 omise est remplacée par une constante non nulle.

Ok, c'est la partie sèche mais enrichie d'informations de ce que fait la boucle for dans votre cas.

Passons maintenant à l'arithmétique des pointeurs:

6.5.6 Opérateurs additifs

Contraintes

2 De plus, soit les deux opérandes doivent être de type arithmétique, soit un opérande doit être un pointeur vers un type d'objet et l'autre doit être de type entier. ( L'incrémentation équivaut à ajouter 1. )

Donc, dans votre cas, vous ajoutez 1 (entier) au type "pointeur vers un objet".

Ce qui équivaut à augmenter l'adresse de la taille de son type pointé comme indiqué dans cette image de tomislav kostic :

CC BY-SA 3.0 by tomislav kostic

Voyons maintenant ce que fait *p.

6.5.3.2 Opérateurs d'adresse et d'indirection

Contraintes

[...]

2 L'opérande de l'opérateur unaire * doit être de type pointeur.

Sémantique

[...]

4 L'opérateur unaire * indique l'indirection. Si l'opérande pointe vers une fonction, le résultat est un désignateur de fonction; si il pointe vers un objet, le résultat est une valeur l désignant l'objet . Si l'opérande a le type "pointeur vers type", le résultat a le type "type". Si une valeur non valide a été affectée au pointeur, le comportement de l'opérateur unaire * n'est pas défini.

C'est encore un peu sec1 mais pour une meilleure compréhension, cela peut être inversé par:

6.5.2.1 Indice de tableau

[...]

Sémantique

2 Une expression suffixe suivie d'une expression entre crochets [] est une désignation en indice d'un élément d'un objet tableau. La définition de l'opérateur d'indice [] est que E1 [E2] est identique à (* ((E1) + (E2))) .

Donc, *((p)+(0)) ce qui est (puisque p+0 Est identique à p ... évident) est égal à p[0], Ne fait rien d'autre que d'évaluer p.

Et puisque nous savons que expression-2 D'une boucle for interrompt l'itération si elle évalue 0, Nous pouvons dire que c'est la même chose que p[0] != 0.

Maintenant, la dernière étape

Regardons simplement l'ami du codeur C;  JSSCA ... Non, attendez ... notre ami a été appelé ... ASCII Maintenant que cela est clarifié, nous pouvons comprendre ce que le 0 représente.

C'est le jeton NULL qui, en C, désigne la fin d'une chaîne.


Si concluant:

Tout cela, c'est:

Itération du corps de cette boucle for, jusqu'à ce que p pointe réellement vers l'adresse, où l'objet est évalué au jeton "fin de chaîne".

Ou:

Laissez p parcourir la chaîne jusqu'à la fin.


Et maintenant, juste pour me citer; Quelque chose que vous ne devriez jamais oublier:
(c'est moi qui souligne .....)

ne variable est déclarée via un déclarant ( spécificateur de type) qui précède l'identifiant qui nomme un objet lvalue qui peut être évalué à sa valeur

Ce n'est ni plus ni moins!


1C'est ce que j'ai promis! ;)

13
dhein

Le * p Haiku

Poétiquement, j'ai essayé de représenter la lutte de * p dans la boucle:

Brave C * p (rogrammers)

Dans la boucle du whilederness

Le NUL les arrêtera

Ceci est un poème haïku, il se compose de trois lignes, la première et la dernière ligne ayant 5 syllabes, et la ligne médiane en 7. Un autre exemple de @Samidamaru (un maître poète Haiku, voir le commentaire ci-dessous): Le premier p est égal à str, Alors p est incrémenté, Jusqu'à ce que * p soit NUL.


Un peu de pop

enter image description here

Ambassadrice de Hour of Code , Jessica Alba


Que fait le * p dans la boucle?

Suivant les conseils imaginaires de Jessica (qui cite D. Knuth (1)), nous allons essayer de voir la signification de * p dans la boucle for:

for (p = str; *p; p++)

Pour atteindre cet objectif, nous examinons d'abord comment l'opérateur unaire "*" fonctionne en C: "L'opérateur unaire * est l'opérateur d'indirection ou de renvoi; lorsqu'il est appliqué à un pointeur, il accède à l'objet vers lequel le pointeur pointe. "(B. Kernighan et D. Ritchie (2))

Donc * p est simplement la valeur pointée par p:

enter image description here

1.1 Un regard plus attentif sur la boucle for

La boucle for est composée de trois instructions:

  1. p = str
  2. * p
  3. p ++

Dans 1. nous assignons le pointeur au tableau str à p. En C, les affectations suivantes ont le même effet:

p = &str[0];
p = str; 

"Par définition, la valeur d'une variable ou d'une expression de type tableau est l'adresse de l'élément zéro du tableau" (K & R (2)). De plus, nous avons que "En évaluant a [i], C le convertit immédiatement en * (a + i). …. il s'ensuit que & a [i] et a + i sont identiques "(K&R (2)). Si nous mettons i = 0, nous obtenons les affectations ci-dessus.

Nous pouvons maintenant affirmer qu'au début de la boucle for, p pointe vers le premier élément de str.

1.2 Le cœur de la question

Passons au point 2., au cœur de votre question. La deuxième expression de la boucle contrôle la condition de sortie: l'instruction "* p" est évaluée et si est fausse la sortie de boucle. Cela signifie que "* p" est équivalent à "* p! = 0" ou en mots: lorsque la valeur pointée par p est zéro, quittez.

Maintenant, pour comprendre quand * p est zéro, nous rappelons que le tableau str a été initialisé comme suit:

char str[128] = "Some Text";

et: "toutes les constantes de chaîne contiennent un caractère de terminaison nul (\ 0) comme dernier caractère" (gnu-manual). Ainsi, la chaîne réellement stockée en mémoire a un\0 à la fin: "Some Text\0".

Dans la troisième instruction, p ++, le pointeur p est avancé à l'élément suivant du tableau str, ainsi, à la 9ème itération * p est devenu 0 (ou\0, NULL, NUL, voir la réponse de @Joe) et la boucle se termine.

1.3 Voir à croire

Une image vaut mille mots, voici une représentation graphique de la boucle:

enter image description here

1.4 Un autre exemple: la même utilisation de * p dans un autre exemple

Dans l'extrait de code suivant * p est utilisé de la même manière mais dans une boucle while:

#include <stdio.h>
int main() {
    char str[128] = "We all scream for ice cream!";
    char *p = str;
    // here we see again the loop exit condition *p == '\0'
    while(*p) {
        printf("%c", *p);
        p++;
    }
    printf("\n");
}

Que la force soit avec toi!


Références

(1) Vol. I, Algorithmes fondamentaux, section 1.1 (1968)

(2) Le langage de programmation C p. 94-99

9
terence hill

Il profite du fait que le terminateur de la chaîne (éventuellement trouvé par celui de la boucle) sera un ASCII NUL, qui est un zéro, ce qui arrive également à évaluer à false , mettant ainsi fin à la boucle for.

Il convient de noter la différence et la similitude entre 0, faux, NULL et ASCII NUL. Voir cette question: Quelle est la différence entre NULL, '\ 0' et

4
Joe

J'ai essayé de satisfaire les désirs de l'attributaire de primes qui ont été mentionnés à plusieurs reprises. Pour rester simple, j'ai limité ma réponse à trois sections de trois lignes chacune, et parce que (comme The Bellman dit dans sa règle des trois) "Ce que je vous dis trois fois est vrai " (le thème de cette réponse).

Technique

La vérité de votre boucle for la termine lorsque l'expression *p correspond à 0 et cette évaluation est effectuée avant chaque itération de la boucle, notez qu'en C 0 est faux et tout le reste est vrai - c'est une définition très très étendue dans d'autres mondes!

La variable de pointeur p est initialisée une fois, pour pointer vers le début du tableau avec p = str et p est incrémenté à la fin de chaque itération, donc *p accède aux éléments successifs du tableau à chaque itération.

L'expression *p sera donc évalué à 0 (faux) lorsque l'élément de tableau lu par *p est le 0 ou '\0' terminateur qui signale la fin d'une "chaîne" C, mais vous ne pouvez pas voir ce zéro dans l'initialisation de str car il est fourni automatiquement par le compilateur.

Lyrique

Expressions de vérité

Ne sont pas compris par les jeunes

Lire Ritchie et Knuth

Lunatique

Jessica Alba est une belle actrice très bien informée, ayant pris en compte les vérités de l'observation du développement de la technologie informatique, comme le révèlent ces citations:

"Tous les cinq ans, j'ai l'impression d'être une personne complètement différente."

"Tout dépend de votre produit et de ses performances. Soit il fonctionne, soit il ne fonctionne pas."

4
Weather Vane

Il y a longtemps, dans un PDP très, très loin, les ressources étaient rares, les noms étaient courts: i pour index, p pour pointeur plairait aux premiers programmeurs Jedi.

Des tests implicites ont révélé la vérité dans l'espace de conditions for. Un seul * Était tout ce qu'ils avaient tapé, faisant confiance à p et le poussant à la fin des chaînes.

À ce jour, ils utilisent la boucle for(e = s;*e;e++) la plus familière et élégante pour défier l'empire C++ et ses cohortes de ctors, dtors et vils itérateurs. Des bits et des octets nus contre des modèles, des exceptions et des types obscurs, seuls les courageux osent encore que C se batte, et dévoilent le void *.

4
chqrlie

Un haïku:

WHY   for (p=str; *p;        p++)
IS    for (p=str; p[0] != 0; p++)
THINK for (i=0;   str[i];    ++i)

MODIFIÉ

Voici quelques détails supplémentaires:

La deuxième ligne de code du "haïku" est équivalente à la première ligne. Le message d'origine demande "qu'est-ce que cela signifie" dans un commentaire de code. La deuxième ligne illustre la réponse par équivalence. * p signifie p [0]. La deuxième clause de la boucle for se soucie de savoir si p [0] est ou non équivalent à zéro.

La troisième ligne de code du "haïku" est une ligne de code qui peut être utilisée de manière conceptuelle: vous pouvez penser que le fonctionnement de la ligne d'origine se comporte comme la troisième ligne.

3
synthetel

String in str

Comme vous pouvez le voir sur l'image, la boucle for commence par *pp pointe le str. À ce stade, *p A S.

En bouclant en continu le for, il atteint finalement str[9] Qui a '\0' Ce qui signifie NULL.

À ce stade, l'instruction de condition *p Dans for (p = str; *p; p++) est égale à NULL, de sorte que le code se détachera de la boucle for.

2
J. Sy

Il s'agit de la condition de la boucle.
Si cette condition n'est pas remplie, la boucle n'est plus exécutée.
*p déréférence le pointeur p et renvoie le caractère pointé dans la chaîne str.
La chaîne de style C str se termine par la valeur \0.
La boucle parcourt chaque caractère (en utilisant p) jusqu'à ce que la condition ne soit pas remplie.

En C, une valeur de 0 ou \0 est comme la signification de false, c'est-à-dire que la condition n'est pas remplie.
Toute autre valeur est similaire à la signification de true, c'est-à-dire que la condition est remplie.

En bref, p itère sur chaque caractère dans str et s'arrête dès qu'il atteint le caractère de terminaison de chaîne \0.

Pourquoi ne pas utiliser p au lieu de *p?
Parce que p est un pointeur et contient une adresse. Il est parfois difficile, voire impossible, d'utiliser uniquement l'arithmétique des adresses. Ce n'est pas une bonne pratique et rend le code difficile à lire.
*p est le pointeur déréférencé et contient la valeur vers laquelle p pointe. Dans ce cas, il est facile d'utiliser les valeurs vers lesquelles p pointe, car vous savez que la chaîne se termine par un \0. Comme condition (if, while, etc.) *p est équivalent à *p != '\0'.

2
Ely

Tout d'abord, vous devez saisir un concept de pointeur, comme son nom l'indique. Le pointeur contient l'adresse de la variable.

    int var=0;
    int *p;
    int p=&var;

dans ce code p est un pointeur et printf("%d",p); affiche l'adresse de la variable var et printf("%d",*p); affiche la valeur de la variable var qui dans cet exemple est 0.

Deuxièmement, vous devez comprendre le fonctionnement des tableaux. Les tableaux sont une sorte de structure de données qui peut stocker une taille fixe [~ # ~] séquentielle [~ # ~] collection d'éléments du même type.

        int array[3]={9,8,7};
        printf("%d",array[0]); //prints what is on 1st position,9
        printf("%d",array[1]); //prints what is on 2nd position,8
        printf("%d",array[2]); //prints what is on 3rd position,7

les opérateurs [] ne sont qu'un travail convivial avec les tableaux. Les trois dernières lignes de code peuvent être remplacées par les lignes suivantes (et elles feront de même):

        printf("%d",*(array+0)); //prints what is on 1st position,9 
        printf("%d",*(array+1)); //prints what is on 2nd position,8
        printf("%d",*(array+2)); //prints what is on 3rd position,7

array est le pointeur sur le premier élément du tableau (contient l'adresse du premier élément du tableau), donc en le déréférençant, nous obtenons la valeur du premier élément, par exemple *array. Nous savons que les tableaux sont séquentiels ce qui signifie que array+1 Pointe vers le deuxième élément du tableau, donc en déréférençant cela, vous obtenez la valeur du deuxième élément, par exemple *(array+1), etc. Memory segment of array

Il en va de même pour les chaînes car elles sont un tableau de caractères, sauf que la chaîne a "\ 0" (caractère nul) à la fin des chaînes.

    char str[128] = "Some Text";
    char *p;

    for (p = str; *p; p++)
    {
        printf("%c",*p);
    }

Ce programme imprime la chaîne str.

p = str // attribue l'adresse du premier caractère de la chaîne str à p, nous ne perdrons pas une piste du premier caractère de la chaîne, nous utilisons donc p pas str pour itérer

*p // cette expression signifie *p!=0 Donc c'est vrai jusqu'à ce que vous arriviez à la fin de la chaîne, rappelez-vous que '0' en ascii a une valeur entière 48

p++ // à la fin du bloc pour vous ajoutez +1 à p pour obtenir l'adresse du prochain caractère

1
Tomislav Kostic

Cela peut s'expliquer comme suit:

for( initialization ; Conditional Expression ; expression3)
{
    Code here will execute while 2nd Expression(Conditional Expression) is true
    true means non-zero value
    '\0' is equivelant to 0,so when *p equal '\0' : loop will terminate
}
0
machine_1