std::vector
被定义为一个带有2个类型参数的类模板:一个参数为存放数据类型,另一个参数为分配器类型
template<class T, class Allocator = allocator<T>> class vector;
vector提供了重载的 operator[]
以便访问和修改其中的元素,通过 operator[]
访问vector边界之外的元素时,该行为是未定义的。
除了使用 operator[]
运算符外,还可以通过 at()
,front()
,back()
访问vector中的元素。at()
方法等同于 operator[]
运算符,区别是 at()
会执行边界检查,如果索引超出边界,会报出out_of_range异常。 front()
和 back()
分别返回vector第一个元素和最后一个元素的引用。在空容器中调用它们会发生未定义行为。
vector真正强大之处在于动态增长的能力,它可以随插入数据的增多而适时扩大容量。并且其数据全部创建在自由存储区。
默认的构造函数创建一个不含元素的vector
std::vector<int> intVector;
可以指定元素个数,并指定这些元素的值
std::vector<int> intVector1(10);
// creates vector of 10 elements
std::vector<int> intVector3(10, 100);
// creates vector of 10 int with value 100
如果没有提供默认值,那么对新对象进行0初始化,指针类型将初始化为 nullptr
可以使用包含初始元素的初始化列表构建vector
std::vector<int> intVector({1, 2, 3, 4, 5, 6});
统一初始化可用于包括vector在内的大部分标准库容器
std::vector<int> intVector1 = {1, 2, 3, 4, 5, 6};
std::vector<int> intVector2{1, 2, 3, 4, 5, 6};
vector内部已经实现深拷贝。因此,出于效率方面的考虑,应该通过引用而不是值传递向函数传递vector
除了普通的复制与赋值外,vector还提供 assign
方法,用于删除所有现有元素,并添加指定的新元素。这个方法特别适合vector的重用。
下面是一个简单的例子。intVector包含10个默认值为0的元素,然后通过 assign
方法删除所有10个元素,并填充5个值为100的元素代之。
std::vector<int> intVector(10);
...
intVector.assign(5, 100);
assign
还可接收初始化列表
intVector.assign({1, 2, 3, 4});
vector还提供 swap
方法,这个方法可以交换两个vector的内容。
std::vector<int> vectorOne(10);
std::vector<int> vectorTwo(5, 100);
vectorOne.swap(vectorTwo);
标准库提供了6个重载比较运算符:==,!=,<,>,<=,>=。如果两个vector的元素数量相等,而且对应元素都相等,那么两个vector相等。两个vector的比较采用字典顺序,比较其中同位第一个不同的元素的大小,其结果就是两个vector的大小关系。如果出现两者的size不同,则size取较小者的大小,比较前size个元素的大小。
std::vector<int> vectorOne(10);
std::vector<int> vectorTwo(10);
if(vectorOne == vectorTwo) {
std::cout << "equal" << std::endl;
} else {
std::cout << "not equal" << std::endl;
}
vectorOne[3] = 50;
if(vectorOne < vectorTwo) {
std::cout << "vectorOne is less than vectorTwo" << std::endl;
} else {
std::cout << "vectorOne is not less than vectorTwo" << std::endl;
}****
output
equal
vectorOne is not less than vectorTwo
上节中我们使用了for循环遍历vector中每个元素
std::vector<double> doubleVector;
...
for(std::vector<double>::iterator iter{begin(doubleVector)};
iter != end(doubleVector); ++iter) {
std::cout << *iter << std::endl;
}
首先,看一下for循环的初始化语句
std::vector<double>::iterator iter{begin(doubleVector)};
前面提到,每个容器都定义了一种名为iterator的类型,以表示那个容器类型的迭代器。 begin()
返回引用容器中第一个元素的相应类型的迭代器。因此,这条初始化语句在iter变量中获得了引用doubleVector中第一个元素的迭代器。下面看一下for循环的比较语句。
iter != end(doubleVector);
这条语句检查迭代器是否超越了vector中元素序列的尾部。当到达这一点时,循环终止。递增语句 ++iter
递增迭代器,以便引用vector中的下一个元素。
for循环体包含一个输出语句
std::cout << *iter << std::endl;
通过 *
解引用 iter
从而获得iter所引用的元素,然后通过cout输出。
上述迭代器的for循环可通过 auto
关键字进行简化
for(auto iter{begin(doubleVector)};
iter != end(doubleVector); ++iter) {
std::cout << *iter << std::endl;
}
有了 auto
编译器就会根据初始化语句右侧的内容自动推导变量iter的类型。在本例中,初始化语句右侧的内容是调用 begin()
得到的结果。
vector支持以下功能
(c)begin()
和(c)end()
返回指向第一个元素和最后一个元素的后一个元素的(const)迭代器。(c)rbegin()
和(c)rend()
返回指向第一个元素的前一个元素和后一个元素的反向迭代器。
如果容器中的元素是对象,那么可对迭代器调用 ->
运算符,调用对象的方法或访问对象成员。
普通的迭代器支持读和写。然而如果对const对象调用begin()和end(),或调用cbegin()和cend(),都将得到const_iterator。const_iterator是只读的,不能通过const_iterator修改它引用的元素。iterator始终可以转换为const_iterator,因此下面这种写法是安全的:
std::vector<type>::const_iterator it{begin(myVector)};
然而,const_iterator不能转换为iterator。如果myVector是const修饰的,那么下面这行代码无法通过编译:
std::vector<type>::iterator it{begin(myVector)};
可以通过 push_back()
方法可向vector追加元素。vector还提供了删除元素的对应方法 pop_back()
。
通过 insert()
方法可以在vector中任意位置插入元素,这个方法在迭代器指定位置添加1个或多个元素,并将所有元素向后移动,给新元素腾出空间。insert()
有5中不同的重载形式:
- 插入单个元素
- 插入单个元素的n个副本
- 从某个迭代器范围插入元素
- 使用移动语义将元素转移到vector中
- 向vector中插入一列元素,这列元素是通过初始化列表指定的
通过 erase()
可在vector中的任意位置删除元素,通过 clear()
可删除所有元素。
erase()
有两种形式:一种接收单个迭代器,删除单个元素,另一种接受两个迭代器,删除迭代器指定的元素范围。
这个示例还演示了erase()的双参数版本和insert()的以下版本:
-
insert(const_iterator pos, const T& x); // 将值x插入位置pos
-
insert(const_iterator pos, size_type n, const T& x); // 将值x在位置pos插入n次
-
insert(const_iterator pos, InputIterator first, InputIterator last); // 将范围[first,last)内的元素插入到位置pos
template <typename T>
void printVector(const std::vector<T>& v) {
for (auto& element : v) { // 这里使用基于范围的for循环
std::cout << element << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> vectorOne{1, 2, 3, 5};
std::vector<int> vectorTwo;
// Oops, we forgot to add 4. Insert it in the correct place
vectorOne.insert(cbegin(vectorOne) + 3, 4);
// Add element 6 through 10 to vectorTwo
for (int i{6}; i <= 10; i++) {
vectorTwo.push_back((i));
}
printVector(vectorOne);
printVector(vectorTwo);
// Add all the elements from vectorTwo to the end of vectorOne
vectorOne.insert(cend(vectorOne), cbegin(vectorTwo), cend(vectorOne) + 5);
printVector(vectorOne);
// Now erase the number 2 through 5 in vectorOne
vectorOne.erase(cbegin(vectorOne) + 1, cbegin(vectorOne) + 5);
printVector(vectorOne);
// Clear vectorTwo entirely
vectorTwo.clear();
// Add 10 copies of the value 100
vectorTwo.insert(cbegin(vectorTwo), 10, 100);
// Decide we only want 9 elements
vectorTwo.pop_back();
printVector(vectorTwo);
}
vector提供两个可获得大小信息的方法:size()
和 capacity()
。size()
方法返回vector中元素的个数,而 capacity()
返回的是vector在重新分配前可保存的元素个数。因此,在重新分配前还能插入的元素个数为 capacity()-size()
通过 empty()
方法可以查询vector是否为空。vector可以为空,但是容量不能为0。
还有非成员 std::size()
和 std::empty()
全局函数,他们可用于所有容器,也可以用于静态分配的C风格数组以及初始化列表。
std::vector<int> vec{1, 2, 3};
std::cout << std::size(vec) << std::endl;
std::cout << std::empty(vec) << std::endl;
如果希望程序尽可能高效,或要确保迭代器不会失效,就要强迫vector预先分配足够的空间,已保存所有元素。
一种预分配空间的方式时调用 reserve()
。这个方法负责保存指定数目的足够空间。
注意
为元素预留空间改变的是容量而不是大小,也就是说,这个过程不会创建真正的元素。不要越界访问。
另一种预分配空间的方法是在构造函数中,或者通过 resize()
或 assign()
方法指定vector中要保存的元素数目。
std::array
的大小是固定的,不能增加或收缩。该容器是为了让数据能分配到栈上。std::array
的行为实际上是和C风格数组相同的。
std::array
的声明需要有两个模板参数,第一个是存放数据类型,第二个是元素数量(数组长度)。
和vector一样,array支持随机访问迭代器,元素都保存在连续内存中。array支持 font()
,back()
,at()
和 operator[]
,还支持使用 fill()
将特定元素填满array。由于array大小固定,所以不支持vector的大小与容量的方法。
example
std::array<int, 3> arr{9, 8, 7};
// size()
std::cout << "array size = " << arr.size() << std::endl;
// 迭代器遍历
for (std::array<int, 3>::iterator it = arr.begin(); it != arr.end(); ++it) {
std::cout << *it << std::endl;
}
// fill()
arr.fill(3);
// const迭代器遍历
for (std::array<int, 3>::const_iterator it = arr.cbegin(); it != arr.cend(); ++it) {
std::cout << *it << std::endl;
}
output
array size = 3
9
8
7
3
3
3
可使用 std::get<n>()
函数模板,从array中检索位于索引位置n的元素。索引必须是常量表达式。使用 std::get<n>()
的优势在于编译器在编译期会检查给定索引是有效的,否则将导致编译错误。
std::array<int, 3> myArray{11, 22, 33};
std::cout << std::get<1>(myArray) <<std::endl;
std::cout << std::get<10>(myArray) <<std::endl; // error
你是否觉得,过去写的for循环太太太太不优雅了呢?
int array[10] = {1,2,3,4,5,6,7,8,9,10};
for(int i = 0; i < 10; i++) {
std::cout << array[i] << std::endl;
}
那么隆重介绍,基于范围的for循环
for(int ele : array) {
std::cout << ele << std::endl;
}
听说你还想对数组的值进行修改?那就取个引用吧
for(int& ele : array) {
ele += 2;
std::cout << ele << std::endl;
}
用这样的语法形式来写就自动的能够从数组中依次取出各个元素,用来初始化这个 ele
这么好用的东西,怎么只能用来给C风格数组用?
std::vector<int> vec{1, 2, 3, 4, 5};
for(auto i : vec) {
std::cout << i << std::endl;
}
在上面的基于范围的 for
循环中, n
表示 arr
中的一个元素, auto
则是让编译器自动推导出 n
的类型。在这里, n
的类型将被自动推导为 vector 中的元素类型 int
。在 n
的定义之后,紧跟一个冒号( :
),之后直接写上需要遍历的表达式, for
循环将自动以表达式返回的容器为范围进行迭代。
提高访问效率且不需要修改元素可使用const引用
std::vector<int> vec{1, 2, 3, 4, 5};
for(const auto& i : vec) {
std::cout << i << std::endl;
}
太优雅了
edit Serein