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

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

目录

一. list的介绍及使用

1.1 list的介绍

1.2 list的使用

1.2.1 list的构造

1.2.2 list iterator迭代器的使用

1.2.3 list size & empty 大小判空

1.2.4 list element access 头尾引用

1.2.5 list modifiers 增删查改

1.2.6 list的迭代器失效

1.2.7 list 排序的使用

二. list的模拟实现

2.1 模拟实现list

三. 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
#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中的某个节点。

示例: 

#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

#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)

#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); 

// 把 的 i 迭代器指向的元素移到 position 

move链表的一段区间的元素 (3)

void splice (iterator position, list& x, iterator first, iterator last);

// 把 的从 [first, last) 迭代器之间的元素移到 position 

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

#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测试出的才是真实水平。

#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中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下: 

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

总结:

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


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

相关文章:

  • Qt 中工具窗体与普通窗体在任务栏中的区别
  • 基于微信小程序的网上商城
  • jmeter-sample
  • MySQL日期转字符串,字符串转日期的函数
  • Skia 图形引擎介绍
  • Vim软件使用技巧
  • Vue3组合式函数(刷新率 useFps)
  • 焊接机器人与线激光视觉系统搭配的详细教程
  • 深度学习零碎知识
  • Linux 如何查看当前使用的shell
  • 【解析 ECharts 图表样式继承与自定义】
  • 【Json-RPC框架】:Json序列化后,不能显式中文?增加emitUTF8配置
  • GIT使用git push后遇到报错的解决办法
  • centos 7误删/bash 拯救方法
  • Jackson 库进行 JSON 序列化时遇到了 ‌无限递归(Infinite Recursion)‌ 问题
  • LabVIEW烟气速度场实时监测
  • Qt常用控件之Layout总篇
  • 科技引领品质生活:三星生活家电用AI开启衣物洗护新纪元
  • 笔记本电脑关不了机是怎么回事 这有解决方法
  • Vue3 + ECharts 数据可视化实战指南