从零开始的C++学习生活 7:vector的入门使用
个人主页:Yupureki-CSDN博客
C++专栏:C++_Yupureki的博客-CSDN博客
目录
前言
1. vector简介
1.1 什么是vector?
1.2 为什么选择vector?
传统数组的局限性
vector的优势
2. vector的基本使用
2.1 构造vector
2.2 迭代器使用
2.3 容量管理
2.4 元素访问
2.5 修改操作
3. 迭代器失效问题(重点)
3.1 导致迭代器失效的操作
扩容操作
插入操作
删除操作
正确示例:
4. vector的模拟实现
4.1 基础框架
4.2 深拷贝问题
4.3 完整实现关键函数
5. 性能优化建议
上一篇:从零开始的C++学习生活 6:string的入门使用-CSDN博客
前言
在前面我们学过了c++中的string,一个c++内置的处理字符串的类。不仅于此,c++还内置了很多像string的容器,如vector,list,stack和queue。
而今天我们要学习的是vector,即顺序表(数组),同样内置了各种我们所需的功能,无论是实战项目还是oj题都十分重要。
本篇博客将带你梳理一遍vector常用的功能。
1. vector简介
1.1 什么是vector?
vector
是C++标准库中的一个序列容器,它封装了动态大小数组的功能。主要特点包括:
-
动态扩容:根据需要自动调整容量
-
随机访问:支持通过下标直接访问元素,时间复杂度O(1)
-
连续存储:元素在内存中连续存放,缓存友好
-
类型安全:模板化设计,编译时类型检查
1.2 为什么选择vector?
传统数组的局限性
int arr[10]; // 固定大小,无法动态调整
arr[10] = 5; // 可能越界,危险!
传统数组存在数组越界的风险,并且不够智能。无法自由在里面插入数据,删除数据并保持元素顺序连续,即无法动态插入数据。
虽然我们学习数据结构时实现了自己的顺序表,但在题目当中难道还要自己手手搓一个吗?显然不现实
vector的优势
随时随用
vector是集成在c++中的一个容器,只需要包含头文件即可使用
#include <vector>
功能丰富
vector内置顺序表中几乎所有需要用到的功能,例如增删查改
2. vector的基本使用
2.1 构造vector
vector的构造和string基本一致
#include <vector>
using namespace std;// 1. 默认构造 - 空vector
vector<int> v1;// 2. 构造包含n个val的vector
vector<int> v2(5, 10); // {10, 10, 10, 10, 10}// 3. 拷贝构造
vector<int> v3(v2); // 与v2相同// 4. 使用迭代器范围构造
int arr[] = {1, 2, 3, 4, 5};
vector<int> v4(arr, arr + 5); // {1, 2, 3, 4, 5}// 5. 初始化列表构造 (C++11)
vector<int> v5 = {1, 2, 3, 4, 5};
2.2 迭代器使用
vector的迭代器可以简单看作是数据类型的指针,如果是vector<int>那么迭代器是int*
void Print()
{iterator it = begin();while (it!=_finish){cout << *it << " ";it++;}cout << endl << "size:" << size() << " " << "capacity:" << capacity() << endl;
}
2.3 容量管理
容量空间 | 接口说明 |
---|---|
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
resize | 改变vector的size |
reserve | 改变vector的capacity |
对于vector的reserve(设置容量)规则是:
如果 n 大于当前容量,则该函数会导致容器重新分配其存储,将其容量增加到 n(或更大)。
在所有其他情况下(即n小于或等于当前容量),函数调用不会导致重新分配,并且容量不受影响。
对于vector的resize(设置有效数据个数)规则是:
如果 n 小于当前容器大小,则内容将减少到其前 n 个元素,删除超出的元素(并销毁它们)。
如果 n 大于当前容器大小,则通过在末尾插入所需数量的元素来扩展内容,以达到 n 的大小。如果指定了 val,则新元素将初始化为 val 的副本,否则,它们将被值初始化。
如果 n 也大于当前容器容量,则会自动重新分配已分配的存储空间。
vector<int> v;cout << v.size(); // 元素个数: 0
cout << v.capacity(); // 总容量: 0
cout << v.empty(); // 是否为空: truev.reserve(100); // 预留100个元素空间
cout << v.capacity(); // 容量变为100v.resize(10, 5); // 调整大小为10,新增元素初始化为5
cout << v.size(); // 元素个数: 10
2.4 元素访问
元素访问 | 接口说明 |
---|---|
operator[] | 数组下标访问 |
front | 数组第一个元素 |
back | 数组最后一个元素 |
data | 数组地址 |
vector<int> v = {1, 2, 3, 4, 5};// 下标访问
cout << v[0]; // 1 (不检查边界)// 首尾元素访问
cout << v.front(); // 1
cout << v.back(); // 5// 数据指针
int* data = v.data(); // 获取底层数组指针
2.5 修改操作
vector增删查改 | 接口说明 |
---|---|
push_back(重点) | 尾插 |
pop_back(重点) | 尾删 |
find | 查找。(注意这个是算法模块实现,不是vector的成员接口) |
insert | 在position之前插入val |
erase | 删除position位置的数据 |
swap | 交换两个vector的数据空间 |
vector<int> v = {1, 2, 3};// 尾部操作
v.push_back(4); // {1, 2, 3, 4}
v.pop_back(); // {1, 2, 3}// 插入操作
v.insert(v.begin() + 1, 10); // {1, 10, 2, 3}// 删除操作
v.erase(v.begin() + 1); // {1, 2, 3}
v.erase(v.begin(), v.begin() + 2); // {3}// 交换
vector<int> v2 = {4, 5, 6};
v.swap(v2); // 交换两个vector的内容find(v.begin(),v.end(),2)//algorithm库中的函数,查找对应值的数据
3. 迭代器失效问题(重点)
迭代器失效是使用vector
时最容易出错的地方,必须特别重视。
3.1 导致迭代器失效的操作
扩容操作
vector<int> v = {1, 2, 3};
auto it = v.begin();v.reserve(100); // 扩容,迭代器it失效!
// cout << *it; // 错误!可能崩溃或输出错误数据
扩容之后,相当于是重新开辟了一块空间,清除掉了之前的空间,而迭代器仍指向之前空间的一个地址,就成了野指针
插入操作
vector<int> v = {1, 2, 3};
auto it = v.begin();v.insert(v.begin(), 0); // 插入可能导致扩容,it失效!
插入元素期间,可能会引起扩容,而导致原空间被释放
删除操作
vector<int> v = {1, 2, 3, 4};
auto it = v.begin() + 1; // 指向2v.erase(v.begin()); // 删除1,后面的元素前移
// cout << *it; // it指向的位置现在是什么?不确定!
删除1时,会使后面的数据前移,即{2,3,4}
之前的it指向第二个位置即2,但删除操作之后数据前移,第二个位置就成了3,it由原来的2变成了3,导致it指向的数据混乱,甚至数组越界
正确示例:
无论是扩容,插入还是删除,都必须保证迭代器有效
vector<int> v = {1, 2, 3, 4};
auto it = v.begin();
while (it != v.end()) {if (*it % 2 == 0) {it = v.erase(it); // erase返回下一个有效迭代器} else {++it;}
}
4. vector的模拟实现
4.1 基础框架
在vector库中,vector的成员变量有_start(数组首地址),_finish(数组末尾元素的下一个地址),_end_of_storage(存储空间的末尾)组成
template<typename T>
class vector {
private:T* _start; // 指向首元素T* _finish; // 指向最后一个元素的下一个位置T* _end_of_storage; // 指向存储空间的末尾public:// 构造函数vector() : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}vector(size_t n, const T& val = T()) {_start = new T[n];_finish = _start + n;_end_of_storage = _finish;for (size_t i = 0; i < n; ++i) {_start[i] = val;}}// 析构函数~vector() {if (_start) {delete[] _start;_start = _finish = _end_of_storage = nullptr;}}// 容量相关size_t size() const { return _finish - _start; }size_t capacity() const { return _end_of_storage - _start; }bool empty() const { return _start == _finish; }// 元素访问T& operator[](size_t pos) { return _start[pos]; }const T& operator[](size_t pos) const { return _start[pos]; }
};
4.2 深拷贝问题
错误方式(使用memcp):
void reserve(size_t n) {if (n > capacity()) {T* tmp = new T[n];memcpy(tmp, _start, size() * sizeof(T)); // 浅拷贝!delete[] _start;_start = tmp;_finish = _start + size();_end_of_storage = _start + n;}
}
问题:如果T
是管理资源的类(如string
),memcpy
会导致多个对象共享同一资源,析构时重复释放。
正确方式:
void reserve(size_t n) {if (n > capacity()) {T* tmp = new T[n];// 使用placement new或循环赋值,调用拷贝构造for (size_t i = 0; i < size(); ++i) {tmp[i] = _start[i]; // 调用赋值运算符,深拷贝}delete[] _start;_start = tmp;_finish = _start + size();_end_of_storage = _start + n;}
}
4.3 完整实现关键函数
// 拷贝构造(深拷贝)
vector(const vector<T>& v) : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {reserve(v.capacity());for (size_t i = 0; i < v.size(); ++i) {_start[i] = v[i];}_finish = _start + v.size();
}// 赋值运算符(现代写法)
vector<T>& operator=(vector<T> v) { // 传值,利用拷贝构造swap(v);return *this;
}void swap(vector<T>& v) {std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}// push_back
void push_back(const T& val) {if (_finish == _end_of_storage) {reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = val;++_finish;
}// insert
iterator insert(iterator pos, const T& val) {assert(pos >= _start && pos <= _finish);if (_finish == _end_of_storage) {size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len; // 扩容后迭代器失效,需要重新计算}// 后移元素for (auto it = _finish; it > pos; --it) {*it = *(it - 1);}*pos = val;++_finish;return pos;
}// erase
iterator erase(iterator pos) {assert(pos >= _start && pos < _finish);for (auto it = pos; it < _finish - 1; ++it) {*it = *(it + 1);}--_finish;return pos;
}
5. 性能优化建议
-
预分配空间:如果知道大概的元素数量,使用
reserve()
避免多次扩容 -
使用emplace_back:C++11引入,避免不必要的拷贝/移动
-
避免在循环中判断容量:在循环前确保容量足够
-
使用swap释放内存:
vector<T>().swap(v)
可以真正释放内存
vector<int> v;
v.reserve(1000); // 预分配空间,避免push_back时多次扩容for (int i = 0; i < 1000; ++i) {v.push_back(i); // 不会触发扩容
}// 释放内存
vector<int>().swap(v); // v现在为空,且capacity为0