C++ STL(七):list容器


1 list基本概念

作用链表链式存储数据。STL中的链表是双向循环链表。

链表:链表list是物理存储单元上非连续的存储结构,数据元素的逻辑顺序通过链表中的指针链接实现。链表由一系列节点组成。

节点的组成
数据域:存储数据元素
指针域:存储下一个节点的地址(指针指向下一个节点)。


list容器的迭代器
链表采用不连续的存储结构,故链表list的迭代器只支持前移后移(不支持跳跃式访问),属于双向迭代器

注:list容器插入或删除元素时,不会导致原有迭代器失效
vector容器插入或删除元素时,会导致原有迭代器失效


list容器的数据结构
STL中的list容器,采用双向循环链表的数据结构:
第1个节点的前继节点为最后1个节点(第1个节点的prev指针指向最后1个节点);
最后1个节点的后继节点为第1个节点(最后1个节点的next指针指向第1个节点)。
list结构
push_front()头插法插入节点。
pop_front()头删法删除节点。
push_back()尾插法插入节点。
pop_back()尾删法删除节点。
front():获取list容器的第1个节点
back():获取list容器的最后1个节点
begin():获取起始迭代器,指向容器中的第1个节点
end():获取结束迭代器,指向容器中的最后1个节点下一个位置


list的优缺点
优点
①采用动态存储分配,不会造成内存浪费或溢出;
②链表,可在任意位置插入或删除元素,只需修改指针指向,无需移动大量元素。

缺点
①链表包括数据域和指针域,占用内存空间较大;
②元素访问/遍历速度较慢。

注1:链表增删快、查询慢;数组增删慢、查询快。
注2:listvector是C++ STL中最常用的容器,各有优缺点。


2 list构造函数

作用:创建list容器。

注:使用list容器时,需包含头文件#include <list>

函数原型
(1)list<T> lst;:默认无参构造函数,采用类模板实现。
(2)list(lst.begin(), lst.end());:拷贝容器对象lst[begin(), end())区间左闭右开,包左不包右)的元素进行初始化。
(3)list(n, elem);:有参构造函数,使用nelem元素进行初始化。
(4)list(const list&lst);:拷贝构造函数,使用已有list对象初始化新的对象。

示例:list构造函数

#include <iostream>
using namespace std;
#include <list>


//函数模板:遍历list容器的通用函数
template<typename T>
void printList(const list<T> &lst) {	//形参使用const,避免被修改
	//形参使用const后,遍历时需使用只读迭代器const_iterator
	//使用typename关键字,防止编译器报错:C2760(语法错误,意外标记“标识符”)
	for (typename list<T>::const_iterator it = lst.begin(); it != lst.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

int main() {
	//1.无参构造函数
	list<int> lst;
	lst.push_back(5);
	lst.push_back(7);
	lst.push_back(9);
	lst.push_front(3);
	lst.push_front(1);
	printList<int>(lst);	//1 3 5 7 9

	//2.区间构造
	list<int> l1(lst.begin(), lst.end());
	printList<int>(l1);		//1 3 5 7 9

	//3.拷贝构造
	list<int> l2(l1);
	printList<int>(l2);		//1 3 5 7 9

	//4.n个elem元素构造
	list<int> l3(5, 6);
	printList<int>(l3);		//6 6 6 6 6

	return 0;
}

3 list赋值【operator=、assign()】和交换【swap()】

赋值操作:通过重载赋值运算符operator=成员函数assign(),对list容器进行赋值。

函数原型
(1)list& operator=(const list &lst);:重载赋值运算符,使用目标list容器,对当前list容器赋值。
(2)assign(begin, end);:拷贝目标list容器中[begin(), end())区间左闭右开,包左不包右)的元素,对当前list容器赋值。
(3)assign(n, elem);:使用nelem元素,对当前list容器赋值。


交换操作:实现list容器的交换。
swap(lst);:将目标list容器lst与自身的元素互换。

示例:list赋值与交换

#include <iostream>
using namespace std;
#include <list>


//函数模板:遍历list容器的通用函数
template<typename T>
void printList(const list<T>& lst) {	//形参使用const,避免被修改
	//形参使用const后,遍历时需使用只读迭代器const_iterator
	//使用typename关键字,防止编译器报错:C2760(语法错误,意外标记“标识符”)
	for (typename list<T>::const_iterator it = lst.begin(); it != lst.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

int main() {
	//无参构造函数
	list<int> lst;
	lst.push_back(5);
	lst.push_back(7);
	lst.push_back(9);
	lst.push_front(3);
	lst.push_front(1);
	printList<int>(lst);	//1 3 5 7 9

	//1.重载赋值运算符
	list<int> l1;
	l1 = lst;		
	printList<int>(l1);		//1 3 5 7 9

	//2.assign()赋值
	list<int> l2;
	l2.assign(lst.begin(), lst.end());
	printList<int>(l2);		//1 3 5 7 9

	//3.n个elem元素:assign(n, elem)
	list<int> l3;
	l3.assign(3, 6);
	printList<int>(l3);		//6 6 6

	/* 容器交换 */
	cout << "交换前l2:" << endl;
	printList<int>(l2);		//1 3 5 7 9
	cout << "交换前l3:" << endl;
	printList<int>(l3);		//6 6 6

	l3.swap(l2);

	cout << "交换后l2:" << endl;
	printList<int>(l2);		//6 6 6
	cout << "交换后l3:" << endl;
	printList<int>(l3);		//1 3 5 7 9

	return 0;
}

4 list大小操作【size()、resize()】

作用:操作list容器的大小(即元素个数)。

函数原型
(1)empty();:判断容器是否为空。
(2)size();:获取容器的大小,即元素个数
(3)resize(int num);重新指定容器的长度为num
若容器变长,则以默认值0填充新位置;若容器变短,则容器末尾超出新长度的元素被删除
(4)resize(int num, elem);重新指定容器的长度为num
若容器变长,则以指定值elem填充新位置;若容器变短,则容器末尾超出新长度的元素被删除

注:list容器不存在容量的概念,即不存在capacity()成员函数。
list支持动态存储分配,通过维护指针域允许随时插入或删除节点。

示例

#include <iostream>
using namespace std;
#include <list>

//函数模板:遍历list容器的通用函数
template<typename T>
void printList(const list<T>& lst) {	//形参使用const,避免被修改
	//形参使用const后,遍历时需使用只读迭代器const_iterator
	//使用typename关键字,防止编译器报错:C2760(语法错误,意外标记“标识符”)
	for (typename list<T>::const_iterator it = lst.begin(); it != lst.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

int main() {
	//无参构造函数
	list<int> lst;
	lst.push_back(5);
	lst.push_back(7);
	lst.push_back(9);
	lst.push_front(3);
	lst.push_front(1);
	printList<int>(lst);	//1 3 5 7 9

	//size():容器大小
	cout << "容器大小:" << lst.size() << endl;		//5

	//empty():判断容器是否为空
	cout << (lst.empty() ? "空" : "非空") << endl;	//非空

	//resize(int num);:重新指定容器的长度为num。
	//若容器变长,则以默认值0填充新位置;若容器变短,则容器末尾超出新长度的元素被删除。
	lst.resize(10);
	printList<int>(lst);	//1 3 5 7 9 0 0 0 0 0

	lst.resize(3);
	printList<int>(lst);	//1 3 5

	//resize(int num, elem); :重新指定容器的长度为num。
	//若容器变长,则以指定值elem填充新位置;若容器变短,则容器末尾超出新长度的元素被删除。
	lst.resize(8, 6);
	printList<int>(lst);	//1 3 5 6 6 6 6 6

	return 0;
}

5 list插入【push_front()、push_back()、insert()】和删除【pop_front()、pop_back()、erase()、clear()、remove()】

(1)list容器插入元素:使用成员函数push_front(..)push_back(..)insert(..)向list容器插入元素。

函数原型
push_front(elem);头插法,向list容器的头部插入元素elem
push_back(elem);尾插法,向list容器的尾部插入元素elem
insert(const_iterator pos, elem);迭代器指向位置pos插入元素elem
insert(const_iterator pos, int n, elem);迭代器指向位置pos插入n个元素elem
insert(const_iterator pos, const_iterator begin, const_iterator end);迭代器指向位置pos插入[begin(), end())区间左闭右开,包左不包右)的元素,无返回值。


(2)list容器删除元素:使用成员函数pop_front()pop_back(..)erase(..)clear(..)删除list容器中的元素。

函数原型
pop_front()头删法,删除list容器头部的第1个元素
pop_back();尾删法,删除list容器尾部的最后1个元素
erase(const_iterator pos);:删除迭代器指向位置的元素。
erase(const_iterator start, const_iterator end);:删除迭代器指向位置[start, end)区间的所有元素。
clear();:清空容器中的所有元素
remove(elem);:删除容器中与指定值elem匹配的所有元素

注1:清空容器的所有元素,lst.clear();等价于lst.erase(lst.begin(), lst.end());
注2:插入元素insert(..)、删除元素erase(..)均需要迭代器对象作为参数。

示例:list容器插入与删除元素

#include <iostream>
using namespace std;
#include <list>

//函数模板:遍历list容器的通用函数
template<typename T>
void printList(const list<T>& lst) {	//形参使用const,避免被修改
	//形参使用const后,遍历时需使用只读迭代器const_iterator
	//使用typename关键字,防止编译器报错:C2760(语法错误,意外标记“标识符”)
	for (typename list<T>::const_iterator it = lst.begin(); it != lst.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

int main() {
	list<int> lst;
	//尾插
	lst.push_back(5);
	lst.push_back(7);
	lst.push_back(9);
	
	//头插
	lst.push_front(3);
	lst.push_front(1);
	printList<int>(lst);	//1 3 5 7 9

	//尾删
	lst.pop_back();
	printList<int>(lst);	//1 3 5 7

	//头删
	lst.pop_front();
	printList<int>(lst);	//3 5 7

	//insert()-插入单个元素
	//在第0个与第1个索引位置插入元素4
	lst.insert(++lst.begin(), 4);
	printList<int>(lst);	//3 4 5 7

	//erase()-删除单个元素
	//删除第1个索引位置的元素
	lst.erase(++lst.begin());
	printList<int>(lst);	//3 5 7

	//insert()-插入多个元素
	//在第0个索引位置前插入3个1
	lst.insert(lst.begin(), 3 , 0);
	printList<int>(lst);	//0 0 0 3 5 7


	list<int> l1;
	//insert()-插入区间元素
	l1.insert(l1.begin(), lst.begin(), lst.end());
	printList<int>(l1);		//0 0 0 3 5 7

	//remove()-删除指定值的元素
	l1.remove(0);
	printList<int>(l1);		//3 5 7

	//erase()-删除区间元素
	l1.erase(l1.begin(), l1.end());
	printList<int>(l1);		//(空)

	//remove()-清空元素
	lst.clear();
	printList<int>(lst);	//(空)

	return 0;
}

6 list数据存取【front()、back()】

作用:对list容器中数据进行存取。

注1:链表底层采用非连续线性的内存空间,且仅支持双向迭代器(只能前移和后移),无法使用索引跳跃式地访问元素。故不支持重载赋值运算符operator[]成员函数at(int index)跳跃式地访问指定索引位置元素。
注2:list容器支持双向迭代器,允许前移it--;和后移it++;
list容器不支持随机访问迭代器,不允许跳跃式移动it += 1;it -= 2;,否则编译器报错:没有与操作数匹配的 += 或 -= 运算符

函数原型
front();:返回容器的第1个元素
back();:返回容器的最后1个元素

示例

#include <iostream>
using namespace std;
#include <list>

//函数模板:遍历list容器的通用函数
template<typename T>
void printList(const list<T>& lst) {	//形参使用const,避免被修改
	//形参使用const后,遍历时需使用只读迭代器const_iterator
	//使用typename关键字,防止编译器报错:C2760(语法错误,意外标记“标识符”)
	for (typename list<T>::const_iterator it = lst.begin(); it != lst.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

int main() {
	list<int> lst;
	//尾插
	lst.push_back(1);
	lst.push_back(3);
	lst.push_back(5);
	lst.push_back(7);
	lst.push_back(9);
	printList<int>(lst);	//1 3 5 7 9

	cout << "第1个元素:" << lst.front() << endl;	//1
	cout << "最后1个元素:" << lst.back() << endl;	//9

	//修改元素
	lst.front() = 0;
	lst.back() = 100;
	printList<int>(lst);	//0 3 5 7 100


	//list容器支持双向迭代器,允许前移和后移
	list<int>::iterator it = lst.begin();
	it++;	//允许后移
	it--;	//允许前移

	//list容器不支持随机访问迭代器,不允许跳跃式移动
	//it += 1;	//报错:没有与操作数匹配的 += 运算符
	//it += 3;	//报错:没有与操作数匹配的 += 运算符
	//it -= 1;	//报错:没有与操作数匹配的 -= 运算符
	//it -= 3;	//报错:没有与操作数匹配的 -= 运算符

	return 0;
}

7 list反转【reverse()】和排序【sort()】

(1)list反转
reverse();:将容器中的元素反转。


(2)list排序
sort();:将容器中的数据进行排序,默认升序排序。

注1:链表排序的sort()函数是list容器的成员函数,而不是标准算法头文件<algorithm>全局函数
注2:可通过回调函数仿函数实现sort()函数按自定义规则排序。


回调函数-实现自定义排序

//回调函数
bool myCompare(int val1, int val2){
	//return val1 < val2;	//升序排序
	return val1 > val2;		//降序排序
}

//使用回调函数,按自定义规则排序
lst.sort(myCompare);

仿函数/函数对象-实现自定义排序

//函数对象/仿函数
class MyCompare {
public:
	//重载函数调用运算符()
	bool operator()(int val1, int val2) {
		//return val1 < val2;	//升序排序
		return val1 > val2;		//降序排序
	}
};

//使用仿函数/函数对象,按自定义规则排序
//MyCompare mc;
//lst.sort(mc);			//函数对象mc
lst.sort(MyCompare());	//匿名函数对象MyCompare()

示例:使用回调函数和仿函数,实现内置数据类型排序

#include <iostream>
using namespace std;
#include <list>

//函数模板:遍历list容器的通用函数
template<typename T>
void printList(const list<T>& lst) {	//形参使用const,避免被修改
	//形参使用const后,遍历时需使用只读迭代器const_iterator
	//使用typename关键字,防止编译器报错:C2760(语法错误,意外标记“标识符”)
	for (typename list<T>::const_iterator it = lst.begin(); it != lst.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

//反转
void func1() {
	list<int> lst;
	//尾插
	lst.push_back(5);
	lst.push_back(8);
	lst.push_back(3);
	lst.push_back(6);
	lst.push_back(1);

	cout << "反转前:" << endl;
	printList<int>(lst);	//5 8 3 6 1

	//反转
	lst.reverse();
	cout << "反转后:" << endl;
	printList<int>(lst);	//1 6 3 8 5
}

//回调函数-自定义排序规则
bool myCompare(int val1, int val2) {
	//return val1 < val2;	//升序排序
	return val1 > val2;	//降序排序
}

//函数对象/仿函数-自定义排序规则
class MyCompare {
public:
	//重载函数调用运算符()
	bool operator()(int val1, int val2) {
		//return val1 < val2;	//升序排序
		return val1 > val2;	//降序排序
	}
};


//排序
void func2() {
	list<int> lst;
	//尾插
	lst.push_back(5);
	lst.push_back(8);
	lst.push_back(3);
	lst.push_back(6);
	lst.push_back(1);

	cout << "排序前:" << endl;
	printList<int>(lst);	//5 8 3 6 1

	//默认升序排序
	lst.sort();
	cout << "升序排序后:" << endl;
	printList<int>(lst);	//1 3 5 6 8

	//降序排序:使用回调函数
	//lst.sort(myCompare);

	//降序排序:使用仿函数/函数对象
	//MyCompare mc;
	//lst.sort(mc);			//函数对象
	lst.sort(MyCompare());	//匿名函数对象

	cout << "降序排序后:" << endl;
	printList<int>(lst);	//8 6 5 3 1
}

int main() {
	//func1();
	func2();

	return 0;
}

8 案例练习:自定义数据类型的排序

注1:对于自定义数据类型,必须指定排序规则,否则编译器不清楚如何排序。
注2:高级排序是在排序规则的基础上额外制定一次或多次逻辑规则。

案例描述
对自定义数据类型Person进行排序,Person类包括姓名、成绩、年龄等成员属性。
排序规则:先按成绩降序排序,再按年龄升序排序。

示例:自定义数据类型Person的排序

#include <iostream>
using namespace std;
#include <list>

class Person {
public:
	string name;
	int score;
	int age;

	Person(string name, int score, int age) {
		this->name = name;
		this->score = score;
		this->age = age;
	}
};

//排序规则的回调函数
bool myCompare1(Person p1, Person p2) {
	//先按照成绩socre降序排序
	if (p1.score == p2.score) {
		//成绩socre相同时,再按年龄age升序排序
		return p1.age < p2.age;
	}
	else {
		return p1.score > p2.score;
	}
}

//排序规则的回调函数:使用三目运算符简化
bool myCompare2(Person p1, Person p2) {
	//成绩不等时,按成绩降序排序
	//成绩相等时,按年龄升序排序
	return (p1.score == p2.score) ? (p1.age < p2.age) : (p1.score > p2.score);
}

int main() {
	list<Person> personList;

	//创建Person对象
	Person p1("普通青年", 60, 20);
	Person p2("聪明青年", 100, 20);
	Person p3("油腻中年", 60, 40);
	Person p4("勤奋青年", 80, 20);
	Person p5("摸鱼青年", 60, 30);
	Person p6("天才少年", 100, 15);

	//向list容器添加Person对象
	personList.push_back(p1);
	personList.push_back(p2);
	personList.push_back(p3);
	personList.push_back(p4);
	personList.push_back(p5);
	personList.push_back(p6);

	for (list<Person>::iterator it = personList.begin(); it != personList.end(); it++) {
		cout << "姓名:" << (*it).name << ", "
			<< "成绩:" << it->score << ", "
			<< "年龄:" << it->age << endl;
	}

	cout << "----------排序后-----------" << endl;

	//自定义排序:调用回调函数
	//personList.sort(myCompare1);
	personList.sort(myCompare2);

	for (list<Person>::iterator it = personList.begin(); it != personList.end(); it++) {
		cout << "姓名:" << (*it).name << ","
			<< "成绩:" << it->score << ","
			<< "年龄:" << it->age << endl;
	}
}

输出结果:

姓名:普通青年, 成绩:60, 年龄:20
姓名:聪明青年, 成绩:100, 年龄:20
姓名:油腻中年, 成绩:60, 年龄:40
姓名:勤奋青年, 成绩:80, 年龄:20
姓名:摸鱼青年, 成绩:60, 年龄:30
姓名:天才少年, 成绩:100, 年龄:15
----------排序后-----------
姓名:天才少年,成绩:100,年龄:15
姓名:聪明青年,成绩:100,年龄:20
姓名:勤奋青年,成绩:80,年龄:20
姓名:普通青年,成绩:60,年龄:20
姓名:摸鱼青年,成绩:60,年龄:30
姓名:油腻中年,成绩:60,年龄:40
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C++ STL容器中的栈(stack)是一种后进先出(LIFO)的数据结构。栈的基本操作包括入栈(push)、出栈(pop)、查看栈顶元素(top)和判断栈是否为空(empty)。栈可以用数组或链表实现,但使用STL容器可以更方便地实现栈的操作。在STL中,栈是由适配器(adapter)stack实现的,可以使用push、pop、top和empty等成员函数来操作栈。栈的应用场景包括函数调用、表达式求值、括号匹配等。 ### 回答2: 栈(stack)是C++ STL(标准模板库)中的一个容器,是一个后入先出(Last In, First Out,LIFO)的数据结构。堆栈的基本操作是退栈(Pop)和入栈(Push),即在栈顶插入和删除元素。除了这两个基本操作,堆栈还提供了访问栈顶元素的方法,即Top()。 堆栈可以通过STL中的std::stack<T>来使用,其中T是元素的类型。堆栈的定义非常简单,只需要使用一个std::stack<T>对象即可。在使用之前,需要包含头文件<stack>。 堆栈的主要特性是插入和删除元素的时间复杂度为常数时间O(1),因为栈只需要在栈顶进行操作。堆栈一般用于实现递归、表达式求值、内存分配等。例如,在递归深度优先搜索中,可以使用堆栈来存储遍历的路径。 堆栈的操作非常简单,以下是常用的操作列表: 1. push():将一个元素插入栈顶。 2. pop():删除栈顶元素。 3. top():返回栈顶元素。 4. empty():判断堆栈是否为空。 5. size():返回堆栈中元素的个数。 下面是一个简单的堆栈的例子,可以更好地理解堆栈的基本操作: #include <iostream> #include <stack> using namespace std; int main() { stack<int> s; // 定义一个int类型的栈 s.push(10); // 将10入栈 s.push(20); // 将20入栈 s.push(30); // 将30入栈 while (!s.empty()) { cout << s.top() << " "; // 输出栈顶元素 s.pop(); // 删除栈顶元素 } return 0; } 在上面的例子中,我们首先定义了一个堆栈s,然后在堆栈s中依次插入了三个元素10、20和30。接下来使用while循环,栈顶元素依次输出,同时删除栈顶元素,直到堆栈为空。由于堆栈是后进先出的,所以输出的顺序是30、20和10。 总之,堆栈是一个非常常用的数据结构,STL中的栈(stack)提供了非常方便的使用,可以减轻我们对堆栈数据结构进行操作的负担,提高代码的可读性和复用性。 ### 回答3: 栈(stack)是 C++ STL(Standard Template Library)中常见的一种容器数据结构,它可以在一端进行元素的插入和删除操作,遵循“后进先出”(LIFO,Last-In-First-Out)的原则。栈的操作不需要访问元素中间的部分,只需要在栈顶执行操作,保证了操作效率。栈可以用数组或链表等数据结构实现,但 C++ STL 提供了封装好的栈容器,使用起来方便且安全。 C++ STL 中栈容器的定义方式为:`std::stack`。栈默认情况下使用双端队列(deque)实现,用户也可以指定其他底层容器,如 vector、list 等。可以使用`push()`向栈顶插入元素,使用`pop()`弹出栈顶元素,使用`top()`获取栈顶元素。栈的元素个数可以使用`size()`来获取,判断栈是否为空可以使用`empty()`,在栈容器中查找某个元素的功能则不支持。 在实际应用中,栈容器可以用来实现函数的递归调用、表达式求值、括号匹配等操作。例如,可以使用栈来判断一个字符串中的括号是否匹配,具体做法是将左括号入栈,遇到右括号时弹出栈顶元素检查是否为相应的左括号。如果不匹配或者栈已经为空,则括号不匹配;如果字符串中所有的括号都匹配,则最后栈为空。 总之,栈作为一种容器数据结构,在实际应用中有着广泛的应用场景,C++ STL 提供的封装好的栈容器,具有使用方便、效率高等特点,可以帮助我们更快更方便地实现各种数据处理和算法设计。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值