Nigdy wcześniej nie widziano pętli for w C ++

164

Konwertowałem algorytm C ++ na C #. Natknąłem się na to for loop:

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

Nie daje błędu w C ++, ale robi to w C # (nie można przekonwertować int na bool). Naprawdę nie mogę tego rozgryźć pętli for, gdzie jest warunek?

Czy ktoś może wyjaśnić?

PS. Żeby sprawdzić, czy aby dostosować WEKTOR do LISTY, czy b.back () odpowiada b [b.Count-1]?

Tomasz
źródło
35
Gdzie jest stan? To byłoby u--. Do oddzielenia poszczególnych części forinstrukcji używa się średników .
David Heffernan
77
To jest całkiem normalna pętla. C # nie konwersji liczb do bools niejawnie więc trzeba skręcić w stan; u-- != 0;
R. Martinho Fernandes
28
@Jessie Good - Dobry kod nie ma nic wspólnego z tym, co jest dozwolone w języku, ma związek z tym, ile czasu zajmie niedokończonemu współpracownikowi przeczytanie kodu. Jeśli powoduje jakiekolwiek zamieszanie, nie jest to najlepsze możliwe rozwiązanie, nawet jeśli jest legalne. Często bardziej rozwlekłe rozwiązanie jest znacznie lepsze niż zwięzłe, a większość kompilatorów i tak skompiluje to samo.
Bill K
18
Mam nadzieję, że po konwersji kodu, dajesz nazw zmiennych lepsze niż b, u, v, itd. Jedynym powodem, dla którego zostały nazwane w ten sposób dlatego, że ktoś chce wyglądać elegancko, dokonując ich kod nieczytelny.
Dan,
33
@houbysoft: jest to ogólny problem z przepełnieniem stosu. Jeśli zadasz bardzo szczegółowe, dobrze zbadane i interesujące pytanie w określonej dziedzinie badawczej, które prowadzi do rozwiązania trudnego i interesującego problemu, a na takie pytanie odpowiesz po ciężkich dniach poszukiwań, otrzymasz tylko kilka kilkunastu gości i jedno lub dwa głosy za od kilku ekspertów w tej dziedzinie. Jeśli chcesz szybko zdobyć dużo reputacji, musisz zadawać takie pytania i odpowiadać na nie. „Jak dodać dwie liczby w php”, „co to doznaczy w C ++” - dostanie tysiące trafień od początkujących szukających samouczka.
vsz

Odpowiedzi:

320

Stan forpętli znajduje się pośrodku - między dwoma średnikami ;.

W C ++ można umieścić prawie każde wyrażenie jako warunek: wszystko, co daje wynik zerowy, oznacza false; niezerowe oznacza true.

W twoim przypadku warunek jest taki u--: kiedy konwertujesz na C #, po prostu dodaj != 0:

for (u = b.size(), v = b.back(); u-- != 0; v = p[v]) 
    b[u] = v; //                     ^^^^ HERE
dasblinkenlight
źródło
55
Nawiasem mówiąc, Thomas mógł być zdezorientowany przez użycie przecinka, jest bardzo różny niż średnik, pozwala robić wiele rzeczy w jednej sekcji pętli for (w tym przypadku zainicjował dwie zmienne). Ostatnim razem, gdy sprawdzałem, te niezwykłe konstrukcje nie były uważane za najbardziej czytelne możliwe rozwiązanie i dlatego mogą być przez niektórych niezadowolone.
Bill K
2
Jeśli dobrze pamiętam, a wyrażenie zawierające przecinek ma wartość ostatniego podwyrażenia po prawej stronie. Pochodzi z C i może być użyte w dowolnym wyrażeniu, nie tylko w pętli for.
Giorgio
7
@Roger To nie byłaby ta sama pętla, ponieważ zmniejszasz u na końcu pętli zamiast na jej początku (tj. Po b [u] = v zamiast przed). W rzeczywistości musiałbyś u = b.size() - 1zamiast tego zainicjować go za pomocą .
Didier L
Fyi: Właśnie przekroczyłem limit 500 tys. Czy to tak, jakby być milionowym klientem gdzieś i coś wygrać? W każdym razie: gratulacje; zawsze patrząc na Twoje precyzyjne, ostre odpowiedzi! Tak trzymaj; do zobaczenia, robiąc milion rzeczy ... pod koniec tego wieku.
GhostCat
165

Wiele trafnych odpowiedzi, ale myślę, że warto wypisać ich odpowiednik w pętli while.

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

Jest równa:

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

Możesz rozważyć refaktoryzację do formatu while () podczas tłumaczenia na język C #. Moim zdaniem jest to wyraźniejsze, mniej pułapka dla nowych programistów i równie wydajne.

Jak zauważyli inni - ale aby moja odpowiedź była kompletna - aby działała w C #, musisz zmienić while(u--)na while(u-- != 0).

... lub while(u-- >0)na wypadek, gdybyś zaczął negatywnie. (OK, b.size()nigdy nie będzie ujemne - ale rozważ ogólny przypadek, w którym być może coś innego zainicjowało u).

Lub, żeby było jeszcze jaśniej:

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

Lepiej być klarownym niż zwięzłym.

szczupły
źródło
29
Ta odpowiedź nie tylko wyjaśnia zły kod, ale także stanowi dobrą alternatywę. 1 w górę !!
polvoazul
2
Na marginesie byłbym ostrożny z while (u-- >0)formularzem. Jeśli odstępy zostaną pomieszane, możesz skończyć z pętlą „w dół do zera”:, while (u --> 0)która na pierwszy rzut oka wprowadza wszystkich w błąd. ( Nie jestem pewien, czy jest to poprawne C #, ale jest w C, i myślę, że może być również w C ++? )
Izkata
9
Nie sądzę, żeby twój kod był koniecznie jaśniejszy. Punktem forzamiast whilewłaśnie, że można umieścić inicjalizacji i góra / dół w jednym sprawozdaniu, a nie koniecznie uczynić kod trudniejsze do zrozumienia. W przeciwnym razie forw ogóle nie powinniśmy używać .
musiphil
1
Ważne jest również, aby przepisać jak najmniej kodu. (W końcu pętla for może się zmienić.) Umieściłeś „u--” w dwóch oddzielnych miejscach, a pętla nie jest tak naprawdę wyraźna (widzę, co robi pętla for na pierwszy rzut oka; muszę przeskanować z wieloma liniami). Zwięzłość też ma swoje zalety. Nie bagatelizuj ich. Mimo to dobry przykład tego, jak inaczej można by to napisać. Ułatwia to zrozumienie każdemu, kto nie jest przyzwyczajony do instrukcji w C ++ (a może nawet w ogóle).
NotKyon
2
@Izkata: To nie jest NIGDY operatorem, jest analizowany jako --token, po którym następuje >token. Dwóch oddzielnych operatorów. Pętla „w dół do zera” jest po prostu prostą kombinacją post-dekrementacji i większego niż. Przeciążanie operatorów C ++ nie tworzy nowych operatorów, po prostu zmienia ich przeznaczenie.
Ben Voigt
66

Warunkiem jest u--;, bo to jest w drugiej pozycji do instrukcji.

Jeśli wartość u--;jest różna od 0, zostanie zinterpretowana jako true(tj. Niejawnie rzutowana na wartość logiczną true). Jeśli zamiast tego jego wartość wynosi 0, zostanie rzutowana na false.

To bardzo zły kod .

Aktualizacja: omówiłem pisanie pętli „for” w tym poście na blogu . Jego zalecenia można podsumować w następujących akapitach:

Pętla for to praktyczna, czytelna (gdy się do niej przyzwyczaisz) i zwięzła konstrukcja, ale musisz jej dobrze używać. Ze względu na jego niezwykłą składnię używanie jej w zbyt pomysłowy sposób nie jest dobrym pomysłem.

Wszystkie części pętli for powinny być krótkie i czytelne. Nazwy zmiennych należy wybierać tak, aby były łatwe do zrozumienia.

Ten przykład wyraźnie narusza te zalecenia.

Daniel Daranas
źródło
3
Albo prawdopodobnie zatrzyma się na u == 0 ...?
Thomas
2
Nie; w C ++ występuje niejawna konwersja z int na bool, z 0 konwertowaniem na fałsz. Pętla zakończy się, gdy u-- == 0. W C # nie ma takiej niejawnej konwersji, więc musiałbyś jawnie powiedzieć u-- == 0. EDYCJA: To jest odpowiedź na twój pierwszy komentarz.
Chris,
48
To okropny kod z jednego bardzo prostego powodu; nie można było tego łatwo pojąć, czytając. To „sprytne”, „hack”; wykorzystuje kombinację struktur kodowania i wiedzę o tym, jak działają za kulisami, aby stworzyć strukturę, która wykonuje swoją pracę, ale która wymyka się zrozumieniu, ponieważ nie jest w formie przewidzianej przez autorów języka i która została przekazana większości użytkowników językowych.
KeithS
4
Doskonała odpowiedź. Dałbym mu +1 ... gdyby nie „To bardzo zły kod”. oświadczenie;)
Sandman4
14
Nie rozumiem, dlaczego tak wielu ludzi wydaje się myśleć, że u-- jest naprawdę złym kodem po prostu z powodu braku (niejawnego w C ++) ! = 0 . Z pewnością każdy, kto pracuje z kodem, doskonale zdaje sobie sprawę, że 0 = fałsz, każda inna wartość = prawda . Jest o wiele więcej możliwości pomyłki dotyczące pre- / post-inkrementacja u , albo ludzie być może zakładając, że u = b.size () będzie zawsze wykonać przed v = b.back () (moje rozumienie jest sekwencja wykonanie nie jest zdefiniowana, ale czekam na poprawkę).
FumbleFingers
23

Będzie to forma Twojej pętli w języku C #.

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

Po prostu zastąp odpowiednik size () i back ().

To, co robi, odwraca listę i przechowuje w tablicy. Ale w C # mamy bezpośrednio zdefiniowaną przez system funkcję do tego. Więc nie musisz również pisać tej pętli.

b = b.Reverse().ToArray();
Narendra
źródło
1
wyjmując v = b.back();z for intializatora, nie zmieniłeś tylko sposobu, w jaki to działa, ponieważ v = p[v]jest on nadpisany przez
João Portela
v = p [v] nie będzie miało żadnego wpływu na wynik końcowy, ponieważ ta linia zostanie wykonana jako ostatnia. A potem v nie jest odwoływane w pętli. Ta linia służy tylko do pokazania, jak pętla jest konwertowana z C ++ do C #.
Narendra
1
w kodzie C ++ v = b.back();było wykonywane raz przed rozpoczęciem iteracji i v = p[v]było wykonywane na początku każdej iteracji. W tej wersji C # v = p[v]jest nadal wykonywana na początku każdej iteracji, ale v = b.back();jest wykonywana zaraz po niej, zmieniając wartość vdla następnej instrukcji b[u] = v;. (być może pytanie zostało zredagowane po przeczytaniu)
João Portela
5
@Rain Problem jest v = b.back(). Masz go wykonującego się przy każdej iteracji pętli zamiast tylko pierwszej - nie wiemy, co to back()robi (czy są jakieś skutki uboczne? Czy zmienia wewnętrzną reprezentację b?), Więc ta pętla nie jest równoważna z tą w pytanie.
Izkata
1
@ Deszcz Dokładnie, przyjrzyj się temu bliżej. Inicjalizacji krok zdarza się tylko raz przed rozpoczęciem pętli, nie na początku każdej iteracji . Twój kod byłby poprawny, gdyby v = b.back()został przeniesiony poza pętlę, nad nią. (Ponadto, jeśli próbujesz komuś odpowiedzieć, użyj @przed jego nazwiskiem, abyśmy otrzymali powiadomienie)
Izkata
15
u = b.size(), v = b.back()

to inicjalizacja.

u--

jest warunkiem.

v = p[v]

jest iteracją

huseyin tugrul buyukisik
źródło
14

Warunek jest wynikiem u--, którego jest wartościąu sprzed obniżenia wartości.

W C i C ++ an int można zamienić na bool przez niejawne wykonanie != 0porównania (0 to false, wszystko inne jest true).

b.back()to ostatni element w kontenerze, czyli b[b.size() - 1]kiedy size() != 0.

Bo Persson
źródło
11

W języku C wszystko niezerowe znajduje się truew kontekstach „boolowskich”, takich jak warunek zakończenia pętli lub instrukcja warunkowa. W języku C # trzeba zrobić to sprawdzić jednoznaczne: u-- != 0.

Joey
źródło
To nie jest kwestia OP. Pyta o ocenę stanu końcowego (czyli „u--” w tym przypadku).
ApplePie
6

Jak stwierdzili inni, fakt, że C ++ ma niejawne rzutowanie na wartość logiczną, oznacza, że ​​warunkowe jest u--, które będzie prawdziwe, jeśli wartość jest różna od zera.

Warto dodać, że pytając „gdzie jest warunek”, masz fałszywe założenie. W obu językach C ++ i C # (i innych językach o podobnej składni) możesz mieć pusty warunek. W tym przypadku jest zawsze wartość true, więc pętla trwa wiecznie, albo aż niektórych innych wyjść warunku (przez return, breaklub throw).

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

Rzeczywiście, dowolną część instrukcji for można pominąć, w takim przypadku po prostu nie jest wykonywana.

Ogólnie for(A; B; C){D}lub for(A; B; C)D;staje się:

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

Można pominąć jeden lub więcej z A, B, C lub D.

W rezultacie pewna przysługa for(;;) dla nieskończonych pętli. Robię to, ponieważ chociaż while(true)jest bardziej popularne, czytam to jako „dopóki prawda przestanie być prawdą”, co brzmi nieco apokaliptycznie w porównaniu z moim czytaniem for(;;) jako „na zawsze”.

To kwestia gustu, ale ponieważ nie jestem jedyną osobą na świecie, która lubi for(;;), warto wiedzieć, co to znaczy.

Jon Hanna
źródło
4

wszystkie odpowiedzi są prawidłowe: -

pętla for może być używana na różne sposoby w następujący sposób:

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
źródło
4
for (u = b.size(), v = b.back(); u--; v = p[v]) 
   b[u] = v;

W powyższym kodzie ui vsą inicjowane za pomocą b.size()i b.back().

Za każdym razem, gdy warunek jest sprawdzany, wykonuje również instrukcję dekrementacji, tj u--.

forPętla wyjdzie kiedy ubędzie 0.

Apte
źródło
3

Błąd napotkany w samym C # rozwiązuje wątpliwości. Pętla for wyszukuje plik

FAŁSZYWE

warunek do rozwiązania. Jak wiemy,

(BOOL) FALSE = (int) 0

ale C # nie może tego przetworzyć samodzielnie, w przeciwieństwie do C ++. Zatem warunek, którego szukasz, jest

u--

ale musisz jawnie podać warunek w C # jako

u--! = 0

lub

u--> 0

Ale nadal staraj się unikać tego rodzaju praktyk kodowania. Plik

pętla while

podana powyżej w odpowiedzi jest jedną z najbardziej uproszczonych wersji twojego

dla pętli.

Abhineet
źródło
@Downvoter: Głosowanie w dół jest w porządku, o ile nie jesteś zadowolony z rozwiązania, ale jednocześnie poświęć trochę czasu na podanie przyczyny, aby można było poprawić odpowiedzi.
Abhineet
3

Jeśli jesteś przyzwyczajony do C / C ++, ten kod nie jest trudny do odczytania, chociaż jest dość zwięzły i niezbyt świetny. Pozwólcie więc, że wyjaśnię części, które są bardziej Cismem niż czymkolwiek innym. Po pierwsze, ogólna składnia pętli C for wygląda następująco:

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

Kod inicjujący jest uruchamiany raz. Następnie warunek jest testowany przed każdą pętlą i na koniec po każdej pętli wywoływany jest przyrost. Więc w twoim przykładzie znajdziesz waruneku--

Dlaczego u--działa jako warunek w C, a nie C #? Ponieważ C niejawnie konwertuje wiele rzeczy, zbytnio jeździ i może powodować problemy. Dla liczby wszystko, co jest niezerowe, jest prawdą, a zero jest fałszem. Więc będzie odliczać od b.size () - 1 do 0. Efekt uboczny w stanie jest trochę denerwujący i lepiej byłoby umieścić go w przyrostowej części pętli for, chociaż dużo C kod to robi. Gdybym to pisał, zrobiłbym to bardziej tak:

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

Przyczyna tego jest przynajmniej dla mnie jaśniejsza. Każda część pętli for wykonuje swoją pracę i nic więcej. W oryginalnym kodzie warunkiem była modyfikacja zmiennej. Część inkrementacyjna robiła coś, co powinno znajdować się w bloku kodu itp.

Operator przecinka może również rzucać ci w pętlę. W C coś x=1,y=2wygląda jak jedna instrukcja, jeśli chodzi o kompilator i pasuje do kodu inicjalizacji. Po prostu ocenia każdą z części i zwraca wartość ostatniej. Na przykład:

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

wydrukowałby 2.

Matt Price
źródło
Problem z twoim przepisywaniem polega na tym, że jeśli b.size () jest bez znaku, będzie iterować przez bardzo długi czas. (Brakuje też średnika). Ale przynajmniej nie przyjąłeś podejścia „Dick i Jane” jest dobrym angielskim ”, jak w przypadku wielu innych odpowiedzi i komentarzy, wychwalając absurdalnie rozwlekłe przepisanie, które jest tylko łatwiejsze do czytania przez neofitów i innych niewykwalifikowanych programistów.
Jim Balter