i<n vs i<=n-1

0

Witam,
dzisiaj na ćwiczeniach z algorytmów wykładowca zrobił jakąś pętle warunkową (for zwykły) który wyglądał tak:

for(i=0;i<=N-1;i++)

gdzie N to ilość wierszy dla macierzy, po czym zrobił wewnętrzną pętle w której dał:

for(j=0;j<=M-1;j++)

gdzie M to ilość kolumn macierzy ( iteracja przez wiersze i kolumny ), zapytałem się go czy nie lepiej dać po prostu:

for(i=0;i<N;i++)
// i
for(j=0;j<M;j++)

na co on odpowiedział coś co nie zabardzo zrozumiałem, mówił że były jakieś ustalenia i że jeżeli chciałbym dać tak jak ja to musiałbym zaczynać od 1.

Teraz moje pytanie: Jaki to ma w ogóle sens? czy to nie tak że zarówno:

for(i=0;i<=N-1;i++)
// i
for(i=0;i<N;i++)

będą zawsze robić dokładnie to samo niezależnie od ustaleń, tylko że pierwsza wersja jest dłużej zapisana?

2

Poniższy zapis pętli:

for(int i = 0; i < n; i++)

jest o wiele częściej stosowany od jego odpowiednika z operatorem <=, dlatego że jest krótszy, a przez to łatwiejszy w zrozumieniu i jest mniejsza szansa popełnienia błędu.

Zamiast się wykłócać z prowadzącym, mogłeś po prostu wybrać dowolne N i podstawić do pętli:

const int n = 5;

for(int i = 0; i < n; i++)
//      i = 0; 0 < 5
//
//      i: 0,1,2,3,4

for(int i = 0; i <= n-1; i++)
//      i = 0; 0 <= 5-1
//      i = 0; 0 <= 4
//
//      i: 0,1,2,3,4

No i jak – różnią się te kody w działaniu, czy się nie różnią? ;)

0

@furious programming: Wielkie dzię

1

Skromny Jeleń: Jeszcze mogłeś zapytać dlaczego stosuje postinkrementacje zamiast preinkrementacji.

2

@YooSy: w tym kontekście nie ma to żadnego znaczenia – i tak iterator będzie inkrementowany po wykonaniu iteracji.

Dziwię się trochę tej nagonce na postinkrementację. Wygląda to tak, jakby ludzie nie rozumieli czym się te zapisy różnią i kiedy stosować dany wariant, więc wszędzie wpychają preinkrementację, bo tak gdzieś usłyszeli i tak robić trzeba. :]

0

Wiem, że w tym przypadku wygenerowany kod powinien być identyczny dla pre i post, choć pewnie nie we wszystkich kompilatorach,
a dla większych obiektów dodatkowe kopiowanie wewnątrz operatora dla it++, będzie wolniejsze od ++it

0
YooSy napisał(a):

a dla większych obiektów dodatkowe kopiowanie wewnątrz operatora dla it++, będzie wolniejsze od ++it

O ile wolniejsze?

0

O czas utworzenia kopii obiektu.

0

@YooSy: ale tutaj nie mamy obiektów – to zwykły int. ;)

Takie rzeczy trzeba sugerować wtedy, gdy ktoś nie ma pojęcia o różnicy w zapisach. No i też wtedy, gdy pisany kod musi być wysokowydajny, choć to tutaj rzadko się zdarza.

0

@furious programming: Tylko zasugerowałem, że warto znać różnicę, ale masz racje, nie ma co na siłę pchać pre. Często czytelność jest ważniejsza od mikro zysków w wydajności.

1
furious programming napisał(a):
YooSy napisał(a):

a dla większych obiektów dodatkowe kopiowanie wewnątrz operatora dla it++, będzie wolniejsze od ++it

O ile wolniejsze?

Jeśli twój kompilator sobie tego sam nie optymalizuje to i tak o żadnej mikro wydajności nie ma co dyskutować. Bo pewnie 100 innych rzeczy robi jeszcze gorzej.

0

warto przy okazji tematu wspomnieć o popularnym błędzie który popełniają programiści głównie przechodzący z języków takich jak C++ do języków takich jak Java i C# gdzie próbują zoptymalizować kod przypisując rozmiar iterowanej tablicy do zmiennej przed pętlą

var c = tab.length;
for (int i = 0; i < c; i ++) ...

zamiast wrzucenia tab.length w pętlę - w praktyce w wielu przypadkach kompilator okazuje się nie być na tyle mądry żeby stwierdzić że zmienna "i" zawsze będzie się mieścić w zakresie tablicy i nie jest w stanie zrobić tutaj optymalizacji w wyniku czego musi dorzucić niepotrzebnie do kodu powolny boundary check odnosząc przeciwny do zamierzonego efekt

0
jarekr000000 napisał(a):
furious programming napisał(a):
YooSy napisał(a):

a dla większych obiektów dodatkowe kopiowanie wewnątrz operatora dla it++, będzie wolniejsze od ++it

O ile wolniejsze?

Jeśli twój kompilator sobie tego sam nie optymalizuje to i tak o żadnej mikro wydajności nie ma co dyskutować. Bo pewnie 100 innych rzeczy robi jeszcze gorzej.

Sorki, ale bzdury -- w przypadku inta i innych typów prostych kompilator optymalizuje, oczywiście. Ale w przypadku obiektów, w których zefiniowane są pre- i postinkrementacja jako metody, NIE MA PRAWA zastąpić jednej drugą. Więc nauczenie się domyślnego stosowania (w przypadku wątpliwości) preinkrementacji jest OK w C++. Szczególnie, że w pętlach itereator często nie musi być typem prostym. No i interfejs iteratora do for(... : ...) w C++11 wymaga preinkrementacji, nie wymaga natomast post-.

0
furious programming napisał(a):

Poniższy zapis pętli:

for(int i = 0; i < n; i++)

jest o wiele częściej stosowany od jego odpowiednika z operatorem <=, dlatego że jest krótszy, a przez to łatwiejszy w zrozumieniu i jest mniejsza szansa popełnienia błędu.

Krótkość zapisu jest najmniejszą z jego zalet. :) Inne to:

0
koszalek-opalek napisał(a):

Sorki, ale bzdury -- w przypadku inta i innych typów prostych kompilator optymalizuje, oczywiście. Ale w przypadku obiektów, w których zefiniowane są pre- i postinkrementacja jako metody, NIE MA PRAWA zastąpić jednej drugą.

Nie musi zastępować - jeśli operator postinkrementacji i konstruktor kopiujący nie mają skutków ubocznych, kompilator jak najbardziej ma prawo zoptymalizować kod tak, że będzie równie szybki – zgodnie z zasadą as-if.

0
Azarien napisał(a):
koszalek-opalek napisał(a):

Sorki, ale bzdury -- w przypadku inta i innych typów prostych kompilator optymalizuje, oczywiście. Ale w przypadku obiektów, w których zefiniowane są pre- i postinkrementacja jako metody, NIE MA PRAWA zastąpić jednej drugą.

Nie musi zastępować - jeśli operator postinkrementacji i konstruktor kopiujący nie mają skutków ubocznych, kompilator jak najbardziej ma prawo zoptymalizować kod tak, że będzie równie szybki – zgodnie z zasadą as-if.

Raczej tak, sęk w tym, że to nie jest zawsze dla -- nawet najlepszego -- kompilatora oczywiste...

1 użytkowników online, w tym zalogowanych: 0, gości: 1