前言:在上一篇中我们讲到了Vector的一些常见功能的使用方式,今天为了进一步的去学习Vector和能够更深度的去理解Vector的一些底层的原理。
💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
目录标题
- Vector的大体框架
- Vector中的构造函数
- 无参构造
- 拷贝构造(传统写法)
- 析构函数
- swap函数
- 赋值重载operator=
- Vector容器中的遍历方式
- operator[]遍历容器
- 迭代器的遍历和范围for
- 打印函数print_vector(const vector<T>& v)
- Vector容器中相关的增删查改的函数
- 查看容器中有效元素的个数- size()
- 查看容器中国的容量的大小- capacity()
- 调整和检查容量的大小- reserve(size_t n)
- 调整向量的大小-resize(size_t n, const T& value = T())
- 尾插数据- push_back(const T& val)
- 尾删数据-pop_back()
- 插入数据-insert(iterator pos,const T& val)
- 删除数据- erase(iterator pos)
- 查看容器中的数据是否为空Empty()
- vector中构造函数的一点延伸
- 拷贝构造的现代写法
- 区间构造函数
- 列表初始化构造(c++11以上的写法)
- 总体代码
Vector的大体框架
在vector中本质是由三个指针变量组成的,且vector中可以存储各种类型的对象,因此我们使用模板
namespace bit
{template<class T>//因为在使用的时候Vector中可以是任意类型所以我们调用模板class vector{public:typedef T* iterator;//迭代器typedef const T* const_iterator;private:iterator _start = nullptr; //容器的起始位置 // T* _startiterator _finish = nullptr;//容器中所使用的元素的最后一个位置iterator _endofstorage = nullptr;//容器中所有元素的最后一个位置};
}
Vector中的构造函数
无参构造
我们也可以直接在成员变量中对其直接给初始值,不写这个无参构造函数也是可以的。
vector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
}
拷贝构造(传统写法)
在拷贝构造中有两种写法,一个传统写法一个现代写法,由于传统写法要调用的函数我们得到后面才会去实现,所以现代写法作者就先放在后面了,这里先写的传统写法.
- 如果我们不自己实现,让编译器去实现,就是浅拷贝,会出现问题,故需我们自己实现深拷贝,v2(v1),即只要让v2拷贝一份新的数据即可(使v2和v1不是指向同一份数据)。
- 因此我们需要先开辟一块和需要拷贝的对象的同样大的空间,让新的对象指向这一块空间,在把值依次拷贝过去。(代码如下)
vector(const vector<T>& V)
{//传统写法_start = new T[V.capacity()];_finish = _start;//让_finish指向现在的位置,然后依次进行赋值_endofstorage = _start + V.capacity();for (size_t i = 0; i < V.size(); i++){*_finish = V[i];_finish++;}//此时finish指向的就是最后一个有效的元素的后一个位置
}
析构函数
关于析构函数这里就不过多的介绍了,就是把容器给销毁,然后置空即可。
~vector()
{delete[] _start;_start = _finish = _endofstorage = nullptr;
}
swap函数
void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}
赋值重载operator=
代码思路:
- 传过来的对象会产生一个临时变量,临时变量会有一块独立的空间,这块空间和原本需要的值进行交换即达到了深拷贝的效果,然后返回*this即可达到连续赋值的效果。
vector<T>& operator=(vector<T> v)//现代写法
{swap(v);return *this;
}
Vector容器中的遍历方式
operator[]遍历容器
1.我们通过运算符重载可以写出两个operator的容器的遍历方式,一种是可以对其进行修改和操作,一种是只可以对其进行操作不可以修改
T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}const T& operator[](size_t pos) const
{assert(pos < size());return _start[pos];
}
迭代器的遍历和范围for
当然了,迭代器也有可读和可写的两个版本(如下所示),且我们在前面的学习中知道,范围for的本质也就是迭代器进行遍历,只要有迭代器我们就能实现范围for了。
iterator begin()//返回起始位置
{return _start;
}iterator end()//结束位置
{return _finish;
}const_iterator begin() const
{return _start;
}const_iterator end()const
{return _finish;
}
void test2()
{vector<int> v1;v1.push_back(1);v1.push_back(1);v1.push_back(1);v1.push_back(1);cout << "迭代器遍历" << endl;vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";it++;}cout << endl;cout << "范围for遍历" << endl;for (auto e : v1)cout << e << " ";cout << endl;
}
打印函数print_vector(const vector& v)
void print_vector(const vector<T>& v)
{for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;
}
Vector容器中相关的增删查改的函数
查看容器中有效元素的个数- size()
代码思路:_finsh指向最后一个元素的后一个元素,_start指向第一个元素,两个指针相减就是元素的个数。
size_t size()const //元素个数
{return _finish - _start;
}
查看容器中国的容量的大小- capacity()
这里和上面的size()函数同理就不过多的解释了。
size_t capacity()const //总的容量
{return _endofstorage - _start;
}
调整和检查容量的大小- reserve(size_t n)
代码思路:
- 我们首先需要看传过来的大小是否比原本的容量还大,如果比原本的容量还大我们就需要对其进行扩容,比他小我们就不需要处理,因为通常都不会进行缩容的操作。
- 在前面string类的模拟实现的时候我们知道在C++中是没有realloc这样的函数的,因此我们只能开辟一块新的空间,然后将原本的值拷贝过去,在将旧空间的指针指向新空间,在释放掉旧的空间。
- 这里我们需要注意的是,我们需要将原本旧的空间的size记录下来,因为在前面的size函数中是元素的个数是_finish - _start,此时_start指向了一块新的空间,如果直接对他们相减就会导致越界等问题的发生(如下图所示)。
void reserve(size_t n)//调整和检查容量
{if (capacity() < n){T* tmp = new T[n];size_t old_size = size();//因原本的_start在后面会改变,放在野指针的出现我们需要对old_size进行存储for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = tmp + old_size;//用之前记录的size来更新_finsh_endofstorage = tmp + n;}
}
调整向量的大小-resize(size_t n, const T& value = T())
代码思路:
1.这里同理,我们需要看传过来的容量是否比原本的大,大的话我们就进行扩容的操作,如果使用者指定了value的值,就将扩容的空间的值全部指定成使用者指定的值,没有指定就使用默认值,小的话对原本的_finish进行更新即可。
2. 这里很多人不懂的是为什么使用匿名对象T(),在前面我们说过自定义类型会调用它的默认构造函数去初始化他的匿名对象,但是T是内置类型的话也会有自己的默认构造函数,如下图所示。
void resize(size_t n, const T& value = T())//使用半缺省,扩容的话就把剩余的部分全部扩容成value
{if (n > size()){reserve(n);while (_finish < _start + n){*_finish = value;_finish++;}}else{_finish = _start + n;}
}
尾插数据- push_back(const T& val)
代码思路:
- 同理们在插入数据的时候需要查看空间的大小是否足够,如果不够扩容即可。
- 直接在原本的_finish进行赋值,再让其往后更新一位即可。
void push_back(const T& val)
{//insert(end(), val);if (_endofstorage == _finish)//判断容量是否是满的{reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = val;_finish++;}
尾删数据-pop_back()
代码思路:
- 我们需要判断是否还有数据,如果没有数据就不能进行删除
- 我们直接和之前顺序表一样的玩法,让其尾部往前移动一位即可。
void pop_back()
{assert(_finish > _start);--_finish;
}
插入数据-insert(iterator pos,const T& val)
代码思路:
- 同理我们需要判断是否需要扩容,如果需要扩容的话我们需要注意的是在调用reserve函数时候会对底层的_start和_finish进行修改,所以我们需要对原本的长度进行记录,否则就无法知道原本的长度了。
- 此时我们要插入的位置,就是新的_start+len此时的位置了。
- 这里我们找到尾部的位置让pos后的位置依次往后移动一位即可,然后再将需要插入的值赋值给pos的位置即可完成插入。
void insert(iterator pos,const T& val)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage)//判断是否需要扩容{size_t len = pos - _start;//同理需要记录原本的长度,因为在reserver后会对原本的_start进行修改reserve(capacity() == 0 ? 4 : capacity() * 2);//扩容了的话原本的_start//如果扩容了就要更新pospos = len + _start;}iterator it = _finish - 1;while (it >= pos){*(it + 1) = *it;--it;}*pos = val;++_finish;
}
讲到这里很多朋友就会发现,那么我们的push_back函数也可以直接调用insert函数即可,两个写成一个就行,代码如下:
void push_back(const T& val)
{insert(end(), val);//直接尾插即可
}
删除数据- erase(iterator pos)
代码思路:
- 我们这里需要判断删除的数据是否在容器有效数据的区间内
- 同理我们只需要将删除数据位置的区间全部往前移动一个覆盖掉删除的数据即可,然后更新_finish即可完成删除。
iterator erase(iterator pos)
{assert(pos >= _start);assert(pos <= _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;return pos;//返回被删除数据的下一个位置,那就是还是原位置,原位置的数据已经被删除了
}
查看容器中的数据是否为空Empty()
bool Empty()
{return _start == _finish;
}
vector中构造函数的一点延伸
拷贝构造的现代写法
代码思路:
- 我们直接用reserve开辟一个一样大的空间,相当于开辟了一块新的空间,达到了深拷贝的效果,然后在对其每个数据进行尾插即可。
vector(const vector<T>& V)
{reserve(V.capacity());//直接开辟一个一样大的空间,然后每个数据进行尾插for (auto& e : V){push_back(e);}
}
区间构造函数
代码思路:
1.这里直接将该区间的数据依次插入到*this中的容器中即可
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}
列表初始化构造(c++11以上的写法)
代码思路:
- initializer_list是C++11引入的一种数据结构,用于表示一个初始化列表(initializer list)。它是一个模板类,定义在<initializer_list>头文件中。initializer_list可以用于在函数参数中传递一组值,也可以用于对象的初始化。它的语法类似于数组,使用花括号{}括起一组值,并以逗号分隔(这里就不过多的讲解了,后面会单独写一篇博客进行讲解)。
- 剩下的同理检查内存容量,然后对这里直接对其每个数据进行尾插就行。
vector(initializer_list<T> il)
{reserve(il.size());for (auto& e : il){push_back(e);}
}
总体代码
using namespace std;
#include <iostream>
#include <assert.h>
namespace bit
{template<class T>class vector{public:typedef T* iterator;//本质就是成员遍历typedef const T* const_iterator;iterator begin()//返回起始位置{return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end()const{return _finish;}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}vector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}//V2(v1) v2是对v1的值拷贝,叫浅拷贝,v1生命周期结束,析构v1,v2生命周期结束释放V2,,vector(const vector<T>& V) {reserve(V.capacity());for (auto& e : V){push_back(e);}//传统写法//_start = new T[V.capacity()];//_finish = _start;//让_finish指向现在的位置,然后依次进行赋值//_endofstorage = _start + V.capacity();//for (size_t i = 0; i < V.size(); i++)//{// *_finish = V[i];// _finish++;//}}vector(initializer_list<T> il){reserve(il.size());for (auto& e : il){push_back(e);}}// 类模板的成员函数可以是函数模板template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}~vector(){delete[] _start;_start = _finish = _endofstorage = nullptr;}vector<T>& operator=(vector<T> v){swap(v);return *this;}size_t size()const //元素个数{return _finish - _start;}size_t capacity()const //总的容量{return _endofstorage - _start;}T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}void reserve(size_t n)//调整和检查容量{if (capacity() < n){T* tmp = new T[n];size_t old_size = size();//因原本的_start在后面会改变,放在野指针的出现我们需要对old_size进行村粗for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = tmp + old_size;_endofstorage = tmp + n;}}void resize(size_t n, const T& value = T())//使用半缺省,扩容的话就把剩余的部分全部扩容成value{if (n > size()){reserve(n);while (_finish < _start + n){*_finish = value;_finish++;}}else{_finish = _start + n;}}void insert(iterator pos,const T& val){assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);//扩容了的话原本的_start//如果扩容了就要更新pospos = len + _start;}iterator it = _finish - 1;while (it >= pos){*(it + 1) = *it;--it;}*pos = val;++_finish;}void push_back(const T& val){//insert(end(), val);if (_endofstorage == _finish)//判断容量是否是满的{reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = val;_finish++;}void pop_back(){assert(_finish > _start);--_finish;}iterator erase(iterator pos){assert(pos >= _start);assert(pos <= _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;return pos;//返回被删除数据的下一个位置,那就是还是原位置,原位置的数据已经被删除了}bool Empty(){return _start == _finish;}private:iterator _start = nullptr; //容器的起始位置 // T* _startiterator _finish = nullptr;//容器中所使用的元素的最后一个位置iterator _endofstorage = nullptr;//容器中所有元素的最后一个位置};template<class T>void print_vector(const vector<T>& v){for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;}void test1(){vector<int> v1;v1.push_back(1);v1.push_back(1);v1.push_back(1);v1.push_back(1);//print_vector(v1);//vector<int> v2 = { 1,2,3,4,5 };//print_vector(v2);vector <int>v3(v1);print_vector(v3);for (auto e : v1){cout << e << " ";}}void test2(){vector<int> v1;v1.push_back(1);v1.push_back(1);v1.push_back(1);v1.push_back(1);cout << "迭代器遍历" << endl;vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";it++;}cout << endl;cout << "范围for遍历" << endl;for (auto e : v1)cout << e << " ";cout << endl;}void test3(){vector<int> v1 = { 1,3,4,6 };v1.insert(v1.begin()+2, 10);print_vector(v1);}
}
好啦,今天的内容就到这里啦,下期内容预告stl中list的使用,博主前段时间有点事情,后面这段时间会加班加点的更新!
结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。