C++list的模拟实现

文章目录

  • 一、观察源码
  • 二、模拟实现
    • [1. 节点结构体](#1. 节点结构体)
    • [2. list类](#2. list类)
    • [3. 迭代器的定义与实现](#3. 迭代器的定义与实现)
      • [(1) 前置++--后置++--模拟实现](#(1) 前置++--后置++--模拟实现)
      • [(2) *和->重载模拟实现](#(2) *和->重载模拟实现)
      • [(3) ==和!=重载实现](#(3) ==和!=重载实现)
    • [4. list成员函数模拟实现。](#4. list成员函数模拟实现。)
      • [(1) begin和end模拟实现](#(1) begin和end模拟实现)
      • (2)insert模拟实现
      • [(3) erase模拟实现](#(3) erase模拟实现)
      • [(4) push_back和push_front](#(4) push_back和push_front)
      • [(5) pop_back和pop_front](#(5) pop_back和pop_front)
      • [(6) size和clear](#(6) size和clear)
      • [(7) 析构函数](#(7) 析构函数)
      • [(8) 拷贝构造](#(8) 拷贝构造)
      • [(9) swap和赋值重载](#(9) swap和赋值重载)
  • 三、完整代码实现与测试用例

一、观察源码

我们可以看到源码中定义了三个自定义对象,分别是节点,迭代器和list类。


二、模拟实现

1. 节点结构体

因为list底层是一个带头节点的双向循环链表嘛,所以我们先使用结构体定义一个节点,里面有三个成员变量,分别是指向下一个节点的指针、指向上一个节点的指针和数据。因为数据的类型是不确定的,所以我们这里使用模板来实现。

然后再写一个构造函数,初始化一下成员变量。(C语言中结构体里是不能写成员函数的,C++中可以,其实C++中的结构体被封装成了类,区别只是默认的访问级别不同罢了。struct默认是public的,class默认是private的。)

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

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

2. list类

之后我们再定义一个list类,该类中保存一个list_node节点的指针,将来通过这个指针进行链表的增删查改,还有一个size_t 的变量,用来记录元素个数。

list_node 太长了,先给他typedef一下,变成Node。然后写个构造函数,构造函数中调用了empty_init(),所以实际的初始化逻辑是在empty_init()函数中实现的。

一开始的时候只有一个头节点,我们直接把next指针和prev指针都指向自己就好了。

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

private:
	Node* _head;
	size_t _size;
};

这里我们先写一个尾插,让程序跑起来再说。

尾插的逻辑比较简单,先用new创建一个新的节点,然后按照带头节点的双向循环队列处理好节点之间的指向关系就好了。

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;
}

3. 迭代器的定义与实现

之后我们建立一个迭代器的结构体,该结构体中有一个成员变量是一个指向list_node类型的指针_node。值得注意的是我们这里使用了模板,而且模板参数有三个,除了T之外还多了个Ref和Ptr,分别代表引用类型和指针类型。

因为迭代器分为两种,一种是普通版本,另一种是const版本,不同版本在实现前置++,*等运算符重载的时候,返回值就会不同。如果不用模板的话,我们就得实现两套迭代器,并且这两套迭代器也仅仅只是在返回值上不相同而已,这样就显得不是很划算,因此我们这里使用模板来控制返回值的类型是const版本还是非const版本。

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

		Node* _node;
		//这里为什么不能加const? 
		//加了const const类型的Node*指针不能赋值给非const的_node
		_list_iterator(Node* node) :_node(node)
		{

		}
	};

(1) 前置+±-后置+±-模拟实现

先利用typedef_list_iterator<T, Ref, Ptr>取个别名 self;

这里我们先简单的实现前置++和后置++

这里要注意的是后置++和前置++的区别:

  1. 后置++有一个int类型的参数,而前置++没有。这个参数并没有实际作用,仅仅只是用来区分是前置++还是后置++罢了。
  2. 前置++返回的是该类型的一个引用即self&,而后置++返回的只是该类型的一个拷贝而已。
cpp 复制代码
self operator++(int)//后置++
{
	self tmp(*this);
	_node = _node->_next;

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

同理我们可以实现前置--和后置--

cpp 复制代码
前后置返回值是啥,有没有参数这些都是有说法的。
		self operator--(int)//后置--
		{
			self tmp(_node);
			_node = _node->_prev;
			return tmp;
		}

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

(2) *和->重载模拟实现

之后我们来实现一下解引用*和->重载。

解引用返回的当然就是节点中的数据了,所以直接return _node->_val即可,至于返回值的类型则由模板参数Res来决定。

->的话则是返回_node->_val的地址。

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

(3) ==和!=重载实现

最后就是实现一下==和!=的重载了。比较简单,不在赘述。

cpp 复制代码
bool operator==(const self& it) const
{
	return it._node == _node;
}

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

4. list成员函数模拟实现。

(1) begin和end模拟实现

begin就是返回指向第一个元素的迭代器(第一个元素不是头节点而是头节点的下一个节点),所以直接返回iterator(_head->next)就可以,这里我们是直接返回了一个匿名对象回去。

end返回的是指向最后一个元素的下一个位置的迭代器,那最后一个元素的下一个位置在双向循环链表中不就是头节点吗?所以这里直接返回iterator(_head)就好了,但是我们这里直接返回了一个_head,不知道大家还记不记得有这么一个知识点**构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。**这里就是利用了这个特性

cpp 复制代码
typedef _list_iterator<T, const T&, const T*> const_iterator;
typedef _list_iterator<T, T&, T*> iterator;
iterator begin()
{
	return iterator(_head->_next);
}
iterator end()
{
	return _head;
}

对于const_iterator来说与iterator同理,不再赘述

cpp 复制代码
const_iterator begin() const
{
	return const_iterator(_head->_next);
}
const_iterator end() const
{
	return _head;
}

(2)insert模拟实现

由于实现insert和erase之后可以在后面的多个函数中进行复用,因此我们先模拟实现insert和erase。

insert有两个参数,一个是要插入位置的迭代器,另一个是要插入的元素。

该函数的功能是在迭代器指向的元素之前插入一个新的元素,返回指向新元素的迭代器。

cpp 复制代码
iterator insert(iterator pos, const T& x)
{
	Node* cur = pos._node;//通过迭代器获得当前元素的指针
	Node* pre = cur->_prev;//获得当前元素的上一个元素的指针
	Node* newnode = new Node(x);//建立一个新节点
	pre->_next = newnode;
	newnode->_prev = pre;

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

	++_size;
	return newnode;
}

(3) erase模拟实现

erase有一个参数,就是要删除元素的迭代器,最后会返回下一个元素的迭代器。

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

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

	next->_prev = pre;
	pre->_next = next;
	--_size;

	return next;
}

(4) push_back和push_front

比较简单直接复用insert就好

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

(5) pop_back和pop_front

比较简单直接复用erase就好

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

(6) size和clear

size函数直接return _size即可

clear函数的话可以利用迭代器进行遍历,依次调用erase函数,达到删除所有节点的目的

cpp 复制代码
size_t size()
{
	return _size;
}
void clear()
{
	iterator it = begin();
	while (it != end())
	{
		erase(it);
		++it;
	}
	_size = 0;
}

(7) 析构函数

析构函数中先直接调用clear函数,释放掉所有节点,然后在释放头节点的空间即可。

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

(8) 拷贝构造

先调用empty_init()进行初始化,然后可以利用迭代器,将元素依次push_back即可。

cpp 复制代码
list(const list<T>& it)
{
	empty_init();
	for (auto& e : it)//有了迭代器利用范围for简单解决
	{
		push_back(e);
	}
}

(9) swap和赋值重载

swap函数中直接调用库里的swap将头节点的指针互换就可以了。

赋值重载中直接利用swap进行交换就可以,注意参数list it只是一个临时变量而已,出了作用域就会销毁,所以我们直接将it的数据和自己的数据互换,不仅拿到了它的数据,还把自己的数据交给了它,然后一出函数,临时变量销毁,资源也会释放,都不用我们手动释放。

cpp 复制代码
void swap(list<T>& it)
{
	std::swap(_head, it._head);
	std::swap(_size, it._size);
}
list<T>& operator=(list<T> it)
{
	swap(it);
	return *this;
}

三、完整代码实现与测试用例

cpp 复制代码
#pragma once
#include<assert.h>
#include<iostream>

namespace hao
{
	template <class T>
	struct list_node
	{
		T _val;
		list_node<T>* _next;
		list_node<T>* _prev;

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

	template<class T, class Ref, class Ptr>
	struct _list_iterator
	{
		typedef list_node<T> Node;
		typedef _list_iterator<T, Ref, Ptr> self;

		Node* _node;
		//explicit 这里为什么不能加const? 
		//加了const const类型的Node*指针不能赋值给非const的
		//-node
		_list_iterator(Node* node) :_node(node)
		{

		}

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

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

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

			return tmp;
		}
		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		前后置返回值是啥,有没有参数这些都是有说法的。
		self operator--(int)//后置--
		{
			self tmp(_node);
			_node = _node->_prev;
			return tmp;
		}

		self& operator--()//前置--
		{
			_node = _node->_prev;
			return *this;
		}
		Ref operator*()
		{
			return _node->_val;
		}
		Ptr operator->()
		{
			return&_node->_val;
		}

	};

	template<class T>
	class list
	{
	public:
		typedef list_node<T> Node;
		typedef _list_iterator<T, const T&, const T*> const_iterator;
		typedef _list_iterator<T, T&, T*> iterator;
		list()
		{
			empty_init();
		}
		list(const list<T>& it)
		{
			empty_init();
			for (auto& e : it)//有了迭代器利用范围for简单解决
			{
				push_back(e);
			}
		}
		list<T>& operator=(list<T> it)
		{
			swap(it);
			return *this;
		}
		void swap(list<T>& it)
		{
			std::swap(_head, it._head);
			std::swap(_size, it._size);
		}
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		void empty_init()
		{
			_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;
			insert(end(), x);
		}
		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		void pop_back()
		{
			erase(--end());
		}
		void pop_front()
		{
			erase(begin());
		}
		iterator begin()
		{
			return iterator(_head->_next);
		}
		iterator end()
		{
			return _head;
		}
		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}
		const_iterator end() const
		{
			return _head;
		}
		size_t size()
		{
			return _size;
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it);
				++it;
			}
			_size = 0;
		}
		//pos位置之前插入
		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* pre = cur->_prev;
			Node* newnode = new Node(x);
			pre->_next = newnode;
			newnode->_prev = pre;

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

			++_size;
			return newnode;
		}
		iterator erase(iterator pos)
		{
			assert(pos != end());

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

			next->_prev = pre;
			pre->_next = next;
			--_size;

			return next;
		}

	private:
		Node* _head;
		size_t _size;
	};
	void print(const list<int>& lt)
	{
		for (list<int>::const_iterator it = lt.begin(); it != lt.end(); it++)
		{
			std::cout << *it << " ";
		}
	}
	void test_list1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

		list<int>::iterator it = lt.begin();

		while (it != lt.end())
		{
			(*it) += 1;
			std::cout << *it << " ";
			++it;
		}
		std::cout << std::endl;

		for (auto e : lt)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;

		print(lt);
	}
	struct A
	{
		A(int a1 = 0, int a2 = 0)
			:_a1(a1)
			, _a2(a2)
		{}

		int _a1;
		int _a2;
	};

	void test_list2()
	{
		list<A> lt;
		lt.push_back(A(1, 1));
		lt.push_back(A(2, 2));
		lt.push_back(A(3, 3));
		lt.push_back(A(4, 4));

		list<A>::iterator it = lt.begin();
		while (it != lt.end())
		{
			//cout << (*it)._a1 << " " << (*it)._a2 << endl;
			std::cout << it->_a1 << " " << it->_a2 << std::endl;

			++it;
		}
		std::cout << std::endl;
	}

	void test_list3()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		lt.push_front(5);
		lt.push_front(6);
		lt.push_front(7);
		lt.push_front(8);
		for (auto e : lt)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;

		lt.pop_front();
		lt.pop_back();

		for (auto e : lt)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;

		lt.clear();
		lt.push_back(10);
		lt.push_back(20);
		lt.push_back(30);
		lt.push_back(40);
		for (auto e : lt)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;

		std::cout << lt.size() << std::endl;
	}

	void test_list4()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

		for (auto e : lt)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;

		list<int> lt1(lt);
		for (auto e : lt1)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;

		list<int> lt2;
		lt2.push_back(10);
		lt2.push_back(20);
		lt2.push_back(30);
		lt2.push_back(40);

		for (auto e : lt2)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;

		lt1 = lt2;

		for (auto e : lt1)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
	}
}
cpp 复制代码
#include"list.h"
using namespace std;


int main()
{
	//hao::test_list1();

	//hao::test_list2();

	//hao::test_list3();

	hao::test_list4();
	return 0;
}
相关推荐
我是谁??22 分钟前
C/C++使用AddressSanitizer检测内存错误
c语言·c++
发霉的闲鱼1 小时前
MFC 重写了listControl类(类名为A),并把双击事件的处理函数定义在A中,主窗口如何接收表格是否被双击
c++·mfc
小c君tt1 小时前
MFC中Excel的导入以及使用步骤
c++·excel·mfc
xiaoxiao涛1 小时前
协程6 --- HOOK
c++·协程
羊小猪~~3 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
脉牛杂德4 小时前
多项式加法——C语言
数据结构·c++·算法
legend_jz4 小时前
STL--哈希
c++·算法·哈希算法
CSUC4 小时前
【C++】父类参数有默认值时子类构造函数列表中可以省略该参数
c++
Vanranrr4 小时前
C++ QT
java·c++·qt
鸿儒5174 小时前
C++ lambda 匿名函数
开发语言·c++