STL库——list(类函数学习)

发布于:2025-08-29 ⋅ 阅读:(16) ⋅ 点赞:(0)

ʕ • ᴥ • ʔ

づ♡ど

 🎉 欢迎点赞支持🎉

个人主页:励志不掉头发的内向程序员

专栏主页:C++语言


前言

在学习完vector过后我们就会发现它和string相比成员函数简化了非常多,学起来也不想string那样困难,这一章节我们学习的list类时我们就会发现它的成员函数和vector相比十分相像,没有多少不同,这就是STL库的魅力所在,我们一起来看看吧


一、标准库中的list类

我们观看STL库就会发现我们vector有的成员函数我们list基本上也有,这些不同容器之间的区别在于底层的不同,但是STL库封装起来我们的上层基本上没有什么区别。

我们可以看到vector的成员函数和list是十分相像的。

二、list的成员函数

我们list类都保存在我们list的头文件中

#include <list>

2.1、构造函数

list构造也分为4种,无参构造、带参构造、迭代器区间构造以及拷贝构造

int main()
{
	// 无参构造
	list<int> a;

	// 带参构造,也就是用4(n)个0(val)构造b
	list<int> b(4, 0);

	// 迭代器区间构造
	vector<int> c({ 1, 2, 3, 4, 5, 6, 7, 8, 9 });
	list<int> d(c.begin() + 1 , c.end() - 4);

	// 拷贝构造
	list<int> e(d);

	return 0;
}

我们可以看到和我们vector的构造函数是一样的。但是与vector不同的是list不支持下标+[]访问我们任意一个元素了,因为list想要支持得从前往后遍历,成本很高。所以想要遍历可以用迭代器和范围for。

2.2、析构函数

系统自动调用,我们不用关心。

2.3、赋值重载

赋值重载也很简单。

int main()
{
	list<int> a;
	list<int> b;

	a.push_back(1);
	a.push_back(2);
	a.push_back(3);
	a.push_back(4);

    // 赋值重载
	b = a;

	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;

	cout << "b: ";
	for (auto x : b)
	{
		cout << x << " ";
	}
	cout << endl;

	return 0;
}

2.4、运算符重载

我们的比较运算符大致规则就和vector一样,这里就不过多赘述了。

2.5、插入和删除函数

2.5.1、push_front/pop_front函数

这两个函数就是头插和头删,因为list的特殊结构,导致它的头删和头插都很容易。

int main()
{
	list<int> a(2, 0);
	a.push_front(1);
	a.push_front(2);
	a.push_front(3);

	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;

	a.pop_front();
	a.pop_front();
	a.pop_front();

	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;

	return 0;
}

2.5.2、push_back/pop_back函数

尾插和尾删也很容易,就不多说了。

int main()
{
	list<int> a(2, 0);
	a.push_back(1);
	a.push_back(2);

	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;

	a.pop_back();
	a.pop_back();

	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;

	return 0;
}

2.5.3、insert/erase函数

这两个函数和vector一样是依靠我们迭代器去插入和删除的。

但是和vector不同之处在于list的迭代器不能进行+/-,如果想要插入或者删除第3个位置的元素,不能直接begin+3或者end-*,只能用一个循环先去改变迭代器位置再去插入或者删除。

int main()
{
	list<int> a({ 1, 2, 3, 4, 5, 6, 7, 8, 9 });
	list<int>::iterator it = a.begin();

	int k = 3;
	while (k--)
	{
		it++;
	}

	a.erase(it);

	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;
	return 0;
}

这使得本来使用率不高的两个函数在list中更低了。

2.6、其他成员函数

2.6.1、迭代器

我们迭代器和原来的用法差不多,比如来个循环遍历list。

int main()
{
	list<int> a;
	list<int> b;

	a.push_back(1);
	a.push_back(2);
	a.push_back(3);
	a.push_back(4);

	list<int>::iterator it = a.begin();

	cout << "a: ";
	while(it != a.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	return 0;
}

可以很成功的答应出来,但是这个迭代器和vector还是有区别的,比如它不支持加减。

vector迭代器erase:

int main()
{
	vector<int> a({ 1, 2, 3, 4, 5, 6, 7, 8, 9 });
	a.erase(a.begin() + 5);

	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;
	return 0;
}

很成功的给删掉了。

list迭代器erase:

int main()
{
	list<int> a({ 1, 2, 3, 4, 5, 6, 7, 8, 9 });
	a.erase(a.begin() + 5);

	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;
	return 0;
}

直接就运行失败了。

这是因为我们链表的迭代器不是原生指针了,它底层的物理空间不是连续的。

我们之前从功能上划分把迭代器分为:

  • iterator
  • const_iterator
  • reverse_iterator
  • const_reverse_iterator

但是迭代器其实还可以从性质方面进行划分:

  • 单向迭代器
  • 双向迭代器
  • 随机迭代器

我们vector/string/deque就是经典的随机迭代器,这种迭代器特点就是除了支持++/--外还支持+/-。而我们list/map/set就是经典的双向迭代器,这种迭代器只支持++/--,不支持+/-。而单向迭代器有forwad_list/unordered_map/unordered_set,这种比双向迭代器支持的还少,只支持++,连--都不支持。它们的性质是由它们底层来决定的。

2.6.2、merge函数

这个函数的作用就是合并我们的链表。

当然它有一个前提条件就是这两个链表必须是有序的。

int main()
{
	list<double> a({ 1.1, 2.2, 3.3, 4.4 });
	list<double> b({ 0.9, 2.1, 3.2, 4.3 });

	a.merge(b);
	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;

	cout << "b: ";
	for (auto x : b)
	{
		cout << x << " ";
	}
	cout << endl;
	return 0;
}

如果不是有序的就会报错。

2.6.3、unique函数

这个函数的主要作用就是去重,它会把list中相同的元素删掉一个,只保留一个,用法和merge相似,list必须有序,不然程序就会失效。

int main()
{
	list<int> a({ 1, 2, 2, 3, 4, 4, 5 });

	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;

	a.unique();

	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;

	return 0;
}

无序的话:

2.6.4、remove函数

这个函数的作用就是删除一个值,它和erase很像,但是erase是给一个迭代器,而这个是给一个元素,它先是去查找这个元素在不在list中,在就把它删除,不在就不动。

int main()
{
	list<int> a({ 1, 2, 2, 5, 4, 4, 5 });
	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;

	a.remove(2);
	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;

	return 0;
}

2.6.5、splice函数

这个函数的作用是把一个list中的数据转移到另外一个list中去,转移的位置是在position之前。

int main()
{
	list<int> a({ 1, 2, 3, 4, 5, 6, 7, 8, 9 });
	list<int> b({ 10, 20, 30, 40 });
	list<int>::iterator it = a.begin();
	it++;
	
	// 相当于是吧b的数据转移到a的it位置之前
	a.splice(it, b);

	cout << "a: ";
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;

	cout << "b: ";
	for (auto x : b)
	{
		cout << x << " ";
	}
	cout << endl;

	return 0;
}

其他的像empty、clear、size之类的就不过多说明了,用法和作用vector一样。

三、流输入/输出

list没有对流输入和输出进行重载,原因可能是不方便,而且没有必要,我们就老老实实自己做就好了。


总结

以上便是我们list常用的成员函数啦。对比起string,我们现在应该很容易就能接受这些内容,这就是STL库封装的好处,下节课就是list的模拟实现了,大家下一章节再见。

🎇坚持到这里已经很厉害啦,辛苦啦🎇

ʕ • ᴥ • ʔ

づ♡ど


网站公告

今日签到

点亮在社区的每一天
去签到