C++手撕简易list

目录

节点的准备

list类

push_back

stl容器的遍历和修改

begin

end

!=   

++

重载*

效果展示: 

const迭代器

方法一:

方法二:

->的重载

insert

push_front

erase 

展示效果

 pop_back && pop_front

 效果展示

clear()&& 析构函数


节点的准备

我们在创建节点的时候,选用struct,而不是class,因为struct是默认公有的,我们在进行类成员访问的时候就不会受到约束

	template<class T>
	struct ListNode {
		ListNode<T>* _prev;
		ListNode<T>* _next;
		T _data;

		ListNode(const T& x=T())
			:_prev(nullptr)
			,_next(nullptr)
			,_data(x)
		{}
	};

为什么在初始化的时候,传值的时候用const T& x = T(),初始化的时候也是要用x

        因为我们在创建链表的时候,不一定就是整型链表,可能是double链表,可能string链表,甚至有可能是自定义链表

        所以试想一下,0可以作为string的初始值吗,显然是不能的

        我们又知道,现在内置类型现在也支持初始化了,所以为了保证不会出错,统一采用T()初始化

知道了节点的基本属性后,就可以利用节点类(ListNode类)来创建链表(list类)了


list类

        因为ListNode是struct,其类成员是公开的,所以在创建list类的时候,所以可以在类里面访问ListNode成员

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

push_back

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

 在push_back成功后,我们该如何访问遍历我们的list呢

我们访问的方式普遍是用迭代器


stl容器的遍历和修改

        我们在之前比如string,vector之类的,在底层实现的迭代器直接通过使用指针即可完成目的,原因就是它们的空间地址在底层是连续的但是链表的空间地址的底层不连续,也就是说我们通过使用指针++得到的指针指向的下一个数据可能不是下一个节点

        我们又该如何实现它呢?

        我们可以创建一个迭代器类,通过迭代器来实现链表的遍历访问,将节点交给迭代器类(__list_iterator),从而实现访问效果

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

那么我们就会知道我们缺少begin, end, 重载!= ,重载++,以及重载*

begin

因为此链表是带有哨兵位,所以一开始访问的地方应该是哨兵位的下一位,即_head->_next;

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

end

我们需要在end位置结束,end又是带头双向循环列表,所以我们要在链表访问绕一圈后再回来之时在头节点处停止,即_head;

		iterator end() {
			return _head;
		}

!=   

我们知道内置类型的是可以直接使用运算符,但是自定义类型则需要自己重新重载

需要注意的是这些运算符的比较是在迭代器类里,即__list_iterator类,所以不能在list类里重载

其次,比较也是通过迭代器类比较,创建的it是属于迭代器的,即iterator it

比什么不同呢,比地址不同就可以了

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

++

        ++依旧分为前置和后置,很简单,令其指向下一节点即可,唯一的是后置++需要返回的是不变的值,返回不变值的话,就需要一个值来代替,tmp

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

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

         关于self& operator++(int)中,self tmp(*this)里,一开始我的疑惑是:欸,人家_node不是指针类吗,拷贝构造我也没写,用它进行拷贝构造的话,只会进行值拷贝哇,不会进行深度拷贝呀。

        到后面想想,在实现list类里,我们本身就是需要通过迭代器实现间接控制list的内容,所以要修改的东西也同样一样

        不写析构函数也是如此道理,迭代器不用析构,因为这些成员都是类模板的,只是借给你用一下的,结果没道理说你用了之后还把我的成员给释放掉

重载*

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

效果展示: 


const迭代器

	void print_list(const list<int>& lt) {
		list<int>::iterator it = lt.begin();
		while (it != lt.end()) {
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}

 当我们传const类型的对象的时候,因为我们写的end,begin等等是非const的

那么我们便会想通过给list加const来改变此情况

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

报出大量的错误,究其原因 

const对象it不能够对其进行修改,因此it++是错误的 

相对于我们想要的const类型的迭代器,我们想要的是it能够进行++,而不是相反,我们要对其进行遍历,只是不要对里面的内容进行修改而已,所以我们在list<T>::iterator it 前面加上const,就直接限定了我连遍历都不能遍历

那我们应该如何做到我们能够遍历链表,但又不会修改里面的值呢,即实现const版的iterator

方法一:

实现一个const_iterator,在iterator的基础上,复制粘贴一份,唯一需要改变的就是operator*

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

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

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

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

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

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

	};

但是这样有会显得很冗余,因为不同的只是类名跟operator* 

那么我们的解决方法是:

方法二:

在原来的类模板上增加一个参数 

将__list_const_iterator 舍弃掉,将__list_iterator中的类模板从template<class T>变为template<class T,class Ref>

将operator的返回类型在重新定义一下

在list类中,对 iterator 和 const_iterator 重新typedef一下

经过此番操作,我们就可以将原本需要两个类来实现的遍历通过修改类模板参数,能够整合到一个类里面


->的重载

当我们的链表存储的不是内置类型的时候,或者链表存储的结构能够存放多个数值,比如类,结构体之类的时候,我们就会通常用 -> 来进行访问我们想要的数值

先看一串代码

struct BB {
	int _b1;
	int _b2;

	BB(int b1=1,int b2=1)
		:_b1(b1)
		,_b2(b2)
	{}
};

int main() {
	list<BB> l;
	l.push_back({1,1});
	l.push_back({2,2});
	l.push_back({3,3});

	list<BB>::iterator it = l.begin();
	while (it != l.end()) {
		cout << it->_b1 << "," << it->_b2 << endl;
		it++;
	}
	return 0;
}

上面这串代码,在STL模板类当中是很自然的,没有任何问题

但在我们的手撕链表中,好像就出现了些许问题

编译器给出的解释也是比较明显了

不是类成员,没有重载等,所以我们在自定义list的时候,需要我们自己重载一下operator->

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

其在对上 it->_a1 或者 it -> _a2 的原型就是

cout << it.operator->()->_a1 << endl;
cout << it.operator->()->_a2 << endl;

因此本来应该是两个箭头的,但是为了可读性省略一个箭头,即原来是it -> -> _a1,现在为了可读性变成了 it->_a1 

当然箭头所表示的指针也有const和 非const,因此要对类模板参数进行扩充

	template<class T,class Ref,class Ptr>
	struct __list_iterator
	{
		typedef ListNode<T> Node;
		typedef __list_iterator<T,Ref,Ptr> self;
		Node* _node;
        
        ...

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

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

    }


insert

先写insert是因为,后面的其它函数可以适当的复用insert

要记录下插入位置的前一个节点,不然随意更换的话,会找不到前一个节点

		iterator insert(iterator pos,const T& x) {
			Node* newnode = new Node(x);
			Node* cur = pos._node;
			Node* prev = cur->_prev;

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

			return newnode;
		}

push_front

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

erase 

删除某一节点

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

展示效果

 pop_back && pop_front

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

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

在实现pop_back之前,我们还要实现一下重载--

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

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

 效果展示

clear()&& 析构函数

		void clear() {
			iterator it = begin();
			while (it != end()) {
				it = erase(it);
			}
		}

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

以上便是此次博文的学习内容,如有错误,还望大佬指正,谢谢阅读!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值