【C++】STL--Vector使用极其模拟实现
目录
1. Vector的介绍
2. Vector的使用
2.1. vector的定义
2.2. vector迭代器的使用
2.3. vector的空间增长问题
3. vector中的增删改查
3.1. push_back(尾插)
3.2. pop_back(尾删)
3.3. insert
3.4 erase
3.5 operator [ ]
4. 测试
1. Vector的介绍
官方文档介绍
- vector表示可变大小数组的序列容器
- 与数组一样,vector采用的是使用连续空间来储存元素,那也就意味着,采用下标对vector进行访问和数组一样高效。但是相较于数组大小的固定,vector的大小是动态可变的,并且这个变化是由容器自动处理的
- 本质上讲,vector是动态分配数组大小实现的,当有新元素插入时,当需要扩容空间时,数组的大小将会重新分配。具体的做法是,重新开辟一块更大的内存,然后将原数组的元素拷贝过去(很明显是一个 代价很高的任务)
- vector的空间分配策略:每一次分配空间都是按照对数级增长(一般是1.2或者是1.5),形象地来讲就是一种囤地策略,每次重新分配都会多囤一点,这样的话插入时,有地方就话直接插入,如此每一次重新分配内存的高额代价就会被多囤地的免费插入摊平,总的来看,末尾插入的时间代价还是常数
- 总的来说,vector访问元素、在末尾添加删除元素比较高效,但是其他操作相对效率低
2. Vector的使用
vector在日常的使用非常广泛,我们应该熟悉它的常用接口。接下来我们从基础的接口开始,学会它的使用及模拟实现。
vector模拟实现的基本结构:
template<class T>
class vector
{
public:typedef T* iterator;typedef const T* const_iterator;//无参构造vector():_start(nullptr) //数组首地址, _finish(nullptr) //当前末尾元素的下一个地址, _endofstoage(nullptr) //当前分配空间的下一地址{}//资源管理~vector(){if (_start){delete[] _start;_start = _finish = _endofstoage = nullptr;}}size_t size() const{return _finish - _start;}size_t capacity() const{return _endofstoage - _start;}
private:iterator _start;iterator _finish;iterator _endofstoage;
};
2.1. vector的定义
构造函数声明constructor 接口说明 vector() (重点) 无参构造 vector (const vector& x); (重点) 拷贝构造 vector ( size_type n, const value_type& val = value_type() ) 构造并初始化 n 个 val vector (InputIterator first, InputIterator last); 使用迭代器进行初始化构造 //无参构造 vector():_start(nullptr), _finish(nullptr), _endofstoage(nullptr) {}//拷贝构造 void swap(vector<T>& v) {std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstoage, v._endofstoage); }//vector(const vector& v) vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstoage(nullptr) {vector tmp(v.begin(), v.end());swap(tmp); }//初始化n个val vector(size_t n, const T& val = T()):_start(nullptr), _finish(nullptr), _endofstoage(nullptr) {reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);} }//使用迭代化区间初始化 template <class InputIterator> vector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _endofstoage(nullptr) {while (first != last){push_back(*first);++first;} }
2.2. vector迭代器的使用
iterator的使用 接口说明 begin+end (重点) 获取第一个数据位置的 iterator/const_iterator , 获取最后一个数据的下一个位置的 iterator/const_iterator rbegin+rend(反向迭代器) 获取最后一个数据位置的 reverse_iterator ,获取第一个数据前一个位置的reverse_iterator iterator begin() {return _start; } iterator end() {return _finish; }const iterator begin() const {return _start; } const iterator end() const {return _finish; }
2.3. vector的空间增长问题
容量空间 接口说明 size 获取数据个数 capacity 获取容量大小 empt 判断是否为空 resize(重点) 改变 vector 的 size reserve(重点) 改变 vector 的 capacity void resize(size_t n, T val = T()) {if (n > capacity()){reserve(n);}if (n > size()){while (_finish <= _start + n){*_finish = val;++_finish;}}else{_finish = _start + n;} }void reserve(size_t n) {size_t sz = size();if (n > capacity()){T* tmp = new T[n];if (_start){for (int i = 0; i < sz; ++i){tmp[i] = _start[i];}delete[] _start;}_start = tmp;}_finish = _start + sz;_endofstoage = _start + n;}
注意:
vector在扩容的时候有一个小细节,那就是vector的扩容在vs和gcc下是有区别的,vs下是1.5倍扩容,然而在在gcc下是2倍扩容。所以不能固话地认为vector的扩容就是2倍。具体增长的多少要根据需求定义。Vs是PJ盘本的STL,g++是SGI版本的STL。
我们可以在VS和Clion两个环境下试一下:
//vs下 int main() {vector<int> v;size_t sz = v.capacity();for (int i = 0; i < 100; ++i){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed: " << sz << '\n';}}return 0; }
VS中的结果:
Clion中gcc的结果:
3. vector中的增删改查
vector 增删查改 | 接口说明 |
push_back (重点) | 尾插 |
pop_back (重点) | 尾删 |
find | 查找。(注意这个是算法模块实现,不是 vector 的成员接口) |
insert | 在 pos 之前插入 val |
erase | 删除 pos 位置的数据 |
swap | 交换两个 vector 的数据空间 |
operator[] (重点) | 像数组一样访问 |
3.1. push_back(尾插)
//尾插 void push_back(const T& x) {if (_finish == _endofstoage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}*_finish = x;++_finish; }
3.2. pop_back(尾删)
这个地方熟悉一点的同学都知道,尾删的逻辑是覆盖而并不是删除:
//尾删 void pop_back(const T& x) {if (_finish > _start){--_finish;} }
3.3. insert
- 首先要判断pos是否合法
- 然后判断是否需要扩容,如果需要这里采用了一个相对量来保证pos的正确性
- 然后从后往前移动元素,前面的覆盖后面的
- 最后插入值,然后更新_finish,返回pos
iterator insert(iterator pos, const T& x) {assert(pos <= _finnsh && pos >= _start);if (_finish == _endofstoage){//这里使用pos到首地址的偏移量//因为扩容之后原先pos的失效了size_t n = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + n;}iterator end = _finish - 1;while(end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos; }
PS:注意这里我们将pos声明为iterator,可能有人会问为什么不使用下标更加方便呢?当时在这里使用下标确实会更加方便,但是这个容器不是vector了,而是list或者map,就并不适配。
3.4 erase
- 判断pos是否合法
- 然后循环congpos开始,后一个覆盖前一个
- 更新_finish
- 返回pos
iterator erase(iterator pos) {assert(pos >= _start && pos <= _finish);iterator end = pos + 1;while (end < _finish){*(end - 1) = *end;++end;}--_finish;return pos; }
3.5 operator [ ]
这个函数比较简单,只要返回pos所在位置的元素即可:
T& operator[](size_t pos) {assert(pos < size());return _start[pos]; }const T& operator[](size_t pos) const {assert(pos < size());return _start[pos]; }
4. 测试
int main() {// 1. 默认构造 + push_backv::vector<int> v1;for (int i = 1; i <= 5; i++) {v1.push_back(i * 10);}cout << "v1 after push_back: ";for (size_t i = 0; i < v1.size(); i++) {cout << v1[i] << " ";}cout << "\n";// 2. 测试 insertauto it = v1.begin() + 2;v1.insert(it, 99);cout << "v1 after insert 99 at pos 2: ";for (size_t i = 0; i < v1.size(); i++) {cout << v1[i] << " ";}cout << "\n";// 3. 测试 erasev1.erase(v1.begin() + 3);cout << "v1 after erase at pos 3: ";for (size_t i = 0; i < v1.size(); i++) {cout << v1[i] << " ";}cout << "\n";// 4. 测试 pop_backv1.pop_back();cout << "v1 after pop_back: ";for (size_t i = 0; i < v1.size(); i++) {cout << v1[i] << " ";}cout << "\n";// 5. 测试拷贝构造v::vector<int> v2 = v1;cout << "v2 (copy of v1): ";for (size_t i = 0; i < v2.size(); i++) {cout << v2[i] << " ";}cout << "\n";// 6. 测试初始化构造 (5个元素,值全为 7)vector<int> v3(5, 7);cout << "v3 (5 elements = 7): ";for (size_t i = 0; i < v3.size(); i++) {cout << v3[i] << " ";}cout << "\n";// 7. 测试迭代器区间构造v::vector<int> v4(v1.begin(), v1.end());cout << "v4 (constructed from v1): ";for (size_t i = 0; i < v4.size(); i++) {cout << v4[i] << " ";}cout << "\n";return 0;
}
(本篇完)