Dzięki temu jest bardziej czytelny, a jeśli zmienisz i++na i+=2(na przykład), będzie działał przez bardzo długi czas (lub być może na zawsze). Teraz, ponieważ zwykle używasz <w przypadkach, w których iterator zwiększa się o więcej niż 1, równie dobrze możesz użyć <również w przypadku, gdy zwiększasz iterator o 1 (dla spójności).
barak manos
112
5 != ijest zły
Slava
27
Zawsze powinieneś być świadomy tego, co robisz, ale i tak popełnisz błędy. Użycie <zmniejsza możliwość wprowadzenia błędu do tego kodu.
Aurast
40
@OleTange Jeśli iterujesz przy użyciu liczb zmiennoprzecinkowych, to już jesteś wkręcony.
CaptainCodeman
29
@Slava ... nie zło. Najwyraźniej nigdy nie ugryzłeś się w C przez zwykłą literówkę, która robi różnicę między if ( i == 5 ) ...i if ( i = 5 ). Bardzo różne rzeczy. Odwrócenie argumentów zapobiega problemowi: ponieważ literały nie są lvals, nie można ich przypisać, a kompilator rzuca literówkę. @Zingam: dobre programowanie defensywne!
Nicholas Carey,
Odpowiedzi:
303
while(time !=6:30pm){Work();}
Jest 18:31 ... Cholera, teraz moja kolejna szansa na powrót do domu to jutro! :)
To pokazuje, że silniejsze ograniczenie ogranicza ryzyko i jest prawdopodobnie bardziej intuicyjne do zrozumienia.
Czasami próba „zmniejszenia ryzyka” kończy się ukrywaniem błędu. Jeśli konstrukcja pętli powinna uniemożliwić niezauważenie 18:30 poślizgnięcia się, wówczas użycie <spowoduje ukrycie błędu, ale użycie !=pokazałoby, że jest problem.
Adrian McCarthy
27
@AdrianMcCarthy: Niekoniecznie; może po prostu wprowadzić drugi błąd, co utrudni diagnozę .
Wyścigi lekkości na orbicie
4
@AdrianMcCarthy: Właśnie o to mi chodzi; możesz mieć kilka takich przypadków, których jeszcze nie widziałeś;)
Wyścigi lekkości na orbicie
6
Myślę, że iteratory są doskonałym przykładem @AdrianMcCarthy point. Zalecane porównanie dla nich to! = Zamiast <.
Taekahn
5
Widziałem dokładnie ten błąd (i spowodował to 3 miesiące debugowania) - ale ta odpowiedź całkowicie nie ma sensu. W podanym przykładzie jest to niemożliwe dla stanu końcowego można przegapić. Można nawet powiedzieć, że świadomy wybór! = Ponad <oznacza mocne potwierdzenie tego faktu. Łagodzenie ryzyka, które ostatecznie nie istnieje, jest dla mnie zapachem kodu. (Powiedziawszy to, możesz również argumentować, że <jest idiomatyczny, że jest to równie dobry powód, aby używać go jak każdy inny.)
Ian Goldby
95
Nie ma powodu technicznego. Istnieje jednak ograniczenie ryzyka, łatwość konserwacji i lepsze zrozumienie kodu.
<lub >są bardziej restrykcyjne niż !=i spełniają dokładnie ten sam cel w większości przypadków (powiedziałbym nawet we wszystkich praktycznych przypadkach).
Istnieje duplikat pytanie tutaj ; i jedna interesująca odpowiedź .
Istnieje kilka typów, takich jak iteratory, które nie obsługują <lub>, ale obsługują == i! =.
Simon B
1
Jest to pętla for z int . Bez iteratorów.
Ely,
7
„Istnieje jednak ograniczenie ryzyka, łatwość konserwacji i lepsze zrozumienie kodu”. Wszystko to są właściwie przyczyny techniczne. :-)
TJ Crowder
@TJCrowder Jaki jest tego powód, gdy chcesz po prostu porozmawiać o tym, co zrobią maszyny?
Brilliand
1
@ DanSheppard Zasadniczo uważam, że ograniczenie ryzyka i łatwość konserwacji to powody biznesowe, a lepsze zrozumienie kodu to powód społeczny (chociaż w tym przypadku wszystkie te powody mają zastosowanie do kwestii technicznych). Uwagi dotyczące „ograniczania ryzyka” nie są w żaden sposób ograniczone do kwestii technicznych, a kwestie „lepszego zrozumienia kodu” mogą ulec zmianie, jeśli pracujesz z inną grupą ludzi (nawet jeśli zaangażowane maszyny w ogóle się nie zmieniają ).
Brilliand
85
Tak, jest powód. Jeśli napiszesz (tak jak stary indeks) dla takiej pętli
for(int i = a; i < b;++i){}
wtedy działa zgodnie z oczekiwaniami dla dowolnych wartości ai b(tj. zero iteracji, gdy a > bzamiast nieskończoności, jeśli użyłeś i == b;).
Z drugiej strony, dla iteratorów piszesz
for(auto it =begin; it !=end;++it)
ponieważ każdy iterator powinny wdrożyć operator!=, ale nie dla każdego iteratora jest możliwe zapewnienie operator<.
Również oparty na zakresie dla pętli
for(auto e : v)
to nie tylko fantazyjny cukier, ale wymiernie zmniejszają szanse na napisanie niewłaściwego kodu.
Niezły przykład. Byłbym zainteresowany sprawą !=zamiast <. Osobiście nigdy tego nie używałem.
Ely,
@Elyasin Nie znalazłem dobrego i nie chciałem pisać złego: Wyobraź sobie, że masz jakiś okrągły pojemnik, w którym zwiększasz o + x moduł wielkości pojemnika N i chcesz zatrzymać pętlę, gdy osiągnąć określony indeks ... cóż, to naprawdę zły przykład. Może znajdę lepszy
idclev 463035818,
Ta sama pętla z! = Zamiast <wyjaśnia, jaka jest przewaga <(lub>) w tym kontekście. for (int i=a; i != b; i++) {}
Paul Smith
10
Używanie czegokolwiek poza !=iteratorami jest dość niebezpieczne, ponieważ czasami iteratory mogą być mniejsze niż w porównaniu (to na przykład wskaźnik), ale porównanie jest bez znaczenia (jest to lista połączona).
Zan Lynx,
1
Naucz się poważnie używać białych znaków.
Miles Rout
70
Możesz mieć coś takiego
for(int i =0; i<5;++i){...if(...) i++;...}
Jeśli zmienna pętli jest zapisana przez wewnętrzny kod, i!=5może nie przerwać tej pętli. Bezpieczniej jest sprawdzić nierówności.
Edytuj o czytelności. Forma nierówności jest znacznie częściej stosowana. Dlatego jest to bardzo szybkie do odczytania, ponieważ nie ma nic specjalnego do zrozumienia (obciążenie mózgu jest zmniejszone, ponieważ zadanie jest wspólne). Więc fajnie jest, aby czytelnicy korzystali z tych nawyków.
Ten rodzaj wewnętrznego „if (warunek) to i ++” to pierwsza rzecz, o której myślałem.
WernerCD
3
Przyszedł w oczekiwaniu, że będzie to najwyższa głosowana odpowiedź; Byłem zaskoczony, że musiałem przewinąć tak daleko, aby go znaleźć. To robi znacznie więcej w kierunku przekonania początkującego programisty niż „no cóż, powinieneś użyć najsilniejszego warunku”. Pozostałe odpowiedzi, jeśli pozostaną na topie, powinny zawierać to jako jasne i oczywiste wyjaśnienie tego, co może pójść nie tak z proponowanym kodem OP.
msouth
1
@msouth W pełni się zgadzam, ale mój POV jest dość subiektywny;)
John d
3
@msouth Uwielbiam to, gdy nieoczekiwane zachowanie produkcyjne ujawnia moje błędy, prawda? :-)
deworde
3
@msouth Posłuchaj, wszystko, co musimy zrobić, to nigdy nie popełniać błędów i wszystko będzie dobrze. Simples.
deworde
48
I na koniec, nazywa się to programowaniem defensywnym , co oznacza, że zawsze bierze się najsilniejszy przypadek, aby uniknąć obecnych i przyszłych błędów wpływających na program.
Jedynym przypadkiem, w którym programowanie obronne nie jest potrzebne, jest stan, w którym stany zostały udowodnione w warunkach wstępnych i następczych (ale wtedy udowodniono, że jest to najbardziej defensywne ze wszystkich programów).
Dobry przykład na poparcie tego: pamięć jest podatna na odwracanie bitów ( SEU ) spowodowane promieniowaniem. W przypadku stosowania intna i != 15stan, licznik zostanie uruchomiony przez długi czas, jeśli coś powyżej czwartego bitu jest odwrócony, zwłaszcza na komputerach, na których sizeof(int)jest 64. SEUs są bardzo poważnym problemem w wyższej wysokości lub w przestrzeni (z powodu wyższego promieniowania) lub w dużych superkomputerach (ponieważ jest tak dużo pamięci).
Josh Sanford,
2
Myślenie, że twój program będzie działał poprawnie, gdy promieniowanie zmienia pamięć, jest optymistycznym i antyatastroficznym sposobem myślenia ... dobry komentarz! :)
Luis Colorado,
37
Twierdziłbym, że wyrażenie takie jak
for(int i =0; i <100;++i ){...}
jest bardziej ekspresyjny niż zamiar
for(int i =0; i !=100;++i ){...}
Ten pierwszy wyraźnie wzywa, że warunek jest testem dla wyłącznej górnej granicy zakresu; ten ostatni jest binarnym testem warunku wyjścia. A jeśli treść pętli nie jest trywialna, może nie być oczywiste, że indeks jest modyfikowany tylko w forsamej instrukcji.
„Chcę, aby ta pętla działała maksymalnie 5 razy” vs. „Chcę uruchomić tę pętlę tyle razy, ile to konieczne, z wyjątkiem dokładnie 5 razy, czego nie chcę”.
biskup
+1 za wzmiankę o górnej granicy zakresu. Myślę, że jest to ważne dla zakresów liczbowych. ! = nie określa poprawnego zakresu w takiej pętli for
element11
22
Iteratory są ważnym przypadkiem, gdy najczęściej używasz !=notacji:
for(auto it = vector.begin(); it != vector.end();++it){// do stuff}
Przyznaję: w praktyce napisałbym to samo, opierając się na range-for:
for(auto& item : vector){// do stuff}
ale sedno pozostaje: zwykle porównuje się iteratory za pomocą ==lub !=.
Iteratory często mają tylko pojęcie równości / nierówności, a nie porządek, dlatego !=ich używasz . Na przykład, w sposób std::vectorPaństwo mogli wykorzystywać <jako iterator jest związany z Index / wskaźnik, ale w std::setnie tak ścisłe zamawianie, jak spacery binarne drzewo.
Martin C.
19
Warunek pętli jest wymuszonym niezmiennikiem pętli.
Załóżmy, że nie patrzysz na korpus pętli:
for(int i =0; i !=5;++i){// ?}
w tym przypadku wiesz na początku iteracji pętli, iktóra nie jest równa 5.
for(int i =0; i <5;++i){// ?}
w tym przypadku wiesz, że na początku iteracji pętli ijest mniejsza niż 5.
Drugi to znacznie, dużo więcej informacji niż pierwszy, nie? Teraz intencje programisty są (prawie na pewno) takie same, ale jeśli szukasz błędów, dobrze jest mieć pewność czytania linii kodu. A drugi wymusza niezmienność, co oznacza, że niektóre błędy, które ugryzłyby cię w pierwszym przypadku, po prostu nie mogą się zdarzyć (lub nie powodują uszkodzenia pamięci, powiedzmy) w drugim przypadku.
Wiesz więcej o stanie programu, czytając mniej kodu, <niż z !=. A na nowoczesnych procesorach zajmują tyle samo czasu, co bez różnicy.
Gdyby inie manipulowano w ciele pętli i zawsze zwiększano ją o 1, a zaczynało się mniej niż 5, nie byłoby różnicy. Ale aby wiedzieć, czy został zmanipulowany, musisz potwierdzić każdy z tych faktów.
Niektóre z tych faktów są stosunkowo łatwe, ale możesz się mylić. Sprawdzanie całego ciała pętli jest jednak uciążliwe.
W C ++ możesz napisać taki indexestyp, że:
for(constint i : indexes(0,5)){// ?}
robi to samo, co jedna z dwóch powyższych forpętli, nawet do kompilatora, optymalizując go do tego samego kodu. Tutaj jednak wiesz, że inie można manipulować w treści pętli, jak to zostało zadeklarowane const, bez kodu niszczącego pamięć.
Im więcej informacji można uzyskać z wiersza kodu bez konieczności rozumienia kontekstu, tym łatwiej jest wyśledzić, co się dzieje. <w przypadku pętli całkowitych daje więcej informacji o stanie kodu w tym wierszu niż !=robi.
Niezupełnie nieskończone - w większości implementacji C ipo cichu zawinie, gdy osiągnie maksimum. Ale tak, najprawdopodobniej dłużej niż jesteś gotowy czekać.
usr2564301
12
Jak widać z innych licznych odpowiedzi, istnieją powody, aby używać <zamiast! =, Które pomogą w przypadkach krawędziowych, warunkach początkowych, niezamierzonej modyfikacji licznika pętli itp.
Szczerze mówiąc, nie sądzę, żebyś wystarczająco podkreślał wagę konwencji. W tym przykładzie inni programiści będą mogli łatwo zobaczyć, co próbujesz zrobić, ale spowoduje to podwójne pobranie. Jednym z zadań podczas programowania jest uczynienie go jak najbardziej czytelnym i znanym wszystkim, więc nieuchronnie, gdy ktoś musi zaktualizować / zmienić kod, nie trzeba wiele wysiłku, aby dowiedzieć się, co robisz w różnych blokach kodu . Gdybym zobaczył, że ktoś go używa !=, przypuszczam, że istniał powód, dla którego go używał, <a jeśli byłaby to duża pętla, przejrzałbym całą tę próbę, aby dowiedzieć się, co zrobiłeś, co sprawiło, że stało się to konieczne ... i to zmarnowany czas.
Jak już powiedział Ian Newson, nie można niezawodnie zapętlić zmiennej pływającej i wyjść z !=. Na przykład,
for(double x=0; x!=1; x+=0.1){}
faktycznie zapętli się w nieskończoność, ponieważ 0,1 nie może być dokładnie przedstawione w liczbach zmiennoprzecinkowych, dlatego licznik wąsko traci 1. Gdy <się kończy.
(Zauważ jednak, że jest to zasadniczo nieokreślone zachowanie, niezależnie od tego, czy otrzymujesz 0,9999 ... jako ostatnią akceptowaną liczbę - co stanowi naruszenie założenia mniejszego niż - lub już kończysz na 1.0000000000000001.)
Używam przymiotnika „techniczny”, aby oznaczać zachowanie / dziwactwa językowe i skutki uboczne kompilatora, takie jak wydajność generowanego kodu.
W tym celu odpowiedź brzmi: nie (*). (*) To „Proszę skonsultować instrukcję obsługi procesora”. Jeśli pracujesz z jakimś zaawansowanym systemem RISC lub FPGA, może być konieczne sprawdzenie, jakie instrukcje są generowane i ile kosztują. Ale jeśli używasz prawie każdy konwencjonalny nowoczesną architekturę, to nie ma istotnej różnicy w poziomie kosztów pomiędzy procesorem lt, eq, nei gt.
Jeśli używasz przypadek krawędź mogłaby się okazać, że !=wymaga trzech operacji ( cmp, not, beq) vs dwóch ( cmp, blt xtr myo). Ponownie RTM w tym przypadku.
W większości przypadków powodem jest defensywność / hartowanie, szczególnie podczas pracy ze wskaźnikami lub złożonymi pętlami. Rozważać
Mniej wymyślnym przykładem może być użycie wartości zwracanych do wykonywania przyrostów, przyjmowanie danych od użytkownika:
#include<iostream>int main(){size_t len =5, step;for(size_t i =0; i != len;){
std::cout <<"i = "<< i <<", step? "<< std::flush;
std::cin >> step;
i += step;// here for emphasis, it could go in the for(;;)}}
Spróbuj tego i wprowadź wartości 1, 2, 10, 999.
Możesz temu zapobiec:
#include<iostream>int main(){size_t len =5, step;for(size_t i =0; i != len;){
std::cout <<"i = "<< i <<", step? "<< std::flush;
std::cin >> step;if(step + i > len)
std::cout <<"too much.\n";else
i += step;}}
Ale prawdopodobnie tego chciałeś
#include<iostream>int main(){size_t len =5, step;for(size_t i =0; i < len;){
std::cout <<"i = "<< i <<", step? "<< std::flush;
std::cin >> step;
i += step;}}
Jest także coś w rodzaju uprzedzenia konwencji <, ponieważ często opiera się na zamawianiu w standardowych kontenerach operator<, na przykład haszowanie w kilku kontenerach STL określa równość poprzez powiedzenie
Dla czytelności i + = krok powinien znajdować się w części dla kontroli. W przypadku, gdybyś zrobił krok = 0
Mikkel Christiansen
1
Chociaż pomogłoby to w odczytaniu dobrego kodu, chciałem podkreślić wadę w błędnej wersji
kfsone
9
Istnieje kilka sposobów pisania dowolnego rodzaju kodu (zwykle), w tym przypadku zdarzają się dwa sposoby (trzy, jeśli policzysz <= i> =).
W tym przypadku ludzie wolą> i <, aby upewnić się, że nawet jeśli coś nieoczekiwanego wydarzy się w pętli (np. Błąd), nie zapętli się w nieskończoność (BAD). Weźmy na przykład następujący kod.
for(int i =1; i !=3; i++){//More Code
i =5;//OOPS! MISTAKE!//More Code}
Gdybyśmy użyli (i <3), bylibyśmy bezpieczni od nieskończonej pętli, ponieważ wprowadziłaby większe ograniczenie.
To naprawdę twój wybór, czy chcesz, aby błąd w programie zamknął całość lub nadal działał z błędem.
można argumentować, że nieskończona pętla byłaby dobrym wskaźnikiem błędu, ale inna argumentowałaby, że i = 5 niekoniecznie jest błędem
njzk2
7
Najczęstszym powodem użycia <jest konwencja. Inni programiści myślą o takich pętlach jako „gdy indeks jest w zakresie”, a nie „dopóki indeks nie osiągnie końca”. Gdy jest to możliwe, przestrzeganie konwencji jest wartością.
Z drugiej strony wiele odpowiedzi tutaj twierdzi, że użycie <formularza pomaga uniknąć błędów. Twierdziłbym, że w wielu przypadkach pomaga to ukryć błędy. Jeśli indeks pętli ma osiągnąć wartość końcową, a zamiast tego faktycznie wykracza poza nią, dzieje się coś, czego się nie spodziewałeś, co może spowodować awarię (lub być efektem ubocznym innego błędu). <Najprawdopodobniej opóźni odkrycie błędu. !=Jest bardziej prawdopodobne, aby doprowadzić do stajni, powiesić, a nawet katastrofy, które pomogą Ci dostrzec błąd wcześniej. Im szybciej zostanie znaleziony błąd, tym taniej go naprawić.
Zauważ, że ta konwencja jest charakterystyczna dla indeksowania tablic i wektorów. Podczas przechodzenia przez prawie jakikolwiek inny typ struktury danych należy użyć iteratora (lub wskaźnika) i bezpośrednio sprawdzać wartość końcową. W takich przypadkach musisz mieć pewność, że iterator osiągnie i nie przekroczy rzeczywistej wartości końcowej.
Na przykład, jeśli przechodzisz przez zwykły ciąg C, zwykle bardziej powszechne jest pisanie:
for(char*p = foo;*p !='\0';++p){// do something with *p}
niż
int length = strlen(foo);for(int i =0; i < length;++i){// do something with foo[i]}
Po pierwsze, jeśli łańcuch jest bardzo długi, druga forma będzie wolniejsza, ponieważ strlenjest to kolejne przejście przez łańcuch.
Z ciągiem std :: C ++ można użyć pętli opartej na zakresie, standardowego algorytmu lub iteratorów, nawet jeśli długość jest łatwo dostępna. Jeśli używasz iteratorów, konwencją jest użycie !=zamiast <, jak w:
for(auto it = foo.begin(); it != foo.end();++it){...}
Podobnie, iteracja drzewa, listy lub deque zwykle wymaga obserwowania wskaźnika zerowego lub innego wartownika, a nie sprawdzania, czy indeks pozostaje w zakresie.
Masz rację, podkreślając znaczenie idiomu w programowaniu. Jeśli spojrzę na kod, który ma znajomy wzorzec, nie muszę tracić czasu na wnioskowanie o wzorzec, ale mogę przejść bezpośrednio do jego rdzenia. Myślę, że to mój główny problem z porównaniami Yody - nie wyglądają idiomatycznie, więc muszę je przeczytać dwa razy, aby upewnić się, że mają na myśli to, co myślę.
Toby Speight,
7
Jednym z powodów, dla których nie należy używać tej konstrukcji, są liczby zmiennoprzecinkowe. !=jest bardzo niebezpiecznym porównaniem przy użyciu liczb zmiennoprzecinkowych, ponieważ rzadko ocenia się na prawdziwe, nawet jeśli liczby wyglądają tak samo. <lub >usuwa to ryzyko.
Jeśli iterator pętli jest liczbą zmiennoprzecinkową, wówczas !=versus <jest najmniejszym z twoich problemów.
Lundin,
7
Istnieją dwa powiązane powody stosowania tej praktyki, które oba mają związek z faktem, że język programowania jest przecież językiem, który będzie czytany przez ludzi (między innymi).
(1) Trochę redundancji. W języku naturalnym zwykle dostarczamy więcej informacji niż jest to absolutnie konieczne, podobnie jak kod korygujący błędy. Tutaj dodatkową informacją jest to, że zmienna pętli i(zobacz, jak użyłem tutaj redundancji? Jeśli nie wiesz, co oznacza „zmienna pętli” lub jeśli zapomniałeś nazwy zmiennej, po przeczytaniu „zmiennej pętli i” masz pełny informacja) jest mniejsza niż 5 podczas pętli, nie różni się tylko od 5. Redundancja poprawia czytelność.
(2) Konwencja. Języki mają określone standardowe sposoby wyrażania określonych sytuacji. Jeśli nie będziesz postępować zgodnie z ustalonym sposobem mówienia, nadal będziesz rozumiany, ale wysiłek adresata wiadomości jest większy, ponieważ niektóre optymalizacje nie będą działać. Przykład:
Nie rozmawiaj przy gorącym zacieru. Po prostu wyjaśnij trudność!
Pierwsze zdanie to dosłowne tłumaczenie niemieckiego idiomu. Drugi to wspólny angielski idiom z głównymi słowami zastąpionymi synonimami. Wynik jest zrozumiały, ale jego zrozumienie zajmuje dużo więcej czasu:
Nie omijaj krzaków. Po prostu wyjaśnij problem!
Dzieje się tak nawet w przypadku, gdy synonimy użyte w pierwszej wersji lepiej pasują do sytuacji niż konwencjonalne słowa w angielskim idiomie. Podobne siły działają, gdy programiści czytają kod. Jest to również powód 5 != ii 5 > idziwne sposoby umieszczenia go, chyba że pracujesz w środowisku, w którym standardowym jest zamiana bardziej normalnych i != 5i i < 5w ten sposób. Takie społeczności dialektyczne istnieją, prawdopodobnie dlatego, że spójność ułatwia pamiętanie pisania 5 == izamiast naturalnego, ale podatnego na błędy i == 5.
Ausgezeichnet . Podobnie, nie byłoby napisać test jako i < 17+(36/-3)(nawet gdy są stosowane stałe gdzie indziej, to musisz napisać swoje imiona ! Dla jasności).
usr2564301
6
Używanie porównań relacyjnych w takich przypadkach jest bardziej popularnym nawykiem niż cokolwiek innego. Popularność zyskała w czasach, gdy takie względy koncepcyjne, jak kategorie iteratorów i ich porównywalność, nie były uważane za priorytetowe.
Powiedziałbym, że należy w miarę możliwości używać porównań równości zamiast porównań relacyjnych, ponieważ porównania równości nakładają mniejsze wymagania na porównywane wartości. Bycie EqualityComparable jest mniejszym wymogiem niż bycie LessThanComparable .
Innym przykładem, który pokazuje szersze zastosowanie porównania równości w takich kontekstach, jest popularna zagadka z implementacją unsignediteracji aż do 0. Można to zrobić jako
for(unsigned i =42; i !=-1;--i)...
Zauważ, że powyższe dotyczy zarówno iteracji podpisanej, jak i niepodpisanej, podczas gdy wersja relacyjna rozpada się na typy niepodpisane.
Oprócz przykładów, w których zmienna pętli (niezamierzona) zmieni się wewnątrz ciała, istnieją inne powody, aby używać operatorów mniejszych lub większych niż:
Druga kula jest co najwyżej niepoważnym powodem. Kod powinien być poprawny i jasny. Kompakt jest znacznie mniej ważny.
Jonathan Leffler,
@JathanathanLeffler Cóż, w REPLII TDD, ta dodatkowa postać to jedna dodatkowa nanosekunda do analizy i razy miliard iteracji, aby znaleźć błąd spowodowany pisaniem 5 != $i, który zabrał mi całą sekundę mojego życia. Uh ... snicker
biskup
1
Oprócz różnych osób, które wspominały, że ogranicza to ryzyko, zmniejsza także liczbę przeciążeń funkcji niezbędnych do interakcji z różnymi standardowymi komponentami biblioteki. Na przykład, jeśli chcesz, aby twój typ był przechowywany w std::setlub używany jako klucz std::maplub używany z niektórymi algorytmami wyszukiwania i sortowania, biblioteka standardowa zwykle używa std::lessdo porównywania obiektów, ponieważ większość algorytmów wymaga jedynie ścisłej słabej kolejności . W związku z tym dobrym nawykiem jest używanie <porównań zamiast !=porównań (tam, gdzie ma to oczywiście sens).
możesz mieć rację co do liczby przeciążeń, ale biorąc pod uwagę wymagania dotyczące obiektów, dla niemal każdego obiektu można zdefiniować !=operator, ale nie zawsze jest możliwe zdefiniowanie ścisłej słabej kolejności. W tym sensie !=byłoby lepiej, ponieważ nakłada mniej wymagań na typ. Nadal jednak pozostaję przy <.
idclev 463035818,
0
Z punktu widzenia składni nie ma problemu, ale logika tego wyrażenia 5!=i nie jest dźwięk.
Moim zdaniem użycie !=do ustawienia granic pętli for nie jest logicznie uzasadnione, ponieważ pętla for zwiększa lub zmniejsza indeks iteracji, więc ustawianie pętli na iterację, dopóki indeks iteracji nie znajdzie się poza granicami (!= do czegoś) nie jest prawidłowe wdrożenie.
To będzie działać, ale jest skłonny do złego zachowania, ponieważ granica przetwarzanie danych jest tracona podczas korzystania !=dla przyrostowego problemu (co oznacza, że od samego początku wiedzieć, czy przyrosty to albo ubytki), dlatego zamiast są używane.!=<>>==>
Logika jest w porządku. Kiedy ito 5wyjściu pętli. Czy chciałeś powiedzieć coś jeszcze?
Dan Getz
1
Jeśli dane nie zostaną poprawnie przeniesione z powodu ciepła, elektromagnetyczny wpływa na twój kod działa wiecznie. Jeśli robisz to na zwykłej stacji roboczej lub serwerze z pamięcią RAM ECC, nie ma problemu (ani logicznego, technicznego ani fizycznego)
i++
nai+=2
(na przykład), będzie działał przez bardzo długi czas (lub być może na zawsze). Teraz, ponieważ zwykle używasz<
w przypadkach, w których iterator zwiększa się o więcej niż 1, równie dobrze możesz użyć<
również w przypadku, gdy zwiększasz iterator o 1 (dla spójności).5 != i
jest złyif ( i == 5 ) ...
iif ( i = 5 )
. Bardzo różne rzeczy. Odwrócenie argumentów zapobiega problemowi: ponieważ literały nie sąlvals
, nie można ich przypisać, a kompilator rzuca literówkę. @Zingam: dobre programowanie defensywne!Odpowiedzi:
Jest 18:31 ... Cholera, teraz moja kolejna szansa na powrót do domu to jutro! :)
To pokazuje, że silniejsze ograniczenie ogranicza ryzyko i jest prawdopodobnie bardziej intuicyjne do zrozumienia.
źródło
<
spowoduje ukrycie błędu, ale użycie!=
pokazałoby, że jest problem.Nie ma powodu technicznego. Istnieje jednak ograniczenie ryzyka, łatwość konserwacji i lepsze zrozumienie kodu.
<
lub>
są bardziej restrykcyjne niż!=
i spełniają dokładnie ten sam cel w większości przypadków (powiedziałbym nawet we wszystkich praktycznych przypadkach).Istnieje duplikat pytanie tutaj ; i jedna interesująca odpowiedź .
źródło
Tak, jest powód. Jeśli napiszesz (tak jak stary indeks) dla takiej pętli
wtedy działa zgodnie z oczekiwaniami dla dowolnych wartości
a
ib
(tj. zero iteracji, gdya > b
zamiast nieskończoności, jeśli użyłeśi == b;
).Z drugiej strony, dla iteratorów piszesz
ponieważ każdy iterator powinny wdrożyć
operator!=
, ale nie dla każdego iteratora jest możliwe zapewnienieoperator<
.Również oparty na zakresie dla pętli
to nie tylko fantazyjny cukier, ale wymiernie zmniejszają szanse na napisanie niewłaściwego kodu.
źródło
!=
zamiast<
. Osobiście nigdy tego nie używałem.for (int i=a; i != b; i++) {}
!=
iteratorami jest dość niebezpieczne, ponieważ czasami iteratory mogą być mniejsze niż w porównaniu (to na przykład wskaźnik), ale porównanie jest bez znaczenia (jest to lista połączona).Możesz mieć coś takiego
Jeśli zmienna pętli jest zapisana przez wewnętrzny kod,
i!=5
może nie przerwać tej pętli. Bezpieczniej jest sprawdzić nierówności.Edytuj o czytelności. Forma nierówności jest znacznie częściej stosowana. Dlatego jest to bardzo szybkie do odczytania, ponieważ nie ma nic specjalnego do zrozumienia (obciążenie mózgu jest zmniejszone, ponieważ zadanie jest wspólne). Więc fajnie jest, aby czytelnicy korzystali z tych nawyków.
źródło
I na koniec, nazywa się to programowaniem defensywnym , co oznacza, że zawsze bierze się najsilniejszy przypadek, aby uniknąć obecnych i przyszłych błędów wpływających na program.
Jedynym przypadkiem, w którym programowanie obronne nie jest potrzebne, jest stan, w którym stany zostały udowodnione w warunkach wstępnych i następczych (ale wtedy udowodniono, że jest to najbardziej defensywne ze wszystkich programów).
źródło
int
nai != 15
stan, licznik zostanie uruchomiony przez długi czas, jeśli coś powyżej czwartego bitu jest odwrócony, zwłaszcza na komputerach, na którychsizeof(int)
jest 64. SEUs są bardzo poważnym problemem w wyższej wysokości lub w przestrzeni (z powodu wyższego promieniowania) lub w dużych superkomputerach (ponieważ jest tak dużo pamięci).Twierdziłbym, że wyrażenie takie jak
jest bardziej ekspresyjny niż zamiar
Ten pierwszy wyraźnie wzywa, że warunek jest testem dla wyłącznej górnej granicy zakresu; ten ostatni jest binarnym testem warunku wyjścia. A jeśli treść pętli nie jest trywialna, może nie być oczywiste, że indeks jest modyfikowany tylko w
for
samej instrukcji.źródło
Iteratory są ważnym przypadkiem, gdy najczęściej używasz
!=
notacji:Przyznaję: w praktyce napisałbym to samo, opierając się na
range-for
:ale sedno pozostaje: zwykle porównuje się iteratory za pomocą
==
lub!=
.źródło
!=
ich używasz . Na przykład, w sposóbstd::vector
Państwo mogli wykorzystywać<
jako iterator jest związany z Index / wskaźnik, ale wstd::set
nie tak ścisłe zamawianie, jak spacery binarne drzewo.Warunek pętli jest wymuszonym niezmiennikiem pętli.
Załóżmy, że nie patrzysz na korpus pętli:
w tym przypadku wiesz na początku iteracji pętli,
i
która nie jest równa5
.w tym przypadku wiesz, że na początku iteracji pętli
i
jest mniejsza niż5
.Drugi to znacznie, dużo więcej informacji niż pierwszy, nie? Teraz intencje programisty są (prawie na pewno) takie same, ale jeśli szukasz błędów, dobrze jest mieć pewność czytania linii kodu. A drugi wymusza niezmienność, co oznacza, że niektóre błędy, które ugryzłyby cię w pierwszym przypadku, po prostu nie mogą się zdarzyć (lub nie powodują uszkodzenia pamięci, powiedzmy) w drugim przypadku.
Wiesz więcej o stanie programu, czytając mniej kodu,
<
niż z!=
. A na nowoczesnych procesorach zajmują tyle samo czasu, co bez różnicy.Gdyby
i
nie manipulowano w ciele pętli i zawsze zwiększano ją o 1, a zaczynało się mniej niż5
, nie byłoby różnicy. Ale aby wiedzieć, czy został zmanipulowany, musisz potwierdzić każdy z tych faktów.Niektóre z tych faktów są stosunkowo łatwe, ale możesz się mylić. Sprawdzanie całego ciała pętli jest jednak uciążliwe.
W C ++ możesz napisać taki
indexes
typ, że:robi to samo, co jedna z dwóch powyższych
for
pętli, nawet do kompilatora, optymalizując go do tego samego kodu. Tutaj jednak wiesz, żei
nie można manipulować w treści pętli, jak to zostało zadeklarowaneconst
, bez kodu niszczącego pamięć.Im więcej informacji można uzyskać z wiersza kodu bez konieczności rozumienia kontekstu, tym łatwiej jest wyśledzić, co się dzieje.
<
w przypadku pętli całkowitych daje więcej informacji o stanie kodu w tym wierszu niż!=
robi.źródło
Tak; OpenMP nie równolegle pętli z
!=
warunkiem.źródło
Może się zdarzyć, że zmienna
i
zostanie ustawiona na jakąś dużą wartość i jeśli użyjesz!=
operatora, skończysz w nieskończonej pętli.źródło
i
po cichu zawinie, gdy osiągnie maksimum. Ale tak, najprawdopodobniej dłużej niż jesteś gotowy czekać.Jak widać z innych licznych odpowiedzi, istnieją powody, aby używać <zamiast! =, Które pomogą w przypadkach krawędziowych, warunkach początkowych, niezamierzonej modyfikacji licznika pętli itp.
Szczerze mówiąc, nie sądzę, żebyś wystarczająco podkreślał wagę konwencji. W tym przykładzie inni programiści będą mogli łatwo zobaczyć, co próbujesz zrobić, ale spowoduje to podwójne pobranie. Jednym z zadań podczas programowania jest uczynienie go jak najbardziej czytelnym i znanym wszystkim, więc nieuchronnie, gdy ktoś musi zaktualizować / zmienić kod, nie trzeba wiele wysiłku, aby dowiedzieć się, co robisz w różnych blokach kodu . Gdybym zobaczył, że ktoś go używa
!=
, przypuszczam, że istniał powód, dla którego go używał,<
a jeśli byłaby to duża pętla, przejrzałbym całą tę próbę, aby dowiedzieć się, co zrobiłeś, co sprawiło, że stało się to konieczne ... i to zmarnowany czas.źródło
Jak już powiedział Ian Newson, nie można niezawodnie zapętlić zmiennej pływającej i wyjść z
!=
. Na przykład,faktycznie zapętli się w nieskończoność, ponieważ 0,1 nie może być dokładnie przedstawione w liczbach zmiennoprzecinkowych, dlatego licznik wąsko traci 1. Gdy
<
się kończy.(Zauważ jednak, że jest to zasadniczo nieokreślone zachowanie, niezależnie od tego, czy otrzymujesz 0,9999 ... jako ostatnią akceptowaną liczbę - co stanowi naruszenie założenia mniejszego niż - lub już kończysz na 1.0000000000000001.)
źródło
Używam przymiotnika „techniczny”, aby oznaczać zachowanie / dziwactwa językowe i skutki uboczne kompilatora, takie jak wydajność generowanego kodu.
W tym celu odpowiedź brzmi: nie (*). (*) To „Proszę skonsultować instrukcję obsługi procesora”. Jeśli pracujesz z jakimś zaawansowanym systemem RISC lub FPGA, może być konieczne sprawdzenie, jakie instrukcje są generowane i ile kosztują. Ale jeśli używasz prawie każdy konwencjonalny nowoczesną architekturę, to nie ma istotnej różnicy w poziomie kosztów pomiędzy procesorem
lt
,eq
,ne
igt
.Jeśli używasz przypadek krawędź mogłaby się okazać, że
!=
wymaga trzech operacji (cmp
,not
,beq
) vs dwóch (cmp
,blt xtr myo
). Ponownie RTM w tym przypadku.W większości przypadków powodem jest defensywność / hartowanie, szczególnie podczas pracy ze wskaźnikami lub złożonymi pętlami. Rozważać
Mniej wymyślnym przykładem może być użycie wartości zwracanych do wykonywania przyrostów, przyjmowanie danych od użytkownika:
Spróbuj tego i wprowadź wartości 1, 2, 10, 999.
Możesz temu zapobiec:
Ale prawdopodobnie tego chciałeś
Jest także coś w rodzaju uprzedzenia konwencji
<
, ponieważ często opiera się na zamawianiu w standardowych kontenerachoperator<
, na przykład haszowanie w kilku kontenerach STL określa równość poprzez powiedzenieJeśli
lhs
irhs
są klasą zdefiniowaną przez użytkownika piszącą ten kod jakoImplementator musi zapewnić dwie funkcje porównawcze. Tak
<
stał się ulubionym operatorem.źródło
Istnieje kilka sposobów pisania dowolnego rodzaju kodu (zwykle), w tym przypadku zdarzają się dwa sposoby (trzy, jeśli policzysz <= i> =).
W tym przypadku ludzie wolą> i <, aby upewnić się, że nawet jeśli coś nieoczekiwanego wydarzy się w pętli (np. Błąd), nie zapętli się w nieskończoność (BAD). Weźmy na przykład następujący kod.
Gdybyśmy użyli (i <3), bylibyśmy bezpieczni od nieskończonej pętli, ponieważ wprowadziłaby większe ograniczenie.
To naprawdę twój wybór, czy chcesz, aby błąd w programie zamknął całość lub nadal działał z błędem.
Mam nadzieję, że to pomogło!
źródło
Najczęstszym powodem użycia
<
jest konwencja. Inni programiści myślą o takich pętlach jako „gdy indeks jest w zakresie”, a nie „dopóki indeks nie osiągnie końca”. Gdy jest to możliwe, przestrzeganie konwencji jest wartością.Z drugiej strony wiele odpowiedzi tutaj twierdzi, że użycie
<
formularza pomaga uniknąć błędów. Twierdziłbym, że w wielu przypadkach pomaga to ukryć błędy. Jeśli indeks pętli ma osiągnąć wartość końcową, a zamiast tego faktycznie wykracza poza nią, dzieje się coś, czego się nie spodziewałeś, co może spowodować awarię (lub być efektem ubocznym innego błędu).<
Najprawdopodobniej opóźni odkrycie błędu.!=
Jest bardziej prawdopodobne, aby doprowadzić do stajni, powiesić, a nawet katastrofy, które pomogą Ci dostrzec błąd wcześniej. Im szybciej zostanie znaleziony błąd, tym taniej go naprawić.Zauważ, że ta konwencja jest charakterystyczna dla indeksowania tablic i wektorów. Podczas przechodzenia przez prawie jakikolwiek inny typ struktury danych należy użyć iteratora (lub wskaźnika) i bezpośrednio sprawdzać wartość końcową. W takich przypadkach musisz mieć pewność, że iterator osiągnie i nie przekroczy rzeczywistej wartości końcowej.
Na przykład, jeśli przechodzisz przez zwykły ciąg C, zwykle bardziej powszechne jest pisanie:
niż
Po pierwsze, jeśli łańcuch jest bardzo długi, druga forma będzie wolniejsza, ponieważ
strlen
jest to kolejne przejście przez łańcuch.Z ciągiem std :: C ++ można użyć pętli opartej na zakresie, standardowego algorytmu lub iteratorów, nawet jeśli długość jest łatwo dostępna. Jeśli używasz iteratorów, konwencją jest użycie
!=
zamiast<
, jak w:Podobnie, iteracja drzewa, listy lub deque zwykle wymaga obserwowania wskaźnika zerowego lub innego wartownika, a nie sprawdzania, czy indeks pozostaje w zakresie.
źródło
Jednym z powodów, dla których nie należy używać tej konstrukcji, są liczby zmiennoprzecinkowe.
!=
jest bardzo niebezpiecznym porównaniem przy użyciu liczb zmiennoprzecinkowych, ponieważ rzadko ocenia się na prawdziwe, nawet jeśli liczby wyglądają tak samo.<
lub>
usuwa to ryzyko.źródło
!=
versus<
jest najmniejszym z twoich problemów.Istnieją dwa powiązane powody stosowania tej praktyki, które oba mają związek z faktem, że język programowania jest przecież językiem, który będzie czytany przez ludzi (między innymi).
(1) Trochę redundancji. W języku naturalnym zwykle dostarczamy więcej informacji niż jest to absolutnie konieczne, podobnie jak kod korygujący błędy. Tutaj dodatkową informacją jest to, że zmienna pętli
i
(zobacz, jak użyłem tutaj redundancji? Jeśli nie wiesz, co oznacza „zmienna pętli” lub jeśli zapomniałeś nazwy zmiennej, po przeczytaniu „zmiennej pętlii
” masz pełny informacja) jest mniejsza niż 5 podczas pętli, nie różni się tylko od 5. Redundancja poprawia czytelność.(2) Konwencja. Języki mają określone standardowe sposoby wyrażania określonych sytuacji. Jeśli nie będziesz postępować zgodnie z ustalonym sposobem mówienia, nadal będziesz rozumiany, ale wysiłek adresata wiadomości jest większy, ponieważ niektóre optymalizacje nie będą działać. Przykład:
Pierwsze zdanie to dosłowne tłumaczenie niemieckiego idiomu. Drugi to wspólny angielski idiom z głównymi słowami zastąpionymi synonimami. Wynik jest zrozumiały, ale jego zrozumienie zajmuje dużo więcej czasu:
Dzieje się tak nawet w przypadku, gdy synonimy użyte w pierwszej wersji lepiej pasują do sytuacji niż konwencjonalne słowa w angielskim idiomie. Podobne siły działają, gdy programiści czytają kod. Jest to również powód
5 != i
i5 > i
dziwne sposoby umieszczenia go, chyba że pracujesz w środowisku, w którym standardowym jest zamiana bardziej normalnychi != 5
ii < 5
w ten sposób. Takie społeczności dialektyczne istnieją, prawdopodobnie dlatego, że spójność ułatwia pamiętanie pisania5 == i
zamiast naturalnego, ale podatnego na błędyi == 5
.źródło
i < 17+(36/-3)
(nawet gdy są stosowane stałe gdzie indziej, to musisz napisać swoje imiona ! Dla jasności).Używanie porównań relacyjnych w takich przypadkach jest bardziej popularnym nawykiem niż cokolwiek innego. Popularność zyskała w czasach, gdy takie względy koncepcyjne, jak kategorie iteratorów i ich porównywalność, nie były uważane za priorytetowe.
Powiedziałbym, że należy w miarę możliwości używać porównań równości zamiast porównań relacyjnych, ponieważ porównania równości nakładają mniejsze wymagania na porównywane wartości. Bycie EqualityComparable jest mniejszym wymogiem niż bycie LessThanComparable .
Innym przykładem, który pokazuje szersze zastosowanie porównania równości w takich kontekstach, jest popularna zagadka z implementacją
unsigned
iteracji aż do0
. Można to zrobić jakoZauważ, że powyższe dotyczy zarówno iteracji podpisanej, jak i niepodpisanej, podczas gdy wersja relacyjna rozpada się na typy niepodpisane.
źródło
Oprócz przykładów, w których zmienna pętli (niezamierzona) zmieni się wewnątrz ciała, istnieją inne powody, aby używać operatorów mniejszych lub większych niż:
<
lub>
jest tylko jednym char, ale!=
dwomaźródło
5 != $i
, który zabrał mi całą sekundę mojego życia. Uh ... snickerOprócz różnych osób, które wspominały, że ogranicza to ryzyko, zmniejsza także liczbę przeciążeń funkcji niezbędnych do interakcji z różnymi standardowymi komponentami biblioteki. Na przykład, jeśli chcesz, aby twój typ był przechowywany w
std::set
lub używany jako kluczstd::map
lub używany z niektórymi algorytmami wyszukiwania i sortowania, biblioteka standardowa zwykle używastd::less
do porównywania obiektów, ponieważ większość algorytmów wymaga jedynie ścisłej słabej kolejności . W związku z tym dobrym nawykiem jest używanie<
porównań zamiast!=
porównań (tam, gdzie ma to oczywiście sens).źródło
!=
operator, ale nie zawsze jest możliwe zdefiniowanie ścisłej słabej kolejności. W tym sensie!=
byłoby lepiej, ponieważ nakłada mniej wymagań na typ. Nadal jednak pozostaję przy<
.Z punktu widzenia składni nie ma problemu, ale logika tego wyrażenia
5!=i
nie jest dźwięk.Moim zdaniem użycie
!=
do ustawienia granic pętli for nie jest logicznie uzasadnione, ponieważ pętla for zwiększa lub zmniejsza indeks iteracji, więc ustawianie pętli na iterację, dopóki indeks iteracji nie znajdzie się poza granicami (!=
do czegoś) nie jest prawidłowe wdrożenie.To będzie działać, ale jest skłonny do złego zachowania, ponieważ granica przetwarzanie danych jest tracona podczas korzystania
!=
dla przyrostowego problemu (co oznacza, że od samego początku wiedzieć, czy przyrosty to albo ubytki), dlatego zamiast są używane.!=
<>>==>
źródło
i
to5
wyjściu pętli. Czy chciałeś powiedzieć coś jeszcze?