从C语言到C++_16(list的介绍和常用接口函数)

简介: 从C语言到C++_16(list的介绍和常用接口函数)

list是个双向带头循环链表

带头双向循环链表我们在数据结构与算法专栏中有过详细的讲解,并且还带大家实现过:

数据结构与算法⑦(第二章收尾)带头双向循环链表的实现_GR C的博客-CSDN博客

我们知道,带头双向循环链表是非常合适任意位置的插入和删除的,时间复杂度都是O(1).


list 在实际的运用中用的没有 vector 多,包括大家在刷题的时候 list 也出现的很少,


因为 list 不支持随机访问,有很多数据堆在那里你可能还需要排序一下,list 要排序,

效率就慢一点,所以用 vector 的情况较多。

但 list 在一些场景也需用到,比如需要频繁的插入和删除,特别是需要在头尾部插入和删除。

1. list 介绍和简单使用

1.1 list介绍

像前面一样查查文档:https://cplusplus.com/reference/list/list/?kw=list

3cda85878e3c4b8abadcab9501006208.png

① list 是一个顺序容器:


是允许你在任意位置进行  插入删除的顺序容器,并提供双向迭代器。


② list的底层是双向链表结构:

双向链表中每个元素存储在互不相关的独立结点中,在结点中通过两个指针指向其前后元素。

③ list 与 forward_list 非常相似:

它们很相似,最大的不同 forward_list 是单链表,只能向前迭代(也让其因此更简单高效)。

④ 与其他的序列式容器相比(array,vector,deque):

list 通常在任意位置进行插入、移除元素的执行效率更好。

list 和 forward_list 最大的缺陷是不支持任意位置的随机访问。举个例子:

如果要访问 list 中的第 6 个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,

在这段位置上迭代需要线性的时间开销。不仅如此,list 还需要一些额外的空间,

以保存每个结点的 "相关联信息"(对于存储类型较小元素的大 list 来说这 可能是一个重要的因素)

1.2 list简单接口函数

看看构造:

和vector差不多,看看其它接口函数:

看不懂英文又不想查的朋友看这里:

还是和vector差不多,还有一些特有的接口没截,等下着重讲讲。简单用下上面吧:

1.3 push_back 和遍历

首先思考一个问题:我们还能用 "下标 + 方框号" 的方式遍历吗?

不行的,因为 list 是链表,是通过指针连接的, 所以  list 不支持随机访问!

而 string 和 vector 可以,是因为它底层的结构是连续的数组,它的物理结构是连续的。

void Test_push_back()
{
  list<int> lt;
  lt.push_back(1);
  lt.push_back(2);
  lt.push_back(3);
  lt.push_back(4);
  lt.push_back(5);
 
  list<int>::iterator it = lt.begin();// 迭代器遍历1到5 
  while (it != lt.end())
  {
    cout << *it << " ";
    ++it;
  }
  cout << endl;
 
  for (auto& e : lt)// 范围for 把1到5乘2
  {
    e *= 2;
    cout << e << " ";
  }
  cout << endl;
 
  list<int>::reverse_iterator rit = lt.rbegin();//反向迭代器倒着遍历
  while (rit != lt.rend()) 
  {
    cout << *rit << " ";
    rit++;
  }
  cout << endl;
}

1.4 list常规接口函数使用

void Test_other()
{
  list<int> lt;
  lt.push_front(10);//头插四个
  lt.push_front(20);
  lt.push_front(30);
  lt.push_front(40);
  list<int>::iterator it = find(lt.begin(), lt.end(), 20);//在20前面插入50
  if (it != lt.end())
  {
    lt.insert(it, 50);
  }
  for (const auto& e : lt)
  {
    cout << e << " ";
  }
  cout << "size = " << lt.size() << endl;
 
  lt.pop_back();//尾删
  lt.pop_front();//头删
  it = find(lt.begin(), lt.end(), 20);//删除20
  if (it != lt.end())
  {
    lt.erase(it);
  }
  for (const auto& e : lt)
  {
    cout << e << " ";
  }
  cout << "size = " << lt.size() << endl;
}

2. list 的其它接口函数

因为list的存储不是连续的,库里的一些函数用不了(比如sort和reverse),

所以list提供了一些接口函数:

2.1 splice 接合

简单来说就是把一个链表转移到另一个链表里去:

void Test_splice()
{
  list<int> lt1;
  lt1.push_back(1);
  lt1.push_back(2);
  lt1.push_back(3);
  lt1.push_back(4);
  lt1.push_back(5);
 
  list<int> lt2;  
  lt2.push_back(10);
  lt2.push_back(20);
  lt2.push_back(30);
  lt2.push_back(40);
  lt2.push_back(50);
 
  lt1.splice(lt1.end(), lt2);//把lt2接合到lt1后面
  for (const auto& e : lt1)// 范围for遍历
  {
    cout << e << " ";
  }
  cout << endl;
}

2.2 remove 删完一个值

remove 只需要给一个元素的值,它就可以自己找自己删!

erase 还需要搞个搞个迭代器,然后还要 if 判断一下,但 remove 就不一样了:

void Test_remove()
{
  list<int> lt;
  lt.push_back(10);
  lt.push_back(20);
  lt.push_back(30);
  lt.push_back(40);
 
  lt.push_back(10);
  lt.push_back(20);
  lt.push_back(30);
  lt.push_back(40);
  for (const auto& e : lt)
  {
    cout << e << " ";
  }
  cout << endl;
 
  // 如果存在元素,删完
  lt.remove(10);
  for (const auto& e : lt)
  {
    cout << e << " ";
  }
  cout << endl;
 
  // 如果待删元素不存在,则无事发生
  lt.remove(50);
  for (const auto& e : lt)
  {
    cout << e << " ";
  }
  cout << endl;
}

2.3 sort和reverse

unique是去重,但是再后面点学的set天生带去重,所以其它接口后面有机会再讲行了。

前面说到:因为list的存储不是连续的,库里的一些函数用不了(比如sort和reverse)。

但是list提供的作用和其差不多,值得注意的是库里的sort底层是快排,快排,链表是用不了的,

那list用上面排序呢?冒泡等是可以的,就是效率太低,这时归并排序就派上用场了,

而且不会多开空间。但是排序大量数据时list的排序就比快排慢了,甚至你拷贝list的数据到vector

用库里的快排,排完再拷回list的时间还要比list自己排得快,但是数据量小的话也差不多:

void Test_sort_reverse()
{
  list<int> lt;
  lt.push_back(1);
  lt.push_back(20);
  lt.push_back(2);
  lt.push_back(40);
  lt.push_back(5);
  lt.push_back(10);
  lt.push_back(30);
  lt.push_back(4);
  lt.push_back(4);
  lt.push_back(50);
  for (const auto& e : lt)
  {
    cout << e << " ";
  }
  cout << endl;
 
  lt.sort();
  for (const auto& e : lt)
  {
    cout << e << " ";
  }
  cout << endl;
  
  lt.reverse();// 懂我意思把
  for (const auto& e : lt)
  {
    cout << e << " ";
  }
  cout << endl;
}

本章完。

list没有很好的OJ题,且一些选择题打算放在下一篇了,模拟实现之后再写好一点。

目录
相关文章
|
1月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
62 23
|
1月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
66 15
|
1月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
60 24
|
1月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
63 16
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
36 3
|
1月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
19 2
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
51 1
|
2月前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
92 10
|
2月前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
68 9
|
2月前
|
存储 Unix Serverless
【C语言】常用函数汇总表
本文总结了C语言中常用的函数,涵盖输入/输出、字符串操作、内存管理、数学运算、时间处理、文件操作及布尔类型等多个方面。每类函数均以表格形式列出其功能和使用示例,便于快速查阅和学习。通过综合示例代码,展示了这些函数的实际应用,帮助读者更好地理解和掌握C语言的基本功能和标准库函数的使用方法。感谢阅读,希望对你有所帮助!
62 8