深入计算机语言之C++:STL之list的模拟实现

🔑🔑 博客主页:阿客不是客

🍓🍓 系列专栏:从C语言到C++语言的渐深学习

欢迎来到泊舟小课堂

😘博客制作不易欢迎各位👍点赞+⭐收藏+➕关注

一、基本框架的实现

1.1 节点的构建

既然是要实现链表,我们首先要做的应该是建构结点。此外,为了和真正的 list 进行区分,我们这里仍然在自己的命名空间内实现。

回想一下我们的《数据结构》专栏中,双链表是如何定义的:

cpp 复制代码
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType val;
	struct ListNode* next;
	struct ListNode* prev;
}LTN;

而我们即将要实现的 list,需要的肯定是 "通用的 list" ,像这种情况 typedef 就帮不上忙了。我们用模板来实现。

**💬 代码:**建构双链表的结点:

cpp 复制代码
namespace bit
{
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;
    }
}

❓ **思考:**为什么这里 ListNode 要加 <T> ?

💡 解读: 因为类模板不支持自动推类型。 结构体模板或类模板在定义时可以不加 <T>,但 使用时必须加 <T>。

准备好 _data,放置好前驱 _next 和后继结点 _prev 后,我们的结点就有了 "结构" 。

1.2 节点的初始化

我们知道,结构体 struct 在 C++ 中升级成了类,因此它也有调用构造函数的权利。也就是说,在创建结构体对象的时会调用构造函数。既然如此,结点的初始化工作,我们可以考虑写一个构造函数去初始化,岂不美哉?

① 将数据给给 data

② 将 next 和 prev 都置成空

这些任务我们可以写到 struct ListNode 的构造函数中,我们还可以设计成全缺省,给一个匿名对象 T() 。如此一来,如果没有指定初识值,它就会按模板类型去给对应的初始值了。

💬 结点初始化:

cpp 复制代码
namespace bit
{
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;

		// list_node<T>(const T& x = T())
        // 一样的
        list_node(const T& x = T())
	        : _prev(nullptr)
	        , _next(nullptr)
	        , _data(x)
        {}
	};
}

1.3 list 类的构建

设计好结点后,我们现在可以开始实现 list 类了。考虑到我们刚才实现的 "结点" ListNode<T> 类型比较长,为了美观我们将其 typedef 成 Node:

cpp 复制代码
typedef list_node<T> Node;

现在,我们用 Node 就表示 ListNode<T> 了,这也符合我们之前的使用习惯。因为是带头(哨兵位)双向循环链表,我们先要带个头儿。我们先要把头结点 _head 给设计出来,而 _prev 和 _next 是默认指向头结点的。

💬 **代码:**类的建造:

cpp 复制代码
template<class T>
class list
{
	typedef list_node<T> Node;
public:
	list()
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
		_size = 0;
	}

private:
	Node* _head;
	size_t _size;
};

1.4 push_back() 尾插

还是按老规矩,我们先去实现一下最经典的 push_back 尾插,好让我们的 list 先跑起来。尾插我们需要做什么呢?我们来冷静分析一下:

我们要先找到尾节点和创建新节点:

在带头双向循环链表中想要找到尾节点再容易不过了,尾结点就是头结点的前驱指针,直接 _head->_prev,然后直接 new 一个新结点 new_node,自动调用我们刚才写的 "建构结点" struct ListNode。

cpp 复制代码
Node* newnode = new Node(x);

至此,我们就找到了尾结点,并准备好要插入的新节点了。

然后将该节点插入尾部,我们在数据结构中已经实现:

💬 **代码:**实现尾插操作:

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

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

我们还可以在类中加入 size 的数据来方便我们取用:

cpp 复制代码
template<class T>
class list
{
	typedef list_node<T> Node;
public:
	list()
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
		_size = 0;
	}

	void push_back(const T& x)
	{
		Node* newnode = new Node(x);
		Node* tail = _head->_prev;

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

		_size++;
	}

private:
	Node* _head;
	size_t _size;
};

尾插写好了,我们来跑一下看看效果如何。 我们随便插入一些数据,然后打开监视窗口看看 push_back 的效果如何。

cpp 复制代码
void list_test1()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
}

🚩 调试结果:

即使我们链表为空,也是可以进行尾插操作的,这就是结构的优势。

二、list 迭代器的实现

2.1 迭代器不全都是原生指针

list 的重点是迭代器,因为这里的迭代器的实现和我们之前讲的实现方式都不同。我们之前讲的 string 和 vector 的迭代器都是一个原生指针,实现起来是非常简单的。但是 list 是一个链表,你的迭代器还能这样去实现吗?在空间上不是连续的,如何往后走?

而这些所谓的 "链接" 其实都是我们想象出来的,实际上根本就不存在。而这些链接的含义只是 "我存的就是你的地址" ,所以我可以找到你的位置。

自带的 解引用* 和 ++ 的功能,是没法在链表中操作的。但是,得益于C++有运算符重载的功能,我们可以用一个类型去对结点的指针进行封装!然后重载运算符 operator++ 和 operator* ,是不是就可以控制其解引用并 ++ 到下一个位置了?

2.2 迭代器的构造

**💬 代码:**只需要用一个结点的指针就可以构造了:

cpp 复制代码
struct list_iterator
{
	typedef list_node<T> Node;
    typedef list_iterator<T> Self;
	Node* _node;

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

2.3 operator++

加加分为前置和后置,我们这里先实现以下前置++。

**💬 代码:**前置++的实现:

cpp 复制代码
Self& operator++()
{
	_node = _node->_next;
	return *this;
}

因为前置是直接改变本体,我们直接 return *this 即可。因为除了作用域还在,所以可以用引用返回, list_iterator<T>& ,为了方便书写,我们对它进行 typedef。

对应的,后置++ 我们可以拷贝构造出一个 tmp 存储原来的值,这样虽然改变本体了,但是返回的还是之前的值,这就实现了后置++。此外,因为前置++后置++都是 operator++,区分方式是后置++用占位符 (int) 占位,这些知识点我们在之前讲解日期类的时候都说过。

**💬 代码:**后置++的实现:

cpp 复制代码
Self operator++(int)
{
	Self tmp(*this);   // 拷贝构造一个tmp存储原来的值
	_node = _node->_next;
	return tmp;
}

2.4 operator*

解引用就是取结点 _node 里的数据,并且 operator* 和指针一样,不仅仅能读数据,还能写数据。为了使 operator* 能支持修改的操作,我们这里用引用返回 & (返回 _node 中 _data 的别名)

**💬 代码:**解引用的重载:

cpp 复制代码
T& operator*()
{
	return _node->_data;
}

有了 operator++ 和 operator* ,我们就可以来完成一下我们的迭代器了。

**💬 代码:**在 list 类中设计 begin 和 end:

cpp 复制代码
template<class T>
class list
{
	typedef list_node<T> Node;
public:
	typedef list_iterator<T> iterator;

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

		return _head->_next; // 发生隐式类型转换
	}

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

		return _head; // 发生隐式类型转换
	}



    // ......

}

因为判断迭代器要用到 != ,所以我们还要实现一下这个操作符的重载。

2.5 operator!=

**❓ 思考:**如何判断是否相等呢?

如果两个迭代器结点的指针指向的是同一个结点,那就说明是相等的迭代器。反之,如果不是就说明不是相等的迭代器!

**💬 代码:**这里我们利用 bool 的性质直接 return 返回要判断的条件即可:

cpp 复制代码
bool operator!=(const Self& s)
{
	return _node != s._node;
}

现在我们可以来测试一下我们的迭代器了:

cpp 复制代码
void list_test1()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);

	list<int>::iterator it = l.begin();
	while (it != l.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

🚩 运行结果如下:

2.6 迭代器的拷贝构造、赋值和析构

❓ **思考:**拷贝构造和赋值重载是否需要自己实现?析构呢?

先说结论 ------ list 的拷贝构造和赋值不需要自己实现,默认生成的即可。

cpp 复制代码
it2(it1)
it2 = it1 浅拷贝

当前迭代器赋值给另一个迭代器是不需要深拷贝的,浅拷贝就可以。

cpp 复制代码
template<class T>
struct list_iterator
{
    typedef ListNode<T> Node; 
    Node* _node;
    ...
}

迭代器这里虽然有一个结点的指针,但是它并不是迭代器管的,是链表 list 管的,链表 list 的析构函数会把这个结点 _node 给释放掉的。所以它的释放和迭代器没什么关系,所以我们不需要关心它的析构。

**总结:**迭代器是借助结点的指针访问修改链表的,结点是属于链表的,而不属于迭代器,所以不用去管它的释放问题。 因此,拷贝构造、赋值重载和析构函数,这些都不需要自己实现,默认生成的可以。

2.7 箭头操作符

迭代器是像指针一样的,所以要重载两个解引用。为什么?指针如果指向的类型是原生的普通类型,要取对象是可以用解引用,但是如果指向而是一个结构,并且我们又要取它的每一个成员变量,就像这样:

cpp 复制代码
struct Date
{
	int _year;
	int _month;
	int _day;

	Date(int year = 1, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
};

void list_test3()
{
	list<Date> l;
	l.push_back(Date(2024, 10, 1));
	l.push_back(Date(2022, 10, 2));
	l.push_back(Date(2022, 10, 3));

	list<Date>::iterator it = l.begin();
	while (it != l.end())
	{
		// cout << *it << " ";  假设我们没有实现流插入,我们自己访问
		cout << (*it)._year << "/" << (*it)._month << "/" << (*it)._day << endl;
		it++;
	}
	cout << endl;
}

🚩 运行结果如下:

我们发现,在我们没有实现日期类的流提取运算符的前提下,想去迭代链表 L,我们就需要 *(it)._xxx 去访问,而大多数主流习惯应该是用 -> 去访问的:

所以我们这里可以去实现一下箭头操作符 ->

💬 **代码:**其实现方式似乎有些出乎意料,思考下原理是什么?

cpp 复制代码
T* operator->()
{
	return &_node->_data;     
}

其实在调用的时候不是一个箭头,而是两个:

cpp 复制代码
cout << it.operator->()->_year << "/" << it.operator->()->_month << "/" << it.operator->()->_day << endl;

第一个箭头去调用运算符重载,第二个是原生箭头去访问。但是这样写的可读性太差了 ,所以编译器这里进行了优化,省略了一个->

**🔺 总结:**所有类型重载 operator-> 时都会省略一个箭头。

2.8 list 链表的打印函数

我们频繁调用打印函数有点太过麻烦,我们可以把它放到一个函数里:

这里考虑到减少拷贝,我们使用引用返回,我们之前也说过这种情况能用 const 就用 const。所以这里就成 const_iterator 了,而我们刚才实现的是普通迭代器,会导致没法遍历。

cpp 复制代码
template<class T>
void print_list(const list<T> v)
{
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

void list_test4()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);

	print_list(l);
}

🚩 **运行结果:**报错

❓ **思考:**想想本质 ------ const 迭代器和普通迭代器的区别是什么?

💡 解答: 普通迭代器访问普通对象,可读可写;const 迭代器访问 const 对象,可读但不可写。所以我们这里自然是 需要实现 const 迭代器,即实现一个 "可读但不可写" 的迭代器。

所以直接在 list_iterator 里面重载一个 const 类型的 operator* 解决不了问题,我们得重新实现一个 list_const_iterator出来。

2.9 const 迭代器的实现

💬 **代码:**定义 const 迭代器

cpp 复制代码
template<class T>
struct list_const_iterator
{
	typedef list_node<T> Node;
	typedef list_const_iterator<T> Self;
	Node* _node;

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

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

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

	Self& operator++()    // 前置++
	{
		_node = _node->_next;
		return *this;
	}
	Self operator++(int)  // 后置++
	{
		Self tmp(*this);   // 拷贝构造一个tmp存储原来的值
		_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;
	}
};

这里我们把 list_iterator 都修改成 list_const_iterator,并且对于解引用 operator* 的重载,我们将其改成 const 引用返回,这样就只能读不能写了。

💬 **代码:**然后我们这里再在 list 中 typedef 一下 const 迭代器:

cpp 复制代码
template<class T>
class list
{
	typedef list_node<T> Node;
public:
	typedef list_iterator<T> iterator;
	typedef list_const_iterator<T> const_iterator;

	iterator begin()
	{
		// return iterator(_head->_next);
		return _head->_next; // 发生隐式类型转换
	}
	iterator end()
	{
		// return iterator(_head);
		return _head; // 发生隐式类型转换
	}

	const_iterator begin() const
	{
		return _head->_next; // 发生隐式类型转换
	}
	const_iterator end() const
	{
		return _head; // 发生隐式类型转换
	}

    // ...
}

**🔨 测试代码:**我们再来调用一下 print_list 函数:

cpp 复制代码
template<class T>
void print_list(const list<T> v)
{
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

void list_test4()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);

	print_list(l);
}

🚩 运行结果:(运行成功)

这种实现方式可以是可以,但是这么实现好像有点麻烦啊!代码是完全冗余的!这个 const 迭代器和普通迭代器也就是类型名称和返回值不一样而已......

有没有办法可以优化一下呢?我们继续往下看!

2.10 用模板实现 const 迭代器

通过加额外的模板参数去控制 operator 的返回值,你能想到吗?

我们观察发现,只有在* 和-> 的返回值有是否加 const 的区别,所以我们可以在模板中增加两个参数:

cpp 复制代码
// T, T&, T*
template<class T, class Ref, class Ptr>

这样的话,我们 operator* 的返回值我们不要用 T&了,我们改成 Ref;operator-> 的返回值我们不要用 T*了,我们改成 Ptr:

cpp 复制代码
Ref operator*()
{
	return _node->_data;
}

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

💬 **代码:**之后我们就可以在 list 中 typedef 的时候就可以做到 "分流" :

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

	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++(int)  // 后置++
	{
		Self tmp(*this);   // 拷贝构造一个tmp存储原来的值
		_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;
	}
};

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()
	{
		// return iterator(_head->_next);
		return _head->_next; // 发生隐式类型转换
	}
	iterator end()
	{
		// return iterator(_head);
		return _head; // 发生隐式类型转换
	}

	const_iterator begin() const
	{
		return _head->_next; // 发生隐式类型转换
	}
	const_iterator end() const
	{
		return _head; // 发生隐式类型转换
	}


    // ......
}

三、list 增删查改的实现

3.1 insert 在 pos 位置前插入

pos 位置插入,我们通过pos 去找到前驱prev,之后创建新结点,再进行 "缝合" 操作,

💬代码:pos位置前插入

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

	cur->_prev = new_node;
	new_node->_next = cur;
	new_node->_prev = cur_prve;
	cur_prve->_next = new_node;
}

优化:

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

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

	return new_node;
}

有了 insert,我们就可以让之前为了快速把 list 跑起来而实现的 push_back 用 insert 复用一下。

**⚡ 代码复用:**push_back

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

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

	//_size++;

	insert(end(), x);
}

push_back 复用 insert,pos 我们给 end() 。因为 end() 是头结点*_head。* push_back 尾插,即在头结点的前一个位置插入,即 end() 位置。

insert 的 cur_prev 就会代表尾结点,会在 cur_prev 后面插入 new_node,并完成 "缝合",这就做到了尾插。

3.2 erase 删除 pos 位置的结点

删除pos 位置结点,步骤如下:

  1. 找到 pos的前驱和后继
  2. 释放 pos位置结点
  3. 将已删除的 pos结点的前驱和后继 "缝合"

📌 注意: 当然我们还要防止哨兵位头结点*_head*被删的情况,头不小心卸了就没法玩了。这里我还是习惯用暴力的方式去解决,用 assert 断言处理。

💬 代码: 删除pos 位置结点

cpp 复制代码
void erase(iterator pos)
{
	assert(pos != end());

	Node* cur = pos._node;
	cur->_next->_prev = cur->_prev;
	cur->_prev->_next = cur->_next;

	delete cur;
}

❓ 思考: erase 以后,pos是否失效?

一定会失效!因为结点的指针指向的结点被干掉了,这当然会失效。为了救迭代器,我们可以学着文档里的处理方式 ------ 返回刚刚被删除的元素的下一个元素。

⚡**改进:**erase

cpp 复制代码
iterator erase(iterator pos)
{
	assert(pos != end());

	Node* cur = pos._node;
	Node* cur_next = cur->_next;
	cur->_next->_prev = cur->_prev;
	cur->_prev->_next = cur->_next;

	delete cur;

	return cur_next;
}

3.3 push_front 头插

push_back 有了,我们顺手再把 push_front 写了,push_front 头插,即在头结点的下一个位置插入,即 begin() 位置

💬 **代码:**push_front:

cpp 复制代码
void push_front(const T& val)
{
	insert(begin(), x);
}

3.4 pop_back 尾删

pop_back 尾删,即删除头结点的前一个结点,即 --end() 位置的结点。

💬**代码:**直接复用就可以了:

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

3.5 pop_front 头删

pop_front 头删,即删除头结点的下一个结点,即 begin() 位置的结点。

**💬 代码:**仍然是复用:

cpp 复制代码
void pop_front()
{
	erase(begin());
}

3.6 clear 清空链表数据

💬 **代码:**清空链表数据:

cpp 复制代码
void clear()
{
	iterator it = begin();
	while (it != end())
	{
		// iterator next = it;
		// ++it;
		// delete next._node;
        it = erase(it);
	}

	_head->_next = _head;
	_head->_prev = _head;
    _head->_size = 0;
}

🔨 测试代码:

cpp 复制代码
void list_test2()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);

	cout << "删除前:";
	list<int>::iterator it = l.begin();
	while (it != l.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	l.clear();
	cout << "删除后:";
	it = l.begin();
	while (it != l.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

🚩 运行结果:

四、list 的拷贝构造和赋值重载

4.1 list 的深浅拷贝

**❓ 思考:**这里的拷贝构造是深拷贝还是浅拷贝?

cpp 复制代码
void list_test4()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);

	list<int> l1(l);

	print_list(l);
	print_list(l1);
}

💡 这里默认生成的拷贝构造是浅拷贝:

比如这里 浅拷贝导致 l 和 l1 指向同一块地址,析构的时候会导致一块空间被释放两次。这些知识我们在之前讲解深浅拷贝的时候就说过,这里没崩仅仅是因为我们还没设计析构。

我们下面实现一下 ~list,再回来看看。

4.2 ~list 析构

实现了 clear 后,我们再去实现 list 的析构函数就很简单了。我们只需要把哨兵位头结点*_head* 给干掉就行了,并且记得要置成空指针。

cpp 复制代码
~list() {
	// 清空链表有效数据
	clear();
	// 干掉头结点
	delete _head;
	_head = nullptr;
}

🚩 运行结果:(崩溃)

自动生成的拷贝构造是浅拷贝,为了解决这个问题,我们需要手动实现一个深拷贝的拷贝构造!

4.3 拷贝构造的实现

cpp 复制代码
list(const list<T>& l)
{
	_head = new Node();
	_head->_next = _head;
	_head->_prev = _head;

	for (auto e : l)
	{
		push_back(e);
	}
}

4.4 赋值重载的实现

**💬 代码:**现代写法

cpp 复制代码
list<T>& operator=(list<T> l)
{
	swap(l);

	return *this;
}

五、完整代码

cpp 复制代码
#pragma once
#include <iostream>
#include <algorithm>
#include<assert.h>
using namespace std;

namespace bit
{
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;

		// list_node<T>(const T& x = T())
		// 一样的
		list_node(const T& x = T())
			: _prev(nullptr)
			, _next(nullptr)
			, _data(x)
		{}
	};

	// T, T&, T*
	template<class T, class Ref, class Ptr>
	struct list_iterator
	{
		typedef list_node<T> Node;
		typedef list_iterator<T, Ref, Ptr> Self;
		Node* _node;

		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++(int)  // 后置++
		{
			Self tmp(*this);   // 拷贝构造一个tmp存储原来的值
			_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;
		}
	};

	//template<class T>
	//struct list_const_iterator
	//{
	//	typedef list_node<T> Node;
	//	typedef list_const_iterator<T> Self;
	//	Node* _node;

	//	list_const_iterator(Node* node)
	//		: _node(node)
	//	{}

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

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

	//	Self& operator++()    // 前置++
	//	{
	//		_node = _node->_next;
	//		return *this;
	//	}
	//	Self operator++(int)  // 后置++
	//	{
	//		Self tmp(*this);   // 拷贝构造一个tmp存储原来的值
	//		_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;
	//	}
	//};

	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()
		{
			// return iterator(_head->_next);
			return _head->_next; // 发生隐式类型转换
		}
		iterator end()
		{
			// return iterator(_head);
			return _head; // 发生隐式类型转换
		}

		const_iterator begin() const
		{
			return _head->_next; // 发生隐式类型转换
		}
		const_iterator end() const
		{
			return _head; // 发生隐式类型转换
		}

		list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}

		// 拷贝构造
		list(const list<T>& l)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;

			for (auto e : l)
			{
				push_back(e);
			}
		}
		//现代写法
		//template <class Iterator>
		//list(Iterator first, Iterator last)
		//{

		//}

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

			return *this;
		}

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

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

		//list(int n, const T& value = T())
		//{

		//}

		void push_back(const T& x)
		{
			//Node* newnode = new Node(x);
			//Node* tail = _head->_prev;

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

			//_size++;

			insert(end(), x);
		}

		// 在pos位置前插入值为val的节点
		iterator insert(iterator pos, const T& val)
		{
			Node* cur = pos._node;
			Node* cur_prve = cur->_prev;
			Node* new_node = new Node(val);

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

			return new_node;
		}

		// 删除pos位置的节点,返回该节点的下一个位置
		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* cur = pos._node;
			Node* cur_prev = cur->_prev;
			Node* cur_next = cur->_next;

			cur_prev->_next = cur_next;
			cur_next->_prev = cur_prev;

			delete cur;

			return cur_next;
		}

		void pop_back()
		{
			erase(_head->_prev);
		}

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

		void pop_front()
		{
			erase(begin());
		}
		
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				//iterator next = it;
				//++it;
				//delete next._node;
				it = erase(it);
			}

			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}

		size_t size() const
		{
			return _size;
		}

		bool empty() const
		{
			return _size == 0;
		}
		
	private:
		Node* _head;
		size_t _size;
	};

	template<class T>
	void print_list(const list<T> v)
	{
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		//list<int>::const_iterator it = l.begin();
		//while (it != l.end())
		//{
		//	cout << *it << " ";
		//	++it;
		//}
		//cout << endl;
	}

	void list_test1()
	{
		list<int> l;
		l.push_back(1);
		l.push_back(2);
		l.push_back(3);
		l.push_back(4);

		list<int>::iterator it = l.begin();
		while (it != l.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	void list_test2()
	{
		list<int> l;
		l.push_back(1);
		l.push_back(2);
		l.push_back(3);
		l.push_back(4);

		cout << "删除前:";
		list<int>::iterator it = l.begin();
		while (it != l.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

		l.clear();
		cout << "删除后:";
		it = l.begin();
		while (it != l.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}


	struct Date
	{
		int _year;
		int _month;
		int _day;

		Date(int year = 1, int month = 1, int day = 1)
			: _year(year)
			, _month(month)
			, _day(day)
		{}
	};

	void list_test3()
	{
		list<Date> l;
		l.push_back(Date(2024, 10, 1));
		l.push_back(Date(2022, 10, 2));
		l.push_back(Date(2022, 10, 3));

		list<Date>::iterator it = l.begin();
		while (it != l.end())
		{
			// cout << *it << " ";  假设我们没有实现流插入,我们自己访问
			//cout << (*it)._year << "/" << (*it)._month << "/" << (*it)._day << endl;
			cout << it.operator->()->_year << "/" << it.operator->()->_month << "/" << it.operator->()->_day << endl;
			it++;
		}
		cout << endl;
	}

	void list_test4()
	{
		list<int> l;
		l.push_back(1);
		l.push_back(2);
		l.push_back(3);
		l.push_back(4);

		list<int> l1(l);
		list<int> l2;
		l2 = l1;

		print_list(l);
		print_list(l1);
		print_list(l2);
	}
}
相关推荐
mit6.82440 分钟前
What is Json?
c++·学习·json
灶龙1 小时前
浅谈 PID 控制算法
c++·算法
菜还不练就废了1 小时前
蓝桥杯算法日常|c\c++常用竞赛函数总结备用
c++·算法·蓝桥杯
sci_ei1232 小时前
高水平EI会议-第四届机器学习、云计算与智能挖掘国际会议
数据结构·人工智能·算法·机器学习·数据挖掘·机器人·云计算
新知图书2 小时前
Linux C\C++编程-文件位置指针与读写文件数据块
linux·c语言·c++
qystca2 小时前
异或和之和
数据结构·c++·算法·蓝桥杯
涛ing3 小时前
19. C语言 共用体(Union)详解
java·linux·c语言·c++·vscode·算法·visual studio
周杰伦_Jay3 小时前
Ollama能本地部署Llama 3等大模型的原因解析(ollama核心架构、技术特性、实际应用)
数据结构·人工智能·深度学习·架构·transformer·llama
mit6.8243 小时前
[实现Rpc] 项目设计 | 服务端模块划分 | rpc | topic | server
网络·c++·笔记·rpc·架构
萌の鱼3 小时前
leetcode 221. 最大正方形
数据结构·c++·算法·leetcode