本次学习重点
1.迭代器区间构造和size_t n 构造
2.string扩容问题
3.erase的缺陷
1.迭代器区间构造和size_t n 构造
vector支持用一段迭代器区间构造,也可以支持任意类型的迭代器区间,所以要写成函数模板
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
first++;
}
}
vector还可以利用给定的初始值进行初始化构造,为了方便使用各种类型的初始值,这里要使用匿名对象临时构造初始值。
如果是内置类型的话,也需要走构造函数吗?是的,内置命令也是要通过构造函数进行初始化的,只是平常使用时重载了一些运算符,用起来会更加方便。
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
但这两个函数在进行调用时会出现问题,编译器无法对要调用哪一个函数进行匹配,区分不出到底是迭代器还是无符号整数,如果要同时满足的话,就要在size_t 构造时加上u标识无符号整数。
2.string扩容问题
vector可以构造string类的动态数组,当我们需要对这个数组进行扩容时就需要考虑一下值拷贝的问题。
模拟实现vector的push_back时,采用的是基于原理的开辟新空间,将旧空间的内容拷贝到新空间去(memcpy),看上去好像并没有什么问题,但如果是元素是string类的话就犯大错了,构造时并不会出现什么,但析构时会报错。
string类底层是字符串类型,我们所构造的string类型是将一系列成员变量对字符串进行映射,而字符串存放的位置在静态区,扩容后,新空间的成员也对静态区的字符串建立了映射,当我们释放掉旧空间时释放的是成员指向的内容而非成员本身,当程序结束析构时就会对同一块空间进行多次释放。
解决方法是利用赋值的特性给旧数据拷贝一份新的数据,放在新的空间里。
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t oldsize = size();
/*memcpy(tmp, _start, sizeof(T) * size());*/
for (size_t i = 0; i < oldsize; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
_finsh = tmp + oldsize;
_endofstorage = tmp + n;
}
}
3.erase的缺陷
vector的erase是删除对应下标的元素,但它并不是万能的。
举个例子,如果需要删除数组里对应的偶数,erase底层走的是挪动元素进行删除,下面的可能不会有什么问题,但当重复的偶数出现时,就会删不干净。
原因是因为迭代器失效了。在第一次删除后,我们挪动数据,挪完后此时迭代器指向的内容并不会进行判断就直接移动到下一个去。
而且还会出现越界问题,要谨慎使用。