Итераторы векторов С++ против указателей

Существует так много альтернативных способов обращения к элементам вектора.

Я мог бы использовать указатель так:

vector<int> v = {10, 11, 12};
int *p = &v[0];
cout << *p;    //Outputs "10"

Я тоже мог бы использовать указатель:

vector<int> v = {10, 11, 12};
vector<int>::pointer p = v.data();
cout << *p;    //Outputs "10"

Я мог бы также использовать тип итератора:

vector<int> v = {10, 11, 12};
vector<int>::iterator i = v.begin();
cout << *i;    //Outputs "10"

Есть ли существенные различия, которые я здесь отсутствует?

Ответ 1

Что касается возможности выполнить задачу под рукой, все они работают одинаково хорошо. В конце концов, все они предоставляют объект, который удовлетворяет требованиям итератора, и вы используете их для указания того же элемента vector. Однако, я бы выбрал опцию vector<int>::iterator, потому что тип более выразителен в отношении того, как мы намерены его использовать.

Необработанный тип указателя int* очень мало говорит о том, что такое p, за исключением того, что он хранит адрес int. Если вы думаете о p изолированно, его тип не говорит вам о том, как вы можете его использовать. Параметр vector<int>::pointer имеет ту же проблему - он просто выражает тип объектов, на которые он указывает, как тип элемента вектора. Нет причин, по которым на самом деле нужно указывать на vector.

С другой стороны, vector<int>::iterator сообщает вам все, что вам нужно знать. В нем явно указано, что объект является итератором и что итератор используется для указания элементов в vector<int>.

Это также имеет преимущество, заключающееся в том, что вам легче ремонтироваться, если вам когда-либо случалось менять тип контейнера. Если вы, например, изменили на std::list, тип указателя просто больше не работал бы, потому что элементы не сохранялись как непрерывный массив. Тип контейнера iterator всегда предоставляет вам тип, который вы можете использовать для итерации по его элементам.


Когда у нас есть Понятия, я ожидаю, что лучшая практика будет чем-то вроде:

ForwardIteratorOf<int> it = std::begin(v);

где ForwardIteratorOf<int> (который, как я полагаю, существует), изменяется на любое понятие, наилучшим образом описывающее ваши намерения для it. Если тип элементов не имеет значения, то просто ForwardIterator (или BidirectionalIterator, RandomAccessIterator или что-то еще).

Ответ 2

Если вы добавите чек:

if ( !v.empty() )

Затем весь пример, который вы показали, в равной степени действителен.

Если вы собираетесь перебирать элементы vector, я бы пошел с:

vector<int>::iterator i = v.begin();

Легче проверить, достиг ли итератор конца вектора с итератором, чем с другими формами.

if ( i != v.end() )
{
   // Do stuff.
}

Ответ 3

Все эти способы имеют свои преимущества, но в основе они очень похожи. Некоторые из них не работают (они вызывают так называемое поведение undefined), когда вектор пуст.

Ответ 4

Все функционально одинаковы в случае, если вы предоставили. Для удобства чтения кода итератор делает более понятным, что операция выполняется над вектором. Однако есть некоторые нюансы, особенно когда речь идет о константных объектах.

Например,

#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    const int a[5] = {1, 2, 30, 45, 500};
    auto it = begin(a);
    it++;
    int i = *it;
    i++;
    cout << i << endl;
} // Prints 3.

С другой стороны, (исключая заголовки, чтобы не загромождать экран),

int main() {
    const int a[5] = {1, 2, 30, 45, 500};
    int *p = a;
    cout << *p << endl;
}

will не не скомпилируется.