list(全)

目录

[一. 认识 list](#一. 认识 list)

[1.1 list 和 vector 的核心区别](#1.1 list 和 vector 的核心区别)

[1.1.1 对比总结:](#1.1.1 对比总结:)

[二. list 的使用](#二. list 的使用)

[2.1 list 的构造](#2.1 list 的构造)

[2.2 list 的迭代器](#2.2 list 的迭代器)

[2.2.1 迭代器的知识补充](#2.2.1 迭代器的知识补充)

[2.3 list 的其他函数接口](#2.3 list 的其他函数接口)

[2.4 list 的增删查改](#2.4 list 的增删查改)

[2.4.1 list 的增](#2.4.1 list 的增)

[2.4.1.1 push_back() 和 push_front()](#2.4.1.1 push_back() 和 push_front())

[2.4.1.2 insert](#2.4.1.2 insert)

[2.4.2 list 的删](#2.4.2 list 的删)

[2.4.2.1 pop_back() 和 pop_front()](#2.4.2.1 pop_back() 和 pop_front())

[2.4.2.2 erase](#2.4.2.2 erase)

[2.4.3 list 的查](#2.4.3 list 的查)

[2.5 其他常用的函数接口](#2.5 其他常用的函数接口)

[2.5.1 reverse](#2.5.1 reverse)

[2.5.2 sort](#2.5.2 sort)

[三. list 迭代器失效](#三. list 迭代器失效)

[四. initializer_list](#四. initializer_list)

[4.1 认识 initializer_list](#4.1 认识 initializer_list)

[4.2 代码示例:](#4.2 代码示例:)

[五. list 的实现](#五. list 的实现)

[5.1 list 的构造和赋值运算符重载](#5.1 list 的构造和赋值运算符重载)

[5.2 迭代器的实现](#5.2 迭代器的实现)

[5.3 list 的插入](#5.3 list 的插入)

[5.4 list 的删除](#5.4 list 的删除)

[5.5 析构、clear()](#5.5 析构、clear())


一. 认识 list

list 的本质是双向循环链表,且带有一个"哨兵位头结点"(不存储 有效数据),结构如下:

  • 双向:每个字节包含前驱指针 (prev) 和后继指针 (next) ,支持向前,向后遍历;
  • 循环:尾节点的 next 指向头结点,头结点的 prev 指向尾结点,形成闭环;
  • 哨兵位头结点:避免插入/删除时判断"是否为空""是否为头结点"的麻烦,简化代码逻辑。

1.1 list 和 vector 的核心区别

对比项 list(双向链表) vector(动态数组)
底层结构 每个元素都是独立节点,通过 prevnext 指针连接,内存不连续 一块连续内存空间存储元素,类似可自动扩容的数组
插入/删除效率 已知迭代器位置时效率高,插入删除只需修改前后节点指针,时间复杂度 O(1) 中间插入或删除需要移动后面的元素,时间复杂度 O(N);尾部插入平均 O(1)
随机访问 不支持下标访问,需要从头或尾遍历寻找元素,时间复杂度 O(N) 支持 operator[]at(),可以根据下标直接定位,时间复杂度 O(1)
迭代器特点 插入、删除节点后,其他元素的迭代器通常不会失效,只影响被删除节点 扩容时会重新分配内存,导致全部迭代器失效;非扩容插入会使插入位置之后的迭代器失效
空间利用 每个节点额外保存两个指针,内存开销较大;节点分散可能造成缓存不友好 存储空间连续,额外空间主要来自预留容量,缓存命中率更高
适用场景 频繁在任意位置插入、删除,并且不需要随机访问 需要大量访问元素、遍历数据、尾部添加元素
查找效率 查找必须遍历链表,时间复杂度 O(N) 支持随机定位,但普通查找仍为 O(N)
排序操作 有成员函数 sort(),利用链表结构避免大量移动元素 通常使用算法库 sort(),依靠连续内存获得更好的性能

1.1.1 对比总结:

  • 如果程序主要进行中间位置频繁插入和删除 ,并且不经常通过下标访问,list 更合适。

  • 如果程序主要进行随机访问、遍历以及尾部插入 ,优先考虑 vector

  • 实际开发中,vector 往往是默认选择,因为连续内存带来的缓存优势通常比链表的插入优势更明显。

二. list 的使用

2.1 list 的构造

构造函数 接口说明 代码示例
list() 构造空 list list<int> l1;(空链表,仅含头结点)
list(size_type n, const T& val = T()) 构造包含 nvallist list<int> l2(5, 3);(元素:3,3,3,3,3)
list(const list& x) 拷贝构造 list<int> l3(l2);(l3 是 l2 的副本)
list(InputIterator first, InputIterator last) 使用 [first,last) 区间构造 int arr[] = {1,2,3}; list<int> l4(arr, arr+3);(元素:1,2,3)
cpp 复制代码
#include <iostream>
using namespace std;
#include <list>

template<class Container>
void Print_List( Container& con)
{
	typename Container::iterator it = con.begin();
	while (it != con.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };

	list<int> a1;
	Print_List(a1);

	list<int> a2(3, 4);
	Print_List(a2);

	list<int> a3(a2);
	Print_List(a3);

	list<int> a4(a, a + sizeof(a) / sizeof(a[0]));
	Print_List(a4);

	return 0;
}

2.2 list 的迭代器

list 的迭代器使用和string、vector 几乎没有差别,这里就不再赘述。

cpp 复制代码
#include <iostream>
using namespace std;
#include <list>

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> dict(a, a + sizeof(a) / sizeof(a[0]));

	//正向遍历
	auto it = dict.begin();
	while (it != dict.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
	//反向遍历
	auto _it = dict.rbegin();
	while (_it != dict.rend())
	{
		cout << *_it << ' ';
		_it++;
	}
	cout << endl;

	return 0;
}

2.2.1 迭代器的知识补充

迭代器可以按功能和性质划分:

**功能:**iterator、reverse_iterator、const_iterator、const_reverse_iterator。

**性质:单向:**forward_list / unordered_map..... ++

**双向:**list / map / set.... ++ / - -

**随机:**vector / string /deque.... ++ / - - / + / -

我们也可以在 cpluplus 网站上查看不同迭代器的性质:

2.3 list 的其他函数接口

list 也提供函数 size()、clear()、empty()、resize()。但是并没有提供 capacity()、reserve()、shrink_to_fit()。

函数声明 接口说明 代码示例(基于 l4 = {1,2,3,4}
size() 返回 list 中有效元素的个数,返回类型为无符号整数(size_type l4.size();(返回 4,表示 l4 中有 4 个元素)
clear() 清空 list 中的所有元素,执行后 size() 变为 0 l4.clear();(执行后 l4 变为空链表 {}
empty() 检测 list 是否为空,空返回 true,非空返回 false l4.empty();(返回 false,因为 l4 中有 4 个元素)
resize(size_type n, T val = T()) 调整 list 中有效元素的个数;若 n > size(),新增元素并初始化为 val;若 n < size(),删除多余元素 l4.resize(6, 10);(修改后 l4{1,2,3,4,10,10}

在访问元素方面,list 并没有重载 和 at(list 不支持随机访问),而是和 string、vector 一样提供 **front()、back()**访问首尾元素。

函数声明 接口说明 代码示例(基于 l4 = {1,2,3,4}
front() 返回 list 第一个元素的引用,可用于访问或修改首元素 l4.front();(返回第一个元素 1,也可以通过 l4.front() = 10; 修改)
back() 返回 list 最后一个元素的引用,可用于访问或修改尾元素 l4.back();(返回最后一个元素 4,也可以通过 l4.back() = 20; 修改)

2.4 list 的增删查改

2.4.1 list 的增

2.4.1.1 push_back() 和 push_front()
cpp 复制代码
#include <iostream>
using namespace std;
#include <list>

template<class Container>
void Print_List( Container& con)
{
	typename Container::iterator it = con.begin();
	while (it != con.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> dict(a, a + sizeof(a) / sizeof(a[0]));

	dict.push_back(100);
	dict.push_front(200);

	Print_List(dict);
	return 0;
}
2.4.1.2 insert
cpp 复制代码
#include <iostream>
using namespace std;
#include <list>

template<class Container>
void Print_List( Container& con)
{
	typename Container::iterator it = con.begin();
	while (it != con.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> dict(a, a + sizeof(a) / sizeof(a[0]));

	dict.insert(dict.begin(), 100);
	Print_List(dict);

	dict.insert(dict.begin(),3, 11);
	Print_List(dict);

	dict.insert(dict.begin(), a, a + sizeof(a) / sizeof(a[0]));
	Print_List(dict);
	return 0;
}

2.4.2 list 的删

2.4.2.1 pop_back() 和 pop_front()
cpp 复制代码
#include <iostream>
using namespace std;
#include <list>

template<class Container>
void Print_List( Container& con)
{
	typename Container::iterator it = con.begin();
	while (it != con.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> dict(a, a + sizeof(a) / sizeof(a[0]));

	dict.pop_back();
	Print_List(dict);

	dict.pop_front();
	Print_List(dict);
	return 0;
}
2.4.2.2 erase
cpp 复制代码
#include <iostream>
using namespace std;
#include <list>

template<class Container>
void Print_List( Container& con)
{
	typename Container::iterator it = con.begin();
	while (it != con.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> dict(a, a + sizeof(a) / sizeof(a[0]));

	dict.erase(dict.begin());
	Print_List(dict);

	dict.erase(dict.begin(),--dict.end());
	Print_List(dict);
	return 0;
}

2.4.3 list 的查

list 和 vector 一样,只能使用标准库里的 find():

cpp 复制代码
#include <iostream>
using namespace std;
#include <list>

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> dict(a, a + sizeof(a) / sizeof(a[0]));

	list<int>::iterator it = find(dict.begin(), dict.end(), 10);
	if (it != dict.end())
	{
		cout << "找到了" << *it << endl;
	}
	else
	{
		cout << "没找到" << endl;
	}
	return 0;
}

2.5 其他常用的函数接口

2.5.1 reverse

cpp 复制代码
#include <iostream>
using namespace std;
#include <list>

template<class Container>
void Print_List( Container& con)
{
	typename Container::iterator it = con.begin();
	while (it != con.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> dict(a, a + sizeof(a) / sizeof(a[0]));

	Print_List(dict);

	dict.reverse();
	Print_List(dict);
	return 0;
}

但是这个函数的实现有点多余,我们可以像 string、vector 一样使用标准库的 reverse():

而且我们发现标准库的 reverse() 要求至少是双向迭代器,不用担心 list 不能使用

cpp 复制代码
#include <iostream>
using namespace std;
#include <list>

template<class Container>
void Print_List( Container& con)
{
	typename Container::iterator it = con.begin();
	while (it != con.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> dict(a, a + sizeof(a) / sizeof(a[0]));

	Print_List(dict);

	reverse(dict.begin(), dict.end());
	Print_List(dict);
	return 0;
}

2.5.2 sort

标准库的 sort 要求迭代器至少是随机迭代器,那么 list 是使用不了的 。所以 list 自己实现了 sort

cpp 复制代码
#include <iostream>
using namespace std;
#include <list>

template<class Container>
void Print_List( Container& con)
{
	typename Container::iterator it = con.begin();
	while (it != con.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

int main()
{
	int a[] = { 1,4,3,7,5,10,50,8,40 };
	list<int> dict(a, a + sizeof(a) / sizeof(a[0]));

	Print_List(dict);

	dict.sort();
	Print_List(dict);
	return 0;
}

三. list 迭代器失效

list 和 vector 不一样,insert 的时候迭代器不会失效,但是 erase 的时候会失效

cpp 复制代码
#include <iostream>
using namespace std;
#include <list>

template<class Container>
void Print_List( Container& con)
{
	typename Container::iterator it = con.begin();
	while (it != con.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

int main()
{
	int a[] = { 1,4,3,7,5,10,50,8,40 };
	list<int> dict(a, a + sizeof(a) / sizeof(a[0]));

	auto it = dict.begin();
	dict.insert(it, -10);
	Print_List(dict);
	//迭代器并没有失效,仍然指向值为1的节点
	dict.insert(it, 100);
	Print_List(dict);
	return 0;
}
cpp 复制代码
#include <iostream>
using namespace std;
#include <list>

template<class Container>
void Print_List( Container& con)
{
	typename Container::iterator it = con.begin();
	while (it != con.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

int main()
{
	int a[] = { 1,4,3,7,5,10,50,8,40 };
	list<int> dict(a, a + sizeof(a) / sizeof(a[0]));

	auto it = dict.begin();
	dict.erase(it);
	Print_List(dict);
	//值为1的节点被删除,it指向的位置被释放,迭代器失效
	dict.erase(it);
	Print_List(dict);
	return 0;
}

四. initializer_list

在 C++11 中支持用 initializer_list 来构造 list:

4.1 认识 initializer_list

initializer_list 是 C++11 为 {} 语法准备的中间桥梁,它本身不负责存数据,只保存编译器生成的临时数组的访问信息,让 vector、list、自定义类都能支持 {1,2,3} 这种初始化方式。

4.2 代码示例:

cpp 复制代码
#include <iostream>
using namespace std;
#include <list>

template<class Container>
void Print_List( Container& con)
{
	typename Container::iterator it = con.begin();
	while (it != con.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

int main()
{
	list<int> dict({ 1,2,3,4,5 });
	Print_List(dict);
	return 0;
}

initializer_list 的构造过程是:当编译器遇到 {1,2,3} 这种初始化列表时,会先创建一个隐藏的 const 数组保存元素,然后构造一个 initializer_list 对象,让它保存数组的首地址和大小。initializer_list 本身不存储数据,只是对这块临时数组的只读访问,所以其中的元素不能修改。

cpp 复制代码
#include <iostream>
#include <initializer_list>
using namespace std;

void Print(initializer_list<int> il)
{
    for (auto e : il)
        cout << e << " ";
}

int main()
{
    initializer_list<int> il = {1, 2, 3};

    Print({4, 5, 6});
}

大致过程:

cpp 复制代码
{1,2,3}
   ↓
生成隐藏数组 const int temp[]{1,2,3}
   ↓
initializer_list保存temp地址和大小
   ↓
通过begin/end访问元素

五. list 的实现

5.1 list 的构造和赋值运算符重载

cpp 复制代码
void empty_init()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	_size = 0;
}

list(initializer_list<T> il)
{
	empty_init();

	for (auto& e : il)
	{
		push_back(e);
	}
}

list(const list<T>& It)
{
	empty_init();

	for (auto& it : It)
	{
		push_back(it);
	}
}

void swap( list<T>& It)
{
	std::swap(this->_head, It._head);
	std::swap(this->_size, It._size);
}

list<T>& operator=( list<T> It)
{
	swap(It);

	return *this;
}

5.2 迭代器的实现

cpp 复制代码
template<class T,class Ref,class Ptr>
struct list_iterator
{
	typedef list_node<T> Node;
	typedef list_iterator<T, Ref, Ptr> Self;

	list_iterator(Node* node)
		:_node(node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}

	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	Self operator--(int)
	{
		Self tmp = *this;
		_node = _node->_prev;
		return tmp;
	}

	Self operator++(int)
	{
		Self tmp = *this;
		_node = _node->_next;
		return tmp;
	}
	
	bool operator==(const Self& v)const
	{
		return _node == v._node;
	}

	bool operator!=(const Self& v)const
	{
		return _node != v._node;
	}


	Node* _node;
};

template<class T>
class list
{
	typedef list_node<T> Node;
public:
	typedef list_iterator<T, T&, T*> iterator;
	typedef list_iterator< T, const T&, const T*> const_iterator;

	iterator begin()
	{
		iterator it(_head->_next);
		return it;
	}

	const_iterator begin()const
	{
		const_iterator it(_head->_next);
		return it;
	}

	iterator end()
	{
		return iterator (_head);
	}

	const_iterator end()const
	{
		return const_iterator (_head);
	}
private:
    Node* _head;
    size_t _size;
}

5.3 list 的插入

cpp 复制代码
void push_back(const T& val )
{
	Node* newnode = new Node(val);
	newnode->_prev = _head->_prev;
	newnode->_next = _head;

	_head->_prev->_next = newnode;
	_head->_prev = newnode;
	

	_size++;
}

void push_front(const T& val)
{
	insert(begin(), val);
}

iterator insert(iterator it, const T& x)
{
	Node* newnode = new Node(x);
	newnode->_prev = it._node->_prev;
	newnode->_next = it._node;
	it._node->_prev->_next = newnode;
	it._node->_prev = newnode;
   
	_size++;

	return newnode;
}

5.4 list 的删除

cpp 复制代码
void pop_back()
{
	erase(--end());
}

void pop_front()
{
	erase(begin());
}

iterator erase(iterator pos)
{
	assert(pos != end());
	iterator it = pos._node->_next;
	
	pos._node->_prev->_next = pos._node->_next;
	pos._node->_next->_prev = pos._node->_prev;

	delete pos._node;

	_size--;
	return it;
}

5.5 析构、clear()

cpp 复制代码
	~list()
	{
		clear();
		delete _head;
		_head = nullptr;
	}

	void clear()
	{
		iterator it = begin();
		while (it != end())
		{
			erase(it);
		}
	}
相关推荐
不灭的黄金瞳12315 小时前
C语言手写顺序表
c语言·开发语言·数据结构
qeen8716 小时前
【数据结构】哈希表的C++实现与封装
数据结构·c++·散列表·哈希表
重生之后端学习16 小时前
239. 滑动窗口最大值[困难]✅
java·数据结构·算法·leetcode·职场和发展
fpcc17 小时前
算法和数据结构—动态规划法
数据结构·算法·动态规划
星星.72219 小时前
C++算法竞赛|二分查找与二分答案:边界模板、浮点二分、STL
数据结构·c++·算法
动词ing19 小时前
【学习笔记】数据结构(数组长度和关键特性+快慢指针+左右指针)
数据结构·笔记·学习
linux-hzh21 小时前
百日算法修炼 · Day 17
数据结构·算法
OPEN-F1 天前
C++入门教程:数组、字符串与指针入门
数据结构·c++·算法
pluviophile_s1 天前
数据结构:第7讲:图
数据结构·笔记