Jamais vu auparavant C ++ for loop

164

Je convertissais un algorithme C ++ en C #. Je suis tombé sur ceci pour la boucle:

for (u = b.size(), v = b.back(); u--; v = p[v]) 
b[u] = v;

Il ne donne aucune erreur en C ++, mais il le fait en C # (impossible de convertir un int en booléen). Je ne peux vraiment pas comprendre cela pour la boucle, où est la condition?

Quelqu'un peut-il expliquer?

PS. Juste pour vérifier, pour adapter un VECTEUR à une LISTE, b.back () correspond-il à b [b.Count-1]?

Thomas
la source
35
Où est la condition? Ce serait u--. Les points-virgules sont utilisés pour délimiter les différentes parties de l' forinstruction.
David Heffernan
77
C'est une boucle tout à fait normale. C # ne convertit pas implicitement les nombres en booléens, vous devez donc transformer la condition en; u-- != 0;
R. Martinho Fernandes
28
@Jessie Good - Un bon code n'a rien à voir avec ce qui est permis dans le langage, il a à voir avec le temps qu'il faut à un collègue non valorisé pour lire le code. Si cela crée une sorte de confusion, ce n'est pas la meilleure solution possible, même si elle est légale. Souvent, une solution plus verbeuse est bien meilleure qu'une solution laconique, et la plupart des compilateurs compileront de la même manière dans les deux cas.
Bill K
18
J'espère que, après la conversion du code, vous donnez les variables meilleurs noms que b, u, v, etc. La seule raison pour laquelle ils ont été nommés ainsi parce que quelqu'un voulait avoir l' air intelligent en faisant leur code illisible.
Dan
33
@houbysoft: c'est un problème général de stackoverflow. Si vous posez une question très détaillée, bien documentée et intéressante dans un domaine de recherche spécifique, qui mène à une solution à un problème difficile et intéressant, et que vous répondez à une telle question après de durs jours de recherche, vous n'en obtiendrez que quelques-uns. des dizaines de visiteurs et une ou deux votes positifs de quelques experts du domaine. Si vous voulez gagner rapidement beaucoup de représentants, vous devez poser des questions comme celles-ci et y répondre. "Comment puis-je ajouter deux nombres en php", "ce que cela dosignifie en C ++" - obtiendra des milliers de résultats de la part de débutants à la recherche d'un tutoriel.
vsz

Réponses:

320

La condition de la forboucle est au milieu - entre les deux points-virgules ;.

En C ++, il est possible de mettre presque n'importe quelle expression comme condition: tout ce qui est évalué à zéro signifie false; non nul signifie true.

Dans votre cas, la condition est la suivante u--: lorsque vous convertissez en C #, ajoutez simplement != 0:

for (u = b.size(), v = b.back(); u-- != 0; v = p[v]) 
    b[u] = v; //                     ^^^^ HERE
dasblinkenlight
la source
55
Au fait, Thomas a peut-être aussi été dérouté par l'utilisation de la virgule, c'est très différent du point-virgule, cela vous permet de faire plusieurs choses dans une section de la boucle for (dans ce cas, il a initialisé deux variables). La dernière fois que j'ai vérifié, ces constructions inhabituelles n'étaient pas considérées comme la solution la plus lisible possible et peuvent donc être désapprouvées par certains.
Bill K
2
Si je me souviens bien, et l'expression contenant une virgule a la valeur de la dernière sous-expression à droite. Cela vient de C et peut être utilisé dans n'importe quelle expression, pas seulement dans une boucle for.
Giorgio
7
@Roger Ce ne serait pas la même boucle puisque vous décrémentez u à la fin de la boucle, au lieu du début (c'est-à-dire après b [u] = v au lieu d'avant). En fait, vous devrez l'initialiser avec à la u = b.size() - 1place.
Didier L
Fyi: Je viens de vous pousser au-dessus de la limite de 500K. Est-ce que c'est comme être le millionième client quelque part et gagner quelque chose? En tout cas: beaucoup de félicitations; toujours à la recherche de vos réponses précises et nettes! Continue; Rencontrez-vous faire la chose 1 million ... plus tard ce siècle.
GhostCat
165

Beaucoup de réponses précises, mais je pense qu'il vaut la peine d'écrire l'équivalent en boucle while.

for (u = b.size(), v = b.back(); u--; v = p[v]) 
   b[u] = v;

Est équivalent à:

u = b.size();
v = b.back();
while(u--) {
   b[u] = v;
   v = p[v];
}

Vous pouvez envisager de refactoriser au format while () lorsque vous traduisez en C #. À mon avis, c'est plus clair, moins un piège pour les nouveaux programmeurs et tout aussi efficace.

Comme d'autres l'ont souligné - mais pour que ma réponse soit complète - pour que cela fonctionne en C #, vous devrez changer while(u--)pour while(u-- != 0).

... ou while(u-- >0)juste au cas où vous commenceriez par négatif. (OK, b.size()ne sera jamais négatif - mais considérons un cas général où peut-être quelque chose d'autre a initialisé u).

Ou, pour le rendre encore plus clair:

u = b.size();
v = b.back();
while(u>0) {
   u--;
   b[u] = v;
   v = p[v];
}

Il vaut mieux être clair que concis.

svelte
la source
29
Cette réponse clarifie non seulement le mauvais code, mais donne également une bonne alternative. 1 up !!
polvoazul
2
En passant, je ferais attention à la while (u-- >0)forme. Si l'espacement est gâché, vous pourriez vous retrouver avec une boucle "jusqu'à zéro":, while (u --> 0)ce qui a tendance à dérouter tout le monde au premier coup d'œil. ( Je ne sais pas si c'est C # valide, mais c'est en C, et je pense que ça peut l'être aussi en C ++? )
Izkata
9
Je ne pense pas que votre code soit nécessairement plus clair. Le but de forau lieu de whileest précisément que vous mettez l'initialisation et l'incrémentation / décrémentation dans une seule instruction, et cela ne rend pas nécessairement le code plus difficile à comprendre. Sinon, nous ne devrions pas utiliser fordu tout.
musiphil
1
Il est également important de réécrire le moins de code possible. (La boucle for pourrait changer, après tout.) Vous avez mis "u--" à deux endroits séparés, et la boucle n'est pas vraiment plus claire (je peux voir ce que fait la boucle for en un coup d'œil; je dois scanner avec plusieurs lignes). Être concis a aussi des avantages. Ne les minimisez pas. Pourtant, un bon exemple de la façon dont cela pourrait être écrit autrement. Cela facilite la compréhension pour quiconque n'est pas habitué aux instructions en C ++ (ou peut-être même pas du tout).
NotKyon
2
@Izkata: Ce n'est JAMAIS un opérateur, il est analysé comme un --jeton suivi d'un >jeton. Deux opérateurs distincts. Une boucle «jusqu'à zéro» est juste une simple combinaison de post-décrémentation et de supérieur à. La surcharge d'opérateurs C ++ ne crée pas de nouveaux opérateurs, elle réutilise simplement les opérateurs existants.
Ben Voigt
66

La condition est u--;, car il est dans la deuxième position de l' instruction for .

Si la valeur de u--;est différente de 0, elle sera interprétée comme true(c'est-à-dire transtypée implicitement en valeur booléenne true). Si, à la place, sa valeur est 0, il sera converti en false.

C'est un très mauvais code .

Mise à jour: j'ai discuté de l'écriture de boucles «for» dans ce billet de blog . Ses recommandations peuvent être résumées dans les paragraphes suivants:

Une boucle for est une construction pratique, lisible (une fois que vous vous y êtes habitué) et laconique, mais vous devez bien l'utiliser. En raison de sa syntaxe peu commune, l'utiliser de manière trop imaginative n'est pas une bonne idée.

Toutes les parties de la boucle for doivent être courtes et lisibles. Les noms de variables doivent être choisis pour faciliter leur compréhension.

Cet exemple enfreint clairement ces recommandations.

Daniel Daranas
la source
3
Ou il s'arrêtera probablement à u == 0 ...?
Thomas
2
Non; en C ++ il y a une conversion implicite de int en bool, avec 0 converti en faux. La boucle se terminera lorsque u-- == 0. En C #, il n'y a pas de conversion implicite de ce type, vous devrez donc dire explicitement u-- == 0. EDIT: Ceci est en réponse à votre premier commentaire.
Chris
48
C'est un code terrible , pour une raison très simple; vous ne pouviez pas le comprendre facilement en le lisant. C'est "intelligent", un "hack"; il utilise une combinaison de structures de codage et la connaissance de leur fonctionnement en coulisse pour créer une structure qui fait le travail mais qui défie la compréhension, car ce n'est pas sous la forme que les auteurs du langage envisageaient et qui a été communiquée à la plupart utilisateurs de langue.
KeithS
4
Excellente réponse. J'aurais +1 ... sinon le "C'est un très mauvais code." statement;)
Sandman4
14
Je ne comprends pas pourquoi tant de gens semblent penser que u-- est vraiment un mauvais code simplement à cause du manque (implicite en C ++) ! = 0 . Quiconque travaille avec du code saura sûrement parfaitement que 0 = false, toutes les autres valeurs = true . Il y a beaucoup plus de possibilités de confusion au sujet de pré / post-incrémenter de u , ou les personnes assumant peut - être u = b.size () sera toujours exécuter avant v = b.back () (je crois comprendre la séquence d'exécution , il est indéfini, mais je tiens à être corrigé).
FumbleFingers
23

Ce sera la forme C # de votre boucle.

// back fetches the last element of vector in c++.
for (u = b.size(), v = b.back(); (u--) != 0; v = p[v]) 
{      
  b[u] = v;      
}

Remplacez simplement l'équivalent pour size () et back ().

Ce qu'il fait, c'est inverser la liste et les stocker dans un tableau. Mais en C #, nous avons directement une fonction définie par le système pour cela. Vous n'avez donc pas besoin d'écrire cette boucle également.

b = b.Reverse().ToArray();
Narendra
la source
1
en v = b.back();supprimant l'initialiseur for, n'avez-vous pas simplement changé son fonctionnement, puisque le v = p[v]est remplacé par
João Portela
v = p [v] n'aura aucun effet sur le résultat final car cette ligne s'exécutera à la fin. Et après cela, v n'est pas référencé dans la boucle. Cette ligne est là juste pour montrer comment la boucle est convertie de C ++ en C #.
Narendra
1
dans le code c ++ a v = b.back();été exécuté une fois avant le début des itérations et a v = p[v]été exécuté au début de chaque itération. Dans cette version C # v = p[v]est toujours exécutée au début de chaque itération mais v = b.back();est exécutée juste après, en changeant la valeur de vpour l'instruction suivante b[u] = v;. (peut-être que la question a été modifiée après l'avoir lue)
João Portela
5
@Rain Le problème est v = b.back(). Vous l'avez exécuté à chaque itération de boucle au lieu de la première - nous ne savons pas ce que back()fait (y a-t-il des effets secondaires? Cela change-t-il la représentation interne de b?), Donc cette boucle n'est pas équivalente à celle de la question.
Izkata le
1
@Rain Exactement, regardez-le de plus près. L' étape d' initialisation n'a lieu qu'une seule fois avant le début de la boucle, et non au début de chaque itération . Votre code serait correct s'il v = b.back()était déplacé en dehors de la boucle, au-dessus. (De plus, si vous essayez de répondre à quelqu'un, utilisez-le @devant son nom pour que nous recevions une notification)
Izkata
15
u = b.size(), v = b.back()

est l'initialisation.

u--

est la condition.

v = p[v]

est l'itération

huseyin tugrul buyukisik
la source
14

La condition est le résultat de u--, qui est la valeur de uavant qu'elle ne soit décrémentée.

En C et C ++, an int est convertible en booléen en faisant implicitement une != 0comparaison (0 est false, tout le reste est true).

b.back()est le dernier élément d'un conteneur, c'est-à- b[b.size() - 1]dire quand size() != 0.

Bo Persson
la source
11

En C, tout ce qui n'est pas nul est truedans des contextes "booléens", tels que la condition de fin de boucle ou une instruction conditionnelle. En C # , vous devez faire ce contrôle explicite: u-- != 0.

Joey
la source
Ce n’est pas la question d’OP. Il pose des questions sur l'évaluation de la condition terminale (c'est-à-dire «u--» dans ce cas).
ApplePie
6

Comme indiqué par d'autres, le fait que C ++ ait un cast implicite en booléen signifie que le conditionnel est u--, qui sera vrai si la valeur est différente de zéro.

Cela vaut la peine d'ajouter que vous avez une fausse hypothèse en demandant "où est le conditionnel". En C ++ et C # (et dans d'autres langages de syntaxe similaire), vous pouvez avoir un conditionnel vide. Dans ce cas , il est toujours vraie, de sorte que la boucle continue pour toujours, ou jusqu'à ce que d'autres sorties de l' état (via return, breakou throw).

for(int i = 0; ; ++i)
  doThisForever(i);

En effet, n'importe quelle partie de l'instruction for peut être omise, auquel cas elle n'est tout simplement pas exécutée.

En général, for(A; B; C){D}ou for(A; B; C)D;devient:

{A}
loopBack:
if(!(B))
  goto escapeLoop;
{D}
{C}
goto loopBack;
escapeLoop:

N'importe lequel ou plusieurs parmi A, B, C ou D peuvent être omis.

En conséquence, certains favorisent for(;;) les boucles infinies. Je le fais parce que pendant que while(true)c'est plus populaire, je lis cela comme "jusqu'à ce que la vérité finisse par être vraie", ce qui semble quelque peu apocalyptique comparé à ma lecture for(;;) comme "pour toujours".

C'est une question de goût, mais comme je ne suis pas la seule au monde à aimer, for(;;)il vaut la peine de savoir ce que cela signifie.

Jon Hanna
la source
4

toutes les réponses sont correctes: -

La boucle for peut être utilisée de différentes manières, comme suit:

Single Statement inside For Loop
Multiple Statements inside For Loop
No Statement inside For Loop
Semicolon at the end of For Loop
Multiple Initialization Statement inside For
Missing Initialization in For Loop
Missing Increment/Decrement Statement
Infinite For Loop
Condition with no Conditional Operator.
Birubisht
la source
4
for (u = b.size(), v = b.back(); u--; v = p[v]) 
   b[u] = v;

Dans le code ci-dessus, uet vsont initialisés avec b.size()et b.back().

Chaque fois que la condition est vérifiée, elle exécute également l'instruction de décrémentation, c'est-à-dire u--.

La forboucle se terminera quand udeviendra 0.

Apte
la source
3

L'erreur rencontrée dans C # lui-même efface le doute. La boucle for recherche un

FAUX

condition pour terminer. Et comme nous le savons,

(BOOL) FALSE = (entier) 0

mais C # ne peut pas traiter cela seul contrairement à C ++. La condition que vous recherchez est donc

u--

mais vous devez explicitement donner la condition en C # comme

u--! = 0

ou

u--> 0

Mais essayez toujours d'éviter ce genre de pratique de codage. le

boucle while

indiqué ci-dessus en réponse est l'une des versions les plus simplifiées de votre

boucle for.

Abhineet
la source
@Downvoter: Le vote négatif est correct dans la mesure où vous n'êtes pas satisfait de la solution, mais en même temps, prenez le temps d'en indiquer la raison, afin que les réponses puissent être améliorées.
Abhineet
3

Si vous êtes habitué à C / C ++, ce code n'est pas si difficile à lire, bien qu'il soit assez laconique et pas si génial en code. Alors laissez-moi vous expliquer les parties qui sont plus Cism qu'autre chose. Tout d'abord, la syntaxe générale d'une boucle C for ressemble à ceci:

for (<initialization> ; <condition>; <increment>)
{
    <code...>
}

Le code d'initialisation est exécuté une fois. Ensuite, la condition est testée avant chaque boucle et enfin l'incrément est appelé après chaque boucle. Donc, dans votre exemple, vous trouverez que la condition estu--

Pourquoi fonctionne-t-il u--comme une condition en C et non en C #? Parce que C convertit implicitement beaucoup de choses trop booléennes et cela peut causer des problèmes. Pour un nombre, tout ce qui est différent de zéro est vrai et zéro est faux. Donc, il compte à rebours de b.size () - 1 à 0. Avoir l'effet secondaire dans la condition est un peu ennuyeux et il serait préférable de le mettre dans la partie incrémentielle de la boucle for, bien que beaucoup de C le code fait cela. Si je l'écrivais, je le ferais plus comme ceci:

for (u = b.size() - 1, v = b.back(); u>=0; --u) 
{
    b[u] = v;
    v = p[v]
}

La raison en est, du moins pour moi, c'est plus clair. Chaque partie de la boucle for fait son travail et rien d'autre. Dans le code d'origine, la condition modifiait la variable. La partie incrémentation faisait quelque chose qui devrait être dans le bloc de code, etc.

L'opérateur virgule peut également vous lancer pour une boucle. En C, quelque chose comme x=1,y=2ressemble à une instruction en ce qui concerne le compilateur et s'inscrit dans le code d'initialisation. Il évalue simplement chacune des parties et renvoie la valeur de la dernière. Donc par exemple:

std::cout << "(1,2)=" << (1,2) << std::endl;

imprimerait 2.

Prix ​​de Matt
la source
Le problème avec votre réécriture est que si b.size () n'est pas signé, il itérera pendant très longtemps. (De plus, il vous manque un point-virgule.) Mais au moins, vous n'avez pas adopté l'approche «Dick and Jane» est un bon anglais »comme le faisaient tant d'autres réponses et commentaires, louant des réécritures absurdement verbeuses qui ne sont que plus faciles à lire par les néophytes et autres programmeurs non qualifiés.
Jim Balter