DataStructure-List

124 阅读12分钟

List


概述

list 是 STL 的容器之一,它是一个类模板。在 STL 中 list 采用双向带头循环链表实现,故本次模拟实现依然采用这种方式(若有疑惑请参考 C 语言基础数据结构)。一般而言,需要对 list 的节点和迭代器进行单独的封装,这将会展现出一个相对复杂的结构,详见下文。


类的实现

成员变量

由于是双向带头循环链表,在类中只需要存储头节点的指针即可,通过头节点即可进行所有链表的访问操作。这里还会储存一个 _size 方便记录容量而非通过遍历链表的方式读取。一般而言节点定义为结构体更为合适,因为默认就是 public,方便使用。用类定义也无伤大雅。

namespace Thepale
{
	template <typename T>
	class list_node //链表节点
	{
	public:
		list_node* _prev;
		list_node* _next;
		T _val;
		
	public:
		list_node(const T& val = T())
			:_prev(nullptr)
			,_next(nullptr)
			,_val(val)
		{}
	};
    
    template <typename T>
	class list
	{
	private:
		typedef list_node<T> list_node;

	private:
		list_node* _head;
		size_t _size;
}

这里对 list_node<T> 进行 typedef 是有意义的,否则头节点的定义应该是:list_node<T>* _head; typedef 后我自认为可读性是更强的,也更不容易出现漏写模板参数等错误。


迭代器

迭代器在链表这一节中被单独拎出来,首先以 vector 为例,来谈谈迭代器。vector 中迭代器是原生指针,所以对迭代器只是简单的 typedef,指针的自增自减操作对编译器而言都是已知操作,例如 int 类型的指针自增就是增加四个字节。(vector 使用原生指针直接封装是一种方法,同样也可以单独封装迭代器)vector 能使用原生指针的很大原因是它从物理空间上是连续存储的,和本身的数据结构有很大关系。而 list 在物理空间上并不连续,也不支持随机访问,当进行自增操作时,若采用原生指针封装必然出现不可预料的错误。故 list 的自增操作我们所希望的是从当前节点移动到下一节点,需要通过运算符重载完成,故 list 的迭代器必须是被单独封装的,否则无法实现运算符重载。


成员变量

template <typename T, typename Ref, typename Ptr>
class list_iterator
{
public:
    typedef list_node<T> list_node;
    typedef list_iterator<T, Ref, Ptr> self;
    
public:
    list_node* _node;
}

迭代器的成员仅需要节点指针即可,在迭代器中对节点指针进行操作即是目的。typedef self 仅仅是为了在返回参数时可以简单明了,维持代码可读性。至于传递三个模板参数是为了解决 const 迭代器的相关问题,具体见:[Iterators](# Iterators)


成员函数

构造函数

构造函数仅需要完成将节点的指针构造为迭代器对象的工作即可。

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

而对于迭代器的拷贝构造、赋值运算符重载,仅完成浅拷贝即可。由于管理的空间并非迭代器所有,迭代器仅是对其进行一系列的运算操作,故不具有释放权限,析构函数也是不必要的。


operator++

自增运算符对应的是迭代器自增的过程,需要完成从当前节点指向下一节点的过程:

self& operator++()
{
    _node = _node->_next;

    return *this;
}

self operator++(int)
{
    self ret = *this;
    _node = _node->_next;

    return ret;
}

这时 self 的价值便体现出来,否则返回值将要写成:list_iterator<T, Ref, Ptr>& list_iterator<T, Ref, Ptr>

operator--
self& operator--()
{
    _node = _node->_prev;

    return *this;
}

self operator--(int)
{
    self ret = *this;
    _node = _node->_prev;

    return ret;
}

operator*

此操作对应着迭代器的解引用,而这一步的目的是取出数据并返回引用:

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

而这里的返回值或许写成:T& 即可,因为有了 T 即有了 T& 和 T*,但这一做并不合适,Ref 和 Ptr 的存在也并非冗余,详见:[Iterators](# Iterators)

operator->

箭头的操作返回的是数据的地址:

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

这一操作可能会让人觉得有些怪异,但当 T 类型为自定义类型时,通过箭头可以获得 T 类型对象的地址,虽然通过解引用操作也可以直接获取 T 类型对象,但它们访问 T 类型对象的成员(假设有 T 类,成员为 public 的 int _a;)的操作分别是这样的:

解引用:(*it)._a; 箭头:it->_a

但其实这是编译器优化后的结果,箭头的实际操作为:it->->_a,故箭头的重载更像是为了符合这一特殊情况而产生的,不必深究。


operator!=

迭代器需要判断是否等于 end() 来判断是否结束循环,故不等于的判断是必要的,直接比较节点地址即可。

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

成员函数

构造函数

链表在构造时必须完成的是生成头节点,并完成头节点的链接。

//无参构造
list()
    :_head(new list_node())
    ,_size(0)
{
    _head->_prev = _head;
    _head->_next = _head;
}

用 n 个 val 构造会复用 resize,具体见:[resize](# resize)

//n 个 val 构造
list(size_t n, const T& val = T())
    :_head(new list_node())
    ,_size(0)
{
    _head->_prev = _head;
    _head->_next = _head;

    resize(n, val);
}

list(int n, const T& val = T()) //防止迭代器区间构造的误识别
    :_head(new list_node())
    ,_size(0)
{
    _head->_prev = _head;
    _head->_next = _head;

    resize(n, val);
}

迭代器区间构造会复用 push_back,详见:[push_back](# push_back)

//迭代器区间构造
template <typename InputIterator>
list(InputIterator first, InputIterator last)
    :_head(new list_node())
    , _size(0)
{
    _head->_prev = _head;
    _head->_next = _head;

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

拷贝构造函数

拷贝构造函数仅需要将需要拷贝的对象的元素一个个取出来,将值一个个 push_back 进新对象即可。

list(const list<T>& ls)
    :_head(new list_node())
    , _size(0)
{
    _head->_prev = _head;
    _head->_next = _head;

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

可以发现构造函数初始化头节点的操作是必要的,且需要进行多次,这里进行单独封装也是可以的,本文并没有这样做。

析构函数

clear() 的工作是清除除了头节点的所有节点,故析构函数仅需清除所有节点后再释放头节点即可,关于 clear 详见:[clear](# clear)

~list()
{
    clear();
    delete _head;
}

赋值运算符重载

仍然采用现代写法,调用拷贝构造交换空间即可。

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

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

    return *this;
}

Iterators

typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;

由于迭代器的使用统一规定是 iterator(例如 list<int>::iterator 是合法的,而并非 list_iterator<int, int&, int*>),虽然链表的迭代器被单独封装,但在链表中将其 typedef 为 iterator 反而是必要的,一是为了遵循标准,二是为了方便使用。这里通过传递 T& 和 const T&,正好区分了普通迭代器和 const 迭代器。因为若传递 const T& 则迭代器中的 Ref 参数为 const T&,在返回 Ref 时,正好返回的时 const T&,从而实现了 const 迭代器的目的。这就是需要传递三个模板参数的原因,这样避免了重新实现一个 const_list_iterator 的冗余操作,因为它们仅仅是在一些函数的返回值上有区别。

从底层而言,不同的参数传递会实例化出不同的对象,以上写法用于区分 const 迭代器和普通迭代器实际上和实现两个迭代器并无二至,只是将这一工作交给了编译器完成。

请注意这两个 typedef 必须是 public 的,因为在类外对迭代器的访问是被允许的。

typedef 也为迭代器的使用提供了便利性和可读性。

iterator begin()
{
    return _head->_next; //第一个有效节点是头节点的下一个位置
}

iterator end()
{
    return _head; //尾节点即最后一个有效节点的下一个位置,即头节点
}

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

const_iterator end() const
{
    return _head;
}

Capacity

size

由于在成员变量中记录了 _size 的值,则在返回 _size 时极其方便,时间复杂度为 O(1),若采用遍历链表的方式读取节点个数,则时间复杂度为 O(N)。

size_t size() const
{
    return _size;
}
empty

判空操作使用 _size 也能方便的完成。

bool empty() const
{
    return _size == 0;
}

Element Access

由于链表不支持随机访问,一般通过遍历访问,库中只提供了两个意义不大的接口:

front
T& front()
{
    assert(!empty());

    return _head->_next->_val;
}

const T& front() const
{
    assert(!empty());

    return _head->_next->_val;
}
back
T& back()
{
    assert(!empty());

    return _head->_prev->_val;
}

const T& back() const
{
    assert(!empty());

    return _head->_prev->_val;
}

Modifiers

insert

insert 的重载较多,而大部分会进行复用,在本篇中会尽量展现这一复用过程,因为往往它们是实际中使用频繁的接口:

iterator insert(iterator pos, size_t n, const T& val)
{
    assert(pos != nullptr);

    list_node* pos_prev = pos._node->_prev;
    list_node* cur = pos._node;
    list_node* new_node = nullptr;

    while (n--)
    {
        new_node = new list_node(val);
		
        //链接
        new_node->_prev = pos_prev;
        new_node->_next = cur;
        pos_prev->_next = new_node;
        cur->_prev = new_node;

        cur = new_node;
        
        _size++;
    }

    return cur; //迭代器返回新插入的元素 - 且这里是单参数的构造触发隐式类型转换
}

这里的 _size 是在循环内自增,若在循环外采用 _size += n; 会因为 size_t 类型导致 _size 错误,需要进一步处理,故直接在循环内处理较为方便。

iterator insert(iterator pos, const T& val)
{
    return insert(pos, 1, val);
}

需要注意 insert 不会存在迭代器失效,在 C++ 官方文档中,返回值是新插入节点位置的迭代器。

push_front

头插即在 begin 位置插入。本质是对 [insert](# insert) 的复用。

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

尾插即在 end 位置插入。本质是对 [insert](# insert) 的复用。

void push_back(const T& val)
{
    insert(end(), val);
}
erase
iterator erase(iterator first, iterator last)
{
    assert(!empty()); //为空不能删除

    list_node* del = first._node;
    list_node* del_prev = nullptr;
    list_node* del_next = nullptr;

    while (del != last._node) //删除节点
    {
        del_prev = del->_prev;
        del_next = del->_next;

        del_prev->_next = del_next;
        del_next->_prev = del_prev;

        delete del;

        del = del_next; //记录下一个要删除的节点
        _size--;
    }

    return del;
}

erase 会导致迭代器失效,返回值为被删除节点的下一个位置。

iterator erase(iterator pos)
{
    iterator first = pos;
    iterator last = ++pos;
    return erase(first, last);
}
pop_front

头删即删除 begin 位置的节点。本质是对 [erase](# erase) 的复用。

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

尾删即删除 end 位置的节点。本质是对 [erase](# erase) 的复用。

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

clear 实则是对 [erase](# erase) 的复用,删除 begin 到 end 区间的所有节点即可。但不包括头节点。

void clear()
{
    if (!empty())
    {
        erase(begin(), end());
    }
}
resize

resize 是对 [push_back](# push_back) 或 [pop_back](# pop_back) 的复用。

void resize(size_t n, const T& val = T())
{
    while (n < _size) { pop_back(); }
    while (n > _size) { push_back(val); }
}

整体实现

#pragma once

#include <iostream>
#include <assert.h>

namespace Thepale
{
    //list_node
	template <typename T>
	class list_node
	{
	public:
		list_node* _prev;
		list_node* _next;
		T _val;
		
	public:
		list_node(const T& val = T())
			:_prev(nullptr)
			,_next(nullptr)
			,_val(val)
		{}
	};

	//list_iterator
	template <typename T, typename Ref, typename Ptr>
	class list_iterator
	{
	public:
		typedef list_node<T> list_node;

	public:
		list_node* _node;
		typedef list_iterator<T, Ref, Ptr> self;
	public:
		list_iterator(list_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 ret = *this;
			_node = _node->_next;

			return ret;
		}

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

		self operator--(int)
		{
			self ret = *this;
			_node = _node->_prev;

			return ret;
		}

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

	//list
	template <typename T>
	class list
	{
	private:
		typedef list_node<T> list_node; //私有

	public:
		typedef list_iterator<T, T&, T*> iterator; //公有
		typedef list_iterator<T, const T&, const T*> const_iterator;


	private:
		list_node* _head;
		size_t _size;

	public:
		//构造函数
		list()
			:_head(new list_node())
			,_size(0)
		{
			_head->_prev = _head;
			_head->_next = _head;
		}

		list(size_t n, const T& val = T())
			:_head(new list_node())
			,_size(0)
		{
			_head->_prev = _head;
			_head->_next = _head;

			resize(n, val);
		}

		list(int n, const T& val = T())
			:_head(new list_node())
			, _size(0) //要复用 resize _size必须是 0,不可以直接初始化 _size
		{
			_head->_prev = _head;
			_head->_next = _head;

			resize(n, val);
		} 

		template <typename InputIterator>
		list(InputIterator first, InputIterator last)
			:_head(new list_node())
			, _size(0)
		{
			_head->_prev = _head;
			_head->_next = _head;

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

		//拷贝构造函数
		list(const list<T>& ls)
			:_head(new list_node())
			, _size(0)
		{
			_head->_prev = _head;
			_head->_next = _head;

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

		//析构函数
		~list()
		{
			clear();
			delete _head;
		}

		//赋值运算符重载
		list<T>& operator=(list<T> ls)
		{
			swap(ls);

			return *this;
		}

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

		iterator end()
		{
			return _head;
		}

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

		const_iterator end() const
		{
			return _head;
		}

		//Capacity
		bool empty() const
		{
			return _size == 0;
		}

		size_t size() const
		{
			return _size;
		}

		//Element Access
		T& front()
		{
			assert(!empty());

			return _head->_next->_val;
		}

		const T& front() const
		{
			assert(!empty());

			return _head->_next->_val;
		}

		T& back()
		{
			assert(!empty());

			return _head->_prev->_val;
		}

		const T& back() const
		{
			assert(!empty());

			return _head->_prev->_val;
		}

		//Modifiers
		iterator insert(iterator pos, size_t n, const T& val)
		{
			assert(pos != nullptr);

			list_node* pos_prev = pos._node->_prev;
			list_node* cur = pos._node;
			list_node* new_node = nullptr;

			while (n--)
			{
				new_node = new list_node(val);

				new_node->_prev = pos_prev;
				new_node->_next = cur;
				pos_prev->_next = new_node;
				cur->_prev = new_node;

				cur = new_node;

				_size++;
			}


 			return cur; //迭代器返回新插入的元素 - 且这里是单参数的构造触发隐式类型转换
		}

		iterator insert(iterator pos, const T& val)
		{
			return insert(pos, 1, val);
		}

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

		void push_back(const T& val)
		{
			insert(end(), val);
		}

		iterator erase(iterator first, iterator last)
		{
			assert(!empty());

			list_node* del = first._node;
			list_node* del_prev = nullptr;
			list_node* del_next = nullptr;

			while (del != last._node)
			{
				del_prev = del->_prev;
				del_next = del->_next;

				del_prev->_next = del_next;
				del_next->_prev = del_prev;

				delete del;

				del = del_next;
				_size--;
			}

			return del;
		}

		iterator erase(iterator pos)
		{
			iterator first = pos;
			iterator last = ++pos;
			return erase(first, last);
		}

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

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

		void clear()
		{
			if (!empty())
			{
				erase(begin(), end());
			}
		}

		void resize(size_t n, const T& val = T())
		{
			while (n < _size) { pop_back(); }
			while (n > _size) { push_back(val); }
		}
		
        //Non-Member Function Overloads
		void swap(list<T>& ls)
		{
			std::swap(_head, ls._head);
			std::swap(_size, ls._size);
		}
	};
}

以下为测试代码(非严谨测试):

#include "list.h"
using namespace std;

#include <list>
#include <vector>

int main()
{
	//构造函数测试
	cout << "构造函数测试" << endl;
	Thepale::list<int> e1; for (auto& e : e1) { cout << e << " "; } cout << endl;
	Thepale::list<int> e2(5, 1314); for (auto& e : e2) { cout << e << " "; } cout << endl;
	std::vector<int> v1(10, 666);
	Thepale::list<int> e3(v1.begin() + 2, v1.end()); for (auto& e : e3) { cout << e << " "; } cout << endl;

	//拷贝构造函数测试
	cout << "拷贝构造函数测试" << endl;
	Thepale::list<int> e4(6, 912);
	Thepale::list<int> e5(e4);  for (auto& e : e5) { cout << e << " "; } cout << endl;

	//赋值运算符重载测试
	cout << "赋值运算符重载测试" << endl;
	Thepale::list<int> e6(5, 716);
	Thepale::list<int> e7(8, 888); for (auto& e : e7) { cout << e << " "; } cout << endl;
	e7 = e6; for (auto& e : e7) { cout << e << " "; } cout << endl;

	//size 测试
	cout << "size 测试" << endl;
	Thepale::list<int> e8(666, 1); cout << e8.size() << endl;

	//empty 测试
	cout << "empty 测试" << endl;
	Thepale::list<int> e9; cout << e9.empty() << endl;
	Thepale::list<int> e10(1, 1); cout << e10.empty() << endl;

	//front、back 测试
	cout << "front、back 测试" << endl;
	Thepale::list<int> e11;
	e11.push_back(1);
	e11.push_back(2);
	e11.push_back(3);
	e11.push_back(4);
	cout << e11.front() << endl; cout << e11.back() << endl;

	//insert 测试
	cout << "insert 测试" << endl;
	Thepale::list<int> e12;
	e12.push_back(1);
	e12.push_back(2);
	e12.push_back(3); for (auto& e : e12) { cout << e << " "; } cout << endl;
	e12.insert(++e12.begin(), 3, 888); for (auto& e : e12) { cout << e << " "; } cout << endl;
	e12.insert(e12.end(), 0); for (auto& e : e12) { cout << e << " "; } cout << endl;
	e12.push_front(10000); for (auto& e : e12) { cout << e << " "; } cout << endl;

	//erase 测试
	cout << "erase 测试" << endl;
	Thepale::list<int> e13(8, 888);
	e13.push_back(666);
	e13.push_back(666);
	e13.push_back(666);
	e13.push_front(111);
	e13.push_front(111);
	e13.push_front(111);
	e13.erase(++e13.begin()); for (auto& e : e13) { cout << e << " "; } cout << endl;
	e13.pop_back(); for (auto& e : e13) { cout << e << " "; } cout << endl;
	e13.pop_front(); for (auto& e : e13) { cout << e << " "; } cout << endl;
	e13.erase(++e13.begin(), --e13.end()); for (auto& e : e13) { cout << e << " "; } cout << endl;
	e13.clear(); for (auto& e : e13) { cout << e << " "; } cout << endl;

	//resize 测试
	cout << "resize 测试" << endl;
	Thepale::list<int> e14;
	e14.resize(10, 102938); for (auto& e : e14) { cout << e << " "; } cout << endl;
		
 	return 0;
}

补充说明

  • 数据结构类型的文章并不具备顺序阅读的可行性,或许在中途可能会遇到还未实现的函数,你可以选择跳转查看或暂时忽略,等掌握了该函数调用的函数再回头查看。
  • C++11 的内容暂时未被添加,例如 emplace,它涉及右值引用和可变模板参数,它会在 C++ 语法篇中被讲解和实现,而这里的数据结构追求可读性强,耦合性低,简洁易懂。故反向迭代器的内容也未被添加,它一般通过封装正向迭代器实现。后续内容将会涉及到它,但它并不适合直接出现在 vector 或 list 中来追求复杂度和理解难度的上升。