C++向量迭代器VS指针

23

有很多替代方法可以处理向量的元素。

我可以使用指针来实现:

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
"处理向量元素",为什么不使用最简单的 v[0] - user1129665
1
@BSH 安全性。如果向量为空怎么办?还可以使用vector::front函数。 - Ryan Haining
1
中间的元素无法通过已检查的实现进行正确性检查(即是否存在任何元素),而其他两个可以。所有发布模式实现都是未经检查的。 - Deduplicator
2
@MFH:自从VS2010以来,迭代器已经在发行版本中不受检查。 - Blastfurnace
@Blastfurnace:你说得没错,SCL和IDL都被设置为0,可以认为“非SCL迭代器”=(语义上)=指针,但是由于Deduplicator声称“vector<int>::iterator和int*在发布配置中很可能是相同的”,我不得不留下一条注释,说明在(现代、当前可用的)VC++版本中永远不会发生这种情况... - MFH
显示剩余6条评论
4个回答

14
就能够执行手头任务而言,它们的效果都相同。毕竟,它们都提供了一个符合迭代器要求的对象,并且你使用它们指向同一个 vector 的元素。然而,我会选择 vector<int>::iterator 这个选项,因为这种类型在表达我们打算如何使用它方面更具表现力。
裸指针类型 int* 并没有告诉你 p 到底是什么,除了它存储了一个 int 的地址。如果你单独考虑 p,它的类型并没有告诉你很多关于它如何使用的信息。选用 vector<int>::pointer 也有同样的问题——它只表达了它所指向的对象类型是一个 vector 的元素类型。实际上,它无需指向 vector 中的任何元素。
另一方面,vector<int>::iterator 告诉你所有需要知道的内容。它明确说明这个对象是一个迭代器,同时该迭代器被用来指向 vector<int> 中的元素。
此外,如果你不得不改变容器类型,这种方式更易于维护。例如,如果你改为使用 std::list,指针类型就无法继续使用了,因为元素不再以连续的数组方式存储。而容器的 iterator 类型总是提供一种可用于迭代其元素的类型。
当我们有了 Concepts 后,我希望最佳实践能够是这样的:
ForwardIteratorOf<int> it = std::begin(v);

如果你所想象的存在 ForwardIteratorOf<int> 被更改为最能描述你对 it 意图的概念。如果元素类型不重要,则可以只用 ForwardIterator(或者 BidirectionalIterator, RandomAccessIterator 或其他)。


3
仅有一句小注释:我认为“auto”仍然是首选。此外,您可能想要补充说明,在发布配置中,“vector<int>::iterator”和“int *”很可能是相同的。 - Deduplicator
@Deduplicator:只有它们从未在VC++中。 - MFH
std::list 没有随机访问迭代器,因此即使使用迭代器,从 std::vector 切换到 std::list 也可能会出现问题。 - rcgldr
1
@MFH vector<int>::iterator 可能没有声明为 int* 的别名,但期望的是在发布模式下对象代码相同。 - Caleth

4
如果您添加了检查:
if ( !v.empty() )

那么,你展示的所有例子都是同样有效的。

如果你想要遍历vector中的元素,我建议使用:

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

使用迭代器比其他形式更容易检查迭代器是否已经到达向量的末尾。

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

1
所有这些方法都有各自的优点,但在核心上它们非常相似。但其中一些方法在向量为空时无法正常工作(会导致所谓的“未定义行为”)。

那么,它们各自的优点是什么?顺便问一下:为什么在UB上加引号? - Deduplicator
1
不是引号,只是引用,以明确这不仅仅是两个单词的组合,而是一个单一的术语。否则我可能会写成UB并让读者自己弄清楚这意味着什么。 - Ulrich Eckhardt

0
根据 cppreference 的描述:
一个指向数组元素的指针满足 LegacyContiguousIterator 的所有要求。
LegacyContiguousIterator 是最强大的迭代器,因为它包含了所有其他迭代器的功能。因此,它们可以是同一个东西,迭代器只是使我们的代码清晰、简洁和可移植的一种方式。
例如,我们可以有一个容器 "C"…
//template <typename T, int N> class C { //for static allocation
template <typename T> class C {
    //T _data[N]; //for static allocation
    T* _data; //need to dynamically allocate _data
public:
    typedef T* iterator;
}

C<int>::iterator 将是一个 int*,两者没有区别。

也许我们不需要完整的 LegacyContiguousIterator 的全部功能,因此我们可以重新定义 C<int>::iterator 为另一个类,该类遵循例如 LegacyForwardIterator 的大纲。这个新的迭代器类可能会重新定义 operator*。在这种情况下,它取决于实现,当尝试访问元素时,int* 可能会导致未定义的行为。

这就是为什么应该优先使用迭代器,但在大多数情况下,它们将是相同的东西。

在这两种情况下,只要我们定义了所有其他必要的成员函数和 typedefs,我们的容器 "C" 就会像其他 STL 容器一样工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接