3.深拷贝问题
3.1vector>
对于下面的代码,我们在上面模拟实现的所有成员函数的基础上观察:
void test_vector9() { vector<vector<int>> vv; vector<int> v(5, 1); vv.push_back(v); vv.push_back(v); vv.push_back(v); vv.push_back(v); for (size_t i = 0; i < vv.size(); i++) { for (size_t j = 0; j < vv[i].size(); j++) { cout << vv[i][j] << " "; } cout << endl; } cout << endl; }
结果不出我们所料,我们所模拟实现的vector也是支持T为vector类型的。
3.1.1提出问题
但如果我们再加上一个 vv.push_back(v);
,看看会发生什么情况:
void test_vector9() { vector<vector<int>> vv; vector<int> v(5, 1); vv.push_back(v); vv.push_back(v); vv.push_back(v); vv.push_back(v); vv.push_back(v); for (size_t i = 0; i < vv.size(); i++) { for (size_t j = 0; j < vv[i].size(); j++) { cout << vv[i][j] << " "; } cout << endl; } cout << endl; } }
没错,就是你所想到的,扩容发生了问题!
3.1.2进行分析
那么为什么出现了这样的情况呢?根据我们的经验,不难猜想:大概是因为由于异地扩容之后,产生了浅拷贝,即我们异地扩容产生的变量的指向仍然是之前指向的位置,并且由于异地扩容之后,会delete[]原空间,这就导致异地扩容的指向也变成了野指针。
当我们到了第五个push_back,也就是需要扩容的时候,我们发现:tmp与原本_start的位置指向的是同一个位置(注意外部的_start与内部的第一个_start指向的位置是一样的),这是由于memcpy引起的,而我们知道,memcpy所引起的异地扩容会释放旧空间,即释放旧位置所指向的位置,但这一释放,就导致了新开辟的tmp内部的指针变量指向的空间也被释放了
因此,我们知道这是由于reserve中的memcpy所造成的的浅拷贝导致的,那么如何进行处理呢?
3.1.3 解决方式
既然浅拷贝的memcpy不行,那我们就可以通过赋值的方式在拷贝中开辟新空间,进行深拷贝:
即将reserve中的memcpy换成如图所示的方式,这样赋值拷贝会开辟新空间(上面的代码中就是开辟了新空间),我们就可以避免浅拷贝的问题,那我们来看看结果:
这样就解决了浅拷贝的问题了。
对于此类情况,事实上是很难发现的,并且处理的方式也不一定想到,因此我们一定要多多积累经验,才能在遇到困难的时候发现问题的关键所在。
因此我们同样也需要注意: 在C++中要避免使用C语言中的函数:memcpy、realloc、malloc等(realloc原地扩还好,若是异地扩容,就会发生我们所提到的错误)
3.2 vector< string >
事实上,string
与vector<int>
的道理是相同的,如果我们仍然用memcpy,会发现在需要扩容的过程中仍然出现浅拷贝造成的错误:
1. 用memcpy
void test_vector10() { vector<string> v; v.push_back("1111111111111"); v.push_back("1111111111111"); v.push_back("1111111111111"); v.push_back("1111111111111"); for (size_t i = 0; i < v.size(); i++) { for (size_t j = 0; j < v[i].size(); j++) { cout << v[i][j] << " "; } cout << endl; } cout << endl; }
没有扩容,可以正常运行。
void test_vector10() { vector<string> v; v.push_back("1111111111111"); v.push_back("1111111111111"); v.push_back("1111111111111"); v.push_back("1111111111111"); v.push_back("1111111111111");//扩容 for (size_t i = 0; i < v.size(); i++) { for (size_t j = 0; j < v[i].size(); j++) { cout << v[i][j] << " "; } cout << endl; } cout << endl; }
引发异常:浅拷贝造成的。
2. 用赋值拷贝
即将memcpy变成赋值拷贝的形式。
扩容也不会出错。
3.3深拷贝问题的总结
多加一个小标题string的目的就是方便我们去理解在自定义类型的情况下都会发生这种扩容出现的问题,而对于内置类型并不会发生,这次学过之后,我们也都应该对这种问题变得敏一些。
4.vector模拟实现的函数汇总
对于这个汇总,我将各个成员函数都集中起来,由于篇幅过长,具体的测试就没必要展示了,我会把完整的代码链接放在最后。
4.1 vector.h
#pragma once namespace cfy { 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; } T& operator[](size_t pos) { assert(pos < size()); return _start[pos];//注意 } const T& operator[](size_t pos) const//重载 { assert(pos < size()); return _start[pos]; } vector()//构造 :_start(nullptr) ,_finish(nullptr) ,_endofstorage(nullptr) {} 传统写法 v2(v1) //vector(const vector<T>& v) // :_start(nullptr) // , _finish(nullptr) // , _endofstorage(nullptr) //{ // //_start = new T[v.capacity()]; // //…… // reserve(v.capacity()); // for (const auto& e : v)//必须&, 否则会拷贝构造调用拷贝构造,因为如果每一个元素(T)也都是vector,这就会导致拷贝构造调用拷贝构造 // { // push_back(e); // } //} 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); } } vector(int n, const T& val = T())//解决vector8的注释中的问题 :_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; } } //现代写法 vector(const vector<T>& v) :_start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { vector<T> tmp(v.begin(), v.end()); swap(tmp);//this和tmp进行swap } ~vector()//析构 { delete[] _start; _start = _finish = _endofstorage = nullptr; } //v1 = v2 //v1 = v1;虽然会付出代价,但是能保证不会出错,极少数情况,能保证正确性,所以可以容忍 vector<T>& operator=(vector<T> v)//由于直接swap,因此不能传引用 { swap(v); return *this; } void reserve(size_t n)//注意迭代器失效问题 { if (n > capacity()) { size_t oldSize = size(); T* tmp = new T[n]; if (_start) { //memcpy(tmp, _start, sizeof(T) * size()); for (size_t i = 0; i < size(); i++) { tmp[i] = _start[i]; } delete[] _start; } _start = tmp; _finish = tmp + oldSize; _endofstorage = _start + n; } } void resize(size_t n, T val = T()) { if (n > capacity()) { reserve(n); } if (n > size()) { while (_finish < _start + n) { *_finish = val; ++_finish; } } else { _finish = _start + n; } } bool empty() const { return _finish == _start; } size_t size() const { return _finish - _start; } size_t capacity() const { return _endofstorage - _start; } void push_back(const T& x) { if (_finish == _endofstorage) { size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newCapacity); } *_finish = x; ++_finish; } void pop_back() { assert(!empty()); --_finish; } //迭代器失效问题:野指针问题:异地扩容导致 iterator insert(iterator pos, const T& val)//不传引用是因为有左值的影响:常量、v.begin() { 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迭代器失效,需要更新处理一下 pos = _start + len; } //挪动数据 iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; end--; } *pos = val; ++_finish; return pos; } iterator erase(iterator pos) { assert(pos >= _start && pos < _finish); iterator begin = pos + 1; while (begin < _finish) { *(begin-1) = *begin; ++begin; } --_finish; return pos; } void swap(vector<T>& v) { std::swap(_start, v._start); std::swap(_finish, v._finish); std::swap(_endofstorage, v._endofstorage); } void clear() { _finish = _start;//不能置空,会发现内存泄漏 } private://成员变量和_size _capacity的本质是一样的,只不过表示方法不一样 iterator _start; iterator _finish; iterator _endofstorage; }; }
4.2test.cpp
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<vector> #include<string> #include<stdlib.h> #include<assert.h> using namespace std; #include"vector.h"//注意包头文件的顺序,std要在vector.h的上面,因为预处理头文件会展开 //会存在std命名空间的函数,因此std需要在上面 int main() { try { cfy::test_vector10(); } catch (const exception& e) { cout << e.what() << endl; } return 0; }