注意:vector是个模板,所以不能声明和定义分离,所以实现它用一个vector.h文件和一个test.cpp文件就行了。
一.成员变量
由三个迭代器 构成:①._start 指向顺序表首个元素。
②._finish 指向最后一个数据的后一个位置。
③._end_of_capacity 指向开辟空间的最后一个空间的下一个元素的位置。
说是迭代器,其实是typedef指针的结果。所以本质上还是指针。
二.reserve
遭遇的大坑

此时_start已指向开辟好的新空间,而size()里的_finish指向扩容前的空间,而这个空间被delete了,所以为空。那么这样减出来的size也为空,绝对不是原空间的size。所以需要在开辟新空间之前,用一个新变量old_size储存原size。

三.begin(),end()


四.print_vector

还可以这么解决:

五.insert
void insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
//扩容
if (_finish == _end_of_capacity)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;//更新pos
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
++_finish;//不要忘了_finish!!
}
六.erase
代码:
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos + 1;
while (it != end())
{
*(it - 1) = *(it);
++it;
}
--_finish;
}
迭代器失效
几乎所有容器在erase以后都会迭代器失效,而insert要看具体情况。迭代器失效以后不要再进行访问,如果一定要访问,需要更新一下迭代器。
insert
情况1.野指针(扩容)
对于需要扩容的情况,pos指向的是原空间,并未移到新空间上,原空间释放后pos仍旧指向那片空间,就变成了野指针。

解决方法:
用一个长度确认相对位置

然后更新pos

以上解决方法在函数内是对的,在扩容以后,在新空间里更新pos,但由于是传值调用,pos是个形参,是个临时拷贝,在函数里确实是更新(改变)了;但在出了函数作用域以后,它的值还是原先的值。所以这么做在函数内部用来执行插入操作是没有问题的,但出了函数就请一定不要再访问位置参数。
情况2.位置意义改变(没有扩容)

如果在insert以后仍旧对p进行访问(利用p去修改空间里的值),p指向它不该指的地方,那么p以后的数据全部乱套了,逻辑上不再对应原本期望的位置。
erase
当用erase实现删除偶数时,在执行erase后,进行it的++(erase以后访问pos),那么此时就会出现迭代器失效。it仍旧指向它不该指的地方。


解决方案:
写erase时设置一个返回值,返回一个迭代器,指向被删除元素的下一个位置。此时更新it,即
it = v.erase(it),再访问(it++)
七.resize
功能:开空间以及用val进行初始化。
1.关于缺省值的探讨

自定义类型的数据给缺省值需要自己类型的数据,则调用默认构造函数构造一个匿名对象,这个对象就是用默认构造函数的缺省值构造的。
内置类型的数据没有默认构造,但非要用上面的方式给缺省值,系统会自动处理数据,int 类型的会被初始化为0,指针类型的会被初始为nullptr。
2.空间的几种情况

①.n < size
删除n以后的数据

②.n >= size
无论size有没有大于_capacity,统统扩容。然后插入数据。

八.operator=,clear
传统派:
在赋值以前,一定要记得把对象里原来的内容给释放(clear)掉,不然会造成空间的泄漏。clear接口能够做到**将空间内的内容清理掉,但不释放空间。**被清理以后的空间可以用来存储要赋值的内容。
vector<T>& operator=(const vector<T>& v)
{//传统派
if (this != &v)
{
clear();
for (auto& e : v)
{
push_back(e);
}
return *this;
}
}
现代派:
实现一个swap函数,交换一下v与*this就行了
void swap(const vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_capacity, v._end_of_capacity);
}
vector<T>& operator=(const vector<T>& v)
{//现代派
swap(v);
return *this;
}
自己遭遇的问题
1.对于没有把测试函数包含进命名域的情况,在测试时创建一个vector对象的时候记得带上自己的命名域,不然系统会默认这个vector是std里面的。同时访问命名域里的函数要带上命名域。

2.测试用例的main函数不要放在命名域内。
3.范围for的底层是函数,所以e那里相当于是形参,所以要传引用,避免传值调用拷贝构造带来的开销。
4.在类里面可以用类名替代类类型,类外不可以。

5.在测试用n个val构造的构造函数时,假如测试用例是10个1,就会报下面的错误:



我明明测试的是vector(size_t n,const T& val),为什么会在区间构造函数处报错呢??
原因在于系统把10和1两个实参识别成了两个迭代器,然后调用的是区间构造函数,而非vector(size_t n,const T& val), 进了区间构造函数以后,会遇到first的解引用,然而first对应的参数10是个int类型的数据,是无法解引用的 。
那为啥就非得识别成迭代器,走区间构造呢?
原因在于系统也是有惰性的,它会选择轻松的那个。用n个val构造时,还需要将函数模板实例化为int类型的函数以后,才能调用。而对于区间构造,就可以直接把值传上去,就不用那么费劲。
解决方案:
