文章目录
- 1:构造函数
- 1.1默认构造函数
- 1.2迭代器构造
- 1.3用n个val构造
- 1.4拷贝构造
- 2:operator=
- 3:析构函数和clear
- 4:迭代器失效问题
- 4.1:删除偶数
- 深浅拷贝
1:构造函数
1.1默认构造函数
vector():_start(nullptr),_end(nullptr),_endofstorage(nullptr){}
1.2迭代器构造
template<class InputIterator>vector(InputIterator first, InputIterator last): _start(nullptr), _end(nullptr), _endofstorage(nullptr){while (first != last){pushback(*first);++first;}}
在类模板中使用函数模板。
1.3用n个val构造
vector(size_t n, const T& val = T()): _start(nullptr), _end(nullptr), _endofstorage(nullptr){reserve(n);for (size_t i = 0; i < n; i++){pushback(val);}}
我们测试一下
void test1(){vector<char> v(10, 1);for (size_t i = 0; i < v.size(); i++){cout << v[i];}}
这个报错产生在
分析:
用10个1构造vector,1是整形,那么T就会推演为int类型,10也是整形,但是这个n个val构造的n是size_t类型,int转为size_t需要发生隐式类型转换,而1.2里面我们写的迭代器,inputiterator只是个名字,int可以被这个名字表达,所以编译器不会去找要转换的,而是直接调用1.2的构造函数,但是int如果作为迭代器,*it访问的时候就会出错,也就是非法的间接寻址。因此为了解决这个问题,我们需要再重载一个构造函数。
vector(int n, const T& val = T()): _start(nullptr), _end(nullptr), _endofstorage(nullptr){reserve(n);for (int i = 0; i < n; i++){pushback(val);}}
1.4拷贝构造
void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_end, v._end);std::swap(_endofstorage, v._endofstorage);} vector(const vector<T>& v): _start(nullptr), _end(nullptr), _endofstorage(nullptr){vector<T> temp(v.begin(), v._end());swap(v);}
和string一样,用库里面的swap进行深拷贝交换。
2:operator=
vector<T>& operator=(vector<T> v){swap(v);return *this;}
和string一样,传值的形参不改变实参,形参出了作用域自动销毁对象。而且这里的swap传形参的引用,形参拿到原本的vector地址,当他出了函数作用域的时候顺便帮vector也析构。
3:析构函数和clear
~vector(){delete[] _start;_start = _end = _endofstorage = nullptr;}void clear(){_end = _start;}
clear是清空数据
4:迭代器失效问题
上篇我们设计了insert和erase成员函数。
void insert(iterator pos, const T& val){assert(pos < _end);assert(pos >= _start);if (_end == _endofstorage){size_t len = pos - _start;int newcapacity = capacity() > 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len;}iterator _finish = _end - 1;while (_finish >= pos){*(_finish + 1) = *_finish;--_finish;}*pos = val;++_end;}void erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator begin = pos+1;while (begin < _finish){*(begin-1) = *(begin);++begin;}--_finish;return pos;}
使用len是解决了内部迭代失效的原因。
void test2(){std::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);std::vector<int>::iterator it = find(v.begin(), v.end(), 3);if (it != v.end()){v.erase(it);}cout << *it << endl;for (auto e : v){cout << e << ' ';}}
当删除了it原本的位置后,
直接崩溃,不打印。
而看看g++
vs报错,g++不报错。
迭代器失效,因此在g++源码里面,是这样实现的。
将erase和insert的返回值设定为iterator。这样可以更新位置后再继续使用。
it = v.erase(it);
4.1:删除偶数
iterator erase(iterator pos){assert(pos >= _start);assert(pos < _end);iterator begin = pos+1;while (begin < _end){*(begin-1) = *(begin);++begin;}--_end;return pos;}
如果是1234,如果是最后一个位置是偶数要删除,那么begin就会从pos+1的位置删除也就是和end一样的位置,那么我们的erase都不会进去,直接end–了,然后此时it还要++,也就是2个迭代器错过了,这样就会一直判断下去导致不断越界。问题就是最后一个数是偶数。
如果是122345,
当it是第一个2的时候,会erase,挪动数据,将下一个2移动到前面一个2的位置,然后还要++it,这样的话就会导致第二个2没有判断到,所以结果不符合。
如果单纯的解决办法是如下
else
{
++it;
}
这样子可以解决末尾有偶数的问题(段错误)。
但是我们参考的是sgi,sgi是linux的,采用的迭代器是原生指针,而vs是windows下的。 迭代器不是原生指针。所以erase和insert等涉及到迭代器失效的需要用迭代器作为返回值。
//void test_vector6()//{// // 要求删除所有偶数// std::vector<int> v;// v.push_back(1);// v.push_back(2);// v.push_back(2);// v.push_back(3);// v.push_back(4);// std::vector<int>::iterator 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;//}
深浅拷贝
void test_vector9(){/* vector<vector<int>> vvRet = Solution().generate(5);for (size_t i = 0; i < vvRet.size(); ++i){for (size_t j = 0; j < vvRet[i].size(); ++j){cout << vvRet[i][j] << " ";}cout << endl;}cout << endl;*/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;}
}
看下面的代码,创建包含5个1的vector,再往一个vector里面插入这种vector,然后打印这个大的vector。
运行结果报错,还出现了随机数。这是为什么?
在前面章节我提到过,当按字节序拷贝的时候会发生浅拷贝,按字节序拷贝的函数有memcpy,而memcpy是在reserve过程的,这里面当我们在总的vector里面插入小的vector的时候一定会发生扩容,当发生扩容的时候就会reserve,将旧数据拷贝到新空间。
void reserve(size_t n){if (n > capacity()){size_t oldsize = size();//需要扩容T* temp = new T[n];//使用new可以调用默认构造,比malloc要舒服的多。//要注意如果start为空的时候,拷贝不了任何东西所以先判断startif (_start){memcpy(temp, _start, sizeof(T) * oldsize);delete[] _start;}_start = temp;_end = _start + oldsize;_endofstorage = _start + n;}}
当开辟好新空间,调用memcpy的时候,是发生的字节序拷贝,将上面4个单元格内容拷贝到下面的几个单元格里面,并且用的是字节序拷贝,所以下面的单元格里面的start都和上面的4个小单元格里面的start指向内容一样,是同一块空间,当调用delete的时候,就会析构掉上面的整体,会调用整体(自定义类型vector int)的析构函数,析构掉每个单元,这时每个小单元格指向的内容都变成了随机的,temp里的单元格和上面的单元格指向内容是一样的,所以temp赋值给下面的总体的start迭代器的时候,相当于把随机值给他,所以导致了这个结果。
本质上就是因为reserve的memcpy导致了浅拷贝,内容先被释放,成为随机空间,又把随机空间赋值给新空间的start导致出错。
解决办法
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 = tmp + oldSize;_endofstorage = _start + n;}}
使用赋值,赋值的话是深拷贝。不会引起上面问题。