Une boucle 'for' à l'intérieur d'une boucle 'for' peut-elle utiliser le même nom de variable de compteur?

107

Puis-je utiliser la même variable de compteur pour une forboucle à l'intérieur d'une forboucle?

Ou les variables vont-elles s'influencer mutuellement? Le code suivant doit-il utiliser une variable différente pour la deuxième boucle, telle que j, ou est-ce ibien?

for(int i = 0; i < 10; i++)
{
  for(int i = 0; i < 10; i++)
  {
  }
}
Uclydde
la source
72
C'est déroutant - cela ne me passerait pas dans une revue de code. Mais c'est légitime. Il existe deux variables différentes appelées i, avec des portées différentes. À utiliser -Wshadowavec GCC pour signaler automatiquement ces problèmes.
Jonathan Leffler
15
Je suis surpris que ce -Wshadowne soit pas inclus dans -Wall.
gauche autour du
5
@leftaroundabout -Wshadowmet également en garde contre l'observation des variables globales, ce qui pourrait facilement devenir ennuyeux dans les projets plus importants.
Cubic
9
@leftaroundabout encore plus surprenant, -Wextrane comprend même pas -Wshadow. Je suppose que c'est assez courant dans certains projets, ou que certains développeurs gcc aiment l'observation en tant que style de codage, pour justifier d'être laissé de côté comme ça.
hyde
5
@leftaroundabout Faire écho à ce que Cubic a dit, -Wshadowa un taux de faux positifs horrible, le rendant complètement inutile. La portée existe pour une raison, et l'observation n'est a priori pas problématique. Maintenant -Wshadow-local(note: non -Wshadow=local ) est très différent. Mais malheureusement, GCC a jusqu'à présent refusé de l'inclure dans le coffre (bien qu'il semble y avoir des fourchettes de GCC qui l'incluent).
Konrad Rudolph

Réponses:

140

Vous pouvez utiliser le même nom (identifiant). Ce sera un objet différent. Ils ne s’affecteront pas. À l'intérieur de la boucle interne, il n'y a aucun moyen de faire référence à l'objet utilisé dans la boucle externe (à moins que vous ne preniez des dispositions spéciales pour cela, comme en lui fournissant un pointeur).

C'est généralement un mauvais style, est sujet à confusion et doit être évité.

Les objets ne sont différents que si l'intérieur est défini séparément, comme avec le que int ivous avez montré. Si le même nom est utilisé sans définir un nouvel objet, les boucles utiliseront le même objet et interféreront les unes avec les autres.

Eric Postpischil
la source
3
utiliser for (i) et for (j) imbriqué, et à l'intérieur de i ++, augmentera la variable de la boucle externe. Cependant, ce que vous dites est correct si vous utilisez le même identifiant dans les deux boucles, car ce sont des variables de portée différente.
KYL3R
3
@BloodGain: «Object» est un terme technique utilisé dans le standard C. Je l'ai utilisé délibérément ici.
Eric Postpischil
1
@EricPostpischil: Ah, je vois, oui. Je n'étais pas au courant de cette définition dans la norme, et j'avais peur que cela induise en erreur les nouveaux programmeurs (car c'est très clairement une question pour les débutants), car C n'a pas d '"objets" au sens où nous utilisons généralement le terme. Je le vois dans la norme C11, et maintenant je suis curieux de savoir s'il était défini de cette façon avant C11.
Bloodgain
1
C'était. C'est 3,14 dans la norme C99, au lieu de 3,15. Donc pas d'excuse de ma part. Cela m'apprendra à vous interroger <: - |
Bloodgain
1
Plus généralement: rien ne vous empêche de réutiliser un nom de variable dans une portée imbriquée. Sauf, bien sûr, la peur du châtiment de Dieu pour avoir écrit un code déroutant.
Isaac Rabinovitch
56

Tout d'abord, c'est absolument légal: le code se compilera et s'exécutera en répétant le corps de la boucle imbriquée 10 × 10 = 100 fois. Le compteur de boucle ià l'intérieur de la boucle imbriquée masquera le compteur de la boucle externe, de sorte que les deux compteurs seraient incrémentés indépendamment l'un de l'autre.

Étant donné que l'extérieur iest masqué, le code à l'intérieur du corps de la boucle imbriquée aurait accès uniquement à la valeur de ide la boucle imbriquée, pas à ipartir de la boucle externe. Dans les situations où la boucle imbriquée n'a pas besoin d'accéder au icode externe, un tel code pourrait être parfaitement justifiable. Cependant, cela est susceptible de créer plus de confusion chez ses lecteurs, c'est donc une bonne idée d'éviter d'écrire un tel code pour éviter les «responsabilités de maintenance».

Remarque: même si les variables de compteur des deux boucles ont le même identifiant i, elles restent deux variables indépendantes, c'est-à-dire que vous n'utilisez pas la même variable dans les deux boucles. L'utilisation de la même variable dans les deux boucles est également possible, mais le code serait difficile à lire. Voici un exemple:

for (int i = 1 ; i < 100 ; i++) {
    for ( ; i % 10 != 0 ; i++) {
        printf("%02d ", i);
    }
    printf("%d\n", i);
}

Les deux boucles utilisent maintenant la même variable. Cependant, il faut un certain temps pour comprendre ce que fait ce code sans le compiler ( démo );

dasblinkenlight
la source
4
Puisque la question est formulée comme "en utilisant la même variable de compteur", je voudrais également souligner que l'observation n'a lieu que lorsque la redéfinition se produit. Omettre le intsur la boucle for interne, c'est-à-dire utiliser en fait la même variable de compteur, fera que la boucle externe ne s'exécutera qu'une seule fois, car la boucle interne partira i == 10. C'est trivial, mais je pense que cela apporte des éclaircissements compte tenu de la façon dont la question a été posée
Easton Bornemeier
@EastonBornemeier Vous avez raison, j'ai pensé que je devrais aborder la question de "la même variable" dans le corps de la réponse. Je vous remercie!
dasblinkenlight
@EricPostpischil "Ombrage variable" est un terme officiel, avec sa propre page sur wikipedia . J'ai cependant mis à jour la réponse pour qu'elle soit conforme au libellé de la norme. Je vous remercie!
dasblinkenlight
2
@dasblinkenlight: En fait, j'ai eu un spasme cérébral à propos de la direction, et le nom intérieur masque le nom extérieur. Mon commentaire précédent était erroné à cet égard. Mes excuses. (Cependant, c'est dans un sens anglais, pas dans un sens officiel - Wikipedia n'est pas une publication officielle pour C ou la programmation en général, et je ne connais aucun office ou organisme faisant autorité qui définit le terme.) La norme C utilise «Cacher», alors c'est préférable.
Eric Postpischil
Sympa, surtout avec l'exemple "même variable". Cependant, je pense que " le code se compilera et s'exécutera comme prévu " serait mieux car "le code se compilera et s'exécutera comme quelqu'un qui l'a lu attentivement et comprend toutes les ramifications attendues" ... comme vous le dites, un code comme celui-ci " est susceptible de créer plus de confusion chez ses lecteurs »et le problème est qu'un lecteur confus pourrait s'attendre à autre chose que ce qu'il fait.
TripeHound
26

Vous pouvez. Mais vous devez être conscient de la portée de l' iart. si nous appelons l'extérieur iavec i_1et l'intérieur iavec i_2, la portée du is est la suivante:

for(int i = 0; i < 10; i++)
{
     // i means i_1
     for(int i = 0; i < 10; i++)
     {
        // i means i_2
     }
     // i means i_1
}

Vous devriez remarquer qu'ils ne s'affectent pas et que leur champ de définition est différent.

OMG
la source
17

C'est tout à fait possible, mais gardez à l'esprit que vous ne pourrez pas traiter le premier i déclaré

for(int i = 0; i < 10; i++)//I MEAN THE ONE HERE
{

  for(int i = 0; i < 10; i++)
    {

    }
}

dans la deuxième boucle dans la deuxième boucle enfant

for(int i = 0; i < 10; i++)
{

  for(int i = 0; i < 10; i++)//the new i
    {
        // i cant see the i thats before this new i here
    }
}

si vous avez besoin d'ajuster ou d'obtenir la valeur du premier i, utilisez j dans la deuxième boucle

for(int i = 0; i < 10; i++)
{

  for(int j = 0; j < 10; j++)
    {

    }
}

et si votre créativité est suffisante, vous pouvez faire les deux en une seule boucle

for(int i ,j= 0; i < 10; (j>9) ? (i++,j=0) : 0 ,j++)
{
    printf("%d %d\n",i,j);
}
Dodo
la source
6
Si j'attrapais des variables ombrées i dans des boucles imbriquées lors d'une révision de code, je la verrais comme une opportunité de coaching. Si je surprends quelqu'un qui obscurcit la boucle interne comme votre dernier exemple (ce n'est PAS une boucle), je pourrais le jeter par la fenêtre.
Bloodgain
c'est une boucle, elle n'a qu'une seule boucle for , si elle était de 2, elle en aurait deux pour les mots-clés ou deux mots-clés while ou un mots
Dodo
3
C'est pourquoi j'ai dit que vous aviez obscurci la boucle. Vous êtes toujours en boucle, vous venez de le cacher avec une syntaxe moins évidente. Et c'est pire à tous points de vue pour cela.
Bloodgain
12

Oui, vous pouvez utiliser le même nom de variable de compteur pour une forboucle interne que pour la forboucle externe .

Depuis la boucle for :

for ( init_clause ; cond_expression ; iteration_expression ) loop_statement
L'instruction d'expression utilisée comme loop_statement établit sa propre portée de bloc, distincte de la portée de init_clause .

for (int i = 0; ; ) {
    long i = 1;   // valid C, invalid C++
    // ...
}  

La portée de loop_statement est imbriquée dans la portée de init_clause .

From C Standards # 6.8.5p5 Instructions d'itération [c'est moi qui souligne]

Une instruction d'itération est un bloc dont la portée est un sous-ensemble strict de la portée de son bloc englobant. Le corps de la boucle est également un bloc dont la portée est un sous-ensemble strict de la portée de l'instruction d'itération .

De C Standards # 6.2.1p4 Portées des identificateurs [c'est moi qui souligne]

.... Dans la portée interne, l'identifiant désigne l'entité déclarée dans la portée interne; l' entité déclarée dans la portée externe est masquée (et non visible) dans la portée interne.

HS
la source
10

Du point de vue du code / compilateur, ce serait une chose parfaitement valide et légale à faire. Le int idéclaré dans la for(int i = 0; i < 10; i++)boucle interne est dans une portée nouvelle et plus petite, de sorte que la déclaration occulte la déclaration de int idans la boucle externe (ou, en d'autres termes: dans la portée interne, tous les accès à la variable ivont à la int idéclaration dans la portée interne, laissant le int idans le champ extérieur intact).

Cela dit, du point de vue de la qualité du code, c'est absolument horrible. C'est difficile à lire, difficile à comprendre et facile à mal comprendre. Ne fais pas ça.

CharonX
la source
8

Oui, vous pouvez l'utiliser mais c'est assez déroutant. La chose la plus importante est la portée de la variable locale à l'intérieur de la boucle. Dans la mesure où une variable est déclarée à l'intérieur d'une fonction, la portée de cette variable est cette fonction.

int a = 5;
// scope of a that has value 5
int func(){
    int a = 10;
   // scope of a that has value 10
}
// scope of a that has value 5

De même, le cas des boucles, la variable déclarée à l'intérieur de la boucle interne a une portée différente et la variable déclarée la boucle externe a une portée différente.

for(int i = 0; i < 10; i++){
    // In first iteration, value of i is 0

    for(int i = 1; i < 10; i++){
        // In first iteration, value of i is 1
    }
    // In first iteration, value of i is 0
}

La meilleure approche consiste à utiliser différentes variables pour les boucles internes et externes.

for(int i = 0; i < 10; i++){

    for(int j = 1; j < 10; j++){

    }

}
Safwan Shaikh
la source
8

Oui, vous pouvez certainement utiliser la même variable de nom.

Les variables de programmation C peuvent être déclarées à trois endroits:
les variables locales: -À l'intérieur d'une fonction ou d'un bloc.
Variables globales: -Out de toutes les fonctions.
Paramètres formels: -Dans les paramètres de fonction.

Mais dans votre cas, vous i scopedevrez vous soucier des choses ci-dessous

for(int i = 0; i < 10; i++)
{
     // i means 1st for loop variable
     for(int i = 0; i < 10; i++)
     {
        // but here i means 2nd for loop  variable
     }
     //interesting thing here i means 1st for loop variable
}

Remarque: il serait préférable d'utiliser des variables différentes pour les boucles internes et externes

Zaynul Abadin Tuhin
la source
6

Oui - et encore plus intéressant, vous pouvez réutiliser un nom de variable chaque fois que vous ouvrez un ensemble d'accolades. Ceci est souvent pratique lors de l'insertion d'un code de diagnostic. Tapez une accolade ouverte '{' suivie de la déclaration et de l'utilisation de variables, puis fermez l'accolade et les variables disparaissent. Cela garantit que vous n'interférerez avec rien dans le corps principal tout en conservant l'avantage de toutes les variables, classes et méthodes déclarées en dehors des accolades.

SuwaneeCreek
la source
3

Règle de portée: une variable déclarée dans une instruction for ne peut être utilisée que dans cette instruction et dans le corps de la boucle.

Si dans votre code vous avez défini plusieurs instances de i dans des boucles internes, chaque instance occupera son propre espace mémoire. Il n'y a donc pas à s'inquiéter des résultats de toute façon, ce serait pareil.

int main(void) {

    int i = 2; //defined with file global scope outside of a function and will remain 2
    if(1)
    {       //new scope, variables created here with same name are different
        int i = 5;//will remain == 5
        for(int i = 0; i < 10; i++)
        {   //new scope for "i"

            printf("i value in first loop: %d \n", i); // Will print 0 in first iteration
            for(int i = 8; i < 15; i++) 
            {   //new scope again for "i", variable with same name is not the same
                printf("i value in nested loop: %d \n", i); // Will print 8 in first iteration
            }
        }

    }

    return 0;
}

Mais il n'est pas recommandé d'utiliser le même nom de variable car il est difficile à comprendre et il devient du code non maintenable plus tard.

Subash J
la source
1

La partie importante est que le paramètre de boucle interne contient int i. Parce qu'il iest redéfini de cette façon, les deux variables ne s'affectent pas; leurs portées sont différentes. Voici deux exemples pour illustrer cela:

for(int i = 0; i < 10; i++) // This code will print "Test" 100 times
{
 for(int i = 0; i < 10; i++)
 {
  puts("Test");
 }
}

Notez que le code ci-dessus inclut int idans le paramètre de boucle interne et que le code ci-dessous inclut uniquement i.

for(int i = 0; i < 10; i++) // This code will print "Test" 10 times
{
 for(i = 0; i < 10; i++)
 {
  puts("Test");
 }
}
Uclydde
la source
0

Eh bien, vous pouvez le faire sans que vos scripts aient un problème, mais vous devez éviter cette structure. Cela conduit généralement à la confusion

Feu
la source