之前已经学习过了string类,接下来介绍c++中的另外两个类—— vector和list;
vector
之前介绍的string类是c++所特定的字符数组;
而vector可以看做是string类的扩展,因为它是一个模板类;
它可以作为任何类型的数组,比如 int ,char,string等等类型;
但是和c语言中的数组不同的是,它是可变的;
vector的特性
1.是可变大小的序列容器
2.vector是采用连续存储空间来存储元素,可以随机访问,但是大小被容器自动处理
3.vector尾插尾删效率高,但是其他位置的删除和插入就不行了
vector的使用方式
vector<类型名> <变量名>
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
int n = v1.size();
for (int i = 0; i < n; i++)
{
cout << v1[i];
}
cout << endl;
return 0;
}
通过string的学习,实际上就已经了解了vector的大部分函数了;
vector唯一和string不一样的就是string可以直接输出,而vector只能一个一个输出;
此外它们的函数基本一样;
vector的迭代器失效(string通用)
每个容器都有迭代器,而迭代器是一个类似于指针的东西,因此迭代器在一些场景会出现失效的情况;
情况1:引起底层空间改变的操作
vector和string这种连续存储的容器的扩容并不是在原来的空间上扩容;
而是新开辟一个更大的空间,然后将数据放到原来的空间,然后释放原空间;
但是我们的迭代器若是未及时更新,就会导致迭代器指向无效空间;
而当我们使用 resize,resere,insert,assign,push_back 等函数的时候;
就可能会导致底层空间需要扩容;
这样我们的迭代器就会失效;
#include<iostream>
#include<vector>
#include<string>
using namespace std;
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector<int>::iterator iv1 = v1.begin() + 2;
v1.reserve(30);
cout << *iv1 << endl;
cout << endl;
return 0;
}
我们可以看到,iv1 迭代器指向的地址已经失效了,无法输出;
因此我们使用迭代器的时候,一定要注意迭代器是否失效;
这种情况string也会出现,因此我们在使用容器的迭代器的时候,一定要注意时刻更新迭代器;
而我们都知道当 vector 的容器存满了内容后会自动扩容;
而它的扩容又是开辟新空间;
因此当我们的迭代器出现问题的时候,可能就是我们的容器扩容了,而迭代器位置为更新的原因
情况2:指定位置删除操作——erase
void test2()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector<int>::iterator iv1 = v1.end();
v1.erase(iv1);
cout << *iv1 << endl;
}
这里是因为我们的迭代器指向了容器的尾端;
而若是我们删除了迭代器所在位置,那么迭代器就会指向了end的位置,从而导致迭代器失效了;
迭代器失效的解决方式:对迭代器重新赋值
list
list 和 vector 以及 string 不一样,它并不是在一片连续的空间的,而是随取随用;
这意味着 list 无法随机访问;
而 list 底层是一个双向循环链表,这使得 list 的头插头删尾插尾删都十分方便快捷;
并且,list 也是一个模板类,可以指定不同类型的 list 变量;
list的使用
void test3()
{
list<int> l1;
l1.push_back(1);
l1.push_back(1);
l1.push_back(1);
list<int>::iterator il1 = l1.begin();
while (il1 != l1.end())
{
cout << *il1 << ' ';
il1++;
}
cout << endl;
}
list的各种成员函数
之前提到过,list的无法随机访问,也就是说list无法通过下标来访问成员;
但是除此之外,list 的成员函数的使用方式和之前的vector,list 都是一样的;
不管是构造函数,还是头插尾插啥的,使用方式都是一样的;
list的迭代器失效
list 的迭代器失效只在 list 删除时会出现;
之前说过,list 是一个双向循环链表;
而链表的每一个节点都是独立的,只是通过指针来链接罢了;
那么这也就说明,list 的 erase 的底层实现和别的容器不同;
list 的 erase 是释放该位置的节点然后返回下一个节点的位置;
而之前的 vector 和 string 都是在连续的一片空间存储;
erase都是将该位置的数据用后面的数据覆盖;
因此只要 erase 的位置不是最后一个,或者说没有扩容,就不会导致迭代器失效;
这也就说明, list 的 erase 会随时随地导致迭代器失效;
void test3()
{
list<int> l1;
l1.push_back(1);
l1.push_back(1);
l1.push_back(1);
list<int>::iterator il1 = l1.begin();
while (il1 != l1.end())
{
l1.erase(il1);
il1++;
}
cout << endl;
}
比如这里,我们将 il1 的位置删除了,那么就直接导致迭代器失效了;
而我们若是想清空 list 的元素,应该这样做:
void test3()
{
list<int> l1;
l1.push_back(1);
l1.push_back(1);
l1.push_back(1);
list<int>::iterator il1 = l1.begin();
while (il1 != l1.end())
{
il1 = l1.erase(il1);
}
cout << endl;
}