详解C++STL容器系列(二)—— list的详细用法和与vector的对比

一、list介绍

  • list是C++STL容器中的顺序容器,这里的顺序容器区别于关联容器,指的是元素在容器中的位置与大小无关。list和vector不同,vector是顺序存储的,内存是连续的;list底层实际上是双向链表,list的内存是分散的,内存可以在各个位置分布。
  • 基于双向链表的数据结构,list具有array、vector、deque等不具备的优势:在任意位置插入和删除元素的时间复杂度O(1),移动元素的效率也很高。因为元素之间是通过指针联系的,在某个元素间插入一个元素改变那指针的指向就可以了。
  • 但是正因为底层是双向链表,无法像vector那样通过**下标[]**直接访问元素,需要从头(尾)遍历元素找到元素。

如果需要大量数据的插入和删除,而对数据的随机访问比较少,使用list是个不错的选择。

二、list创建

list的创建和vector类似,但要注意list存储的地址不是连续的,迭代器不可以偏移。

//1.创建一个空的list
list<int> values1;
//2.创建10个空间的list,默认值为0
list<int> values2(10);
//3.创建10个值为2的list
list<int> values3(10, 2);
//4.值为[1,2,3,4,5]
list<int> values4 = {1, 2, 3, 4, 5};
//5.使用另一个list来初始化list
list<int> values5(values4);
//6.使用其他容器如vector来初始化list
vector<int> arr = { 1, 2, 3 };
list<int> values6(values5.begin() + 1, values5.end());

三、list方法

iterators(迭代器)

名字描述
begin返回指向容器中第一个元素的双向迭代器。
end返回指向容器最后一个元素所在位置后一个位置的双向迭代器
rbegin返回容器逆序的第一个元素的双向迭代器
rend返回容器逆序的最后一个元素的前一个位置的双向迭代器
cbegin和begin()功能相同,在其基础上增加了 const 属性,不能用于修改元素。
cend和end()功能相同,在其基础上增加了 const 属性,不能用于修改元素。
crbegin和rbegin()功能相同,在其基础上增加了 const 属性,不能用于修改元素。
crend和rend()功能相同,在其基础上增加了 const 属性,不能用于修改元素。

Capacity(容量)

名字描述
size返回实际元素的个数
max_size返回元素个数的最大值。这个值非常大,一般是2^32-1
empty判断vector是否为空,为空返回true否则false
resize调整容器的大小

Element access(元素访问)

名字描述
front返回第一个元素的引用
back返回最后一个元素的引用。

Modifiers(修改器)

名字描述
push_front在容器头部插入元素
push_back在容器尾部插入元素
emplace_front在容器头部生成元素。同push_front,效率更高
emplace_back在容器尾部生成元素。同push_back,效率更高
insert在容器指定位置插入元素
emplace在容器指定位置擦插入元素。同insert,效率更高
erase删除容器中一个或某区域内的元素
assign用新元素替换容器中的原有内容
swap交换两个容器中的元素。必须保证两个容器存储的是同一数据类型
clear清除容器内容,size=0,存储空间不变

list operations

名字描述
splice将一个list容器中的元素插入另一个容器的指定位置
remove(val)删除容器中所有值等于val的元素
remove_if删除容器中满足某个条件的元素
unique删除容器中相邻的重复元素,只保留一个
merge合并两个实现已经排好序的list容器,并且合并后的list容器仍然是有序的
sort将容器排序,通过更改容器中元素的位置
reverse反转容器中元素的位置

对比vector

  • vector有size和capacity,一个是当前容器大小,另一个是可以存储容器大小;而list只有size,因为list不需要扩容,所以不需要提前预留空间。
  • vector访问容器可以通过下标[]和at,而list只能直接访问头部和尾部元素,访问某个具体元素需要遍历list。
  • 因为list是双向链表,可以从头部加入元素,而vector只能尾部插入。
  • list内置了一些算法中的方法,如:sort、reverse,并且可以轻松删除元素:remove、remoce_if;而vector并不具备。
  • 总的来说,list相对于vector,插入和删除简单,访问难。

四、list的具体用法

4.1 iterators

  • 使用迭代器访问元素,注意迭代器的名称。

  • 当然用auto会很方便。

list<int> values = {1, 2, 3, 4, 5};
for (list<int>::iterator it = values.begin(); it != values.end(); ++it)
{
    cout << *it << endl;
}
for (list<int>::reverse_iterator it = values.rbegin(); it != values.rend(); ++it)
{
    cout << *it << endl;
}
for (list<int>::const_iterator it = values.cbegin(); it != values.cend(); ++it)
{
    cout << *it << endl;
}
for (list<int>::const_reverse_iterator it = values.crbegin(); it != values.crend(); ++it)
{
    cout << *it << endl;
}

4.2 Capacity

和vector比起来少了capacity,所以只有对size进行判断和修改的方法。

list<int> values = {1, 2, 3, 4, 5};
cout << values.size() << endl;                      //输出5
cout << values.max_size() << endl;                  //输出357913941
cout << values.empty() << endl;                     //输出0,因为不为空
values.resize(0);
cout << values.empty() << endl;                     //输出1,因为resize将元素size=0,所以为空
values.resize(5, 6);                                //重置list为5个值为6
for (auto value : values) cout << value << endl;    //输出5个6

4.3 Element access

不能够像vector、array那样对任意元素访问,只能够访问front、back。

list<int> values = {1, 2, 3, 4, 5};
cout << values.front() << endl;     //输出1
cout << values.back() << endl;      //输出5

4.4 Modifiers

push_front、push_back、emplace_front、emplace_back

list可以从头部插入,也可以从尾部插入

list<int> values;
for (int i = 1; i <= 5; i++)
{
    values.push_front(i);
    values.emplace_front(i);
    values.push_back(i);
    values.emplace_back(i);
}

insert、emplace

insert有四种用法:

  • 在指定位置(迭代器)插入某个元素

    //插入3和4,输出3 4
    list<int> values;
    values.insert(values.begin(), 3);
    auto it = values.begin();
    it++;
    values.insert(it, 4);
    

    注意,不可values.insert(values.begin() + 1, 3);,因为list的迭代器不可随意访问元素,没有重载+运算符,而且底层是双向链表,不具备随意访问性质。

    所以需要偏移时,定义一个迭代器it,it++就可以了。并且it += 2子类的操作也不可,因为it++并不是简单的加一,有点类似于指针的偏移,找到下一个元素所在位置的迭代器,是重载后的++。

  • 在指定位置插入n个置为val的元素

    list<int> values;
    values.insert(values.end(), 3, 10);
    
  • 在指定位置插入一系列元素

    list<int> values;
    values.insert(values.end(), {1, 2, 3});//使用{}
    
  • 在指定位置插入同一个类型容器的元素

    list<int> values;
    vector<int> arr = {4, 5, 6};
    values.insert(values.end(), arr.begin() + 1, arr.end());
    

emplace和insert类似都是插入元素,不过emplace只能插入一个元素

list<int> values;
values.emplace(values.end(), 3);

erase

erase通过迭代器删除某个或某个范围的元素。

list<int> values;
values.emplace(values.end(), 5, 1);
values.erase(values.begin());//某个元素
values.erase(values.begin(), values.end());//某个范围的元素

assign

assign修改整个list,有两种用法:

  • 修改为n个值为val

    list<int> values;
    values.assign(5, 3);
    
  • 修改为某个容器一定范围的值

    list<int> values;
    list<int> values2 = { 1, 2, 3 };
    values.assign(values2.begin(), values2.end());
    

swap

将list本身和另一个相同数据类型的list交换所有元素。

list<int> values = {4, 5, 6};
list<int> values2 = { 1, 2, 3 };
values.swap(values2);

4.5 list operations

splice

splice是list类容器独有的功能,可以将两个list容器的内容进行拼接,有三种用法:

  • 从当前容器的某个迭代器开始,将另一个list容器的所有元素加入当前容器。

    list<int> values = {4, 5, 6};
    list<int> values2 = { 1, 2, 3 };
    values.splice(values.begin(), values2);//最后values = {1,2,3,4,5,6}
    
  • 从当前容器的某个迭代器开始,将另一个list容器的某个元素加入当前容器

    list<int> values = {4, 5, 6};
    list<int> values2 = { 1, 2, 3 };
    values.splice(values.begin(), values2, values2.begin());//最后values = {1,4,5,6}
    
  • 从当前容器的某个迭代器开始,将另一个list容器的某个范围内容的加入当前容器

    list<int> values = {4, 5, 6};
    list<int> values2 = { 1, 2, 3 };
    values.splice(values.begin(), values2, values2.begin(), values2.end());//{1,2,3,4,5,6}
    

splice的用法其实和insert很像,不过splice只能操作list,而insert可以是其他类型的容器。

remove

remove:删除值为val的所有元素。

list<int> values = {1, 2, 3, 1, 2, 3};
values.remove(2);//values = {1,3,1,3}

remove_if

成员函数 remove_if() 期望传入一个一元断言作为参数。一元断言接受一个和元素同类型的参数或引用,返回一个布尔值,断言返回 true 的所有元素都会被移除。

list<int> values = {1, 2, 3, 1, 2, 3};
values.remove_if([&](int n) {return n == 1; });

如以上例子,就通过lambda表达式,将值n=1的元素删除。

unique

unique可以删除list容器中相邻的相同元素,只留下一个。注意,是相邻的相同元素,不相邻的即使之前出现也不会删除的。

list<int> values = {1, 1, 2, 2, 3, 4, 3};
values.unique();//values = {1,2,3,4,3}

sort

sort顾名思义就是排序,和算法中的sort一致。

用法,直接对整个list排序,默认升序;降序输入参数:greater< int >()

list<int> values = {1, 9, 2, 2, 3, 4, 3};
values.sort();//升序
values.sort(greater<int>());//降序

merge

合并两个实现已经排好序的list容器,两者合并后的list容器仍然是有序的。

list<int> values = { 1,3,5 };
list<int> values2 = { 2, 4, 6 };
values.merge(values2);

注意,两个list都要排序好才能够merge,否则会出错。可以用sort方法排序,并且排序方向需一致。

reverse

将list容器中的元素翻转。

list<int> values = { 1,3,5 };
values.reverse();//values = {5,3,1}

五、list和vector对比

vector的用法详解传送门详解C++STL容器系列(一)—— vector的详细用法和底层原理

vectorlist
底层实现原理动态数组双向链表
空间利用率空间上是连续的,具有缓存和扩容机制,空间利用率高空间上是离散的,内存分散,空间利用率低
随机访问支持随机访问,O(1)不支持随机访问,需要从头开始遍历
插入和删除能在尾部实现O(1)的插入和删除;插入和删除其他部分需要O(n)的时间复杂度可在任意位置进行插入和删除,时间复杂度都为O(1)
迭代器等同于指针,可以像指针一样偏移在指针的基础上进行了疯转,迭代器不支持+
使用场景对于随机访问要求高,不关心插入、删除效率有大量的插入、删除操作,不关心随机访问

参考

  • 9
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 26
    评论
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暗夜无风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值