09.【C++】list链表(STL中的列表容器,C++封装的带头双向链表,可实现指定类型的增删查改,迭代器操作等功能)

目录

[一. list的介绍及使用](#一. list的介绍及使用)

[1.1 list的介绍](#1.1 list的介绍)

[1.2 list的使用](#1.2 list的使用)

[1.2.1 list的构造](#1.2.1 list的构造)

[1.2.2 list iterator迭代器的使用](#1.2.2 list iterator迭代器的使用)

[1.2.3 list size & empty 大小判空](#1.2.3 list size & empty 大小判空)

[1.2.4 list element access 头尾引用](#1.2.4 list element access 头尾引用)

[1.2.5 list modifiers 增删查改](#1.2.5 list modifiers 增删查改)

[1.2.6 list的迭代器失效](#1.2.6 list的迭代器失效)

[1.2.7 list 排序的使用](#1.2.7 list 排序的使用)

[二. list的模拟实现](#二. list的模拟实现)

[2.1 模拟实现list](#2.1 模拟实现list)

[三. list与vector的对比](#三. list与vector的对比)


一. list的介绍及使用

1.1 list的介绍

list文档介绍

1.2 list的使用

list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口。

1.2.1 list的构造

|-----|--------------------------------------------------------------------------------------|-----------------------------|
| | 构造函数constructor | 接口说明 |
| (1) | list (size_type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
| (2) | list() | 构造空的list |
| (3) | list (const list& x) | 拷贝构造函数 |
| (4) | list (InputIterator first, InputIterator last) | 用[first, last)区间中的元素构造list |

cpp 复制代码
#include<iostream>
#include<list>
using namespace std;
// list的构造
void test_list1()
{
    list<int> l1;                         // (2) 构造空的l1
    list<int> l2(4, 100);                 // (1) l2中放4个值为100的元素
    list<int> l3(l2.begin(), l2.end());   // (4) 用l2的[begin(), end())左闭右开的区间构造l3
    list<int> l4(l3);                     // (3) 用l3拷贝构造l4

    int array[] = { 16,2,77,29 };         // (5) 以数组为迭代器区间构造l5
    list<int> l5(array, array + sizeof(array) / sizeof(int));

    list<int> l6{ 1,2,3,4,5 };            // (6) 列表格式初始化C++11

    // 用迭代器方式打印l5中的元素
    list<int>::iterator it = l5.begin();
    while (it != l5.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // C++11范围for的方式遍历
    for (auto& e : l5)
        cout << e << " ";
    cout << endl;
}
int main()
{
    test_list1();
	return 0;
}

1.2.2 list iterator迭代器的使用

此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

示例:

cpp 复制代码
#include<algorithm>
#include<iostream>
#include<list>
#include<vector>
using namespace std;
// 打印函数
template<typename T>
void Print(T ls)
{
    for (auto a : ls)
        cout << a << " ";
    cout << endl;
}
void test_list4()
{
    int array[] = { 16,2,77,29 };         // 以数组为迭代器区间构造
    list<int> l1(array, array + sizeof(array) / sizeof(int));
    Print(l1);
    //sort(l1.begin(), l1.end());   //报错:不定义该运算符或到预定义运算符可接收的类型的转换
    reverse(l1.begin(), l1.end());  // list是BidirectionalIterator
    Print(l1);                      // 只能用BidirectionalIterator迭代器和向前兼容的迭代器支持的算法
                                    // sort函数在algorithm算法库里是RandomAccessIterator,所以list不能用

    vector<int> v1(array, array + sizeof(array) / sizeof(int));
    Print(v1);
    sort(v1.begin(), v1.end());     // vector是RandomAccessIterator
    Print(v1);                      // 能用RandomAccessIterator迭代器和向前兼容的迭代器支持的算法
    reverse(v1.begin(), v1.end());  // sort函数vector迭代器本来就支持
    Print(v1);                      // reverse函数是RandomAccessIterator迭代器向前兼容的迭代器支持的算法
    
    // 但是list库里自己定义了sort函数
    cout << endl;
    l1.sort();
    Print(l1);
}
int main()
{
    test_list4();
    return 0;
}

|-------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
| 函数声明 | 接口说明 |
| begin+end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
| rbegin+rend | 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置 |

【注意】

  1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动

  2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

1.2.3 list size & empty 大小判空

|-----|----------------------------------------------------------------------|------------------------------|
| | 函数声明 | 接口说明 |
| (1) | empty | 检测list是否为空,是返回true,否则返回false |
| (2) | size | 返回list中有效节点的个数 |

1.2.4 list element access 头尾引用

|-----|----------------------------------------------------------------------|--------------------------|
| | 函数声明 | 接口说明 |
| (1) | front | 返回list的第一个节点中值的引用& |
| (2) | back | 返回list的最后一个节点中值的引用& |

示例:1.2.3、1.2.4

cpp 复制代码
#include<iostream>
#include<list>
using namespace std;
// 打印函数
template<typename T>
void Print(list<T> ls)
{
    for (auto a : ls)
        cout << a << " ";
    cout << endl;
}
void test_list2()
{
    int array[] = { 16,2,77,29 };         // 以数组为迭代器区间构造
    list<int> l1(array, array + sizeof(array) / sizeof(int));
    Print(l1);
    cout << l1.size() << endl;  // (1) 返回l1的size
    cout << l1.empty() << endl; // (2) 返回l1是否为空
    cout << l1.front() << endl; // (3) 返回l1首元素的引用
    cout << l1.back() << endl;  // (4) 返回l1最后一个元素的引用
}
int main()
{
    test_list2();
    return 0;
}

1.2.5 list modifiers 增删查改

|-----|-------------------------------------------------------------------------------------|----------------------------------------------------------|
| | 函数声明 | 接口说明 |
| (1) | push_front | 在list首元素前插入值为val的元素 |
| (2) | pop_front | 删除list中第一个元素 |
| (3) | push_back | 在list尾部插入值为val的元素 |
| (4) | pop_back | 删除list中最后一个元素 |
| (5) | insert | 在list position 位置中插入值为val的元素 |
| (6) | erase | 删除list position位置的元素 |
| (7) | swap | 交换两个list中的元素 |
| (8) | clear | 清空list中的有效元素 |
| (9) | splice | 在L1的position前插入另一个链表L2的元素,内存上相当于move,插入后L2的元素就clear了 |

list中还有一些操作,需要用到时大家可参阅list的文档说明。

示例1:(1)~(8)

cpp 复制代码
#include<iostream>
#include<list>
using namespace std;
// 打印函数
template<typename T>
void Print(list<T> ls)
{
    for (auto a : ls)
        cout << a << " ";
    cout << endl;
}
void test_list3()
{
    int array[] = { 16,2,77,29 };         // 以数组为迭代器区间构造
    list<int> l1(array, array + sizeof(array) / sizeof(int));
    Print(l1);

    l1.push_front(100); // (1) 头插
    Print(l1);

    l1.pop_front();     // (2) 头删
    Print(l1);

    l1.push_back(99);   // (3) 尾插
    Print(l1);

    l1.pop_back();      // (4) 尾删
    Print(l1);
    cout << endl;

    list<int>::iterator pos = find(l1.begin(),l1.end(),77);
    l1.insert(pos, 55); // (5) 指定位置之前插入 在77之前插入55
    Print(l1);

    pos = find(l1.begin(), l1.end(), 2);
    l1.erase(pos);      // (6) 删除指定位置的元素 删除2
    Print(l1);
    cout << endl;

    list<int> l2(3, 100);
    swap(l1, l2);       // (7) 交换两个列表的元素,大小可以不同
    Print(l1);
    Print(l2);

    l1.clear();
    Print(l1);          // (8) 清空列表的元素
    cout << "clear" << endl;
}
int main()
{
    test_list3();
    return 0;
}

示例2:(9)

| move链表 (1) | void splice (iterator position, list& x); // 把 x 的所有元素 移到 position前面 |
| move链表的某个迭代器指向的元素(2) | void splice (iterator position, list& x, iterator i); // 把 x 的 i 迭代器指向的元素 移到 position前 |

move链表的一段区间的元素 (3) void splice (iterator position, list& x, iterator first, iterator last); // 把 x 的从 [first, last) 迭代器之间的元素 移到 position

注意,splice可以移动其他链表的元素,也可以移动自身链表的元素。

cpp 复制代码
#include<iostream>
#include<list>
using namespace std;
// 打印函数
template<typename T>
void Print(T ls)
{
    for (auto a : ls)
        cout << a << " ";
    cout << endl;
}
void test_list5()
{
    int array1[] = {1,2,3,8,9,10 };        // 以数组为迭代器区间构造l1
    list<int> l1(array1, array1 + sizeof(array1) / sizeof(int));
    int array2[] = { 4,5,6,7 };           // 以数组为迭代器区间构造l2
    list<int> l2(array2, array2 + sizeof(array2) / sizeof(int));

    list<int>::iterator it = find(l1.begin(), l1.end(), 3);
    ++it;
    l1.splice(it, l2);      // (1) 将l2的所有元素移到l1的3元素之后
    Print(l1);
    Print(l2);
    cout << "splice" << endl;

    l1.splice(l1.begin(), l1, --l1.end());
    Print(l1);              // (2) 将l1的最后一个元素移动到第一个元素之前
    l1.splice(l1.end(), l1, l1.begin());
    Print(l1);              // (2) 将l1的第一个元素移动到最后一个元素的位置

    it = find(l1.begin(), l1.end(), 5);
    l1.splice(l1.begin(), l1, ++it, l1.end());
    Print(l1);              // (3) 将l1的从5后面到最后一个元素启动到第一个元素之前
}
int main()
{
    test_list5();
    return 0;
}

1.2.6 list的迭代器失效

前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

1.2.7 list 排序的使用

list自带的sort排序效果较差,不如list拷贝到vector调用算法库排序在拷贝回list效果好。

注意:排序测试不要用Debug,用Release测试出的才是真实水平。

cpp 复制代码
#include<iostream>
#include<list>
#include<vector>
#include<algorithm>
using namespace std;

void test_op1()
{
	srand(time(0));
	const int N = 1000000;

	list<int> lt1;
	vector<int> v;

	for (int i = 0; i < N; ++i)
	{
		auto e = rand() + i;
		lt1.push_back(e);
		v.push_back(e);
	}

	// 算法库中的sort
	int begin1 = clock();
	sort(v.begin(), v.end());
	int end1 = clock();

	// list自己的sort
	int begin2 = clock();
	lt1.sort();
	int end2 = clock();

	printf("vector sort:%d\n", end1 - begin1);
	printf("list sort:%d\n", end2 - begin2);
}

void test_op2()
{
	srand(time(0));
	const int N = 1000000;

	list<int> lt1;
	list<int> lt2;

	for (int i = 0; i < N; ++i)
	{
		auto e = rand() + i;
		lt1.push_back(e);
		lt2.push_back(e);
	}
	// list拷贝给vector,用算法库将vector排序,在把vector拷贝回list
	int begin1 = clock();
	// 拷贝vector
	vector<int> v(lt2.begin(), lt2.end());
	// 排序
	sort(v.begin(), v.end());
	// 拷贝回lt2
	lt2.assign(v.begin(), v.end());
	int end1 = clock();

	// list自带的sort
	int begin2 = clock();
	lt1.sort();
	int end2 = clock();

	printf("list copy vector sort copy list sort:%d\n", end1 - begin1);
	printf("list sort:%d\n", end2 - begin2);
}

int main()
{
	test_op1();		// vector调用库函数sort排序,和list自带排序对比
	cout << endl;
	test_op2();		// list拷贝到vector排完序再拷贝回list,和list自带排序对比
	return 0;		//总结:list自带的排序效果差
}

二. list的模拟实现

2.1 模拟实现list

【免费】C++list的模拟实现资源-CSDN文库

三. list与vector的对比

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

|-------|-----------------------------------------------------------------------|-------------------------------------------|
| | vector | list |
| 底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
| 随机访问 | 支持下标随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
| 插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低。 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
| 空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
| 迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
| 迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |

总结:

|--------|----|---------------------------|
| vector | 优点 | 1.尾插尾删效率不错,支持高效下标随机访问 |
| vector | 优点 | 2.物理空间连续,所以高速缓存利用率高 |
| vector | 缺点 | 1.空间需要扩容,扩容有一些代价(效率和空间浪费) |
| vector | 缺点 | 2.头部和中间插入删除效率低 |
| list | 优点 | 1.按需申请释放空间,不需要扩容 |
| list | 优点 | 2.任意位置插入删除 |
| list | 缺点 | 1.不支持下标随机访问 |

相关推荐
siy23333 分钟前
[c语言日寄]浮点数的排序
c语言·开发语言·数据结构·笔记·学习
天人合一peng7 分钟前
C# PaddleOCR字符识别
开发语言·c#
才华是浅浅的耐心8 分钟前
基于 Python 爬取 TikTok 搜索数据 Tiktok爬虫(2025.3.17)
开发语言·python
小安同学iter12 分钟前
SpringMVC(八)Knife4j 接口文档
java·开发语言·spring boot·spring·java-ee·intellij-idea
阿饼24023 分钟前
算法——图论——最短路径(多边权)
c++·算法·动态规划·图论
无名之逆39 分钟前
轻量级、高性能的 Rust HTTP 服务器库 —— Hyperlane
服务器·开发语言·前端·后端·http·rust
微笑伴你而行1 小时前
C Sharp 集合
c语言·开发语言·c#
dapeng-大鹏1 小时前
Go语言进化之旅:从1.18到1.24的语法变革
开发语言·golang
无名之逆1 小时前
探索Hyperlane:用Rust打造轻量级、高性能的Web后端框架
服务器·开发语言·前端·后端·算法·rust