一、平时做项目时vector和list都是经常用到的,但是他们具体有什么区别呢?底层实现又是什么原理呢?下面做详细介绍。
二、概念:
1)Vector 的底层结构是动态顺序表,在内存中是一段连续的空间。
连续存储的容器,动态数组,在堆上分配空间。
底层实现:数组
两倍容量增长:
vector 增加(插入)新元素时,如果未超过当时的容量,则还有剩余空间,那么直接添加到最后(插入指定位置),然后调整迭代器。如果没有剩余空间了,则会重新配置原有元素个数的两倍空间,然后将原空间元素通过复制的方式初始化新空间,再向新空间增加元素,最后析构并释放原空间,之前的迭代器会失效。
访问:vector支持随机访问,可以利用下标精准定位到一个元素上。
插入和删除:vector任意位置插入和删除的效率低,因为它每插入一个元素(尾插除外),都需要搬移数据,而且插入还有可能要增容,这样一来还要开辟新空间,拷贝元素,是旧空间,效率会更低。
插入:在最后插入(空间够):很快
在最后插入(空间不够):需要内存申请和释放,以及对之前数据进行拷贝。
在中间插入(空间够):内存拷贝
在中间插入(空间不够):需要内存申请和释放,以及对之前数据进行拷贝。
删除:在最后删除:很快
在中间删除:内存拷贝
空间利用率:vector由于底层是动态顺序表,在内存中是一段连续的空间,所以不容易造成内存碎片,空间利用率高,缓存利用率高。
适用场景:经常随机访问,且不经常对非尾节点进行插入删除。
2)List 的底层结构是带头节点的双向循环链表,在内存中不是一段连续的空间。
动态链表,在堆上分配空间,每插入一个元数都会分配空间,每删除一个元素都会释放空间。
底层:双向链表
访问:list不支持随机访问,要想访问list中的某个元素只能是从前向后或从后向前依次遍历。
插入和删除:list任意位置插入和删除的效率高,他不需要搬移元素,只需要改变插入或删除位置的前后两个节点的指向即可。
插入:很快,一般是常数开销
删除:很快,一般是常数开销
空间利用率:list的底层节点动态开辟空间,小结点容易造成内存碎片,空间利用率低,缓存利用率低。
适用场景:经常插入删除大量数据
三、区别:
1)vector底层实现是数组;list是双向 链表。
2)vector支持随机访问,list不支持。
3)vector是顺序内存,list不是。
4)vector在中间节点进行插入删除会导致内存拷贝,list不会。
5)vector一次性分配好内存,不够时才进行2倍扩容;list每次插入新节点都会进行内存申请。
6)vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好。
四、应用
vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随即访问和数据存储,而不在乎插入和删除的效率,使用vector。
list拥有一段不连续的内存空间,如果需要高效且大量的插入和删除,而不关心随机访问,则应使用list。
五、备注
昨天同事做dll封装时,函数原型的形参是引用类型的vector par_vec,void func(&par_vec),函数实现代码会给par_vec赋值。上位机调用的时候,先声明了一个vector变量x_vec,然后调用函数func,并把X_vec传递给func。发现x_vec是返回的乱码的东西,然而dll的函数并没有报错。deug调试进入dll源代码,发现也没有报错,并且正常赋值了par_vec。这是什么原因呢?同事在vector变量赋值的时候直接采用的=的形式,par_vec = temp_vec。我让他修改了代码,改成
par_vec .assign(temp_vec.begin(), temp_vec.end());就解决了这个问题。原来par_vec .assign会先清空par_vec,然后再把temp_vec的数据拷贝给par_vec。后面我让他测试先par_vec.clear();然后par_vec = temp_vec;也解决了他的问题。看来=号不能随便使用,如果传进的实参是需要被完全覆盖的,一定要先清空该实参的内存数据。
C++vector和list区别及应用
于 2023-01-13 10:17:12 首次发布