总的来说,如果需要高效的随机访问和较低的内存开销,vector是更好的选择;而如果需要频繁进行插入和删除操作且不需要随机访问,选择list则更为合适。根据具体的应用场景和需求选择合适的容器可以显著提升程序的性能和开发效率。
vector和list是C++标准模板库(STL)中的两种不同的容器类,它们在数据结构、随机存取以及插入删除等方面存在区别。以下是具体分析:
一、数据结构
- vector:底层采用动态顺序表实现,拥有一段连续的内存空间。支持随机访问,可以通过下标快速定位元素。
- list:底层是双向循环链表,内存空间不连续。不支持随机访问,必须通过迭代器按顺序访问元素。
list底层数据结构示意图
二、随机存取
- vector:效率高,时间复杂度为O(1),因为内存连续,支持[]操作符和迭代器算术运算符。
- list:效率低,时间复杂度为O(n),需要通过指针顺序遍历到目标位置。
三、插入删除
- vector:插入和删除操作效率较低,尤其是非尾部操作,需要移动元素或重新分配内存,时间复杂度为O(n)。
- list:插入和删除操作效率较高,只需改变指针,无需移动元素,时间复杂度为O(1)。list只能头插、尾插、头删、尾删
四、空间利用率
- vector:不容易造成内存碎片,缓存利用率高。
- list:容易产生内存碎片,缓存利用率低。
五、迭代器
- vector:支持随机访问迭代器,可以进行算术运算。
因此vector<T>::iterator
支持"+", “+=”, "<"等操作符(重载)。 - list:只能顺序访问,不支持随机访问,迭代器不支持算术运算。插入和删除操作不会使非当前迭代器失效。
因此list<T>::iterator
不支持"+", “+=”, "<"等操作符(没有重载)。
但是两个迭代器都重载了 “++” 运算符
- 此外vector的迭代器是原生态的指针,插入和删除操作时可能会扩容,将数据拷贝到另一个空间中,导致所有迭代器失效。
- list 的迭代器是对原生态指针进行封装得到,这使得单个元素的list占用内存空间比vector更大,因为含有一个指针域;插入和删除操作时不开辟新的内存空间,所以失效的只有当前迭代器,其他的迭代器不受影响。
六、使用场景
- vector:适用于需要高效随机存取、尾部插入和删除操作的场景。
- list:适用于大量插入和删除操作且不关心随机存取的场景。
在选择时,应考虑以下几点因素:
- 当数据量较大且需要频繁修改时,如编辑器的撤销操作,选择list可能更合适。
- 当涉及大量数据的排序或搜索时,vector因其随机访问的特性而更加适用。
例子:
#include <iostream>
#include <vector>
#include <list>
using namespace std;
void test()
{
vector<int> v;
list<int> l;
int n = 8;
for(int i=0; i<n; i++)
{
v.push_back(i);
l.push_back(i);
}
cout<<"v[2]= "<<v[2]<<endl;
//list没有重载[]运算符,会报错
//cout<<"l[2]= ",<<l[2]<<endl;
cout<<(v.begin() < v.end())<<endl;//返回1,表示true
//cout<<(l.begin() < l.end())<<endl;//list没有重载逻辑运算符<,>
cout<<*(v.begin()+1)<<endl;
//cout<<*(l.begin()+1)<<endl; //编译错误,list::iterator没有重载+
vector<int>::iterator itv=v.begin();
list<int>::iterator itl=l.begin();
itv = itv+2;
//itl=itl+2; //编译错误,list::iterator没有重载+
itl++; //list::iterator中重载了++,只能使用++进行迭代访问。
itl++;
cout<<*itv<<endl;
cout<<*itl<<endl;
}
int main()
{
test();
return 0;
}
其他容器
deque
双向开口,动态的分段连续空间
stack
先进后出,无法遍历
queue
先进先出,无法遍历