目录
vector是这是C++中的典型容器,它是动态类型的顺序表,任何类型的元素都可以存放。接下来将在以下几个方面进行模拟实现:构造模块、迭代器模块、容量模块、元素访问模块、修改模块。
因为类中的函数非常多,所以只着重实现一些常用的函数。(本文代码均在win10系统下的vs2019中验证)
在构造模块,先来思考一下该设置些什么成员变量?首先要有一个 _start 指针指向一块空间,其次一个 _finish 指针指向这块空间中最有一个有效元素的下一个位置,_end指针指向空间的最后一个位置的下一个位置。而空间中的有效元素个数和容量可以通过指针相减得到。
用下面这幅图理解一下三个指针的位置:总空间有8个位置,前五个位置有元素,_finish指向第六个位置,_end需要指向第八个位置后面的空间。当有效元素把位置占满的时候,_finish和_end自然就指向同一块空间。

由于vector类是可以存放任意类型的元素的,要模拟实现它自然也要做到存放任意类型。那么就需要用到模板了。T是存放的元素类型,T*是指向T类型元素的指针。
代码一:注意创建对象时必须声明存储什么类型的对象。语法:Vector
既然是空参构造,那么说明现在并不给对象中存放元素,那直接把三个指针都置空就好。
- //代码一
- #include "iostream"
- using namespace std;
-
- //Vector类存储的数据是任意类型,所以需要设置模板参数
- template<typename T>
- class Vector {
- public:
- T* _start;//指向T类型元素的首元素的指针
- T* _finish;//指向最后一个有效元素下一个位置
- T* _end;//指向顺序表空间最后一个位置的下一个位置。
- public:
- Vector()
- :_start(nullptr)
- ,_finish(nullptr)
- ,_end(nullptr)
- {}
- };
-
- int main() {
- Vector<int>v;
- }
代码二:这里n的类型一定要给成int,不可以给成size_t。不然运行时会遇到错误,在区间构造时就知道了。
- //代码二
- Vector(int n, const T& val = T())
- :_start(new T[n])//在初始化时直接从堆上申请空间
- {
- for (int i = 0; i < n; i++) {
- _start[i] = val;
- }
- //_finish指向最后一个有效元素的下一个位置
- _finish = _start + n;
- //_end指向顺序表最后一个位置的下一个位置
- _end = _finish;
- }
vector类的区间构造很强大,不仅是在使用方法中介绍的利用数组来进行区间构造,也可以用链表来进行区间构造,所以实现时要格外注意不可以直接拷贝,需要将区间内元素遍历一遍才可以。
在区间构造时候因为可以用链表来构造,所以传入的迭代器自然也是链表的迭代器,这样一来,T类型的迭代器就不能满足条件了,所以我们也要为迭代器设置模板参数,让任意类型的迭代器都可以进行区间构造。
代码三:这里搭配测试用例解释一下第二个循环。
看一下区间构造,还记得上面为什么需要把参数类型给成int吗?如果你想构造对象时初始化10个5,是不是这样写 Vector
模板不会进行类型转换,根据你的参数推演,如果能找到匹配的就使用。检测到size_t发现不匹配就继续找,找到区间构造后发现可以使用,因为它可以把 Iterator 当成int类型。
可是原本的迭代器是需要解引用的,现在对整形数字解引用,可不就出错了么?
- //代码三
- template<typename Iterator>
- Vector(Iterator first, Iterator last) {
- Iterator it = first;
- size_t num = 0;//统计元素个数
- while (it != last) {
- num++;
- it++;
- }
-
- _start = new T[num];
- _finish = _start;
- while (first != last) {
- *_finish = *first;
- _finish++;
- first++;
- }
- _end = _finish;
- }
-
- int main() {
- int arr[] = { 1,2,3,4,5,6 };
- Vector<int>v(arr,arr+sizeof(arr)/sizeof(arr[0]));
- }
搭配图片理解一下第二个循环:
arr指向的是首元素,sizeof(arr)/sizeof(arr[0]) 是求数组元素个数,共6个。arr+6就指向了数组外的空间。当first移动到last位置说明元素遍历完毕,此时对应的_finish也指向了最后一个有效元素的后一个位置。(同时这也是顺序表的最后一个位置的后一个位置了,因为申请时只申请了这么多空间)
重点来了:注意first迭代器也是可以++的,这是因为对应的类中现在基本都会重载++,这样链表也就不需要用指针指向next这样的操作来访问了,会方便很多,也更直观。

代码四:
- //代码四
- Vector(const Vector
& v) - :_start(new T[v.Size()]) //这里用到的Size函数后面会实现的
- {
- size_t num = v.Size();
- for (size_t i = 0; i < num; i++) {
- _start[i] = v._start[i];
- }
- //让对应的指针指向应该指向的位置
- _finish = _start + num;
- _end = _finish;
- }
代码五:因为这里传递的是值,会发生值拷贝,产生临时对象,那么我们把两个对象的指针指向的空间交换即可。一定要返回,因为要实现连续赋值。
- //代码五:
- Vector& operator=(const Vector v) {
- std::swap(_start, v._start);
- std::swap(_finish, v._finish);
- std::swap(_end, v._end);
- return *this;
- }
代码六:这里检查指针是否为空,虽然经过测试指针指向空值时,delete[] 指针不会报错,但出于谨慎,还是加上检查比较好。
- //代码六
- ~Vector() {
- if (_start) {
- delete[] _start;
- }
- _start = _finish = _end = nullptr;
- }
迭代器模块的模板参数要单独给出,不可以使用区间构造那块的,因为迭代器不涉及多种元素,它只返回本类的元素迭代器,只有一种。
代码七:
- //代码七
- typedef T* iterators;
-
- iterators Begin() {
- return _start;
- }
-
- iterators End() {
- return _finish;
- }
代码八:反向迭代器可以直接复用正向迭代器。
- //代码八
- typedef T* iterators;
-
- iterators Rbegin() {
- return End();
- }
-
- iterators Rend() {
- return Begin();
- }
这里就可以体会到指针的便捷了,直接相减就得到了元素个数。
代码九:
- //代码九
- size_t Size() const{
- return _finish - _start;
- }
代码十:道理同上。
- //代码十
- size_t Capacity()const {
- return _end - _start;
- }
代码十一: 这两个指针相同说明并没有元素的存在。
- //代码十一
- bool Empty()const {
- return _start == _finish;
- }
代码十二:只考虑扩大,不考虑缩小。
- //代码十三
- void Reserve(size_t newCapacity) {
- size_t oldCapacity = Capacity();
- size_t oldSize = Size();
-
- T* temp = new T[newCapacity];
- //当要设置的新空间大于旧空间
- if (newCapacity > oldCapacity) {
- //判断首指针是否是空
- if (_start) {
- //进行深拷贝
- //思考:memcpy可以使用吗?
- for (size_t i = 0; i < oldSize; i++) {
- temp[i] = _start[i];
- }
- //释放旧空间
- delete[] _start;
- }
- _start = temp;
- _finish = _start + oldSize;
- _end = _start + newCapacity;
- }
- }
在扩容函数中思考一个问题,为什么没有使用memcpy函数?原因是memcpy是按字节拷贝,会有程序崩溃的风险。使用下面的例子来验证。
代码十三:举例,假设使用扩容函数中使用memcpy函数,并且vector中存储S类型的对象。这里因为是举例子,所以代码给的很粗糙,理解一下意思就好。
首先使用s1和s2构造v对象,然后进行尾插,这样会导致扩容。
- //代码十三
- class S {
- public:
- char* str;
- };
-
- int main(){
- S s1;
- S s2;
- vector
v{s1,s2}; -
- S s3;
- v.push_back(s3);
- }
下面这幅图就是验证图。
S对象内存储的是指针,构造Vector时里面有两个S类型的元素,两个s元素中分别有一个指针指向一个有"\0"元素的空间。然后尾插一个元素,导致扩容。
扩容后使用memcpy按字节将vector中的元素拷贝到新空间中。一切看起来都那么完美。可是旧的vector对象中地址指针(蓝色的地址线)需要释放,释放后新空间中的指针(红色地址线)却依然指向被释放的空间。这不就是野指针吗?这样一来,一旦试图访问这块空间,必然程序崩溃。

这里要告诉大家,不要使用memset函数。因为memset函数也是按字节将元素直接放到指定空间中。C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。
字符的字节是1字节,如果你要用它放n个T类型数据到指向的空间,它是怎么做的?它会把T类型的数据往空间中的前n个字节,每个字节都放一个!
代码十四:
- //代码十四:
- void Resize(size_t newSize, const T& val = T()) {
- size_t oldCapacity = Capacity();
- size_t oldSize = Size();
- //要设置的有效元素个数大于现在的有效元素
- if (newSize > oldSize) {
- //要设置的有效元素个数大于现有空间,扩容
- if (newSize > oldCapacity) {
- Reserve(newSize);
- }
- //思考:memeset可以用吗?
- for (size_t i = oldSize; i < newSize; i++) {
- _start[i] = val;
- }
- }
- _finish = _start + newSize;
- }
代码十五:这里给出两种类型,以便于const对象使用。
- //代码十五
- T& Front() {
- return _start[0];
- }
- const T& Front()const {
- return _start[0];
- }
代码十六:两种类型,道理同上。
- //代码十六
- T& Back() {
- return *(_finish - 1);
- }
- const T& Back()const {
- return *(_finish - 1);
- }
代码十七:也是两种类型。注意assert函数使用时需要包含头文件。
- //代码十七
- #include
- T& operator[](size_t index) {
- //需要判断是否越界
- assert(-1
Size()); - return _start[index];
- }
- const T& operator[](size_t index)const {
- assert(-1
Size()); - return _start[index];
- }
代码十八:尾插可能需要扩容,一旦扩容,就给的空间大一点,尽量减少扩容次数。
- //代码十八
- void Push_back(const T& val) {
- //判断顺序表是否已经存满了
- if (_finish == _end) {
- Reserve(Capacity() * 2);
- }
- *_finish = val;
- _finish++;
- }
代码十九:
- //代码十九
- void Pop_back() {
- if (Empty()) {
- return;
- }
- _finish--;
- }
代码二十: 需要考虑越界问题
- //代码二十
- iterators Insert(iterators index, const T& val) {
- //判断迭代器是否越界
- if (index < _start || index > _finish)
- return End();
- //判断空间是否存满
- if (_start == _finish)
- Reserve(Capacity() * 2);
-
- auto it = _finish;
- while (it != index) {
- *it = *(it - 1);
- it--;
- }
- *index = val;
- _finish++;
- return index;
- }
代码二十一:需要考虑越界问题
- //代码二十一
- iterators Erase(iterators index) {
- //判断迭代器是否越界
- if (index < _start || index > _finish)
- return End();
- auto it = index;
- //将index后的元素往前搬移一位
- while (it != _finish) {
- *it = *(it + 1);
- it++;
- }
- _finish--;
- return index;
- }
代码二十二:
- //代码二十二
- void Clear() {
- _finish = _start;
- }
将代码进行调整后改成整体代码如下:
- #include
- #include "iostream"
- using namespace std;
-
- //Vector类存储的数据是任意类型,所以需要设置模板参数
- template<typename T>
- class Vector {
- private:
- T* _start;//指向T类型元素的首元素的指针
- T* _finish;//指向最后一个有效元素下一个位置
- T* _end;//指向有效空间的下一个位置。
- public:
- //构造模块
- //
- //无参构造
- Vector()
- :_start(nullptr)
- ,_finish(nullptr)
- ,_end(nullptr)
- {}
- //半缺省构造
- Vector(int n, const T& val = T())
- :_start(new T[n])//在初始化时直接从堆上申请空间
- {
- for (int i = 0; i < n; i++) {
- _start[i] = val;
- }
- //_finish指向最后一个有效元素的下一个位置
- _finish = _start + n;
- //_end指向顺序表最后一个位置的下一个位置
- _end = _finish;
- }
- //区间构造
- template<typename Iterator>
- Vector(Iterator first, Iterator last) {
- Iterator it = first;
- size_t num = 0;//统计元素个数
- while (it != last) {
- num++;
- it++;
- }
-
- _start = new T[num];
- _finish = _start;
- while (first != last) {
- *_finish = *first;
- _finish++;
- first++;
- }
- _end = _finish;
- }
- //拷贝构造函数
- Vector(const Vector
& v) - :_start(new T[v.Size()]) //这里用到的Size函数后面会实现的
- {
- size_t num = v.Size();
- for (size_t i = 0; i < num; i++) {
- _start[i] = v._start[i];
- }
- //让对应的指针指向应该指向的位置
- _finish = _start + num;
- _end = _finish;
- }
- //赋值运算符重载
- Vector& operator=(const Vector v) {
- std::swap(_start, v._start);
- std::swap(_finish, v._finish);
- std::swap(_end, v._end);
- return *this;
- }
- //析构函数
- ~Vector() {
- if (_start) {
- delete[] _start;
- }
- _start = _finish = _end = nullptr;
- }
- //迭代器模块
- //
- typedef T* iterators;
- //正向迭代器
- iterators Begin() {
- return _start;
- }
- iterators End() {
- return _finish;
- }
-
- //反向迭代器
- iterators Rbegin() {
- return End();
- }
- iterators Rend() {
- return Begin();
- }
- //容量模块
- //
- //有效元素个数
- size_t Size() const{
- return _finish - _start;
- }
- //容量大小
- size_t Capacity()const {
- return _end - _start;
- }
- //判空函数
- bool Empty()const {
- return _start == _finish;
- }
- //扩容函数
- void Reserve(size_t newCapacity) {
- size_t oldCapacity = Capacity();
- size_t oldSize = Size();
-
- T* temp = new T[newCapacity];
- //当要设置的新空间大于旧空间
- if (newCapacity > oldCapacity) {
- //判断首指针是否是空
- if (_start) {
- //进行深拷贝
- //思考:memcpy可以使用吗?
- for (size_t i = 0; i < oldSize; i++) {
- temp[i] = _start[i];
- }
- //释放旧空间
- delete[] _start;
- }
- _start = temp;
- _finish = _start + oldSize;
- _end = _start + newCapacity;
- }
- }
- //设置有效元素函数
- void Resize(size_t newSize, const T& val = T()) {
- size_t oldCapacity = Capacity();
- size_t oldSize = Size();
- //要设置的有效元素个数大于现在的有效元素
- if (newSize > oldSize) {
- //要设置的有效元素个数大于现有空间,扩容
- if (newSize > oldCapacity) {
- Reserve(newSize);
- }
- //思考:memeset可以用吗?
- for (size_t i = oldSize; i < newSize; i++) {
- _start[i] = val;
- }
- }
- _finish = _start + newSize;
- }
- //元素访问模块
- //
- //获取首元素
- T& Front() {
- return _start[0];
- }
- const T& Front()const {
- return _start[0];
- }
- //获取尾部元素
- T& Back() {
- return *(_finish - 1);
- }
- const T& Back()const {
- return *(_finish - 1);
- }
- //重载[]
- T& operator[](size_t index) {
- //需要判断是否越界
- assert(-1
Size()); - return _start[index];
- }
- const T& operator[](size_t index)const {
- assert(-1
Size()); - return _start[index];
- }
- //修改模块
- //
- //尾插
- void Push_back(const T& val) {
- //判断顺序表是否已经存满了
- if (_finish == _end) {
- Reserve(Capacity() * 2);
- }
- *_finish = val;
- _finish++;
- }
- //尾删
- void Pop_back() {
- if (Empty()) {
- return;
- }
- _finish--;
- }
- //任意位置的插入
- iterators Insert(iterators index, const T& val) {
- //判断迭代器是否越界
- if (index < _start || index > _finish)
- return End();
- //判断空间是否存满
- if (_start == _finish)
- Reserve(Capacity() * 2);
-
- auto it = _finish;
- while (it != index) {
- *it = *(it - 1);
- it--;
- }
- *index = val;
- _finish++;
- return index;
- }
- //任意位置删除
- iterators Erase(iterators index) {
- //判断迭代器是否越界
- if (index < _start || index > _finish)
- return End();
- auto it = index;
- //将index后的元素往前搬移一位
- while (it != _finish) {
- *it = *(it + 1);
- it++;
- }
- _finish--;
- return index;
- }
- //清空有效元素
- void Clear() {
- _finish = _start;
- }
- };
-
- int main() {
- Vector<int> v(10, 5);
- v.Push_back(9);
- v.Erase(v.End());
- }