Pętla iteratora vs pętla indeksu [duplikat]

Możliwy duplikat:
Dlaczego warto używać iteratorów zamiast indeksów tablicy?

Przeglądam swoją wiedzę na temat C++ i natknąłem się na Iteratory. Jedno chcę wiedzieć, co czyni je tak wyjątkowymi i chcę wiedzieć, dlaczego to: {]}
using namespace std;

vector<int> myIntVector;
vector<int>::iterator myIntVectorIterator;

// Add some elements to myIntVector
myIntVector.push_back(1);
myIntVector.push_back(4);
myIntVector.push_back(8);

for(myIntVectorIterator = myIntVector.begin(); 
        myIntVectorIterator != myIntVector.end();
        myIntVectorIterator++)
{
    cout<<*myIntVectorIterator<<" ";
    //Should output 1 4 8
}

Jest lepsze niż to:

using namespace std;

vector<int> myIntVector;
// Add some elements to myIntVector
myIntVector.push_back(1);
myIntVector.push_back(4);
myIntVector.push_back(8);

for(int y=0; y<myIntVector.size(); y++)
{
    cout<<myIntVector[y]<<" ";
    //Should output 1 4 8
}

I tak Wiem, że nie powinienem używać przestrzeni nazw std. Właśnie wziąłem ten przykład ze strony cprogramming. Więc czy możesz mi powiedzieć, dlaczego to drugie jest gorzej? Co za różnica?

Author: Community, 2013-01-17

8 answers

Szczególną rzeczą w iteratorach jest to, że dostarczają one kleju między algorytmami a kontenerami . W przypadku kodu ogólnego rekomendacją byłoby użycie kombinacji algorytmów STL (np. find, sort, remove, copy) itd. który wykonuje obliczenia, które masz na myśli w swojej strukturze danych(vector, list, map itd.), oraz dostarczenie tego algorytmu z iteratorami do kontenera.

Twój konkretny przykład może być napisany jako kombinacja Algorytm for_each i kontener vector (Zobacz opcję 3) poniżej), ale jest to tylko jeden z czterech różnych sposobów iteracji na std::vector:

1) iteracja oparta na indeksach

for (std::size_t i = 0; i != v.size(); ++i) {
    // access element as v[i]

    // any code including continue, break, return
}

zalety : znane każdemu, kto zna kod w stylu C, może pętlować za pomocą różnych kroków (np. i += 2).

wady: tylko dla sekwencyjnych kontenerów dostępu losowego (vector, array, deque), nie działa na list, forward_list lub asocjacyjny kontenery. Również kontrola pętli jest trochę słowna (INIT, check, increment). Ludzie muszą być świadomi indeksowania opartego na 0 W C++.

2) iteracja oparta na iteratorze

for (auto it = v.begin(); it != v.end(); ++it) {
    // if the current index is needed:
    auto i = std::distance(v.begin(), it); 

    // access element as *it

    // any code including continue, break, return
}

zalety: bardziej ogólny, działa dla wszystkich kontenerów (nawet nowe niestandardowe kontenery asocjacyjne, mogą również korzystać z różnych kroków (np. std::advance(it, 2));

wady: potrzeba dodatkowej pracy, aby uzyskać indeks bieżącego elementu(może być O (N) dla listy lub forward_list). Ponownie, Kontrola pętli jest trochę gadatliwa (INIT, check, increment).

3) STL for_each algorithm + lambda

std::for_each(v.begin(), v.end(), [](T const& elem) {
     // if the current index is needed:
     auto i = &elem - &v[0];

     // cannot continue, break or return out of the loop
});

zalety: tak samo jak 2) plus mała redukcja kontroli pętli (bez sprawdzania i przyrostu), może to znacznie zmniejszyć szybkość błędów (błędny init, sprawdzanie lub przyrost, błędy off-by-one).

wady: tak samo jak jawny iterator-loop plus ograniczone możliwości sterowania przepływem w pętli (nie można użyć continue, break lub return) i brak opcji dla różnych kroków (chyba że używasz adaptera iteratora, który przeciąża operator++).

4) Zakres-dla pętli

for (auto& elem: v) {
     // if the current index is needed:
     auto i = &elem - &v[0];

    // any code including continue, break, return
}

zalety: bardzo kompaktowa Kontrola pętli, bezpośredni dostęp do bieżącego elementu.

wady : dodatkowe oświadczenie, aby uzyskać indeks. Nie można używać różnych kroków.

Czego użyć?

Dla Twojego konkretnego przykładu iteracji std::vector: jeśli naprawdę potrzebujesz indeksu (np. dostęp do poprzedniego lub następnego elementu, drukowanie/logowanie indeksu wewnątrz pętli itp.) lub potrzebujesz kroku innego niż 1, wtedy wybrałbym jawnie indeksowaną pętlę, w przeciwnym razie wybrałbym pętlę range-for.

Dla algorytmów generycznych na kontenerach generycznych wybrałbym jawną pętlę iteratora, chyba że kod nie zawierał kontroli przepływu wewnątrz pętli i wymagał stride 1, w takim przypadku wybrałbym STL for_each + lambda.

 132
Author: TemplateRex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:09

Iteratory sprawiają, że Twój kod jest bardziej ogólny.
Każdy kontener biblioteki standardowej zapewnia iterator, więc jeśli zmienisz klasę kontenera w przyszłości, pętla nie będzie miała wpływu.

 8
Author: Alok Save,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-17 07:18:47

Iteratory są pierwszym wyborem nad operator[]. C++11 zapewnia std::begin(), std::end() funkcje.

Ponieważ twój kod używa tylko std::vector, nie mogę powiedzieć, że jest duża różnica w obu kodach, jednak operator [] może nie działać tak, jak zamierzasz. Na przykład, jeśli używasz map, operator[] wstawi element, jeśli nie został znaleziony.

Ponadto, używając iterator twój kod staje się bardziej przenośny między kontenerami. Możesz dowolnie przełączać kontenery z std::vector na std::list lub inny kontener bez większych zmian, jeśli używasz iterator taka reguła nie ma zastosowania do operator[].

 6
Author: billz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-17 07:38:21

Z iteratorami wektorowymi nie dają żadnej rzeczywistej przewagi. Składnia jest brzydsza, dłuższa do pisania i trudniejsza do odczytania.

Iteracja nad wektorem za pomocą iteratorów nie jest szybsza i bezpieczniejsza (w rzeczywistości, jeśli wektor zostanie ewentualnie zmieniony podczas iteracji za pomocą iteratorów, wpakuje cię w duże kłopoty).

Idea posiadania pętli generycznej, która działa, gdy zmienisz później typ kontenera, jest również w większości nonsensowna w rzeczywistych przypadkach. Niestety ciemna strona ściśle język pisany bez poważnego wnioskowania o pisaniu (trochę lepiej teraz z C++11, Jednak) jest to, że trzeba powiedzieć, jaki jest typ wszystkiego na każdym kroku. Jeśli później zmienisz zdanie, nadal będziesz musiał chodzić i zmieniać wszystko. Co więcej, różne kontenery mają bardzo różne kompromisy, a zmiana typu kontenera nie zdarza się tak często.

Jedynym przypadkiem, w którym iteracja powinna być zachowana, jeśli to możliwe, jest pisanie kodu szablonu, ale to (I nadzieja dla Ciebie) nie jest najczęstszym przypadkiem.

Jedynym problemem występującym w Twojej jawnej pętli indeksu jest to, że size Zwraca wartość unsigned (błąd projektowy w C++), a porównanie pomiędzy signed i unsigned jest niebezpieczne i zaskakujące, więc lepiej unikać. Jeśli używasz porządnego kompilatora z włączonymi ostrzeżeniami, powinna być na tym diagnostyka.

Zwróć uwagę, że rozwiązaniem nie jest użycie unsiged jako indeksu, ponieważ arytmetyka między niepodpisanymi wartościami jest również pozornie nielogiczna (jest to arytmetyka modulo i x-1 może być większa niż x). Zamiast tego powinieneś oddać rozmiar do liczby całkowitej przed jej użyciem. Może mieć jakiś sens używać niepodpisanych rozmiarów i indeksów (zwracając dużą uwagę na każde wyrażenie, które piszesz) tylko wtedy, gdy pracujesz nad 16-bitową implementacją C++ (16-bitowy był powodem posiadania niepodpisanych wartości w rozmiarach ).

Jako typowy błąd, który może wprowadzić Rozmiar niepodpisany rozważ:

void drawPolyline(const std::vector<P2d>& points)
{
    for (int i=0; i<points.size()-1; i++)
        drawLine(points[i], points[i+1]);
}

Tutaj błąd jest obecny ponieważ jeśli przekażesz pusty wektor points, wartość points.size()-1 będzie ogromną liczbą dodatnią, co sprawi, że zapętlisz się w segfault. Rozwiązaniem roboczym może być

for (int i=1; i<points.size(); i++)
    drawLine(points[i - 1], points[i]);

Ale ja osobiście wolę zawsze usuwać unsinged - ness z int(v.size()).

Brzydota używania iteratorów w tym przypadku jest pozostawiona jako ćwiczenie dla czytelnika.

 5
Author: 6502,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:17:54

To zawsze zależy od tego, czego potrzebujesz.

Powinieneś używać operator[], gdy potrzebujesz bezpośredniego dostępu do elementów w wektorze (gdy musisz indeksować określony element w wektorze). Nie ma nic złego w używaniu go nad iteratorami. Musisz jednak sam zdecydować, który (operator[] lub Iteratory) najlepiej odpowiada twoim potrzebom.

Użycie iteratorów umożliwi Ci przełączenie się na inne typy kontenerów bez większych zmian w kodzie. Innymi słowy, użycie iteratorów sprawi, że Twoje kod bardziej ogólny i nie zależy od określonego typu kontenera.

 4
Author: Mark Garcia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-17 07:38:42

Pisząc kod klienta w kategoriach iteratorów całkowicie usuniesz kontener.

Rozważ ten kod:

class ExpressionParser // some generic arbitrary expression parser
{
public:
    template<typename It>
    void parse(It begin, const It end)
    {
        using namespace std;
        using namespace std::placeholders;
        for_each(begin, end, 
            bind(&ExpressionParser::process_next, this, _1);
    }
    // process next char in a stream (defined elsewhere)
    void process_next(char c);
};

Kod klienta:

ExpressionParser p;

std::string expression("SUM(A) FOR A in [1, 2, 3, 4]");
p.parse(expression.begin(), expression.end());

std::istringstream file("expression.txt");
p.parse(std::istringstream<char>(file), std::istringstream<char>());

char expr[] = "[12a^2 + 13a - 5] with a=108";
p.parse(std::begin(expr), std::end(expr));

Edit: rozważ oryginalny przykład kodu, zaimplementowany za pomocą:

using namespace std;

vector<int> myIntVector;
// Add some elements to myIntVector
myIntVector.push_back(1);
myIntVector.push_back(4);
myIntVector.push_back(8);

copy(myIntVector.begin(), myIntVector.end(), 
    std::ostream_iterator<int>(cout, " "));
 1
Author: utnapistim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-17 09:43:22

Dobrą rzeczą w iteratorze jest to, że później, jeśli chcesz przełączyć swój wektor na inny kontener STD. Wtedy forloop będzie nadal działać.

 0
Author: Caesar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-17 07:18:41

To kwestia prędkości. korzystanie z iteratora umożliwia szybszy dostęp do elementów. podobne pytanie padło tutaj:

Co jest szybsze, iteracja wektora STL z vector:: iterator czy z AT ()?

Edytuj: szybkość dostępu różni się w zależności od procesora i kompilatora

 -2
Author: Nicolas Brown,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:13