Czy istnieje techniczny powód, aby używać> (<) zamiast! = Przy zwiększaniu o 1 w pętli „for”?

198

Prawie nigdy nie widzę takiej forpętli:

for (int i = 0; 5 != i; ++i)
{}

Czy istnieje techniczny powód, aby użyć >lub <zamiast !=zwiększania o 1 w forpętli? Czy to raczej konwencja?

Zingam
źródło
104
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.

Antonio
źródło
25
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ź .

Ely
źródło
6
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.

idclev 463035818
źródło
4
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.

John D.
źródło
6
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).

Paul Ogilvie
źródło
2
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.

Nicholas Carey
źródło
13
„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 !=.

Escualo
źródło
2
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( const int 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.

Jak - Adam Nevraumont
źródło
13

Tak; OpenMP nie równolegle pętli z !=warunkiem.

użytkownik541686
źródło
13

Może się zdarzyć, że zmienna izostanie ustawiona na jakąś dużą wartość i jeśli użyjesz !=operatora, skończysz w nieskończonej pętli.

LSchueler
źródło
1
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.

RyanP
źródło
12

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.)

po lewej stronie
źródło
10

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ć

// highly contrived example
size_t count_chars(char c, const char* str, size_t len) {
    size_t count = 0;
    bool quoted = false;
    const char* p = str;
    while (p != str + len) {
        if (*p == '"') {
            quote = !quote;
            ++p;
        }
        if (*(p++) == c && !quoted)
            ++count;
    }
    return count;
}

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

if (lhs < rhs) // T.operator <
    lessthan
else if (rhs < lhs) // T.operator < again
    greaterthan
else
    equal

Jeśli lhsi rhssą klasą zdefiniowaną przez użytkownika piszącą ten kod jako

if (lhs < rhs) // requires T.operator<
    lessthan
else if (lhs > rhs) // requires T.operator>
    greaterthan
else
    equal

Implementator musi zapewnić dwie funkcje porównawcze. Tak <stał się ulubionym operatorem.

kfsone
źródło
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.

Mam nadzieję, że to pomogło!

Random Programmer 123
źródło
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.

Adrian McCarthy
źródło
3
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.

Ian Newson
źródło
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.


źródło
1
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.

Mrówka
źródło
2

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ż:

  • Negacje utrudniają zrozumienie kodu
  • <lub >jest tylko jednym char, ale !=dwoma
MiCo
źródło
4
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).

Andre Kostur
źródło
1
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.!=<>>==>

Cosmin Gherghel
źródło
2
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)
Markus