访问元素

std::vector有两种主要的访问元素的方法

基于索引的访问:

这可以使用下标运算符 [] 或成员函数 at() 来完成

两者都返回对 std::vector 中相应位置的元素的引用(除非它是一个 vector<bool> ),因此它既可以被读取也可以被修改(如果向量不是 const)。

[]at() 的不同之处在于 [] 不能保证执行任何边界检查,而 at() 确实如此。访问 index < 0index >= size 的元素是 []未定义行为 ,而 at() 抛出了一个 std::out_of_range异常。

注意: 为清楚起见,下面的示例使用 C++ 11 样式初始化,但运算符可以与所有版本一起使用(除非标记为 C++ 11)。

Version => C++ 11
std::vector<int> v{ 1, 2, 3 };
// using []
int a = v[1];    // a is 2
v[1] = 4;        // v now contains { 1, 4, 3 }

// using at()
int b = v.at(2); // b is 3
v.at(2) = 5;     // v now contains { 1, 4, 5 }
int c = v.at(3); // throws std::out_of_range exception

因为 at() 方法执行边界检查并且可以抛出异常,所以它比 [] 慢。这使得 [] 成为首选代码,其中操作的语义保证索引在边界内。在任何情况下,对矢量元素的访问都是在恒定时间内完成的。这意味着访问向量的第一个元素与访问第二个元素,第三个元素等具有相同的成本(及时)。

例如,考虑这个循环

for (std::size_t i = 0; i < v.size(); ++i) {
    v[i] = 1;
}

在这里我们知道索引变量 i 总是处于边界内,因此在每次调用 operator[] 时检查 i 是否在界限内会浪费 CPU 周期。

front()back() 成员函数允许向量的第一和最后的元件容易参考存取,分别。这些位置经常使用,特殊访问器比使用 [] 的替代方案更具可读性:

std::vector<int> v{ 4, 5, 6 }; // In pre-C++11 this is more verbose

int a = v.front();   // a is 4, v.front() is equivalent to v[0]
v.front() = 3;       // v now contains {3, 5, 6}
int b = v.back();    // b is 6, v.back() is equivalent to v[v.size() - 1]
v.back() = 7;        // v now contains {3, 5, 7}

注意 : 在空向量上调用 front()back()未定义的行为 。在调用 front()back() 之前,你需要使用 empty()成员函数(检查容器是否为空)来检查容器是否为空。使用’empty()‘测试空向量的一个简单示例如下:

int main ()
{
  std::vector<int> v;
  int sum (0);

  for (int i=1;i<=10;i++) v.push_back(i);//create and initialize the vector

  while (!v.empty())//loop through until the vector tests to be empty
  {
     sum += v.back();//keep a running total
     v.pop_back();//pop out the element which removes it from the vector
  }

  std::cout << "total: " << sum << '\n';//output the total to the user

  return 0;
}

上面的例子创建了一个带有 1 到 10 的数字序列的向量。然后它将向量的元素弹出,直到向量为空(使用’empty()’)来防止未定义的行为。然后计算向量中的数字之和并显示给用户。

Version => C++ 11

data() 方法返回指向由 std::vector 使用的原料内存在内部存储它的元件。在将矢量数据传递给需要 C 样式数组的遗留代码时,最常使用此方法。

std::vector<int> v{ 1, 2, 3, 4 }; // v contains {1, 2, 3, 4}
int* p = v.data(); // p points to 1
*p = 4;            // v now contains {4, 2, 3, 4}
++p;               // p points to 2
*p = 3;            // v now contains {4, 3, 3, 4}
p[1] = 2;          // v now contains {4, 3, 2, 4}
*(p + 2) = 1;      // v now contains {4, 3, 2, 1}
Version < C++ 11

在 C++ 11 之前,可以通过调用 front() 并获取返回值的地址来模拟 data() 方法:

std::vector<int> v(4);
int* ptr = &(v.front()); // or &v[0]

这是有效的,因为向量总是保证将它们的元素存储在连续的内存位置,假设向量的内容不会覆盖一元 operator&。如果是这样,你将不得不在 pre-C++ 11 中重新实现 std::addressof 。它还假定向量不为空。

迭代器:

迭代器在“Iterating over std::vector”和 Iterators 文章中有更详细的解释。简而言之,它们的行为类似于向量元素的指针:

Version => C++ 11
std::vector<int> v{ 4, 5, 6 };

auto it = v.begin();
int i = *it;        // i is 4
++it; 
i = *it;            // i is 5
*it = 6;            // v contains { 4, 6, 6 }
auto e = v.end();   // e points to the element after the end of v. It can be 
                    // used to check whether an iterator reached the end of the vector:
++it; 
it == v.end();      // false, it points to the element at position 2 (with value 6)
++it;
it == v.end();      // true

std::vector<T> 的迭代器实际上标准的 38s 符合标准,但大多数标准库不这样做。不这样做既改善了错误消息,又捕获了非可移植代码,并且可用于通过非发布版本中的调试检查来检测迭代器。然后,在发布版本中,围绕底层指针的类被优化掉。

你可以将引用或指针持久保存到向量的元素以进行间接访问。除非你在 vector 中的元素之前或之前添加/删除元素,否则这些对 vector 中元素的引用或指针保持稳定并且访问仍然定义,或者你导致 vector 容量发生变化。这与使迭代器无效的规则相同。

Version => C++ 11
std::vector<int> v{ 1, 2, 3 };
int* p = v.data() + 1;     // p points to 2
v.insert(v.begin(), 0);    // p is now invalid, accessing *p is a undefined behavior.
p = v.data() + 1;          // p points to 1
v.reserve(10);             // p is now invalid, accessing *p is a undefined behavior.
p = v.data() + 1;          // p points to 1
v.erase(v.begin());        // p is now invalid, accessing *p is a undefined behavior.