文章目录
- 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为空的时候,拷贝不了任何东西所以先判断start
if (_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;
}
}
使用赋值,赋值的话是深拷贝。不会引起上面问题。