C++【STL】之list的使用

简介: C++ STL list类常用接口详细讲解,干货满满!

list介绍

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。

  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。

  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。

  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。

  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

list使用

本文介绍的是list的部分常用接口,大佬们想了解更多关于list类的细节,一定要请前往官方文档(点击跳转)查阅学习

1. 默认成员函数

1.1 构造函数

list支持三种构造方式:

  • 默认构造:构造一个list对象,此时只有一个哨兵位节点
  • 带参构造:构造一个list对象,初始化对象有nval值的节点
  • 迭代器区间构造:根据传入的迭代器区间,构造出目标区间值的list对象
int main()
{
   
   
    vector<int> arr = {
   
    1,2,3,4,5,6 };
    list<int> l1; //默认构造
    list<int> l2(8, 1); //带参构造,8个val值为1的节点
    list<int> l3(arr.begin(), arr.end()); //迭代器区间构造
    return 0;
}

1.2 拷贝构造

拷贝已有的list对象来构造出一个新的相同值对象

int main()
{
   
   
    list<int> l1(3, 6);
    list<int> l2(l1);

    cout << "l1: ";
    for (auto e : l1) cout << e << " ";
    cout << endl;

    cout << "l2: ";
    for (auto e : l2) cout << e << " ";
    return 0;
}

1.3 赋值重载

将原有list对象的值赋值给另一个已存在的对象

int main()
{
   
   
    const char* ps = "happy new year";
    list<char> l1(ps, ps + strlen(ps)); //源对象
    list<char> l2; //目标对象
    l2 = l1;
    for (auto e : l2) cout << e;
    return 0;
}

注意: 即使目标对象比源对象小,也能进行赋值

1.4 析构函数

析构函数会在对象生命周期结束时自动调用,进行内存释放,调用后哨兵位也将被释放

2. 迭代器

由于list的空间不是连续的,所以它的迭代器是不同于stringvector的随机迭代器的,list类中使用的是双向迭代器,只能做单纯的双向移动

list中的迭代器

  • 正向迭代器iterator
  • 反向迭代器reveser_iterator
  • const版本正反迭代器
int main()
{
   
   
    string str = "hello world";
    list<char> l1(str.begin(), str.end()); //迭代器区间构造
    //正向遍历
    list<char>::iterator it = l1.begin();
    while (it != l1.end())
    {
   
   
        cout << *it;
        it++;
    }
    cout << endl;
    //反向遍历
    list<char>::reverse_iterator rit = l1.rbegin();
    while (rit != l1.rend())
    {
   
   
        cout << *rit;
        rit++;
    }
    return 0;
}

因为list 的空间不是连续的,所以不支持下标的随机访问,对 list 对象进行遍历时,只能使用迭代器

3. 容量操作

empty()接口:判空

size()接口:查看大小

max_size()接口:检查大小调整时的合法性

int main()
{
   
   
    list<int> l1(88, 6);
    cout << l1.size() << endl; 
    cout << l1.empty() << endl;
    cout << l1.max_size() << endl;
    return 0;
}

4. 数据访问

front()接口:访问哨兵位的下一个节点

back()接口:访问链尾数据

int main()
{
   
   
    vector<int> v = {
   
    4,1,2 };
    list<int> l1(v.begin(), v.end()); //迭代器区间构造
    cout << "front: " << l1.front() << endl;
    cout << "back: " << l1.back() << endl;
    return 0;
}

5. 数据修改

5.1 插入删除

assign()接口:赋值

push_front()接口:头插

pop_front()接口:头删

push_back()接口:尾插

pop_back()接口:尾删

int main()
{
   
   
    vector<int> vs = {
   
    4,1,2 };
    list<int> l1(vs.begin(), vs.end());
    for (auto e : l1) cout << e << " "; //4 1 2
    cout << endl;

    l1.assign(3, 6); //赋值为3个6
    for (auto e : l1) cout << e << " "; //6 6 6
    cout << endl;

    l1.push_front(12); //头插12
    for (auto e : l1) cout << e << " "; //12 6 6 6
    cout << endl;

    l1.pop_front(); //头删
    for (auto e : l1) cout << e << " "; //6 6 6
    cout << endl;

    l1.push_back(24); //尾插24
    for (auto e : l1) cout << e << " "; //6 6 6 24
    cout << endl;

    l1.pop_back(); //尾删
    for (auto e : l1) cout << e << " "; //6 6 6 
    cout << endl;
    return 0;
}

list对于首尾数据的操作效率很高

insert()接口:任意位置插入

erase()接口:任意位置删除

任意位置的操作需要配合迭代器和全局查找函数find()使用,一段数据中如果有相同值,find()会返回第一次找到的位置

int main()
{
   
   
    vector<int> vv = {
   
    4,1,2 };
    list<int> l1(vv.begin(), vv.end());
    for (auto e : l1) cout << e << " "; //4 1 2
    cout << endl;

    auto poss = find(l1.begin(), l1.end(), 2);
    l1.insert(poss, 6); //指定位置插入6
    for (auto e : l1) cout << e << " "; //4 1 6 2
    cout << endl;

    l1.insert(poss, 3, 8); //指定位置插入3个8
    for (auto e : l1) cout << e << " "; //4 1 6 8 8 8 2
    cout << endl;

    l1.insert(poss, vv.begin(), vv.end()); //指定位置插入一段数据
    for (auto e : l1) cout << e << " "; //4 1 6 8 8 8 4 1 2 2
    cout << endl;

    poss = find(l1.begin(), l1.end(), 1);
    l1.erase(poss); //删除指定位置的值
    for (auto e : l1) cout << e << " "; //4 6 8 8 8 4 1 2 2
    cout << endl;

    l1.erase(l1.begin(), l1.end()); //全删
    for (auto e : l1) cout << e << " ";
    return 0;
}

==注意:==

erase后会存在迭代器失效问题,需要及时更新迭代器位置!!!

5.2 交换调整清理

swap()接口:list对象交换

resize()接口:调整大小,若调整后的大小大于原大小,会尾插T()的值

clean()接口:清理

void Print(list<int>& l1, list<int>& l2)
{
   
   
    cout << "l1 size(): " << l1.size() << endl;
    for (auto e : l1) cout << e << " ";
    cout << endl;

    cout << "l2 size(): " << l2.size() << endl;
    for (auto e : l2) cout << e << " ";
    cout << endl;
    cout << "--------------------" << endl;
}

int main()
{
   
   
    vector<int> vc = {
   
    4,1,2 };
    list<int> l1(vc.begin(), vc.end());
    list<int> l2(vc.rbegin(), vc.rend());
    Print(l1, l2);

    l1.swap(l2); //交换l13和l14
    Print(l1, l2);

    l1.resize(1); //调整大小为1
    l2.resize(12); //调整大小为12
    Print(l1, l2);

    l2.clear(); //清理l13
    Print(l2, l2);
    return 0;
}

6. 其他操作

list类中还有诸如拼接、移除、逆置等操作,特殊场景使用起来很方便

6.1 链表拼接

splice()接口:对原链表中指定区间进行拼接,拼接后,原位置处的节点(区间)不再有,已被拼接到其他地方

void Print(list<int>& l1, list<int>& l2)
{
   
   
    cout << "l1: ";
    for (auto e : l1) cout << e << " ";
    cout << endl;

    cout << "l2: ";
    for (auto e : l2) cout << e << " ";
    cout << endl;
    cout << "--------------------" << endl;
}

int main()
{
   
   
    vector<int> vx = {
   
    1, 2, 3, 4, 5, 6 };
    list<int> l1(vx.begin(), vx.begin() + 3); //123
    list<int> l2(vx.begin() + 3, vx.end()); //456
    Print(l1, l2);

    l1.splice(l1.end(), l2);    //l2拼接到l1的结尾
    Print(l1, l2);

    l1.splice(l1.end(), l1, l1.begin()); //拼接到结尾
    Print(l1, l2);

    auto first = l1.begin();
    first++;    //指向第二个节点
    auto last = l1.end();    //指向最后一个节点
    l1.splice(l1.begin(), l1, first, last);    //拼接到开头
    Print(l1, l2);
    return 0;
}

6.2 链表移除

remove()接口:这个接口就是find() + erase()的封装版,使用起来非常方便

int main()
{
   
   
    vector<int> va = {
   
    4,1,2 };
    list<int> l1(va.begin(), va.end());
    for (auto e : l1) cout << e << " ";
    cout << endl;

    l1.remove(2); //移除元素2
    for (auto e : l1) cout << e << " ";
    cout << endl;
    return 0;
}

6.3 排序

由于库中的排序std::sort使用的是快排,需要支持下标随机访问,所以list对象不能使用,list类自己也提供了排序算法,不过效率较低,还不如先将数据拷贝到vector中,排序完后再拷贝回来的效率高

下面一段代码就是测试两种排序方法的效率:

int main()
{
   
   
    srand((size_t)time(NULL)); //随机数
    int n = 10000000;
    vector<int> tmp;
    tmp.reserve(n);
    list<int> l1;
    list<int> l2;
    int val = 0;
    int i = 0;
    while (i < n)
    {
   
   
        //放入随机数
        val = rand() % 100 + 1;
        l1.push_back(val);
        l2.push_back(val);
        i++;
    }
    //使用list::sort 排序
    int begin1 = clock();
    l1.sort();
    int end1 = clock();

    int begin2 = clock();
    //先拷贝到vector中
    for (auto e : l2)
    {
   
   
        tmp.push_back(e);
    }
    //使用std::sort 排序(快排)
    std::sort(tmp.begin(), tmp.end());
    //再拷贝回去
    int pos = 0;
    for (auto& e : l2)
    {
   
   
        e = tmp[pos++];
    }
    int end2 = clock();
    cout << "list::sotr time is: " << end1 - begin1 << endl;
    cout << "std::sort time is: " << end2 - begin2 << endl;

    return 0;
}

6.4 链表逆置

reverse()接口:将list对象逆置

int main()
{
   
   
    string ss = "happy";
    list<char> l1(ss.begin(), ss.end());
    for (auto e : l1) cout << e;
    cout << endl;

    l1.reverse();
    for (auto e : l1) cout << e;
    cout << endl;
    return 0;
}


C++【STL】之list的使用,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!

文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正!

目录
相关文章
|
21天前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
24 1
|
1月前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
51 7
|
1月前
|
存储 编译器 C++
C++ initializer_list&&类型推导
在 C++ 中,`initializer_list` 提供了一种方便的方式来初始化容器和传递参数,而右值引用则是实现高效资源管理和移动语义的关键特性。尽管在实际应用中 `initializer_list&&` 并不常见,但理解其类型推导和使用方式有助于深入掌握现代 C++ 的高级特性。
23 4
|
2月前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
93 4
|
2月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
98 5
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
74 2
|
2月前
|
存储 算法 Linux
【c++】STL简介
本文介绍了C++标准模板库(STL)的基本概念、组成部分及学习方法,强调了STL在提高编程效率和代码复用性方面的重要性。文章详细解析了STL的六大组件:容器、算法、迭代器、仿函数、配接器和空间配置器,并提出了学习STL的三个层次,旨在帮助读者深入理解和掌握STL。
79 0
|
1月前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
45 0
|
3月前
|
存储 算法 C++
【C++打怪之路Lv10】-- list
【C++打怪之路Lv10】-- list
28 1
|
10天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
50 18