list

目录​​​​​​​

1. list的介绍及使用

1.1 list的介绍

1.2 list的使用(带头双向循环链表)

 1.2.1 list尾插及迭代器遍历链表

1.2.2 emplace_front

1.2.3 erase 

1.2.4 swap 

1.2.5 splice

1.2.6 sort 

2. list源码 

3. list的模拟实现

 3.1 注意点

3.1.1 list中的迭代器 

3.1.2 const迭代器

3.1.3 迭代器的构造 

3.1.4 swap接口  

3.1.5 size  

3.2 知识点

3.2.1 operator-> 

3.2.2 封装 

3.2.3 类里面可以不加模板参数 

4. list模拟实现 


1. list的介绍及使用

1.1 list的介绍

cplusplus.com/reference/list/list/?kw=listicon-default.png?t=O83Ahttps://cplusplus.com/reference/list/list/?kw=list

1.2 list的使用(带头双向循环链表)

 1.2.1 list尾插及迭代器遍历链表

int main()
{
	list<int> lt1;
	//尾插
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);
	lt1.emplace_back(10);
	//支持initializer_list初始化
	list<int> lt2 = { 1,2,3,4,5 };
	//迭代器 - 返回的底层就是带头双向循环链表
	list<int>::iterator it1 = lt1.begin();
	while (it1 != lt1.end())
	{
		cout << *it1 << " ";
		it1++;
	}
	cout << endl;
	//支持迭代器就支持范围for
	for (auto e : lt2)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

1.2.2 emplace_front

emplace_front和push_front的区别

int main()
{
	list<Pos> lt;
	Pos p1(1, 1);
    //构造+拷贝构造
	lt.push_back(p1);//pushback有名对象
	cout << "*******" << endl;
	lt.push_back(Pos(2,2));//pushback匿名对象
	cout << "*******" << endl;
	lt.push_back({3,3});//会隐式类型转换成Pos
	cout << "*******" << endl;
	lt.emplace_back(p1);
	cout << "*******" << endl;
	lt.emplace_back(Pos(2, 2));
	cout << "*******" << endl;
	//lt.emplace_back({ 3,3 });// - err
	//可以传多个参数,也可以将初始化pos的值给传过去
    //直接构造
	lt.emplace_back( 3,3);
	return 0;
}

 

 最后一个只有构造,他的优势是不仅仅可以传pos,也可以传构造pos的参数,构造pos的参数可能有很多哥,所以就有可变参数这个概念,接收到参数诸侯不去构造pos,参数直接往下传,传了之后直接去构造,所以其他的用法就是拷贝+构造,而最后一个是直接构造,所以emplace_back高效要看怎么用,像最后一个用法肯定更高效,像上面的用法就和push_back一样,但是不排除有的编译器会优化。

1.2.3 erase 

int main()
{
	list<int> lt1 = { 1,2,3,4,5 };
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;
	int x = 0;
	cin >> x;
	auto it = find(lt1.begin(), lt1.end(),x);
	if (it != lt1.end())
	{
		lt1.erase(it);
	}
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

 

1.2.4 swap 


list::swap - C++ Referenceicon-default.png?t=O83Ahttps://legacy.cplusplus.com/reference/list/list/swap/

 这个swap更高效,因为底层不是走什么深拷贝,只交换这里根节点的指针。

1.2.5 splice

list::splice - C++ Referenceicon-default.png?t=O83Ahttps://legacy.cplusplus.com/reference/list/list/splice/将另一个链表的值转移到这个链表(转移不是粘贴)。

// splicing lists
#include <iostream>
#include <list>

int main ()
{
  std::list<int> mylist1, mylist2;
  std::list<int>::iterator it;

  // set some initial values:
  for (int i=1; i<=4; ++i)
     mylist1.push_back(i);      // mylist1: 1 2 3 4

  for (int i=1; i<=3; ++i)
     mylist2.push_back(i*10);   // mylist2: 10 20 30

  it = mylist1.begin();
  ++it;                         // points to 2

  mylist1.splice (it, mylist2); // mylist1: 1 10 20 30 2 3 4
                                // mylist2 (empty)
                                // "it" still points to 2 (the 5th element)
                                          
  mylist2.splice (mylist2.begin(),mylist1, it);
                                // mylist1: 1 10 20 30 3 4
                                // mylist2: 2
                                // "it" is now invalid.
  it = mylist1.begin();
  std::advance(it,3);           // "it" points now to 30

  mylist1.splice ( mylist1.begin(), mylist1, it, mylist1.end());
                                // mylist1: 30 3 4 1 10 20

  std::cout << "mylist1 contains:";
  for (it=mylist1.begin(); it!=mylist1.end(); ++it)
    std::cout << ' ' << *it;
  std::cout << '\n';

  std::cout << "mylist2 contains:";
  for (it=mylist2.begin(); it!=mylist2.end(); ++it)
    std::cout << ' ' << *it;
  std::cout << '\n';

  return 0;
}

splice也可以用来调整链表的顺序。

int main()
{
	list<int> lt1 = { 1,2,3,4,5 };
	//LRU - LRU是Least Recently Used的缩写,即最近最少使用。
	int index = 0;
	while (cin >> index)
	{
		auto pos = find(lt1.begin(),lt1.end(), index);
		if (pos != lt1.end())
		{
			lt1.splice(lt1.begin(), lt1, pos);
		}
		for (auto e : lt1)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	return 0;
}

1.2.6 sort 

list::sort - C++ Referenceicon-default.png?t=O83Ahttps://legacy.cplusplus.com/reference/list/list/sort/

int main()
{
	list<int> lt1 = { 1,20,-3,40,5 };
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;
	lt1.sort();//默认排升序,排降序的话也有仿函数的概念
	lt1.sort(greater<int>());//降序 - 归并排序
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

这里有一个现象,list里面有一个sort的接口,而vector中没有,而vector用的话得去调算法库的sort, 

int main()
{
	vector<int> v1 = { 1,20,-3,40,5 };
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	//sort(v1.begin(), v1.end());//升序
	sort(v1.begin(), v1.end(), greater<int>());//降序
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

 因为算法库的sort在链表中是不能用的,那就不得不说到迭代器的功能角度的分类,之前的迭代器就是const迭代器,const反向迭代器,反向迭代器,这些都是从使用上的角度分的,其实还有从功能的角度上分的:

单向迭代器:单向就只支持++,不支持--                        forwad_list/unordered_xxx

双向迭代器:双向就是支持++也支持--                         list

随机迭代器:随机就是支持++,也支持--,还支持+和-     string/vector

库里面的sort虽然是模板啥都能传,但是名字以及暗示了必须传随机迭代器,因为sort的底层是快排,快排的底层为了效率会考虑三数取中,那就需要随机访问,底层走的是快排,需要随机访问,这样它的效率才会高,因为list不是随机迭代器,因为list会进行减,不是随机迭代器就不支持减,链表的双向迭代器不支持减,只有底层是连续的空间,两个位置减就算出距离了,链表可以减,但是效率太低了, 得遍历链表,所以以后用的时候得看名称,看它支持哪些迭代器,

看容器的迭代器是什么类型?

其实receres这个逆置函数得传双向迭代器。

那么问题就来了,如果要求是双向迭代器,那么传随机有没有问题,比如说上面的reverse传string或者vector的迭代器区间支持不?

其实是支持的, 其实这三种迭代器的关系可以说是继承关系,随机迭代器是特殊的双向迭代器,或者特殊的单向迭代器,随机迭代器的话就只能传随机,双向的话就只能传双向和随机,单向的话三个都可以传,

对比:

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

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

	vector<int> v;

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

	int begin1 = clock();
	// 排序
	sort(v.begin(), v.end());
	int end1 = clock();

	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();
		lt1.push_back(e);
		lt2.push_back(e);
	}

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

	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();
	test_op2();

	return 0;
}

vector算法底层用的是sort,sort底层是快排,快排底层用的是递归,也不是完全递归,使用了自省排序 ,递归在DeBug版本下特别吃亏,因为DeBug版本不会优化,一直建立栈帧,所以在测试递归的时候用Release。测试性能我们还要以Release为主,因为实践环境当中都是Release。

 test_op2()函数:

把链表中的数据拷贝到vector中排好序然后再拷贝回来都比链表中的sort函数排序要快,所以list的sort如果有少量的数据排序还可以,如果数据量多就不要使用list中的sort, 因为它的效率相比vector还是差很多。

2. list源码 

C Plus Plus: C++program code - Gitee.comicon-default.png?t=O83Ahttps://gitee.com/Axurea/c-plus-plus/tree/master/2024_10_13_STLSourceCode

3. list的模拟实现

 3.1 注意点

3.1.1 list中的迭代器 

3.1.2 const迭代器

 

3.1.3 迭代器的构造 

 

3.1.4 swap接口  

 

3.1.5 size  

 size的话就要遍历链表,效率有点低,所以我们可以添加一个成员变量。 

3.2 知识点

3.2.1 operator-> 

3.2.2 封装 

3.2.3 类里面可以不加模板参数 

 

4. list模拟实现 

C Plus Plus: C++program code - Gitee.comicon-default.png?t=O83Ahttps://gitee.com/Axurea/c-plus-plus/tree/master/2024_10_16_list

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值