list底层实现

1.list库函数测试及简要说明

1.1 sort

list提供sort(底层是归并)是为了便捷性,其实库里提供的函数都是有说法的,比如vector没有直接提供头插和头删,list的迭代器没有直接+运算符重载,因为成本高,不期望你在使用容器时使用这些行为,如果偶尔vector要头删、头插,这是ok的,通过insert可以间接完成,或者list要偶尔排序,小数据量排序,也是可以的,有时候大佬们的智慧结晶需要小火慢炖,慢慢体会

我们看到下面的测试结果,当数据量较大时,还是有明显的差距的,vector就比较适合排序,不适合删除和插入;但是list就不适合直接排序,适合插入删除,所以如果我们有要经常性地大量数据排序的需求,那就先放到合适的容器中这也是容器多种多样,各有各的使用场景,要按需选取

cpp 复制代码
void mytest_list3() {
	srand(time(0));
	int N = 10;
	list<int> lt1, lt2;
	vector<int> v;
	v.reserve(N);
	for (int i = 0; i < N; i++) {
		auto e = rand();
		lt1.push_back(e);
		lt2.push_back(e);
	}
	int begin1 = clock();
	lt1.sort();
	int end1 = clock();

	int begin2 = clock();
	for (auto e : lt2)
		v.push_back(e);
	sort(v.begin(), v.end());
	int i = 0;
	for (auto& e : lt2)
		e = v[i++];
	int end2 = clock();

	printf("list sort:%d\n", end1 - begin1);
	printf("vector sort:%d\n", end2 - begin2);
}
int main() {
	mytest_list3();
	return 0;
}

输出结果(VS 2022 release)

因为debug模式特点是可以调试,会压大量调试信息,特别是递归的情况下,效率会大幅度下降,list的sort是由循环写的,而不是递归,因为递归要找中间结点,不方便;release模式下递归和循环才是"平等"赛跑,linux下g++编译器默认是64位,release版本,debug模式要加-g

1.2 reverse

list本身提供了reverse,算法库里也提供了reverse,效果一样;STL两大组件,算法和容器,算法将容器共有的一些算法抽取出来,比如sort,reverse,find等,算法统一使用容器的迭代器进行调用,不关心数据的底层数据结构,各容器比如list的迭代器要向vector,string的原生指针一样进行++、解引用,这些由容器各自实现;而从算法角度来看,所有容器的使用方法都是采用迭代器,但底层实现已经大有不同

一般算法库里有的情况下,容器不会再单独提供,但如果算法库里的算法无法满足容器本身的需求,容器内部会单独实现,比如string可以使用算法库里的find,但也提供了find,因为需求种类较为多样,查找字符、字符串等(string产生时间早于stl,严格来说不属于stl,但是string是容器,和slt容器很类似)

1.3 splice

cpp 复制代码
void mytest_list4() {
	list<int> lt1,lt2;
	for (int i = 0; i < 10; i++)
		lt1.push_back(i);
	for (int i = -10; i <0; i++)
		lt2.push_back(i);
	print(lt1);
	print(lt2);
	list<int>::iterator it = lt2.begin();
	for (int i = 0; i < 5; i++) {
		it++;
	}
	lt1.splice(lt1.begin(), lt2,lt2.begin(),it);
	print(lt1);
	print(lt2);
	lt1.splice(lt1.begin(), lt2, lt2.begin());
	print(lt1);
	print(lt2);
	lt1.splice(lt1.begin(), lt2);
	print(lt1);
	print(lt2);
	lt1.splice(lt1.begin(), lt1,++lt1.begin(), lt1.end());
	print(lt1);
}
int main() {
	mytest_list4();
	return 0;
}

输出

cpp 复制代码
0 1 2 3 4 5 6 7 8 9
-10 -9 -8 -7 -6 -5 -4 -3 -2 -1
//lt2的前五个转移到lt1的第一个数据之前
-10 -9 -8 -7 -6 0 1 2 3 4 5 6 7 8 9
-5 -4 -3 -2 -1
//lt2的第一个数据转移到lt1的第一个数据之前
-5 -10 -9 -8 -7 -6 0 1 2 3 4 5 6 7 8 9
-4 -3 -2 -1
//lt2所有数据转移到lt1的第一个数据之前
-4 -3 -2 -1 -5 -10 -9 -8 -7 -6 0 1 2 3 4 5 6 7 8 9
//lt1第一个位置之后的数据全部移到最前面
-3 -2 -1 -5 -10 -9 -8 -7 -6 0 1 2 3 4 5 6 7 8 9 -4

1.4 remove = find + erase

cpp 复制代码
void mytest_list5() {
	int a[] = { 0,1,2,3,4,5,6,7,8,9 };
	list<int> lt1(a,a+10);
	print(lt1);
	list<int>::iterator it = find(lt1.begin(), lt1.end(),3);
	if (it != lt1.end()) {
		lt1.erase(it);
	}
	print(lt1);
	lt1.remove(4);
	print(lt1);
	lt1.remove(100);//如果是lt1中没有的值,不做处理
	print(lt1);
}

输出

cpp 复制代码
0 1 2 3 4 5 6 7 8 9
0 1 2 4 5 6 7 8 9
0 1 2 5 6 7 8 9
0 1 2 5 6 7 8 9

1.5 insert

list的insert不涉及迭代器失效的问题,因为vector进行insert时迭代器失效比如auto it=v.begin()+3,it是第三个位置的指针,要么是异地扩容,it成为野指针;要么是插入之后,pos及之后的指针指向的数据和插入之前指向的数据不一样了;要么vector进行erase删除第三个位置数据,it的值不变,指向第三个位置的含义也不变,关键就是vector中第三个之后的数据都往前挪了,第三个位置之后的原本的迭代器的指向的数据和erase之前不同了,这才是问题;

那么list不涉及元素挪动和异地扩容的问题,it获取到第三个位置的数据,插入之后,it指向的数据与插入之前指向的数据一致,但是it不再是指向第三个位置了,而是第四个

cpp 复制代码
void mytest_list6() {
	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);
	lt1.push_front(10);
	lt1.push_front(20);
	print(lt1);

	list<int>::iterator it = lt1.begin();
	for (int i = 0; i < 5; i++) {
		it++;
	}
	lt1.insert(it, 100);
	*it *= 10;//作用的是插入之前的第五个数据,值为4
	print(lt1);
}

输出

cpp 复制代码
20 10 1 2 3 4
20 10 1 2 3 100 40

1.6 erase

cpp 复制代码
void mytest_list7() {
	list<int> lt1;

	for (int i = 0; i < 10; i++)
		lt1.push_back(i);
	print(lt1);

	list<int>::iterator it = lt1.begin();
	while (it != lt1.end()) {//这个地方是!=,而不是<,因为链表的逻辑关系和物理位置大学没有必然联系
		if (*it % 2 == 0)
			it = lt1.erase(it);
		else
			it++;
	}
	print(lt1);
}
int main() {
	mytest_list7();
	return 0;
}

输出

cpp 复制代码
0 1 2 3 4 5 6 7 8 9
1 3 5 7 9

2 底层实现

2.1 迭代器

算法库里的sort是不支持list的,因为底层是快排,只有randam access迭代器才能支持,而list是bidiretional迭代器

下图只表示不同种类迭代器的功能范畴,B包含A是指B有的功能,A都能支持

在实现list迭代器的时候注意链表的数据存储并不是连续的,这就要求我们把指针封装成一个类,进行++、*、->等一系列运算符的重载,其实任何迭代器都是指针实现的,只不过有的时候指针就足够了,比如string和vector,但有的时候指针不足以满足需求,所以还要进行封装,让迭代器能够像原生指针那样++、 *、->等等,但是由于list底层并不是连续存储,+的代价较大,所以没有直接提供,和迭代器的分类有关,容器的迭代器是什么类型取决于容器本身的存储性质,比如vector和string本身连续存储就支持随机存取,是randan access,+/-的代价都不大

cpp 复制代码
#pragma once
namespace diy {
	template<class T>//定义节点
	struct list_node {
		list_node<T>* _prev;
		list_node<T>* _next;
		T _val;

		list_node(const T& val = T()):
			_prev(nullptr),
			_next(nullptr),
			_val(val)
		{}
	};

	//因为有const对象和普通对象的两种迭代器,所以定义了两个类,实现有些冗余,但是模版存在的意义就是根据参数定义不同的类
	template<class T>
	struct __list_iterator {
		typedef list_node<T> Node;
		Node* _node;

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

		__list_iterator(const __list_iterator<T>& it){
			_node = it._node;
		}

		T& operator*() {
			return _node->_val;
		}

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

		__list_iterator<T> operator++(int) {//后置
			__list_iterator<T> tmp(*this);
			_node = _node->_next;
			return tmp;
		}

		bool operator==(const __list_iterator<T>& it) {
			return _node == it._node;
		}

		bool operator!=(const __list_iterator<T>& it) {
			return _node != it._node;
		}
	};

	template<class T>
	struct __list_const_iterator {
		typedef list_node<T> Node;
		Node* _node;

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

		__list_const_iterator(const __list_const_iterator<T>& it) {
			_node = it._node;
		}

		const T& operator*() {
			return _node->_val;
		}

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

		__list_const_iterator<T> operator++(int) {//后置
			__list_const_iterator<T> tmp(*this);
			_node = _node->_next;
			return tmp;
		}

		bool operator==(const __list_const_iterator<T>& it) {
			return _node == it._node;
		}

		bool operator!=(const __list_const_iterator<T>& it) {
			return _node != it._node;
		}
	};

	template<class T>
	class list {
	public:
		list() {
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
		}

		typedef __list_iterator<T> iterator;
		typedef __list_const_iterator<T> const_iterator;
		/*typedef const  __list_iterator<T> const_iterator; 这样实现是有问题的,因为const修饰的是指针,而不是指针指向的内容,但list迭代器一定是需要++或--的,因为需要进行遍历等操作
		const T* ptr1; 要的是这种效果
		T* const ptr2; 不是这种效果*/

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

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

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

		const_iterator end() const{
			return _head;//iterator(_head)
		}

引出类型别名参数

cpp 复制代码
template<class T, class Ref,class Ptr>//T是基础类型参数,Ref是类型别名参数,可以是&和const等,用于实例化不同类,满足需求;Ptr用于->操作符重载时的不同返回值
struct __list_iterator {
	typedef list_node<T> Node;
	Node* _node;
	typedef __list_iterator<T, Ref,Ptr> self;
	__list_iterator(Node* node):
		_node(node)
	{}

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

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

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

	self operator++(int) {//后置
		self tmp(*this);
		_node = _node->_next;
		return tmp;
	}

	bool operator==(const self& it) {
		return _node == it._node;
	}

	bool operator!=(const self& it) {
		return _node != it._node;
	}
};

template<class T>
class list {
public:
		typedef __list_iterator<T,T&> iterator;
		typedef __list_iterator<T, const T&> const_iterator;

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

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

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

		const_iterator end() const{
			return _head;//iterator(_head)
		}

	private:
		typedef list_node<T> Node;
		Node* _head;
		size_t _size;
	};
}

*/-> 运算符重载

cpp 复制代码
struct A {
	A(int a1 = 0, int a2 = 0) :
		_a1(a1),
		_a2(a2)
	{}
	int _a1;
	int _a2;
};
void test3() {
	diy::list<diy::A> lt;
	lt.push_back(diy::A(1, 1));
	lt.push_back(diy::A(2, 2));
	lt.push_back(diy::A(3, 3));
	lt.push_back(diy::A(4, 4));
	lt.push_back(diy::A(5, 5));
	diy::list<diy::A>::iterator it = lt.begin();
	while (it != lt.end()) {
		cout << (*it)._a1 << " " << it->_a2<< " ";
		it++;
	}
	cout << endl;
}
int main(){
	test3();
	return 0;
}

2.2 构造

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

//默认构造
list() {
	empty_init();
}

//拷贝构造
list(const list<T>& lt) {
//list(const list<T>& lt)也可以,在类内,可以用类名代替类型名,但有了模版之后,类名是类名,类型名是类根据不同参数实例化出来的类型的名字,二者并不相同
	empty_init();

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

//迭代器构造
template<class InputIterator >
list(InputIterator first, InputIterator last) {
	empty_init();

	auto it = first;
	while (it != last) {
		push_back(*it);
		it++;
	}
}

2.3 库函数实现

2.3.1 插入

cpp 复制代码
iterator insert(iterator pos, const T& val) {
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* newnode = new Node(val);

	prev->_next = newnode;
	newnode->_prev = prev;
	cur->_prev = newnode;
	newnode->_next = cur;

	_size++;
	return newnode;
}

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

//尾插
void push_back(const T& val) {
	/*Node* tmp = new Node(val);
	Node* tail = _head->_prev;
	tmp->_next = _head;
	_head->_prev = tmp;
	tail->_next = tmp;
	tmp->_prev = tail;
	_size++;*/
	insert(end(), val);
}

2.3.2 删除

cpp 复制代码
iterator erase(iterator pos) {
	assert(pos != end());
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;
	prev->_next = next;
	next->_prev = prev;

	delete cur;
	cur = nullptr;

	_size--;

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

void pop_back() {
	erase(--end());
}

2.3.2 其它函数

cpp 复制代码
#pragma once
#include <assert.h>
#include <algorithm>
namespace diy {

	template<class T>
	class list {
	public:
		//复制运算符重载
		void swap(list<T>& lt) {
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}
	
		list<T>& operator=(list<T> tmp) {//传值传参
			swap(tmp);
			return *this;
		}
		

		~list() {
			clear();
			delete _head;
			_head = nullptr;
		}
		
		void clear() {
			iterator it = begin();
			while (it != end()) {
				it = erase(it);
			}
		}

		size_t size() const{
			/*iterator it = begin();
			size_t n = 0;
			while (it != end()) {
				n++;
				it++;
			}
			return n;*/
			return _size;
		}

	private:
		typedef list_node<T> Node;
		Node* _head;
		size_t _size;
	};
}

牛刀小试

  1. 下面有关vector和list的区别,描述错误的是( )
    A.vector拥有一段连续的内存空间,因此支持随机存取,如果需要高效的随机存取,应该使用vector
    B.list拥有一段不连续的内存空间,如果需要大量的插入和删除,应该使用list
    C.vector::iterator支持"+"、"+="、"<"等操作符
    D.list::iterator则不支持"+"、"+="、"<"等操作符运算,但是支持了[ ]运算符

D

  1. 以下代码实现了从表中删除重复项的功能,请选择其中空白行应填入的正确代码( )
    A. p=cur+1;aList.erase(p++);
    B.p=++cur; p == cur ? cur = p = aList.erase(p ) : p = aList.erase(p );
    C.p=cur+1;aList.erase(p );
    D.p=++cur;aList.erase(p );
cpp 复制代码
template<typename T>
void removeDuplicates(list<T> &aList){
	T curValue;
	list<T>::iterator cur, p;
	cur = aList.begin();
	while (cur != aList.end()){
		curValue = *cur;
		//空白行 1
		while (p != aList.end()){
			if (*p == curValue){
				//空白行 2
			}
			else{
				p++;
			}
		}
	}
}

B

  1. 以下程序输出结果为( )
    A.4 3 2 1 0 5 6 7 8 9
    B.0 1 2 3 4 9 8 7 6 5
    C.5 6 7 8 9 0 1 2 3 4
    D.5 6 7 8 9 4 3 2 1 0
cpp 复制代码
int main(){
	int ar[] = { 0,1, 2, 3, 4,  5, 6, 7, 8, 9 };
	int n = sizeof(ar) / sizeof(int);
	list<int> mylist(ar, ar+n); 
	list<int>::iterator pos = find(mylist.begin(), mylist.end(), 5);
	reverse(mylist.begin(), pos);
	reverse(pos, mylist.end());
	list<int>::const_reverse_iterator crit = mylist.crbegin();
	while(crit != mylist.crend()){
		cout<<*crit<<" ";
		++crit;
	}
	cout<<endl;
}

C

  1. 下面程序的输出结果正确的是( )
    A.1 2 3 4 5 6 7 8 9
    B. 1 2 3 4 6 7 8 9
    C.程序运行崩溃
    D.1 2 3 4 0 5 6 7 8 9
cpp 复制代码
int main(){
	int array[] = { 1, 2, 3, 4, 0, 5, 6, 7, 8, 9 };
	int n = sizeof(array) / sizeof(int);
	list<int> mylist(array, array+n);
	auto it = mylist.begin();
	while (it != mylist.end()){
		if(* it != 0)
			cout<<* it<<" ";
		else
			it = mylist.erase(it);
		++it;
	}
	return 0;
}

B

相关推荐
近津薪荼2 小时前
递归专题(4)——两两交换链表中的节点
数据结构·c++·学习·算法·链表
乐观勇敢坚强的老彭2 小时前
c++寒假营day01下午
c++·算法
散峰而望2 小时前
【算法竞赛】树
java·数据结构·c++·算法·leetcode·贪心算法·推荐算法
近津薪荼2 小时前
递归专题(3)——反转链表
数据结构·c++·学习·算法·链表
code monkey.2 小时前
【Linux之旅】Linux 动静态库与 ELF 加载全解析:从制作到底层原理
linux·服务器·c++·动静态库
水饺编程3 小时前
第4章,[标签 Win32] :文本的格式化,等待完善
c语言·c++·windows·visual studio
似霰3 小时前
Android 平台智能指针使用与分析
android·c++
阿猿收手吧!3 小时前
【C++】实现自旋锁:三种高效实现与实战指南
服务器·网络·c++
代码游侠3 小时前
C语言核心概念复习(三)
开发语言·数据结构·c++·笔记·学习·算法