概念:
vector是表示可变大小数组的序列容器。它跟数组一样,只不过它可以动态的改变数组的大小。

这里简单的看看vector的使用:
vector是个类模板,所以它的实例化跟类模板的实例化一样,要指定类型:
cpp
vector<int> vt;
vt.push_back(1);
vt.push_back(2);
vt.push_back(3);
vt.push_back(4);
for (int i = 0; i < vt.size(); i++)//for循环
{
cout << vt[i] << " ";
}
cout << endl;
vector<int>::iterator it = vt.begin();//迭代器
while (it != vt.end())
{
cout << *it << " ";
it++;
}
cout << endl;
for (auto e : vt)//范围for
{
cout << e << " ";
}
cout << endl;
return 0;
其实在学习了string类之后,有很多方法我们都会很熟练。
来看看它的构造函数:

这里介绍一下里面我们没见过的概念:
value-type:他其实就是那个模板类型的T
alloc:它是一个空间配置器,也就是内存池,这里我们使用它给的缺省值就行。
这里来看看都是怎么使用的:
cpp
vector<int> v1;//默认构造
vector<int> v2(10, 1);//用10个1初始化
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
vector<char> v3(10, 'x');//用十个字符x初始化
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
vector<string> v4(10, "xxx");//用十个字符串xxx初始化
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
string s("hello world");
vector<char>v5(s.begin(), s.end());//迭代器区间初始化
for (auto e : v5)
{
cout << e << " ";
}
cout << endl;
如果我们指定的类型和用来初始化的类型不同的话,会有一个提升的:
cpp
vector<int>v6(3, 'x');
这个时候会将char提升为整型:

然后我们也可以这样进行初始化:
cpp
int a[] = { 1,3,16,20 };
vector<int> v1(a,a+4);
因为迭代器它是像指针一样的类型,可能是指针,也可能不是,在这里,a是首元素的地址,它的底层实现是指针,所以在这种情况下也是可以使用的。
那么通过上面的一些用法,我们来看看string和vector的区别:
1.string规定了字符串后面有一个隐藏的\0,而vector是没有的如果自己不插入的话
2.string的接口比vector的接口丰富
然后在一些算法当中,找到数据的方法其实就是迭代器:
这里介绍一下快排算法:
sort

可以看到快排的底层找数据就是用的迭代器,这里的comp参数,它的作用其实是让我们可以自己控制升降序。
我们来测试一下:
cpp
int a[] = { 24,18,31,20 };
vector<int> v1(a, a + 4);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
sort(v1.begin(), v1.end());
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
然后呢,我们也可以通过greater类控制降序,less类控制升序:
具体使用:
cpp
int a[] = { 24,18,31,20 };
vector<int> v1(a, a + 4);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
greater<int> gt;//控制降序
sort(v1.begin(), v1.end(),gt);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
less<int> ls;//控制升序
sort(v1.begin(), v1.end(), ls);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
然后我们再来看看扩容:
reserve:
我们来看这段代码:
cpp
vector<int> v1;
v1.reserve(5);
for (size_t i = 0; i < 5; i++)
{
v1[i] = i;
}
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
这段代码运行之后会中止,原因是什么:
首先,reserve它是只扩容,也就是改变capacity,而不会让size改变,也就是说这个时候的size是0,而我们的operator[]它有个强制检查:
assert(n<size)
而上面代码i=0和size相等,所以会强制检查,导致程序终止
解决方法是,我们可以使用resize,resize是开空间加初始化,所以size是不会等于零的,另一种是我们可以尾插给入数据
cpp
vector<int> v1;
//v1.reserve(5);
v1.resize(5);
for (size_t i = 0; i < 5; i++)
{
v1[i] = i;
}
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
vector<int> v1;
v1.reserve(5);
for (size_t i = 0; i < 5; i++)
{
v1.push_back(i);
}
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
由于之前详细的学习过string类和string类的模拟实现,所以接下来我将用模拟实现的方式来介绍一下vector的底层实现:
首先我们先来看看stl库里面的vector的成员变量:
cpp
iterator start;
iterator finish;
iterator end_of_storage;
可以看到,vector不同于我们的string类,它是用迭代器作为它的一个成员变量的,因为vector他是管理任意类型的数组,所以他的迭代器肯定跟数组是有关的

从上面这张图可以看出,这些成员变量是可以用来计算我们的元素个数和容量大小的,具体方法就是以前学习过的指针的相减。
从这里我们也就知道了这些成员变量的含义:
start:指向数组的首元素
finish:指向最后一个元素的下一个位置
end_of_storage:容量
接下来我们来实现一下它的构造和析构:
首先要声明和定义一下模板:
cpp
template<class T>
class vector
{
public:
typedef T* iterator;
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
构造和析构:
cpp
vector()//构造函数初始化
:_start(nullptr)
, _finish(nullptr)
, _end_of_finish(nullptr)
{
}
~vector()
{
if (_start)
{
delete[] _start;//因为这是数组,开辟了多个空间,所以要用delete[]
_start=_finish=_end_of_storage=nullptr;
}
}
迭代器失效问题:
迭代器其底层实际就是一个指针,或者是对指针进行了封装(这个情况在list下有体现)
迭代器失效情况在VS的检查下有两种,一种是野指针问题,一种是位置挪动问题
1.野指针问题,如改变空间,导致迭代器指向的空间被销毁,那么迭代器就会变成野指针,导致迭代器失效
2.位置改变,比如我们执行erase函数,如果迭代器指向的是最后一个位置,那么删除最后一个位置,迭代器就处于end位置,这个时候是没有元素的,那么迭代器就失效了,因此在vs的检查中,迭代器位置改变,那么编译器就会强制报错。
解决方法就是使用前对迭代器重新赋值
用insert和erase来看看迭代器失效的解决:
insert:
cpp
iterator insert(iterator it, const T& val)
{
if (it >= _start && it <= _finish)
{
if (_finish == _end_of_storage)
{
size_t len = it - _start;//记录迭代器和新空间_start的相对位置
reserve(capacity() == 0 ? 4 : capacity() * 2);
it = len + _start;//得到新空间迭代器的位置
}
iterator end = _finish;
while (end != it)
{
*end = *(end-1);
end--;
}
*it = val;
++_finish;
}
return it;
}
如果我们不对迭代器的位置进行重新赋值,那么程序就会出错。insert就是野指针问题,而下面说到的erase则是位置问题
erase:
cpp
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos;
while (it!= _finish - 1)
{
*it = *(it + 1);
it++;
}
_finish--;
return pos;
}
先看它的返回值:

这个意思是返回被删除元素的下一个元素,它在被删除元素删除之后所在的新位置。
比如

我们要删除3,所以返回的是3的下一个元素在3被删除之后的新位置,4在3被删除之后的新位置其实就是原来3的位置;所以上面的代码我们直接返回pos。
深拷贝中的浅拷贝:
memcpy的一些问题:
- memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
- 如果拷贝的是内置类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
这是什么意思呢:
cpp
space::vector<string> v;
v.push_back("111111");
v.push_back("222222");
v.push_back("333333");
v.push_back("444444");
v.push_back("555555");
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
这段代码运行会出错,原因是,我们扩容时,memcpy是浅拷贝,会将string对象进行值拷贝,这样拷贝出来的string对象中的_str指针和原来的string对象中的str指针指向同一空间,这样析构就会对同一片空间析构两次,导致程序崩溃

解决方法:
cpp
void reserve(size_t n)
{
assert(n > capacity());
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
//memcpy(tmp, _start, sizeof(T) * sz);
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = sz + _start;
_end_of_storage =_start + n;
}
_start[i]和tmp[i]解引用之后是string,然后这里就是string对象的赋值,string的赋值是深拷贝,就解决了这个问题。
补充:c++在有了模板之后,对内置类型进行了升级,使得内置类型也有了构造:
cpp
vector(size_t n, const T& val = T())
{
resize(n, val);
}
所以在这里我们用n个数据来初始化,这里T可能是string,vector,也可能是内置类型,所以我们采用匿名对象来作为缺省值。
迭代器区间初始化。
cpp
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
为什么加模板,是因为这个迭代器不一定只是vector类型的迭代器,也可以是string的等等。