目录
概述
在道家哲学中,“道”代表宇宙的根本规律和本质原理,无形而永恒;“术”代表具体的方法和技巧,有形而实践。
简而言之:学会“术”,就学会了怎么使用具体的容器,知道怎么去定义一个具体的容器以及对应的基本操作;学会“道”,你就知道具体容器在内存上具体长什么样,每一个操作的具体原理,以及存在的性能瓶颈。
在我后续对容器教程的系列的文章中,我都将从“道”与“术”两个角度加以说明。如今AI已经非常强大,任意一个AI都可以让一个小白,可以“零”成本、快速地运用“术法”内容。
如果你迫切想要知道怎么使用vector,可以直接跳过道法篇,进入术法篇,里面记录着vector 每一招每一式的用法,不懂道法,亦可成功。
如果你希望提高的水平,建议你先从道法篇看起,一旦你正在理解了其核心原理,术法篇你看起来将无招胜有招,成为真正的高手,就像武侠小说中内力深厚的高手,看一遍招式就能学会。
vector 道法篇:
vector 核心原理基于动态数组的设计,它通过连续内存块存储元素,支持快速随机访问和动态调整大小。
随机访问:随机访问指的是可以直接通过索引位置立即访问容器中的任意元素,而不需要从头开始遍历。
相信很多初学者对“随机访问”这个词不是很理解,根据道长的经验,即使是很多摸爬滚打很多年的程序员,尤其是常在应用层活动的API工程师,常有困惑。
举个例子:
(1)如果5个人并排站在一起(内存上连续的),你就可以很容易地找到第五个人(随机访问)。(2)假如这五个人战的位置并不是并排,而是藏起来的,第一个人知道第二个人在哪,第二个人又知道第三个人在哪,以此类推,第五个人的位置只有第四个人知道,那么你要找到第5个人,你就只能从第一个人逐个找(非随机访问)。
vector 两个重点:
(1)在内存布局上是连续的--> 支持随机访问。
(2)可以动态调整大小。
系列特性说明
基于这两个“道法”原理,就决定了他的一系列特性。
(1) capacity(容量)
由于在内存布局上是联系的,它需要预先分配出一块空间,这块空间可以存储多少个数据,就是容量。
(2)size(大小)
当前已经存储的数据个数。
(3) 在尾部插入数据很(最快O(1)1,最慢O(n)。
如果当前数据还没占满容量,就能很快在结尾处插入一个数据(O(1)),如果刚好容量已经满了,那么基于“动态大小”的特性,vector可以进行扩容(不是简单地增加内存大小,做不到),重新申请一块更大的连续的内存,在把原来的数据,全部搬运到新的内存,在在新内存最后插入数据,这种情况就是O(n)。
(4) 内存布局:
Vector 对象内部包含三个指针:
start:指向内存块的起始地址(第一个元素)。finish:指向最后一个元素的下一个位置(即有效元素结束点)。end_of_storage:指向内存块的结束地址(容量边界)。 这些指针定义了一个连续的内存区间:[start, finish)存储有效元素,[finish, end_of_storage)是空闲空间。
也就是说,定义一个vector,大小 = 三个指针大小+capacity*每个元素的大小。
我们通过重定义new 操作符,来捕获内存分配情况:
#include <new>
#include <iostream>
#include <vector>
// 重载全局new操作符
void* operator new(size_t size) {
void* ptr = std::malloc(size);
std::cout <<"-->new size: "<<size <<" byte"<<" src: "<<ptr<<std::endl;
return ptr;
}
// 重载全局delete操作符
void operator delete(void* ptr) noexcept {
if (ptr) {
std::cout <<"-->free src: "<<ptr<<std::endl;
std::free(ptr);
}
}
int main(){
std::cout<<"test t0"<<std::endl;
std::vector<int>* vec1 = new std::vector<int>();
std::cout<<"capacity t1: "<<vec1->capacity()<<std::endl;
vec1->push_back(1);
std::cout<<"capacity t2: "<<vec1->capacity()<<std::endl;
vec1->push_back(2);
std::cout<<"capacity t3: "<<vec1->capacity()<<std::endl;
vec1->push_back(3);
std::cout<<"capacity t4: "<<vec1->capacity()<<std::endl;
vec1->push_back(4);
std::cout<<"capacity t5: "<<vec1->capacity()<<std::endl;
}
可以直接使用在线编译工具运行https://www.onlinegdb.com/,输出如下:
test t0
-->new size: 24 byte src: 0x58220ca11d30
capacity t1: 0
-->new size: 4 byte src: 0x58220ca11d50
capacity t2: 1
-->new size: 8 byte src: 0x58220ca11d70
-->free src: 0x58220ca11d50
capacity t3: 2
-->new size: 16 byte src: 0x58220ca11d50
-->free src: 0x58220ca11d70
capacity t4: 4
capacity t5: 4
我的编译平台64位,即每个指针占8个字节,可见初始new 出一个Vector,申请的内存为24字节(3个指针),t1 和 t2 之间加入一个数据,容量变为1,申请4字节;t2和t3之间容量变为2,重新申请8字节,释放之前申请的4字节内存,t3-t4 容量变为4,重新申请16字节,是否之前的内存。可见符合们之前所描述的内存布局。
既然会自动扩容,那么就存在一个扩容因子,即容量不够时候,扩充的容量,这个不同编译器实现不一样,gcc的扩容因子为2,从上面的结果也可以看到,每次扩容时候,容量翻倍。
vector 术法篇:
前面我通过“道法”篇,详细介绍了vector的实现原理,以及在内存上的布局,至此,对于vector 你应该知道其真正长什么样了,不再被其表像所迷惑,也应该知道其性能瓶颈了。
📋 目录
-
成员类型
-
构造函数
-
析构函数
-
赋值操作
-
元素访问
-
迭代器
-
容量操作
-
修改器
-
非成员函数
-
完整示例
🏗️ 成员类型
| 类型 | 定义 | 说明 |
|---|---|---|
value_type | T | 容器中存储的元素类型 |
allocator_type | Allocator | 用于管理内存的分配器类型 |
size_type | std::size_t | 无符号整数类型,用于表示大小 |
difference_type | std::ptrdiff_t | 有符号整数类型,用于表示距离 |
reference | value_type& | 元素的引用类型 |
const_reference | const value_type& | 元素的常量引用类型 |
pointer | Allocator::pointer | 指向元素的指针类型 |
const_pointer | Allocator::const_pointer | 指向常量元素的指针类型 |
iterator | 随机访问迭代器 | 指向元素的迭代器 |
const_iterator | 常量随机访问迭代器 | 指向常量元素的迭代器 |
reverse_iterator | std::reverse_iterator<iterator> | 反向迭代器 |
const_reverse_iterator | std::reverse_iterator<const_iterator> | 常量反向迭代器 |
🏗️ 构造函数
| 构造函数 | 说明 | 示例 |
|---|---|---|
vector() noexcept | 默认构造函数 | std::vector<int> v1; |
explicit vector(const Allocator& alloc) noexcept | 带分配器的默认构造 | std::vector<int> v2(alloc); |
vector(size_type count, const T& value, const Allocator& alloc = Allocator()) | 构造count个value | std::vector<int> v3(5, 10); |
explicit vector(size_type count, const Allocator& alloc = Allocator()) | 构造count个默认值 | std::vector<int> v4(5); |
template<class InputIt> vector(InputIt first, InputIt last, const Allocator& alloc = Allocator()) | 范围构造 | std::vector<int> v5(arr, arr+3); |
vector(const vector& other) | 拷贝构造 | std::vector<int> v6(v5); |
vector(const vector& other, const Allocator& alloc) | 带分配器的拷贝构造 | std::vector<int> v7(v5, alloc); |
vector(vector&& other) noexcept | 移动构造 | std::vector<int> v8(std::move(v6)); |
vector(vector&& other, const Allocator& alloc) | 带分配器的移动构造 | std::vector<int> v9(std::move(v7), alloc); |
vector(std::initializer_list<T> init, const Allocator& alloc = Allocator()) | 初始化列表构造 | std::vector<int> v10{1,2,3}; |
🗑️ 析构函数
| 函数 | 说明 | 示例 |
|---|---|---|
~vector() | 销毁所有元素并释放内存 | 自动调用 |
🔄 赋值操作
| 函数 | 说明 | 示例 |
|---|---|---|
vector& operator=(const vector& other) | 拷贝赋值 | v1 = v2; |
vector& operator=(vector&& other) noexcept | 移动赋值 | v1 = std::move(v2); |
vector& operator=(std::initializer_list<T> ilist) | 初始化列表赋值 | v1 = {4,5,6}; |
void assign(size_type count, const T& value) | 赋值count个value | v1.assign(3, 100); |
template<class InputIt> void assign(InputIt first, InputIt last) | 赋值范围元素 | v1.assign(arr, arr+3); |
void assign(std::initializer_list<T> ilist) | 赋值初始化列表 | v1.assign({7,8,9}); |
🔍 元素访问
| 函数 | 说明 | 示例 | 异常安全 |
|---|---|---|---|
reference at(size_type pos) | 带边界检查访问 | int x = v1.at(0); | 抛出std::out_of_range |
const_reference at(size_type pos) const | const版本at | const int x = v1.at(0); | 同上 |
reference operator[](size_type pos) | 无边界检查访问 | int x = v1[0]; | 未定义行为(越界时) |
const_reference operator[](size_type pos) const | const版本operator[] | const int x = v1[0]; | 同上 |
reference front() | 访问首元素 | int x = v1.front(); | 空vector未定义行为 |
const_reference front() const | const版本front | const int x = v1.front(); | 同上 |
reference back() | 访问尾元素 | int x = v1.back(); | 空vector未定义行为 |
const_reference back() const | const版本back | const int x = v1.back(); | 同上 |
T* data() noexcept | 访问底层数组 | int* p = v1.data(); | 无 |
const T* data() const noexcept | const版本data | const int* p = v1.data(); | 无 |
🔄 迭代器
| 函数 | 说明 | 示例 |
|---|---|---|
iterator begin() noexcept | 指向首元素的迭代器 | auto it = v1.begin(); |
const_iterator begin() const noexcept | const版本begin | auto it = v1.begin(); |
const_iterator cbegin() const noexcept | 指向首元素的const迭代器 | auto it = v1.cbegin(); |
iterator end() noexcept | 指向尾后位置的迭代器 | auto it = v1.end(); |
const_iterator end() const noexcept | const版本end | auto it = v1.end(); |
const_iterator cend() const noexcept | 指向尾后位置的const迭代器 | auto it = v1.cend(); |
reverse_iterator rbegin() noexcept | 指向反向首元素的迭代器 | auto it = v1.rbegin(); |
const_reverse_iterator rbegin() const noexcept | const版本rbegin | auto it = v1.rbegin(); |
const_reverse_iterator crbegin() const noexcept | 指向反向首元素的const迭代器 | auto it = v1.crbegin(); |
reverse_iterator rend() noexcept | 指向反向尾后位置的迭代器 | auto it = v1.rend(); |
const_reverse_iterator rend() const noexcept | const版本rend | auto it = v1.rend(); |
const_reverse_iterator crend() const noexcept | 指向反向尾后位置的const迭代器 | auto it = v1.crend(); |
📦 容量操作
| 函数 | 说明 | 示例 | 时间复杂度 |
|---|---|---|---|
bool empty() const noexcept | 检查是否为空 | if(v1.empty()) | O(1) |
size_type size() const noexcept | 返回元素数量 | size_t s = v1.size(); | O(1) |
size_type max_size() const noexcept | 返回最大可能大小 | size_t m = v1.max_size(); | O(1) |
void reserve(size_type new_cap) | 增加容量 | v1.reserve(100); | 最多O(n) |
size_type capacity() const noexcept | 返回当前容量 | size_t c = v1.capacity(); | O(1) |
void shrink_to_fit() | 减少容量适应大小 | v1.shrink_to_fit(); | 最多O(n) |
🛠️ 修改器
| 函数 | 说明 | 示例 | 时间复杂度 |
|---|---|---|---|
void clear() noexcept | 清空所有元素 | v1.clear(); | O(n) |
iterator insert(const_iterator pos, const T& value) | 插入元素 | v1.insert(v1.begin(),10); | 最多O(n) |
iterator insert(const_iterator pos, T&& value) | 移动插入元素 | v1.insert(v1.begin(),std::move(x)); | 最多O(n) |
iterator insert(const_iterator pos, size_type count, const T& value) | 插入count个元素 | v1.insert(v1.begin(),3,10); | 最多O(n) |
template<class InputIt> iterator insert(const_iterator pos, InputIt first, InputIt last) | 插入范围 | v1.insert(v1.begin(),arr,arr+3); | 最多O(n) |
iterator insert(const_iterator pos, std::initializer_list<T> ilist) | 插入初始化列表 | v1.insert(v1.begin(),{1,2,3}); | 最多O(n) |
template<class... Args> iterator emplace(const_iterator pos, Args&&... args) | 原位构造元素 | v1.emplace(v1.begin(),10); | 最多O(n) |
template<class... Args> void emplace_back(Args&&... args) | 在末尾原位构造 | v1.emplace_back(10); | 平摊O(1) |
void push_back(const T& value) | 在末尾添加元素 | v1.push_back(10); | 平摊O(1) |
void push_back(T&& value) | 在末尾移动添加 | v1.push_back(std::move(x)); | 平摊O(1) |
void pop_back() | 移除末尾元素 | v1.pop_back(); | O(1) |
iterator erase(const_iterator pos) | 擦除单个元素 | v1.erase(v1.begin()); | 最多O(n) |
iterator erase(const_iterator first, const_iterator last) | 擦除范围 | v1.erase(v1.begin(),v1.begin()+2); | 最多O(n) |
void resize(size_type count) | 改变大小(默认构造) | v1.resize(10); | 最多O(n) |
void resize(size_type count, const T& value) | 改变大小(指定值) | v1.resize(10,5); | 最多O(n) |
void swap(vector& other) noexcept | 交换内容 | v1.swap(v2); | O(1) |
🔗 非成员函数
| 函数 | 说明 | 示例 |
|---|---|---|
bool operator==(const vector& lhs, const vector& rhs) | 相等比较 | if(v1 == v2) |
bool operator!=(const vector& lhs, const vector& rhs) | 不等比较 | if(v1 != v2) |
bool operator<(const vector& lhs, const vector& rhs) | 小于比较 | if(v1 < v2) |
bool operator<=(const vector& lhs, const vector& rhs) | 小于等于比较 | if(v1 <= v2) |
bool operator>(const vector& lhs, const vector& rhs) | 大于比较 | if(v1 > v2) |
bool operator>=(const vector& lhs, const vector& rhs) | 大于等于比较 | if(v1 >= v2) |
void swap(vector& lhs, vector& rhs) noexcept | 特化swap算法 | std::swap(v1, v2); |
代码示例
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
// 1. 创建和初始化
std::vector<int> vec = {1, 2, 3, 4, 5};
// 2. 元素访问
std::cout << "第一个元素: " << vec.front() << std::endl;
std::cout << "最后一个元素: " << vec.back() << std::endl;
std::cout << "第三个元素: " << vec[2] << std::endl;
std::cout << "带边界检查访问: " << vec.at(1) << std::endl;
// 3. 容量操作
std::cout << "大小: " << vec.size() << std::endl;
std::cout << "容量: " << vec.capacity() << std::endl;
std::cout << "是否为空: " << (vec.empty() ? "是" : "否") << std::endl;
// 4. 修改操作
vec.push_back(6); // 添加元素
vec.pop_back(); // 移除最后一个元素
vec.insert(vec.begin() + 2, 99); // 在位置2插入
vec.erase(vec.begin() + 1); // 移除位置1的元素
// 5. C++11 新特性
vec.emplace_back(100); // 原位构造
vec.shrink_to_fit(); // 缩减容量
// 6. 迭代器使用
std::cout << "正向遍历: ";
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
std::cout << "反向遍历: ";
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 7. 范围for循环 (C++11)
std::cout << "范围for循环: ";
for (const auto& element : vec) {
std::cout << element << " ";
}
std::cout << std::endl;
// 8. 算法使用
auto found = std::find(vec.begin(), vec.end(), 99);
if (found != vec.end()) {
std::cout << "找到元素99在位置: " << std::distance(vec.begin(), found) << std::endl;
}
return 0;
}
第一个元素: 1
最后一个元素: 5
第三个元素: 3
带边界检查访问: 2
大小: 5
容量: 5
是否为空: 否
正向遍历: 1 99 3 4 5 100
反向遍历: 100 5 4 3 99 1
范围for循环: 1 99 3 4 5 100
找到元素99在位置: 1
结语,关于vector 的封装,我在OmniCpp 项目中有封装,感兴趣的可以关注下。

2002

被折叠的 条评论
为什么被折叠?



