'<' versus '! =' jako warunek w pętli „for”?

31

Załóżmy, że masz następującą forpętlę *:

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

który zwykle można również zapisać jako:

for (int i = 0; i != 10; ++i) {
    // ...
}

Końcowe wyniki są takie same, więc czy są jakieś rzeczywiste argumenty przemawiające za stosowaniem jednego? Osobiście używam tego pierwszego na wypadek, gdyby iz jakiegoś powodu szaleje i pomija wartość 10.

* Przepraszam za użycie magicznych liczb, ale to tylko przykład.

gablin
źródło
18
Najlepszym rozwiązaniem dla któregokolwiek z nich jest użycie operatora strzałki:int i = 10; while(i --> 0) { /* stuff */ }
corsiKa
@glowcoder mój operator strzałek jest moim ulubionym
Carson Myers,
3
@glowcoder, fajnie, ale przecina się z tyłu. Nie zawsze możesz tego chcieć.
2
@ SuperElectric, parsuje jakwhile ((i--) > 0)
Kristopher Johnson
17
Istnieje (prawdopodobnie apokryficzna) historia o wypadku przemysłowym spowodowanym przez testowanie pętli while dla sygnału wejściowego czujnika! = MAX_TEMP. Niestety, pewnego dnia sygnał wejściowy czujnika zmienił się z mniejszego niż MAX_TEMP na większy niż MAX_TEMP bez każdego przejścia przez MAX_TEMP. Proces przegrzał się bez wykrycia i wybuchł pożar. Czasami istnieje różnica między! = I <.
Charles E. Grant,

Odpowiedzi:

59

Powodem wyboru jednego lub drugiego jest zamiar, w wyniku czego zwiększa on czytelność .

Cel: pętla powinna działać tak długo, jak ijest mniejsza niż 10, nie tak długo, jak inie jest równa 10. Nawet jeśli ta ostatnia może być taka sama w tym konkretnym przypadku, to nie to, co masz na myśli, więc nie powinna być tak napisanym.

Czytelność: dzięki zapisaniu tego, co masz na myśli, łatwiej jest to zrozumieć. Na przykład, jeśli użyjesz i != 10, ktoś czytający kod może zastanawiać się, czy wewnątrz pętli jest jakiś sposób, iaby stać się większy niż 10 i że pętla powinna być kontynuowana (btw: zły styl to bałagan z iteratorem gdzie indziej niż w nagłówku for-statement, ale to nie oznacza, że ludzie nie rób tego w wyniku czego oczekują opiekunowie).

Deckard
źródło
3
Nie, pętla nie powinna działać tak długo, jak ijest „mniejsza niż 10”, powinna działać tak długo, jak długo (w tym przypadku) górna granica nie zostanie osiągnięta. Kiedy używasz uzasadnienia przedziału / iteratora C ++, o wiele bardziej logiczne jest wyrażanie tego za pomocą !=niż <.
Konrad Rudolph,
14
@Konrad W ogóle się z tym nie zgadzam. Najważniejsze jest, aby nie być dogmatycznym, ale raczej wybrać konstrukt, który najlepiej wyraża zamiar warunku. I tak, jeśli wybierzesz pętlę przez coś zaczynającą się od 0 i przesuwającą się w górę, wtedy <faktycznie wyraża to dokładnie.
Deckard
6
@Konrad, brakuje ci sensu. Operator inkrementacji w tej pętli pokazuje, że warunek pętli jest górną granicą, a nie porównaniem tożsamości. Gdybyś się zmniejszał, byłaby to dolna granica. Jeśli wykonujesz iterację nad niezamówioną kolekcją, tożsamość może być właściwym warunkiem.
Alex Feinman,
3
@Alex przyrost nie był moim celem. Interwał niekoniecznie musi mieć zamówienie. Powtarza tylko… cóż, coś, aż do końca. Na przykład elementy w zestawie skrótów. W C ++ odbywa się to za pomocą iteratorów i forpętli. Używanie <tutaj byłoby głupie. Wydaje się, że Deckard to potwierdza. Bardzo zgadzam się z jego komentarzem w odpowiedzi na mój.
Konrad Rudolph,
1
Uwaga: jeśli używasz bufora obrotowego ze wskaźnikami pościgu, MUSISZ użyć!=
SF.
48

<Wzór jest ogólnie użyteczny nawet jeśli przyrost nie dzieje się dokładnie 1.

Pozwala to na jeden wspólny sposób wykonywania pętli, niezależnie od tego, jak to się faktycznie robi.


źródło
6
Pozwala również ina modyfikację wewnątrz pętli całkowicie bezpiecznie, jeśli zajdzie taka potrzeba.
Matthew Scharley,
1
lub jeśli „i” zostanie zmodyfikowane całkowicie niepewnie ... Inny zespół miał dziwny problem z serwerem. Zgłaszał 100% wykorzystania procesora i musi to być problem z serwerem lub systemem monitorowania, prawda? Nie, znalazłem warunek pętli napisany przez „eksperta starszego programistę” z tym samym problemem, o którym mówimy.
jqa
12
Tyle że nie wszystkie C ++ dla pętli mogą korzystać <, takie jak iteratory w sekwencji, a więc !=byłby to jeden wspólny sposób wykonywania pętli w C ++.
David Thornley,
@SnOrfus: Nie do końca analizuję ten komentarz. Zasadniczo nie uzyskasz rzetelnie wyjątków od zbytniego zwiększania iteratora (chociaż istnieją bardziej specyficzne sytuacje, w których to zrobisz).
David Thornley,
Cóż, podążam za <wzorem, ponieważ wymaga jednego naciśnięcia klawisza zamiast dwóch z >wzorcem i czterech z !=. To naprawdę kwestia wydajności podobnej do OCD.
Spoike,
21

W C ++ zaleceniem Scotta Myersa w bardziej efektywnym C ++ (pozycja 6) jest zawsze używanie drugiego, chyba że masz ku temu powód, ponieważ oznacza to, że masz tę samą składnię dla indeksów iteratora i liczb całkowitych, dzięki czemu możesz bezproblemowo przełączać się między intiteratorem bez żadnych zmian w składni.

W innych językach nie ma to zastosowania, więc myślę, że <jest to raczej preferowane ze względu na punkt Thorbjørna Ravna Andersena.

Nawiasem mówiąc, pewnego dnia rozmawiałem o tym z innym deweloperem, a on powiedział, że powodem, dla którego wolałbym <więcej, !=jest to, że imoże przypadkowo zwiększyć o więcej niż jeden, a to może spowodować, że warunek przerwania nie będzie spełniony; to jest IMO ładunek nonsensów.


źródło
17
„ładunek bzdur” ... do dnia, w którym przypadkowo masz dodatkowe i ++ w ciele pętli.
2
+1, szczególnie za „ładunek bzdur”, ponieważ tak jest. Jeśli ciało pętli przypadkowo zwiększy licznik, masz znacznie większe problemy.
Konrad Rudolph,
12
@B Tyler, jesteśmy tylko ludźmi i wcześniej zdarzały się większe błędy. Rozważ <bardziej defensywne programowanie niż !=wtedy ...
2
@ Thorbjørn Ravn Andersen - nie mówię, że się z tobą nie zgadzam, zgadzam się; <jest bardziej defensywny, a moi koledzy go nienawidzą, jeśli piszę, !=więc tego nie robię. Jednak jest coś !=w C ++ i właśnie to przedstawiłem.
9
Jednym ze scenariuszy, w których można uzyskać przypadkowe dodatkowe, i++jest zmiana pętli while w pętlę for. Nie jestem jednak pewien, czy posiadanie połowy liczby iteracji jest lepsze niż (prawie) nieskończona pętla; ten drugi prawdopodobnie uczyniłby błąd bardziej oczywistym.
ak2
12

Używanie (i <10) jest moim zdaniem bezpieczniejszą praktyką. Łapie maksymalną liczbę potencjalnych przypadków rezygnacji - wszystko, co jest większe lub równe 10. Porównaj to z drugim przypadkiem (i! = 10); wyłapuje tylko jeden możliwy przypadek rzucenia - gdy i ma dokładnie 10 lat.

Produktem ubocznym tego jest to, że poprawia czytelność. Dodatkowo, jeśli przyrost będzie wynosił cokolwiek innego 1, może pomóc zminimalizować prawdopodobieństwo wystąpienia problemu, gdybyśmy popełnili błąd podczas pisania sprawy zamykającej.

Rozważać:

1) for (i = 1; i < 14; i+=2)

2) for (i = 1; i != 14; i+=2)

Chociaż oba przypadki są prawdopodobnie wadliwe / błędne, drugi może być WIĘCEJ błędny, ponieważ nie zostanie zamknięty. Pierwszy przypadek zostanie zakończony i istnieje większa szansa, że ​​zakończy się w odpowiednim miejscu, mimo że 14 to prawdopodobnie niewłaściwa liczba (15 prawdopodobnie byłoby lepsze).

iskrzący
źródło
Pierwszy przypadek może mieć rację! Jeśli chcesz iterować po wszystkich liczbach naturalnych mniejszych niż 14, nie ma lepszego sposobu na wyrażenie tego - obliczenie „właściwej” górnej granicy (13) byłoby głupie.
maaartinus,
9

Oto kolejna odpowiedź, której nikt jeszcze nie wymyślił. forpętle powinny być używane, gdy trzeba iterować po sekwencji . Użycie !=jest najbardziej zwięzłą metodą określenia warunku zakończenia pętli. Jednak stosowanie mniej restrykcyjnego operatora jest bardzo powszechnym idiomem programowania obronnego. Dla liczb całkowitych nie ma to znaczenia - jest to tylko osobisty wybór bez bardziej szczegółowego przykładu. Pętle nad kolekcjami z iteratorami, których chcesz używać !=z powodów, które podali inni. Jeśli weźmiesz pod uwagę sekwencje floatlub double, to chcesz uniknąć !=za wszelką cenę.

Chciałem zwrócić uwagę, że forjest używany, gdy trzeba iterować sekwencję. Wygenerowana sekwencja ma punkt początkowy, odstęp i warunek zakończenia. Są one zwięźle określone w forinstrukcji. Jeśli okaże się, że (1) nie obejmuje części krokowej forlub (2) określając coś w rodzaju truewarunku ochronnego, nie powinieneś używać forpętli!

whilePętla służy do kontynuowania pracy, gdy warunek jest spełniony specyficzny. Jeśli nie przetwarzasz sekwencji, prawdopodobnie whilezamiast tego potrzebujesz pętli. Argumenty warunku ochronnego są tutaj podobne, ale decyzja między pętlą whilea forpętlą powinna być bardzo świadoma. whilePętli pod cenione w środowisku C ++ MOM.

Jeśli przetwarzasz kolekcję przedmiotów (bardzo częste forużycie pętli), naprawdę powinieneś użyć bardziej specjalistycznej metody. Niestety std::for_eachjest dość bolesny w C ++ z wielu powodów. W wielu przypadkach oddzielenie korpusu forpętli w funkcji wolnostojącej (choć nieco bolesne) skutkuje znacznie czystszym rozwiązaniem. Podczas pracy ze zbiorami, należy rozważyć std::for_each, std::transformczy std::accumulate. Implementacja wielu algorytmów staje się zwięzła i krystalicznie czysta, gdy jest wyrażana w ten sposób. Nie wspominając już o tym, że izolowanie ciała pętli w oddzielną funkcję / metodę zmusza cię do skoncentrowania się na algorytmie, jego wymaganiach wejściowych i wynikach.

Jeśli używasz Java, Python, Ruby, a nawet C ++ 0x , powinieneś używać odpowiedniej pętli foreach kolekcji . Gdy kompilatory C ++ implementują tę funkcję, wiele forpętli zniknie, podobnie jak tego rodzaju dyskusje.

D.Shawley
źródło
2
„Jednak stosowanie mniej restrykcyjnego operatora jest bardzo powszechnym idiomem programowania obronnego”. To mniej więcej to podsumowuje.
James P.
+1 dla discussin różnice w porównaniu z intencją while, fori foreach(lub język equivilant)
Kevin Peno
Ściśle mówiąc, pętla służy do kontynuowania pracy, dopóki warunek jest specyficzny nie spotkałwhile
Alnitak
@Alnitak - D'oh ... Naprawię to :)
D.Shawley
Byłem zdezorientowany dwoma możliwymi znaczeniami „mniej restrykcyjnego”: może oznaczać, że operator jest łagodny w wartościach, które przekazuje ( <) lub operator jest łagodny w wartościach, które zawodzi ( !=).
Mateen Ulhaq,
4

Używając „mniej niż” jest (zazwyczaj) poprawne semantycznie, naprawdę masz na myśli odliczanie, aż inie będzie już mniej niż 10, więc „mniej niż” jasno wyraża twoje intencje. Użycie „nierównomiernego” oczywiście działa praktycznie w przypadkach wywołań, ale ma nieco inne znaczenie. W niektórych przypadkach może to być potrzebne, ale z mojego doświadczenia nigdy tak nie było. Jeśli naprawdę masz przypadek, w którym imoże być więcej lub mniej niż 10, ale chcesz nadal zapętlać, aż będzie równy 10, wtedy ten kod naprawdę będzie wymagał bardzo wyraźnego komentarza i prawdopodobnie mógłby być napisany przy użyciu innej konstrukcji, takiej jak whilebyć może jako pętla.

Steve
źródło
2

Jednym z powodów, dla których wolałbym więcej less thanniż, not equalsjest działanie jako strażnik. W niektórych ograniczonych okolicznościach (złe programowanie lub odkażanie) not equalsmożna to pominąć less than, ale nadal będzie obowiązywać.

Oto jeden przykład, w którym brak kontroli odkażania doprowadził do dziwnych wyników: http://www.michaeleisen.org/blog/?p=358

James P.
źródło
0

Oto jeden z powodów, dla których wolisz używać <zamiast !=. Jeśli używasz języka z zasięgiem zmiennych globalnych, co się stanie, jeśli zmodyfikuje się inny kod i? Jeśli używasz <raczej niż !=, najgorsze, co się dzieje, to to, że iteracja kończy się szybciej: być może niektóre inne inkrementy kodu iprzypadkowo i pomijasz kilka iteracji w pętli for. Ale co się stanie, jeśli zapętlisz od 0 do 10, a pętla dojdzie do 9, a niektóre źle napisane przyrosty wątków iz jakiegoś dziwnego powodu. Następnie twoja pętla kończy iterację i przyrosty, itak że wartość wynosi teraz 11. Ponieważ pętla pominęła warunek wyjścia ( inigdy nie równa się 10), teraz zapętla się w nieskończoność.

Nie musi to być szczególnie dziwaczna logika typu wątkowość i zmienne globalne, które to powoduje. Być może piszesz pętlę, która wymaga powrotu. Jeśli mutujesz iwewnątrz pętli i psujesz swoją logikę, mając ją tak, że ma ona górną granicę, a nie !=jest mniej prawdopodobne, że pozostawi cię w nieskończonej pętli.

Tom Morris
źródło
3
Oczywiście wydaje się to idealnym argumentem dla każdej pętli i ogólnie bardziej funkcjonalnym stylem programowania. Ale dlaczego miałbyś to robić, gdy zmienne zmienne są o wiele bardziej zabawne ?
Tom Morris,
3
Nie sądzę, żeby to był naprawdę dobry powód. Tak czy inaczej masz błąd, który należy znaleźć i naprawić, ale nieskończona pętla sprawia, że ​​błąd jest raczej oczywisty.
ak2
0

W świecie osadzonym, szczególnie w hałaśliwym otoczeniu, nie można liczyć na to, że pamięć RAM koniecznie zachowuje się tak, jak powinna. W warunkowym (dla, podczas gdy, jeśli) porównywaniu za pomocą „==” lub „! =” Zawsze ryzykujesz, że twoje zmienne pominą tę kluczową wartość, która kończy pętlę - może to mieć katastrofalne konsekwencje - Mars Lander konsekwencje poziomu. Użycie „<” lub „>” w tym stanie zapewnia dodatkowy poziom bezpieczeństwa w celu wychwycenia „nieznanych niewiadomych”.

W oryginalnym przykładzie, gdyby ibyły niewytłumaczalnie katapultowane do wartości znacznie większej niż 10, porównanie „<” natychmiast wychwyciłoby błąd i opuścił pętlę, ale „! =” Kontynuowałoby odliczanie do momentu izawinięcia wokół 0 i wstecz do 10.

oosterwal
źródło
2
Istnieje inna pokrewna odmiana z kodem, na przykład w for (i=4; i<length; i++) csum+=dat[i];przypadku, gdy prawidłowe pakiety zawsze miałyby lengthco najmniej cztery, ale jeden może otrzymywać uszkodzone pakiety. Jeśli różne typy pakietów mają różne wymagania dotyczące długości, można sprawdzić poprawność długości w ramach przetwarzania, która jest inna dla każdego typu pakietu, ale najpierw sprawdzić poprawność sumy kontrolnej w ramach wspólnej logiki. Jeśli zostanie odebrany pakiet niedostatecznej długości, tak naprawdę nie ma znaczenia, czy obliczenie sumy kontrolnej zgłasza „dobre” czy „złe”, ale nie powinno się zawiesić.
supercat