当前位置: 首页 > article >正文

C++——list的了解和使用

目录

引言

forward_list与list

标准库中的list

一、list的常用接口

1.list的迭代器

2.list的初始化

3.list的容量操作

4.list的访问操作

5.list的修改操作

6.list的其他操作

二、list与vector的对比

结束语


引言

本篇博客要介绍的是STL中的list。

求点赞收藏评论关注!!!十分感谢!!!

forward_list与list

首先我们先简单了解一下forward_list与list:

forward_list与list都是C++标准模板库(STL)中的容器,它们提供了不同的链表实现,适用于不同的场景。

forward_list

定义与结构:

1.forward_list是C++11引入的一种容器,它提供了一种单向链表的数据结构。

2.它只维护一个指向下一个节点的指针,因此内存使用相对高效。

特点:

1.只能在单向遍历,即只能从前往后遍历,不能反向遍历。

2.在已知位置的情况下,插入和删除操作非常高效,时间复杂度为O(1)。

3.不支持通过索引访问元素,只能使用迭代器进行访问。

适用场景:

1.适用于需要频繁进行前向遍历和插入、删除操作的场景。

2.当内存使用要求较高,且不需要双向遍历时,forward_list是更好的选择。

list

定义与结构:

1.list是C++标准库中基于带头双向循环链表实现的容器。

2.它支持双向迭代器,可以从前往后和从后往前遍历。

特点:

1.在任何位置进行插入和删除操作的时间复杂度都是近似O(1)。

2.支持双向遍历,迭代器使用更灵活。

3.与forward_list相比,内存占用稍多,因为每个节点需要维护两个指针(一个指向前一个节点,一个指向下一个节点)。

适用场景:

1.适用于需要双向遍历和更灵活操作的场景。

2.当需要在列表中间频繁插入或删除元素时,list是更好的选择。

具体内容可以看看这两篇文章:

数据结构——单链表

数据结构——双向链表

本文的重点是list。

标准库中的list

一、list的常用接口

list接口

1.list的迭代器

list的迭代器访问元素与我们之前学习的其他容器的迭代器访问一样。

int main()
{
	list<int> li = { 1, 2, 3, 4, 5 };
	list<int>::iterator it = li.begin();
	cout << "顺序遍历:";
	while (it != li.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	cout << "逆序遍历:";
	list<int>::reverse_iterator rit = li.rbegin();
	while (rit != li.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	return 0;
}

由于list的迭代器是双向迭代器,支持++和--操作,因此可以在list中向前和向后遍历。

2.list的初始化

list的初始化与我们之前学习的其他容器的初始化一样,我们直接看个简单的使用示例:

int main()
{
    // 默认构造函数
    list<int> numbers1;
    cout << "默认构造: ";
    for (const auto& num : numbers1) 
    {
        cout << num << " ";
    }
    cout << endl;
    // n个val构造
    list<int> numbers2(5, 5);
    cout << "n个val构造: ";
    for (const auto& num : numbers2) 
    {
        cout << num << " ";
    }
    cout << endl;

    int arr[] = { 1, 2, 3, 4, 5 };
    // 通过vector的迭代器初始化
    list<int> numbers3(arr, arr + sizeof(arr) / sizeof(arr[0]));
    cout << "迭代器区间构造: ";
    for (const auto& num : numbers3) 
    {
        cout << num << " ";
    }
    cout << endl;

    list<int> numbers4 = { 6, 7, 8, 9, 10 };
    // 拷贝构造
    list<int> numbers5(numbers4);
    cout << "拷贝构造: ";
    for (const auto& num : numbers5) 
    {
        cout << num << " ";
    }
    cout << endl;

    numbers1 = numbers2;
    // 赋值重载
    cout << "赋值重载: ";
    for (const auto& num : numbers1) 
    {
        cout << num << " ";
    }
    cout << endl;
    return 0;
}

输出结果为:

3.list的容量操作
函数名称功能
size返回列表中元素的数量
max_size返回列表可容纳的最大元素数量
empty检查列表是否为空,是则返回 true,否则返回 false
resize重新设置列表中元素的数量,超过原来数量则用默认值填充
clear清空列表中的所有元素

这些函数也是很容易理解的,我们还是直接看代码示例:

int main()
{
	list<int> li = { 1,2,3,4,5 };
	cout << "Size of list: " << li.size() << endl;
	cout << "Max size of list: " << li.max_size() << endl;
	if (li.empty())
	{
		cout << "empty" << endl;
	}
	else
	{
		cout << "not empty" << endl;
	}
	li.clear();
	if (li.empty())
	{
		cout << "empty" << endl;
	}
	else
	{
		cout << "not empty" << endl;
	}
	return 0;
}

输出结果为

与deque这一容器一样,list也没有reserve这一接口。

int main()
{
	list<int> li = { 1,2,3 };
	li.resize(5);
	for (auto& num : li)
	{
		cout << num << " ";
	}
	cout << endl;
	li.resize(2);
	for (auto& num : li)
	{
		cout << num << " ";
	}
	return 0;
}

输出结果为;

4.list的访问操作
函数名称功能
back返回列表最后一个元素
front返回列表第一个元素

由于list 是一个双向链表,不支持高效的随机访问。在链表中,访问某个元素需要从头节点开始顺序遍历,直到找到目标元素。因此,为 list 提供下标运算符或 at 方法并不合适。

访问操作的使用示例如下:

int main()
{
	list<int> li = { 1,2,3 };
	cout << li.front() << endl;
	cout << li.back() << endl;

	return 0;
}

输出结果为:

5.list的修改操作

常用的修改操作有如下几个:

函数名称功能
push_back在列表尾部添加元素
push_front在列表头部添加元素
pop_back删除列表最后一个元素
pop_front删除列表第一个元素
insert在指定位置插入元素
erase删除指定位置或区间的元素
swap交换两个列表
assign使用指定列表替换原列表

这些接口我们也是十分熟悉了,我们直接看代码示例:

尾删和尾插:

int main()
{
	list<int> li;
	li.push_back(1);
	li.push_back(2);
	li.push_back(3);
	li.push_back(4);
	for (auto& num : li) 
	{
		cout << num << " ";
	}
	cout << endl;
	li.pop_back();
	for (auto& num : li)
	{
		cout << num << " ";
	}
	cout << endl;
	return 0;
}

输出结果为:

头删和头插:

int main()
{
	list<int> li;
	li.push_front(1);
	li.push_front(2);
	li.push_front(3);
	li.push_front(4);
	for (auto& num : li)
	{
		cout << num << " ";
	}
	cout << endl;
	li.pop_front();
	for (auto& num : li)
	{
		cout << num << " ";
	}
	cout << endl;
	return 0;
}

输出结果为:

assign和swap:

int main()
{
	list<int> li1 = { 1,1,1,1,1 };
	li1.assign(3, 3);
	for (auto& num : li1)
	{
		cout << num << " ";
	}
	cout << endl;
	list<int> li2 = { 2,2,2,2,2 };
	li1.swap(li2);
	for (auto& num : li1)
	{
		cout << num << " ";
	}
	cout << endl;
	for (auto& num : li2)
	{
		cout << num << " ";
	}
	return 0;
}

输出结果为:

insert:

int main()
{
	list<int> li = { 1,2,3,4,5 };
	list<int>::iterator it = li.begin();
	it = li.insert(it, 6);
	it = li.insert(it, 7);
	for (auto& num : li)
	{
		cout << num << " ";
	}
	return 0;
}

输出结果为:

erase:

int main()
{
	list<int> li = { 1,2,3,4,5 };
	list<int>::iterator it = li.begin();
	it = li.erase(it);
	for (auto& num : li)
	{
		cout << num << " ";
	}
	return 0;
}

输出结果为:

6.list的其他操作

接下来我们来学习list的其他操作:

函数名称功能描述
splice将元素从一个列表转移到另一个列表
remove移除具有特定值的元素
remove_if移除满足条件的元素
unique移除重复的值
sort对容器中的元素进行排序
merge合并已排序的列表
reverse反转元素的顺序

splice:

splice 是 list 提供的一个成员函数,用于将一个列表(list)中的元素移动到另一个列表中,而不需要进行元素复制或移动操作。

使用示例:

int main()
{
	list<int> li1 = { 1,2,3 };
	list<int> li2 = { 4,5,6 };
	list<int> li3 = { 7,8,9 };
	list<int> li4 = { 0 };
	// 将 li2 的所有元素拼接到 li1 的末尾
	// li1 现在包含 {1, 2, 3, 4, 5, 6}
	// li2 现在为空 {}
	li1.splice(li1.end(), li2);
	for (auto num : li1)
	{
		cout << num << " ";
	}
	cout << endl;
	// 获取 li3 的迭代器,指向第一个元素(7)
	auto begin1 = li3.begin();
	// 将迭代器向前移动一位,指向第二个元素(8)
	++begin1;

	// 将 li3 的第一个元素(7)移动到 li4 的末尾
	// li4 现在包含 {0, 7}
	// li3 现在包含 {8, 9}
	li4.splice(li4.end(), li3, li3.begin(), begin1);
	for (auto num : li4)
	{
		cout << num << " ";
	}
	return 0;
}

输出结果为:

remove:

用于从容器中移除所有等于指定值的元素。

与成员函数 erase 不同,成员函数 erase 按元素的位置擦除元素(使用迭代器),此函数  按元素的值删除元素。

int main()
{
	list<int> li = { 1,2,3,3,4,5 };
	li.remove(3);
	for (auto num : li)
	{
		cout << num << " ";
	}
	cout << endl;

	return 0;
}

输出结果为:

remove_if:

用于从容器中移除满足特定条件的元素。

bool fun(int num)
{
	return num == 3;
}

int main()
{
	list<int> li = { 1,2,3,3,4,5 };
	li.remove_if(fun);
	for (auto num : li)
	{
		cout << num << " ";
	}
	cout << endl;

	return 0;
}

输出结果为:

unique:

用于移除容器中连续重复的元素。

int main()
{
	list<int> li = { 1,2,3,3,4,5 };
	li.unique();
	for (auto num : li)
	{
		cout << num << " ";
	}
	return 0;
}

输出结果为:

sort:

用于对容器中的元素进行排序。

int main()
{
	list<int> li = { 9,1,5,3,2,4,8,0,7,6 };
	li.sort();
	for (auto num : li)
	{
		cout << num << " ";
	}
	return 0;
}

输出结果为:

merge:

用于将两个已排序的范围合并成一个有序范围。

要求输入的两个范围必须是有序的(通常是升序)。它会将两个范围中的元素按顺序合并到目标范围中。目标范围必须有足够的空间来存储合并后的结果。

int main()
{
	list<int> li1 = { 1,3,2,5,7 };
	list<int> li2 = { 2,3,4,6,8 };
	li1.sort();
	li2.sort();
	li1.merge(li2);
	for (auto num : li1)
	{
		cout << num << " ";
	}
	return 0;
}

输出结果为:

reverse:

用于反转容器中元素的顺序。

int main()
{
	list<int> li = { 9,1,5,3,2,4,8,0,7,6 };
	li.reverse();
	for (auto num : li)
	{
		cout << num << " ";
	}
	return 0;
}

输出结果为:

二、list与vector的对比

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

结束语

求点赞收藏评论关注!!!

感谢各位大佬的支持!!!


http://www.kler.cn/a/520251.html

相关文章:

  • 音频 PCM 格式 - raw data
  • 乒乓球日常烧拍日记之四海绵支撑
  • javascript-es6 (一)
  • Java 大视界 -- Java 大数据中的隐私增强技术全景解析(64)
  • 基于微信小程序的英语学习交流平台设计与实现(LW+源码+讲解)
  • 鸿蒙next 自定义日历组件
  • 美格智能AIMO智能体+DeepSeek-R1模型,AI应用的iPhone时刻来了
  • fantastic-admin5.0发布,Vue3 + django重构(一)后端
  • LeetCode题练习与总结:最短无序连续子数组--581
  • android的gradle
  • fiscoBcos中使用工具脚本在本地搭建一条4节点链
  • 周末总结(2024/01/25)
  • C#常考随笔3:对象比较obj1.Equals(obj2)== true时候,hashcode是否相同?
  • Java的循环结构
  • 【数据结构】_链表经典算法OJ:合并两个有序数组
  • 随笔十七、eth0单网卡绑定双ip的问题
  • 题解 洛谷 Luogu P1113 杂务 图论 BFS C++
  • 计算机网络之链路层
  • CommonAPI学习笔记-1
  • 【Oracle篇】使用Hint对优化器的执行计划进行干预(含单表、多表、查询块、声明四大类Hint干预)
  • 牛客训练营(一)补题
  • 【2025AI发展预测】2.2025的风口与发展,我们如何主动拥抱这一浪潮
  • 可见光通信代码仿真
  • 狗狗能吃萝卜吗?
  • vim可视化模式的进阶操作
  • C# 类(Class)