访问元素
在 std::vector
中有两种主要的访问元素的方法
- 基于索引的访问
- 迭代器
基于索引的访问:
这可以使用下标运算符 []
或成员函数 at()
来完成 。
两者都返回对 std::vector
中相应位置的元素的引用(除非它是一个 vector<bool>
),因此它既可以被读取也可以被修改(如果向量不是 const
)。
[]
和 at()
的不同之处在于 []
不能保证执行任何边界检查,而 at()
确实如此。访问 index < 0
或 index >= 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.