【C++】STL之list深度剖析及模拟实现

目录

前言

一、list 的使用

 1、构造函数

2、迭代器

3、增删查改

4、其他函数使用

二、list 的模拟实现

 1、节点的创建

 2、push_back 和 push_front

 3、普通迭代器

 4、const 迭代器

 5、增删查改(insert、erase、pop_back、pop_front)

 6、构造函数和析构函数

  6.1、默认构造

  6.2、构造 n 个 val 的对象

  6.3、拷贝构造

  6.4、迭代器区间构造

  6.5、 赋值运算符重载

  6.6、析构函数

三、list 模拟实现源代码

四、list 的迭代器失效

五、list 和 vector的对比


前言

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。
  3. list 与 forward_list 非常相似:最主要的不同在于 forward_list 是单链表,只能朝前迭代,已让其更简单高效。
  4. 与其他的序列式容器相比(array,vector,deque),list 通常在任意位置进行插入、移除元素的执行效率更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问.

一、list 的使用

 1、构造函数

构造函数 接口说明
list (size_type n, const value_type& val = value_type()) 构造的list中包含n个值为val的元素
list() 构造空的list
list (const list& x) 拷贝构造函数
list (InputIterator first, InputIterator last) 用[first, last)区间中的元素构造list
int main()
{
	// 默认构造
	list lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	// 拷贝构造
	list lt2(lt);
	// 构造 n 个节点
	list lt3(5, 1);
	// 迭代器区间构造
	list lt4(lt.begin(), lt.end());

	return 0;
}

2、迭代器

函数声明 接口说明
begin + end 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin + rend 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 reverse_iterator,即begin位置
int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	list lt(a, a + 9);
	auto it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	return 0;
}

迭代器一般是用来遍历和查找的; 

而反向迭代器的使用是类似的,只不过调用的函数换成了 rbegin 和 rend 。

注意:反向迭代器的迭代使用的也是++。但迭代器区间一样是[rbegin, rend);

3、增删查改

函数声明 接口说明
push_front 在list首元素前插入值为 val 的元素
pop_front 删除 list 中第一个元素
push_back 在list尾部插入值为 val 的元素
pop_back 删除 list 中最后一个元素
insert 在list position 位置中插入值为 val 的元素
erase 删除list position 位置的元素
swap 交换两个 list 中的元素
clear 清空 list 中的有效元素
int main()
{
	vector v = { 1,2,3,4,5,6,7,8,9 };
	list lt(v.begin(), v.end());
	for (auto e : lt) cout << e << " ";
	cout << endl;

	lt.push_front(10);
	lt.push_back(20);
	for (auto e : lt) cout << e << " ";
	cout << endl;

	lt.pop_front();
	lt.pop_back();
	for (auto e : lt) cout << e << " ";
	cout << endl;

	auto pos = find(lt.begin(), lt.end(), 5);
	lt.insert(pos, 50);
	for (auto e : lt) cout << e << " ";
	cout << endl;

	pos = find(lt.begin(), lt.end(), 8);
	lt.erase(pos);
	for (auto e : lt) cout << e << " ";
	cout << endl;

	return 0;
}

4、其他函数使用

函数声明 接口说明
empty 检测 list 是否为空,是返回 true ,否则返回 false
size 返回 list 中有效节点的个数
front 返回 list 的第一个节点中值的引用
back 返回 list 的最后一个节点中值的引用

二、list 的模拟实现

 1、节点的创建

template
struct list_node//节点
{
	list_node* _next;
	list_node* _prev;
	T _data;
    // 构造函数
	list_node(const T& x = T())
		:_next(nullptr)
		, _prev(nullptr)
		, _data(x)
	{}
};

   由于节点存储的数据可能是任意类型,所以我们需要将将节点定义为模板类。这里我们需要写一个给缺省值的默认构造函数,便于之后在主类中new一个新节点时直接初始化,同时将两个指针置为空,将数据写入数据域中。

 2、push_back 和 push_front

class list 
{
public:
	typedef list_node node;
	
private:
	node* _head;
}
//尾插
void push_back(const T& x) const
{
	node* new_node = new node(x);
	node* tail = _head->_prev;
	//链接节点之间的关系
	tail->_next = new_node;
	new_node->_prev = tail;
	new_node->_next = _head;
	_head->_prev = new_node;
}
//头插
void push_front(const T& x)
{
	node* head = _head->_next;
	node* new_node = new node(x);

	_head->_next = new_node;
	new_node->_prev = _head;
	new_node->_next = head;
	head->_prev = new_node;
}

 这里模拟的头插和尾插也很简单,因为和我们之前在数据结构时候的双向循环链表是一样的,只需要找到头或者尾,然后链接四个节点间的关系即可。

 3、普通迭代器

注意:list 的迭代器是自定义类型,不是原生指针node*。

迭代器为自定义类型,其中*,++等都是通过运算符重载来完成的。

所以我们需要重载的符号:*,->,前置++,后置++,前置--,后置--,!=,==

template
struct __list_iterator
{
	typedef list_node node;
	typedef __list_iterator self;
	node* _node;

	//构造函数
	__list_iterator(node* n)
		:_node(n)
	{}
	//重载*运算符
	T& operator*()
	{
		return _node->_val;
	}
	T* operator->()
	{
		return &_node->_data;
	}
	//重载前置++运算符
	self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	//重载后置++运算符
	self operator++(int)
	{
		self tmp(*this);
		_node = _node->_next;
		return tmp;
	}
	//重载前置--运算符
	self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	//重载后置--运算符
	self operator--(int)
	{
		self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}
	//重载!=运算符
	bool operator!=(const self& s)
	{
		return _node != s._node;
	}
	//重载==运算符
	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};

 此处我实现了一个简单的正向迭代器,使用一个模板参数T表示类型。

 当普通迭代器封装好了之后,我们需要在list类中来实现它的 begin() 和 end() 方法。由于迭代器的名字一般都是 iterator,而且对于范围for来说,也只能通过 iterator 来转换为迭代器进行遍历。所以这里我们将其typedef为iterator。

template
class list//链表
{
	typedef list_node node;
public:
	typedef __list_iterator iterator;

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

	iterator end()
	{
		return iterator(_head);
	}
private:
	node* _head;
};

 4、const 迭代器

  const迭代器与普通迭代器的区别在于const迭代器指向的内容是不能修改的,但是它的指向是可以修改的。

template
class list//链表
{
	typedef list_node node;
public:
	typedef __list_const_iterator const_iterator;

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

	const_iterator end()
	{
		return const_iterator(_head);
	}
private:
	node* _head;
};

  我们最好的做法就是在__list_iterator 的类模板中的添加两个模板参数,然后再 list 类中 typedef 两份分别将第二个参数分别改成 T& 和 const T& 的类型,本质上就是让编译器根据传入的 Ref 的不同来自动示例化出 const 迭代器类,而我们还需要重载一个->运算符,因为list中可能存储的是自定义类型,这个自定义类型如果要是有多个成员变量的话,我们就需要使用->来解引用访问成员变量,同样还是要区分普通迭代器和const 迭代器,所以就增加了另一个模版参数 Ptr。具体的解决做法如下:

template
struct __list_iterator
{
	typedef list_node node;
	typedef __list_iterator self;
	node* _node;

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

	Ref operator*()//解引用
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	...
};

然后,最终在链表类中使用如下:

template
class list//链表
{
	typedef list_node node;
public:
	typedef __list_iterator iterator;//普通迭代器
	typedef __list_iterator const_iterator;//const迭代器

	iterator begin()
	{
		return iterator(_head->_next);//匿名对象的返回
	}
	const_iterator begin() const
	{
		return const_iterator(_head->_next);
	}
	iterator end()
	{
		return iterator(_head);
	}
	const_iterator end() const
	{
		return const_iterator(_head);
	}
private:
	node* _head;
};

 5、增删查改(insert、erase、pop_back、pop_front)

// 指定位置插入
void insert(iterator pos, const T& x)
{
	node* cur = pos._node;
	node* prev = cur->_prev;
	node* new_node = new node(x);

	prev->_next = new_node;
	new_node->_prev = prev;
	new_node->_next = cur;
	cur->_prev = new_node;
}
// 指定位置删除
iterator erase(iterator pos)
{
	assert(pos != end());

	node* prev = pos._node->_prev;
	node* next = pos._node->_next;

	prev->_next = next;
	next->_prev = prev;
	delete pos._node;

	return iterator(next);
}
// 尾删
void pop_back()
{
	erase(--end());
}
// 头删
void pop_front()
{
	erase(begin());
}

 6、构造函数和析构函数

  6.1、默认构造

  由于后面会频繁对空进行初始化,所以在这里对它进行了封装,方便后面的调用。

void empty_init()//空初始化
{
	_head = new node;
	_head->_next = _head;
	_head->_prev = _head;
}
list()
{
	empty_init();
}

  6.2、构造 n 个 val 的对象

//用n个val构造对象
list(int n, const T& val = T())
{
	empty_init();
	for (int i = 0; i < n; i++)
	{
		push_back(val);
	}
}

  6.3、拷贝构造

//拷贝构造传统写法
list(const list& lt)
{
	empty_init();
	for (auto e : lt)
	{
		push_back(e);
	}
}
//拷贝构造现代写法
list(const list& lt)
{
	empty_init();
	list tmp(lt.begin(), lt.end());
	swap(tmp);
}

  6.4、迭代器区间构造

template 
list(Iterator first, Iterator last)
{
	empty_init();
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

  6.5、 赋值运算符重载

//赋值运算符重载
list& operator=(list lt)//注意这里不能用引用
{
	swap(lt);
	return *this;
}

  6.6、析构函数

//要全部清理掉
~list()
{
	clear();
	delete _head;
	_head = nullptr;
}
//不释放头结点
void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
		//这样也可以
		//erase(it++);
	}
}

三、list 模拟实现源代码

template
struct list_node//节点
{
	list_node* _next;
	list_node* _prev;
	T _data;

	list_node(const T& x = T())
		:_next(nullptr)
		, _prev(nullptr)
		, _data(x)
	{}
};
template
struct __list_iterator
{
	typedef list_node node;
	typedef __list_iterator self;
	node* _node;

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

	Ref operator*()//解引用
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	//前置++
	self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	//后置++
	self operator++(int)
	{
		self tmp(*this);
		_node = _node->_next;
		return tmp;
	}
	//前置--
	self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	//后置--
	self operator--(int)
	{
		self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}
	bool operator!=(const self& s)
	{
		return _node != s._node;
	}
	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};
template
class list//链表
{
	typedef list_node node;
public:
	typedef __list_iterator iterator;//普通迭代器
	typedef __list_iterator const_iterator;//const迭代器

	iterator begin()
	{
		return iterator(_head->_next);//匿名对象的返回
	}
	const_iterator begin() const
	{
		return const_iterator(_head->_next);
	}
	iterator end()
	{
		return iterator(_head);
	}
	const_iterator end() const
	{
		return const_iterator(_head);
	}
	void empty_init()//空初始化
	{
		_head = new node;
		_head->_next = _head;
		_head->_prev = _head;
	}
	list()
	{
		empty_init();
	}
	//迭代器区间构造
	template 
	list(Iterator first, Iterator last)
	{
		empty_init();
		while (first != last)
		{
			push_back(*first);//push_back使用的前提是要有哨兵位的头结点
			++first;
		}
	}
	// 交换函数
	void swap(list& tmp)
	{
		std::swap(_head, tmp._head);
	}
	//现代拷贝构造
	list(const list& lt)
	{
		list tmp(lt.begin(), lt.end());
		swap(tmp);
	}
	//现代赋值写法
	list& operator=(list lt)
	{
		swap(lt);
		return *this;
	}
	~list()//要全部清理掉
	{
		clear();
		delete _head;
		_head = nullptr;
	}
	void clear()//不释放头结点
	{
		iterator it = begin();
		while (it != end())
		{
			it = erase(it);
			//这样也可以
			//erase(it++);
		}
	}
	void insert(iterator pos, const T& x)
	{
		node* cur = pos._node;
		node* prev = cur->_prev;
		node* new_node = new node(x);

		prev->_next = new_node;
		new_node->_prev = prev;
		new_node->_next = cur;
		cur->_prev = new_node;
	}
	iterator erase(iterator pos)
	{
		assert(pos != end());

		node* prev = pos._node->_prev;
		node* next = pos._node->_next;

		prev->_next = next;
		next->_prev = prev;
		delete pos._node;

		return iterator(next);
	}
	//尾插
	void push_back(const T& x) const
	{
		//node* new_node = new node(x);
		//node* tail = _head->_prev;
		链接节点之间的关系
		//tail->_next = new_node;
		//new_node->_prev = tail;
		//new_node->_next = _head;
		//_head->_prev = new_node;
		insert(end(), x);
	}
	//头插
	void push_front(const T& x)
	{
		//node* head = _head->_next;
		//node* new_node = new node(x);

		//_head->_next = new_node;
		//new_node->_prev = _head;
		//new_node->_next = head;
		//head->_prev = new_node;
		insert(begin(), x);
	}
	//尾删
	void pop_back()
	{
		erase(--end());
	}
	//头删
	void pop_front()
	{
		erase(begin());
	}
private:
	node* _head;
};

四、list 的迭代器失效

  当我们使用 erase 进行删除后,此时指向删除位置的迭代器就失效了,再次使用就会令程序崩溃。

  因此若要多次删除,则需要在使用后利用 erase 的返回值更新迭代器,这样使用才不会出现错误。

int main()
{
	vector v = { 1, 2,3,5,4,6 };
	list lt(v.begin(), v.end());
	list::iterator pos = find(lt.begin(), lt.end(), 3);
	for (int i = 0; i < 3; i++)
	{
		pos = lt.erase(pos);   //利用erase的返回值更新迭代器
	}
	for (auto e : lt) cout << e << " ";
	cout << endl;
	return 0;
}

五、list 和 vector的对比

vector list
底 层 结 构 动态顺序表,一段连续空间 带头结点的双向循环链表
随 机 访 问 支持随机访问,访问某个元素效率O(1) 不支持随机访问,访问某个元素 效率O(N)
插 入 和 删 除 任意位置插入和删除效率低,需要搬移元素,时间复杂 度为O(N),插入时有可能需要增容,增容:开辟新空 间,拷贝元素,释放旧空间,导致效率更低 任意位置插入和删除效率高,不 需要搬移元素,时间复杂度为 O(1)
空 间 利 用 率 底层为连续空间,不容易造成内存碎片,空间利用率 高,缓存利用率高 底层节点动态开辟,小节点容易 造成内存碎片,空间利用率低, 缓存利用率低
迭 代 器 原生态指针 对原生态指针(节点指针)进行封装
迭 代 器 失 效 在插入元素时,要给所有的迭代器重新赋值,因为插入 元素有可能会导致重新扩容,致使原来迭代器失效,删 除时,当前迭代器需要重新赋值否则会失效 插入元素不会导致迭代器失效, 删除元素时,只会导致当前迭代 器失效,其他迭代器不受影响
使 用 场 景 需要高效存储,支持随机访问,不关心插入删除效率 大量插入和删除操作,不关心随机访问


本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。

【C++】STL之list深度剖析及模拟实现_第1张图片

 

你可能感兴趣的:(c++,开发语言)