C++ 标准库(STL)系列教程, 序列容器 vector 部分。
CPP 序列容器快速入门(2) vector
引言
序列容器是C++标准模板库(STL)中的重要组成部分,它们允许我们按顺序存储和访问元素。这些容器为开发者提供了灵活且高效的数据结构,用于处理各种复杂的编程任务。
其中,vector 作为可以动态增长的序列容器,能够根据需要自动分配和释放内存。它允许我们像操作普通数组一样,通过索引快速访问元素,同时还提供了在尾部高效添加和删除元素的方法。这使得 vector 成为处理可变大小数据集的理想选择。
在本入门教程中,我们将详细探讨 vector 容器的使用方法,帮助读者快速掌握这一基本而实用的工具。
vector (动态数组)
vector 是可变大小的容器,元素严格顺序排列(相当于CPP中的动态数组的扩展)。
vector 的存储是自动管理的,按需扩张收缩。 vector 通常占用多于静态数组的空间,因为要分配更多内存以管理将来的增长。
vector扩容的步骤如下:
- 申请更大的内存空间
- 将旧空间的数据按顺序复制/移动到新空间
- 释放旧空间
因此扩容是比较耗时的操作,应该尽量减少扩容次数。 我们再介绍一下 vector 的扩容策略。
vector 只在额外内存耗尽时进行重分配,一般新分配的空间是当前空间的 1.5~2 倍。 分配的内存总量可用 capacity() 函数查询。 可以通过调用 shrink_to_fit()返回多余的内存给系统。
如果元素数量已知,那么 reserve() 函数可用于消除重分配。 但是调用 reserve() 后,插入只会在它将导致 vector 的大小大于 capacity() 的值时触发重新分配。
vector 上的常见操作复杂度(效率)如下:
- 随机访问——常数 O(1)。
- 在末尾插入或移除元素——均摊常数 O(1)。
- 插入或移除元素——与到 vector 结尾的距离成线性 O(n)。
隐式定义的成员函数
构造函数
: 初始化容器析构函数
: 销毁容器的每个元素operator=
: 赋值运算符- 复制赋值运算符。以 other 内容的副本替换内容。
- 移动赋值运算符。用移动语义以 other 的内容替换内容(即从 other 移动 other 中的数据到此容器中)。之后 other 处于合法但未指定的状态。
assign
: 将值赋给容器
1 |
|
元素访问
对于元素访问,vector 和 array 的接口是一致的。 因此在工程实践中,大多数情况都会用 vector 代替 array , 因为做相同的事时,vector 比 array 更灵活。
at
: 带越界检查访问指定的元素(引用)返回位于指定位置 pos 的元素的引用,有边界检查。 若 pos 不在容器范围内,则抛出
std::out_of_range
类型的异常。operator[]
: 访问指定的元素(引用)返回位于指定位置 pos 的元素的引用。不进行边界检查。 通过此运算符访问不存在的元素是未定义行为。
front
: 访问第一个元素返回到容器首元素的引用。 在空容器上对
front
的调用是未定义的。back
: 访问最后一个元素。返回到容器中最后一个元素的引用 在空容器上对
back
的调用是未定义的。data
: 直接访问底层连续存储,返回指向底层元素存储的指针对于非空容器,返回的指针与首元素地址比较相等。 如果
size()
是 0,那么data()
有可能会也有可能不会返回空指针。
1 |
|
迭代器
vector 的迭代器接口与 array 也是完全一致的。
begin
cbegin
: 返回指向起始的迭代器,c 前缀代表 const 不可修改(下同)返回指向 容器 首元素的迭代器。 如果 容器 为空,那么返回的迭代器等于 end()。
end
cend
: 返回指向末尾的迭代器返回指向 容器 末元素后一元素的迭代器。 此元素表现为占位符;试图访问它导致未定义行为。
rbegin
crbegin
: 返回指向起始的逆向迭代器返回指向逆向的 容器 的首元素的逆向迭代器。它对应非逆向 容器 的末元素。 如果 容器 为空,那么返回的迭代器等于 rend()。
rend
crend
: 返回指向末尾的逆向迭代器返回指向逆向的 容器 末元素后一元素的逆向迭代器。它对应非逆向 容器 首元素的前一元素。此元素表现为占位符,试图访问它导致未定义行为。
1 |
|
容量
vector 的容量接口中有一部分与 array 是一致的:
empty
: 检查容器是否为空size
: 返回元素数max_size
: 返回可容纳的最大元素数
但由于vector是动态数组,因此可以改变容量:
reserve
: 预留存储空间增加 vector 的容量(不重新分配存储的情况下能最多能持有的元素的数量)到大于或等于 new_cap 的值。 reserve() 不会更改 vector 的 size()。
只有 new_cap 大于当前的 capacity() 时才会分配新存储; 若分配新存储, 则所有迭代器和到元素的引用失效。实际是因为旧空间已经释放了。 调用 reserve() 后,插入只会在它将导致 vector 的大小大于 capacity() 的值时触发重新分配。
注意 reserve 不能减少分配内存,需要用 shrink_to_fit ,且该操作依赖具体实现。
capacity
: 返回当前存储空间能够容纳的元素数返回当前分配存储的容量(元素数)。
shrink_to_fit
: 释放未使用的内存(减少内存的使用)请求移除未使用的容量,是减少 capacity() 到 size()非强制性请求。请求是否达成依赖于实现。 若分配新存储, 则所有迭代器和到元素的引用失效。
1 |
|
修改器
clear
: 清除内容从容器擦除所有元素。此调用后 size() 返回零。
使任何指代所含元素的引用、指针和迭代器失效。 任何尾后迭代器也会失效。 保持 vector 的 capacity() 不变。
insert
: 插入元素插入元素到容器中的指定位置。
- 在 pos 前插入 value; 返回指向被插入 value 的迭代器。
- 在 pos 前插入 value 的 count 个副本; 返回指向首个被插入元素的迭代器, count == 0 时返回 pos。
- 在 pos 前插入来自迭代器范围 [first, last) 的元素; 返回指向首个被插入元素的迭代器, first == last 时返回 pos。
- 在 pos 前插入来自 initializer_list ilist 的元素
如果操作后新的 size() 大于原 capacity() 则会发生重分配,指代元素的所有迭代器(包括 end() 迭代器)和所有引用均会失效。 若无重分配,则仅插入点之前的迭代器和引用保持有效。
emplace
: 原位构造元素在紧接 pos 之前的位置向容器插入新元素;返回指向被安置的元素的迭代器。
迭代器失效原理与 insert 相同。
erase
: 擦除元素 从容器擦除指定的元素。
- 移除位于 pos 的元素; 返回最后移除元素之后的迭代器,如果 pos 指代末元素,那么返回 end() 迭代器。
- 移除范围 [first, last) 中的元素; 返回最后移除元素之后的迭代器,如果在移除前 last == end(),那么返回更新的 end() 迭代器。如果范围 [first, last) 为空,那么返回 last。
擦除后,指向位于擦除点或其后的元素的迭代器(包括 end() 迭代器)和引用均会失效。
push_back
: 将元素添加到容器末尾追加给定元素 value 到容器尾。
- 对于常量引用,初始化新元素为 value 的副本。
- 对于右值引用,移动 value 进新元素。
迭代器失效原理与 insert 相同。
emplace_back
: 在容器末尾原位构造元素添加新元素到容器尾。元素通过 std::allocator_traits::construct 构造,通常用放置式 new 于容器所提供的位置原位构造元素。 迭代器失效原理与 insert 相同。
pop_back
移除末元素移除容器的末元素。 在空容器上调用 pop_back 是未定义行为。 指向最后元素的迭代器(包括 end() 迭代器)和引用失效。
resize
: 改变存储元素的个数重设容器大小以容纳 count 个元素。 如果当前大小大于 count,那么减小容器到它的前 count 个元素。 如果当前大小小于 count,追加额外的默认插入的元素/ value 的副本。
swap
: 交换内容将内容以及容量与 other 的交换。不在单独的元素上调用任何移动、复制或交换操作。 所有迭代器和引用仍然有效。end() 迭代器失效。
注意与 array 的区别:array 会交换所有的元素; vector 的实质是交换指针。
1 |
|
至此常见的 vector 相关用法已经介绍完毕了,接下来会尝试代码实现这个容器(敬请期待)。