删除所有的偶数
void vectorTest5() { vector<int> v; v.push_back(1); v.push_back(2); v.push_back(2); v.push_back(3); v.push_back(4); v.push_back(6); auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) { it = v.erase(it); } else { it++; } } for (auto e : v) { cout << e << " "; } cout << endl; }
注:删除 it 位置后,我们需要接收一下 erase 函数的返回值,erase 函数的返回值为删除元素的下一个元素的位置。如果不接受 erase 函数的返回值,就会有可能出现各种情况。见下图:
注:我们要统一认为调用 erase(it) 函数后,迭代器 it 会失效。
front 和 back
T& front() { assert(!empty()); return *_start; } const T& front() const { assert(!empty()); return *_start; } T& back() { assert(!empty()); return *(_finish - 1); } const T& back() const { assert(!empty()); return *(_finish - 1); }
swap 和 clear
void swap(vector<T>& v) { std::swap(_start, v._start); std::swap(_finish, v._finish); std::swap(_endofstorage, v._endofstorage); } void clear { _finish = _start; }
注:swap 函数交换两个类对象成员变量的值就行了,clear 函数不能将 _start 置为 nullptr,否则将会造成内存泄漏问题。
析构函数
~vector() { delete[] _start; _start = _finish = _endofstorage = nullptr; }
拷贝构造函数
传统写法1
vector(const vector<T>& v) { size_t Size = v.size(); _start = new T[Size]; for (size_t i = 0; i < Size; ++i) { _start[i] = v._start[i]; } _finish = _endofstorage = _start + Size; }
注:该写法不能用 memcpy 函数来拷贝数据,因为这样会导致浅拷贝问题(将会在下面的内容讲解)。
传统写法2
vector(const vector<T>& v) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { reserve(v.capacity()); //reserve(v.size()); for (auto& e : v) { push_back(e); } }
注:该写法使用范围 for 来尾插数据,一定要加引用。因为 e 有可能是自定义类型,不用引用的话,会存在拷贝构造。
现代写法
// 用迭代器来构造对象 template <class InputIterator> vector(InputIterator first, InputIterator last) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { while (first != last) { push_back(*first); ++first; } } vector(const vector<T>& v) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { vector<T> tmp(v.begin(), v.end()); swap(tmp); }
赋值运算符重载
// v1 = v2 // v1 = v1 极少数情况,且能保证正确性,所以这样写没有什么问题 vector<T>& operator=(vector<T> v) { swap(v); return *this; }
用 n 个 val 来构造
vector(size_t n, const T& val = T()) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { reserve(n); for (size_t i = 0; i < n; ++i) { push_back(val); } }
其实只写这个函数接口的话,会带来一个问题。我们来看一下。
我本来想要 10 个 1来构造出来一个 vector 对象,编译器怎么给我报了个非法间接寻址的错误呢?原因是在这,因为用迭代器去构造对象的函数比上面写的用 n 个 val 构造对象的函数更匹配(int 需要类型转换成 size_t,而迭代器模板构造函数不用类型转换),编译器会调用更加匹配的函数,所以就导致了非法间接寻址。
那如果我们就是想用 n 个 val 来构造对象的函数,应该解决呢?我们可以再多写个下面的函数来形成函数重载。
vector(int n, const T& val = T()) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { reserve(n); for (int i = 0; i < n; ++i) { push_back(val); } }
👉memcpy 带来的浅拷贝问题👈
现在我们模拟实现的 vector 已经差不多完成了,那我们拿上一篇博客写的杨辉三角来验证一下我们写的 vector 对不对。
我们一运行起来,程序就崩溃了。为什么这样子呢?其实就是 memcpy 带来的浅拷贝问题。
为了分析方便,我们换一个例子来说明这个问题,然后再回过头来分析杨辉三角的问题。
可以看到,向 vv 尾插4个 v,没有任何的问题。那插入5个 v呢?
插入5个 v,就出现问题了。为什么呢?因为插入5个 v 就要扩容,而扩容就要将原来的数据拷贝到新空间里。reserve 函数里的拷贝数据是采用 memcpy 函数来拷贝数据,而 memcpy 函数是按照字节来拷贝的,是浅拷贝,而我们所想要的是深拷贝。原来问题就是出现在这里。那我们再调试起来看看。
那我们怎么解决这个问题呢?其实很简单,只需要修改一下 reserve 函数里的拷贝数据的形式就可以了。
void reserve(size_t n) { if (n > capacity()) { size_t oldSize = size(); T* tmp = new T[n]; // _start为nullptr时,不需要拷贝数据 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 = tmp + oldSize; _endofstorage = tmp + n; } }
修改过后,我们的程序就可以跑起来了。那我们再来跑一下杨辉三角的程序,看能不能跑起来。
可以看到,杨辉三角的程序也是可以跑起来的,因为其问题也是扩容时的浅拷贝导致的。
完整代码
#pragma once namespace Joy { 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; } size_t size() const { return _finish - _start; } size_t capacity() const { return _endofstorage - _start; } vector() : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) {} vector(int n, const T& val = T()) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { reserve(n); for (int i = 0; i < n; ++i) { push_back(val); } } vector(size_t n, const T& val = T()) : _start(nullptr) , _finish(nullptr) , _endofstorage(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) , _endofstorage(nullptr) { while (first != last) { push_back(*first); ++first; } } void swap(vector<T>& v) { std::swap(_start, v._start); std::swap(_finish, v._finish); std::swap(_endofstorage, v._endofstorage); } vector(const vector<T>& v) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { vector<T> tmp(v.begin(), v.end()); swap(tmp); } vector<T>& operator=(vector<T> v) { swap(v); return *this; } ~vector() { delete[] _start; _start = _finish = _endofstorage = nullptr; } 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 (n > capacity()) { int oldSize = size(); T* tmp = new T[n]; if (_start) { for (size_t i = 0; i < oldSize; ++i) { tmp[i] = _start[i]; } delete[] _start; } _start = tmp; _finish = tmp + oldSize; _endofstorage = tmp + n; } } void resize(size_t n, const T& val = T()) { if (n > capacity()) { reserve(n); } if (n > size()) { while (_finish < _start + n) { *_finish = val; ++_finish; } } else { _finish = _start + n; } } void push_back(const T& val = T()) { if (_finish == _endofstorage) { size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newCapacity); } *_finish = val; ++_finish; } void pop_back() { assert(!empty()); --_finish; } bool empty() const { return _start == _finish; } iterator insert(iterator pos, const T& val) { assert(pos >= _start); assert(pos < _finish); if (_finish == _endofstorage) { size_t len = pos - _start; size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newCapacity); pos = _start + len; } // Ų iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = val; ++_finish; return pos; } iterator erase(iterator pos) { assert(!empty()); assert(pos >= _start); assert(pos < _finish); iterator begin = pos + 1; while (begin < _finish) { *(begin - 1) = *begin; ++begin; } --_finish; return pos; } void clear() { _finish = _start; } private: T* _start; T* _finish; T* _endofstorage; }; }
👉总结👈
本篇博客讲解了 vector 的模拟实现,带大家理解 vector 的底层实现。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️