11.vector的模拟实现
在学习了vector各个接口的作用后,为了更加了解vector的底层架构,我们还是来模拟实现一下vector容器。
如果还不了解vector容器的可以先阅读上一篇文章:10.vector容器
目录
一、说明
二、模拟实现
1.成员变量
2.构造、拷贝构造和析构函数
3.size()和capacity()
4.reserve()
5.尾插和尾删
6.begin()和end()
7.operator[ ]
测试代码:
8.指定位置插入
9.指定位置删除
10.clear
11.initializer_list支持的构造
12.swap
13.operator==
14.关于深拷贝问题
源码:
vector.h
test.cpp
一、说明
- 由于模板的声明和定义不能分离到两个文件,所以不再创建vector.cpp文件,而是只由两个文件构成,vector.h和test.cpp。声明和定义都会被放到vector.h文件中。
- 为了不与库中的vector定义冲突,所以把类放在命名空间ddd中。
二、模拟实现
1.成员变量
首先我们来看成员变量如何定义,由于vector的结构和string类似,所以我们初步可能会想到写成这样。
template<class T>
class vector {private:T* _a;size_t _size;size_t _capacity;
};
但是我们在这里尝试另一种写法,这种写法更接近底层库中的实现方法。它用三个迭代器代替实现,实质就是三个指针:
- _start指向数组的开始位置,相当于原来的_a
- _finish指向数组结尾的位置的下一个位置,相当于_a + _size
- _endofstorage指向申请空间的结束位置的下一个位置,相当于_a + _capacity
template<class T>
class vector {
public:typedef T* iterator;typedef const T* const_iterator;
private:iterator _start;// 数组开始位置iterator _finish;// 数组结束位置iterator _endofstorage;// 数组容量的结束位置
};
2.构造、拷贝构造和析构函数
默认构造函数将成员变量都初始化为nullptr即可
// 默认构造
vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr)
{}// 拷贝构造
vector(const vector<T>& v) :_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{reserve(v.capacity());for (auto& e : v) {push_back(e);}
}//析构
~vector{if (_start) {delete[] _start;_start = _finish = _endofstorage = nullptr;
}
3.size()和capacity()
size_t size() {return _finish - _start;
}size_t capacity() {return _endofstorage - _start;
}
4.reserve()
reserve主要是用于扩容
void reserve(size_t n) {if (n > capacity()) {size_t oldsize = size();T* tmp = new T[n];if (_start) {memcpy(tmp, _start, sizeof(T) * oldsize);}_start = tmp;//_finish = _start + size();_finish = _start + oldsize;_endofstorage = _start + n;}
}
这里有个注意点:_finish不能直接等于_start + size(),因为size()是返回_finish - _start的值,而此时_start已经指向了tmp,所以size()的返回值会出现问题。要解决这个问题,一是先改_finish后改_start,但更常用的方式是提前存储size()的值,然后再用oldsize去改变_finish的值。
5.尾插和尾删
void push_back(const T& x) {if (_finish == _endofstorage) {reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}void pop_back() {assert(size() > 0);--_finish;
}
6.begin()和end()
为了使用迭代器和范围for,需要先实现begin()和end(),为了支持const,需要额外的两个版本
iterator begin() {return _start;
}
iterator end() {return _finish;
}
const_iterator begin() const{return _start;
}
const_iterator end() const{return _finish;
}
7.operator[ ]
为了方便打印,需重载[ ],且需要分为const和非const版本
T& operator[](size_t i) {assert(i < size());return _start[i];
}
const T& operator[](size_t i) const{assert(i < size());return _start[i];
}
测试代码:
在实现了以上接口后我们就能进行简单的测试了
8.指定位置插入
iterator insert(iterator pos, const T& x) {assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage) {// 记录pos和_start位置的差值size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);// 更新pos位置pos = _start + len;}// 挪动数据iterator end = _finish - 1;while (end >= pos) {*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;
}
这里有一个非常需要注意的问题!insert插入时如果触发了扩容,扩容后_start会指向新空间,而pos指向的位置是原空间,这就导致了迭代器失效问题。所以要提前记录pos和_start的相对位置,然后在扩容之后更新pos指向的位置。
接下来再看一个情景,关于迭代器失效问题
- 可以看到300被成功插入了,而再次对*it操作时,1000却没有修改成功。
- 这是因为在插入过程中触发了扩容,导致it失效。而之前在insert函数中对pos的操作只能影响函数体内的局部变量,不能反过来影响it。
- 又因为it插入可能会触发扩容,导致迭代器失效问题,且由于扩容在不同编译器有不同操作方式,所以it什么时候失效不确定。
- 这种情况下使用it极其危险,相当于野指针。所以我们认为,同一个迭代器进行过一次插入操作后就是失效的,将不再使用。
如果真的要多次使用,可以用返回值进行重新赋值
9.指定位置删除
void erase(iterator pos) {assert(pos >= _start && pos < _finish);while (pos < _finish - 1) {*pos = *(pos + 1);++pos;}--_finish;
}
再看一个关于迭代器失效问题的场景
- 可以看到程序直接崩溃了,因为在进行删除操作时,本质是将数据左移,而左移完成后it指向的实际已经是下一个数据了,这会导致一些问题。
- 比如后面再++it,就相当于跳过了一个数据,即使没有越界访问,也会导致程序出错,无法完成预期目的。
- 因此当一个迭代器进行erase操作后我们也认为它是失效的,不再使用。
- 并且一些编译器会进行强制检查,如vs,发生这类情况时程序会直接崩溃。
而要解决这种问题,就要给erase函数设置一个返回值,返回下一个数据的位置(其实就是pos)
修改后:
iterator erase(iterator pos) {assert(pos >= _start && pos < _finish);iterator i = pos;while (i < _finish - 1) {*i = *(i + 1);++i;}--_finish;return pos;
}
为了不改变原pos的值,再定义一个i来实现遍历,最后返回pos
10.clear
void clear() {_finish = _start;
}
11.initializer_list支持的构造
vector(initializer_list<T> il)
{reserve(il.size());for (auto& e : il){push_back(e);}
}
12.swap
void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}
13.operator==
vector<T>& operator=(vector<T> v)
{swap(v);return *this;
}
14.关于深拷贝问题
前面实现的reserve存在深拷贝问题,因为vector是模板容器,他的函数类型也可以是各种形式。当拷贝涉及指针时,使用memcpy会只拷贝指针而不拷贝指针指向的数据,这就导致两者指向的是同一块空间,当其中一块数据修改或释放时会影响另一块的数据。
解决方式是不使用memcpy而是直接使用=来赋值,因为这样对于自定义类型,他会自动调用拷贝构造进行深拷贝。
修改后:
void reserve(size_t n) {if (n > capacity()) {size_t oldsize = size();T* tmp = new T[n];if (_start) {//memcpy(tmp, _start, sizeof(T) * oldsize);for (size_t i = 0; i < oldsize; i++) {tmp[i] = _start[i];}delete[] _start;}_start = tmp;//_finish = _start + size();_finish = _start + oldsize;_endofstorage = _start + n;}
}
源码:
vector.h
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<assert.h>
using namespace std;// 模板的声明和定义不能分离到两个文件,所以不再创建Vector.cpp
namespace ddd {template<class T>class vector {public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start;// 数组开始位置iterator _finish;// 数组结束位置iterator _endofstorage;// 数组容量的结束位置public:// 构造vector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}// initializer_list支持的常量数组初始化vector(initializer_list<T> il){reserve(il.size());for (auto& e : il){push_back(e);}}// 拷贝构造vector(const vector<T>& v) :_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(v.capacity());for (auto& e : v) {push_back(e);}}// 析构~vector(){if (_start) {delete[] _start;_start = _finish = _endofstorage = nullptr;}}// size和capacitysize_t size() {return _finish - _start;}size_t capacity() {return _endofstorage - _start;}// begin和enditerator begin() {return _start;}iterator end() {return _finish;}const_iterator begin() const {return _start;}const_iterator end() const {return _finish;}// 扩容void reserve(size_t n) {if (n > capacity()) {size_t oldsize = size();T* tmp = new T[n];if (_start) {//memcpy(tmp, _start, sizeof(T) * oldsize);for (size_t i = 0; i < oldsize; i++) {tmp[i] = _start[i];}delete[] _start;}_start = tmp;//_finish = _start + size();_finish = _start + oldsize;_endofstorage = _start + n;}}// 重载[]T& operator[](size_t i) {assert(i < size());return _start[i];}const T& operator[](size_t i) const {assert(i < size());return _start[i];}// 尾插void push_back(const T& x) {if (_finish == _endofstorage) {reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;}// 尾删void pop_back() {assert(size() > 0);--_finish;}//指定位置插入iterator insert(iterator pos, const T& x) {assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage) {// 记录pos和_start位置的差值size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);// 更新pos位置pos = _start + len;}// 挪动数据iterator end = _finish - 1;while (end >= pos) {*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}// 指定位置删除iterator erase(iterator pos) {assert(pos >= _start && pos < _finish);iterator i = pos;while (i < _finish - 1) {*i = *(i + 1);++i;}--_finish;return pos;}// 清空void clear() {_finish = _start;}// 重定义大小// 涉及到一个模板怎么给缺省参数的问题,一般是给一个匿名对象// C++对内置类型也添加了构造函数,所以内置类型也可以适配void resize(size_t n, T val = T()) {if (n > size()) {reserve(n);while (_finish != _start + n) {*_finish = val;++_finish;}}else {_finish = _start + n;}}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}vector<T>& operator=(vector<T> v){swap(v);return *this;}};
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS
#include"vector.h"void test1() {ddd::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.pop_back();for (auto e : v) {cout << e << " ";}cout << endl;cout << v[0] << endl;cout << v.size() << endl;cout << v.capacity() << endl;
}void test2() {ddd::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto e : v) {cout << e << " ";}cout << endl;int x;cin >> x;auto it = find(v.begin(), v.end(), x);if (it != v.end()) {v.insert(it, x * 100);// 因为it插入可能会触发扩容,导致迭代器失效问题// 又由于扩容在不同编译器有不同操作方式,所以it什么时候失效不确定// 在这种情况下使用it极其危险,相当于野指针,所以坚决不能再使用//*it = 1000;}v.erase(v.begin());for (auto e : v) {cout << e << " ";}
}void test3() {ddd::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(2);v.push_back(4);v.push_back(4);v.push_back(5);for (auto e : v) {cout << e << " ";}cout << endl;// 删除所有偶数auto it = v.begin();while (it != v.end()) {if (*it % 2 == 0) {it = v.erase(it);}else {++it;}}for (auto e : v) {cout << e << " ";}
}int main() {//test1();//test2();test3();
}
总结:
以上就是本篇文章的所有内容了,如果觉得有帮助可以点赞收藏加关注支持一下!