目录
vecotr模拟实现
insert
erase
拷贝构造
思考题
resize
vector>深浅拷贝问题
习题 电话号码的字母组合
习题 删除有序数组中的重复项
源代码里面,核心成员是下面红框三个
观察这里的代码发现这里的迭代器都是原生指针
#include
#include using namespace std; namespace my_space { template 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; } vector() :_start(nullptr) , _finish(nullptr) , _end_of_storage(nullptr) {} ~vector() { delete[] _start; _start = _finish = _end_of_storage = nullptr; } size_t capacity() const { return _end_of_storage - _start; } const T& operator[](size_t pos) const { assert(pos < size()); return _start[pos]; } T& operator[](size_t pos) { assert(pos < size()); return _start[pos]; } size_t size() const { return _finish - _start; } void reserve(size_t n) { if (n > capacity()) { size_t sz = size(); T* tmp = new T[n]; if (_start) { memcpy(tmp, _start, sizeof(T) * sz); delete[] _start; } _start = tmp; _finish = _start + sz; _end_of_storage = _start + n; } } void push_back(const T& x)//加const防止隐式类型转换的中间常量传参错误,引用可避免深拷贝 { if (_finish == _end_of_storage) { reserve(capacity() == 0 ? 4 : capacity() * 2); } *_finish = x; ++_finish; } void pop_back() { assert(_finish > _start); --_finish; } private: iterator _start; iterator _finish; iterator _end_of_storage; }; };
void insert(iterator pos, const T& x) { assert(pos >= _start); assert(pos <= _finish); if (_finish == _end_of_storage) { reserve(capacity() == 0 ? 4 : capacity() * 2); } // 挪动数据 iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; }
这种写法有一个缺陷
在3的前面插入30,此时程序正常运行
这是因为扩容时出现了问题,pos本来是指向start中的,扩容之后start被销毁,新空间是tmp,pos就成了野指针
pos失效了
void insert(iterator pos, const T& x) { assert(pos >= _start); assert(pos <= _finish); if (_finish == _end_of_storage) { size_t len = pos - _start; reserve(capacity() == 0 ? 4 : capacity() * 2); pos = _start + len; } // 挪动数据 iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; }
这样就不会出错了,用相对位置记住pos之前所在位置
如果连续插入有时会报错,有时则不会
这是因为pos的修改不会影响p,pos是形参,p有可能会失效,有可能不会失效取决于有没有扩容,所以在p位置插入数据后,不要再去访问p,因为有可能会失效
但是如果用引用,就跟库里面的insert不一致了,我们尽量要跟库里面保持一致
还有一种情况,当空间足够时,我们插入数据也会崩,也会导致迭代器失效
it指向2时满足条件 ,在2的前面插入4
然后++it,it仍然指向2,这样每次++it会导致it一直指向2,所以程序会崩
修改方法就是用一个变量来接收insert的返回值,库里面的insert,会返回新插入数据的下标,我们接收这个下标跟库里面的insert保持一致
这样就可以
如果时偶数,对it++俩次即可
iterator insert(iterator& pos, const T& x) { assert(pos >= _start); assert(pos <= _finish); if (_finish == _end_of_storage) { size_t len = pos - _start; reserve(capacity() == 0 ? 4 : capacity() * 2); pos = _start + len; } // 挪动数据 iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; return pos; } void test_vector1() { vector
v; v.reserve(10); v.push_back(1); v.push_back(2); v.push_back(4); v.push_back(3); v.push_back(4); v.push_back(5); auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) { it = v.insert(it, *it * 2); ++it; } ++it; } for (size_t i = 0; i < v.size(); ++i) { cout << v[i] << " "; } cout << endl; } 去掉reserve扩容之后,插入很多数据,程序仍然可以正常运行
void erase(iterator pos) { assert(pos >= _start); assert(pos <= _finish); iterator begin = pos + 1; while (begin < _finish) { *(begin - 1) = *begin; ++begin; } --_finish; }
如果earse设置了缩容,缩容是以时间换空间,效率较低,缩容也会存在迭代器失效问题
使用上面代码删除所有的偶数
此时正常打印
但这种情况有崩溃
此时又没有删掉
当it走到2时,用4去覆盖掉it ,然后++it,it指向了3
it指向4之后,5过去覆盖,之后++it,it指向finish
这时因为it错开了第一个4,++导致it快了一步
同理
it指向2,3把it覆盖了,++it,然后it指向了4
指向4之后,finish又覆盖4,++it,it成了野指针
所以会崩溃
所以:insert/erase pos位置,不要随便访问pos,一定要更新,直接访问可能会让迭代器失效
解决办法,使用时加个else语句即可
这里默认的拷贝构造是浅拷贝,程序结束会析构俩次,所以崩溃
我们这需要自己设置一个深拷贝
传统写法:
vector(const vector
& v) { _start = new T[v.size()]; memcpy(_start, v._start, sizeof(T) * v.size()); _finish = _start + v.size(); _end_of_storage = _start + v.size(); } 也可这样写
vector(const vector
& v) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { reserve(v.size()); for (const auto& e : v) { push_back(e); } } 现代写法:
vector里面有一个构造是支持迭代器区间构造
之前我们已经有了类模板
现在我们在类模板,成员函数里面再加一个模板
也就是说现在的这个拷贝构造函数里面可以用双重模板,在定义模板是因为我们需要一个迭代区间,而之前我们已经有了iterator,现在是InputIterator,这样写代表我们不仅可以用T类型模板,iterator迭代器,也可以用其他模板和其他迭代器,这样比较灵活
便于我们实现这种情况
当前函数写这样运行的时候会直接报错,因为如果编译器没有对s进行初始化,push_back要调用reverse,删除旧数组空间的时候会报错
因此我们要对s进行初始化
template
vector(InputIterator first, InputIterator last) : _start(nullptr) , _finish(nullptr) , _end_of_storage(nullptr) { while (first != last) { push_back(*first); ++first; } } 现代写法:
template
vector(InputIterator first, InputIterator last) : _start(nullptr) , _finish(nullptr) , _end_of_storage(nullptr) { while (first != last) { push_back(*first); ++first; } } void swap(vector & v) { std::swap(_start, v._start); std::swap(_finish, v._finish); std::swap(_end_of_storage, v._end_of_storage); } vector(const vector & v) :_start(nullptr) , _finish(nullptr) , _end_of_storage(nullptr) { vector tmp(v.begin(), v.end()); swap(tmp); } vector
& operator=(vector v) { swap(v); return *this; } 这里传参的时候不进行引用传参,他会调用拷贝构造,根据我们写的拷贝可知,实参会被删除,保留形参,v就是保留下来的形参,然后跟一交换即可,这样比较方便
vector(size_t n, const T& val = T()) 插入n个val,val是T类型
使用N个vakue去构造,T()是T类型的匿名对象,匿名对象就要调用默认构造,如果是Date这些自定义类型,就需要我们人为的把构造函数写好,如果T是int,int也有自己的默认构造,内置类型也有自己的默认构造
vector(size_t n, const T& val = T()) :_start(nullptr) ,_finish(nullptr) ,_end_of_storage(nullptr) { reserve(n); for (int i = 0; i < n; i++) { push_back(val); } }
当这样初始化会报错
报错信息在这一行:非法的间接寻址
当编译器进行类型匹配的时候,如果只传了一个参数,编译器会把那个参数传给最左边的形参(前面博客有说过)
而传俩个参数都是int类型,编译器在进行类型匹配的时候会找最匹配的类型来匹配
一个参数是unsigned int,一个是int
对于这里传参传的是int,int,这个对拷贝构造来说特别匹配,first 和last都是int,然后对int解引用就报错
而下面这个不报错因为,对于拷贝构造来说不匹配,所以它进不去拷贝构造
void resize(size_t n, const T& val = T()) { if (n > capacity()) { reserve(n); } if (n > _finish) { while (_finish < _start + n) { *finish = val; ++_finish; } } else { _finish = _start + n; } }
reserve对_finish不是很敏感,resize可以改变_finish
vector(const vector
& v) //拷贝构造函数 { _start = new T[v.size()]; memcpy(_start, v._start, sizeof(T) * v.size()); _finish = _start + v.size(); _end_of_storage = _start + v.size(); } class Solution { public: vector > generate(int numRows) { vector > vv; vv.resize(numRows); for (int i = 0; i < vv.size(); ++i) { vv[i].resize(i + 1, 0); vv[i].front() = vv[i].back() = 1; } for (size_t i = 0; i < vv.size(); ++i) { for (size_t j = 0; j < vv[i].size(); ++j) { if (vv[i][j] == 0) { vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1]; } } } return vv; } }; void test_vector1() { Solution().generate(5); } 上面代码是拷贝构造函数以及杨辉三角的调用
程序直接崩溃
这是因为在函数返回的时候要调用一次拷贝构造,而该拷贝构造对外面是深拷贝,对里面是浅拷贝
这种拷贝方式只是对最外层完成了慎深拷贝,而对于里层确实浅拷贝
问题出在了memcpy上,memcpy是一个字节一个字节往过拷贝,导致这里里层的_start指向同一块空间,析构的时候会析构俩次,因此这里不能用memcpy
这样修改拷贝构造,程序即可正常运行
vector(const vector
& v) { _start = new T[v.size()]; // memcpy(_start, v._start, sizeof(T) * v.size()); for (int i = 0; i < v.size(); ++i) { _start[i] = v._start[i]; } _finish = _start + v.size(); _end_of_storage = _start + v.size(); } 防止reserve出现这种问题,对reserve也可进行修改
void reserve(size_t n) { if (n > capacity()) { size_t sz = size(); T* tmp = new T[n]; if (_start) { //memcpy(tmp, _start, sizeof(T) * sz); for (size_t i = 0; i < sz; ++i) { tmp[i] = _start[i]; } delete[] _start; } _start = tmp; _finish = _start + sz; _end_of_storage = _start + n; } }
17. 电话号码的字母组合 - 力扣(LeetCode)
class Solution { char *numToStr[10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"}; public: void Combine(string digits,int di,vector
& retv,string CombinStr) { if(di==digits.size()) { retv.push_back(CombinStr); return ; } int num=digits[di]-'0'; string str=numToStr[num]; for(auto ch:str) { Combine(digits,di+1,retv,CombinStr+ch); } } vector letterCombinations(string digits) { vector v; if(digits.empty()) return v; string str; Combine(digits,0,v,str); return v; } }; str是一个用来临时存储组合的结果,digits是用户输入的数字,di是下标,如digits是“238”,di=0,就代表表示字符2,所以要给-'0',把每个按键对应的字母存到一个数组中,通过下标来访问
26. 删除有序数组中的重复项 - 力扣(LeetCode)
class Solution { public: int removeDuplicates(vector
& nums) { int src=0; int dst=0; while(src