c++ 双向链表类模板的一种详细、简单的写法

双向类模板链表

一.双向链表

  • 首先我们要复习下双向链表的大概思路:


(当然实际储存并不这样摆放的,只是便于理解)
这是一个节点的基本结构:
prev:是一个指向前面一个节点的指针。
next: 是一个指向后面一个节点的指针。
data: 任意T类型的数据。

图例:
在这里插入图片描述(三个节点:head, 中间,tail)
可以看到
中间节点的prev存放着head这个节点的地址
中间节点的next存放着tali这个节点的地址

二.双向链表类模板的思路

分为三部分:
在这里插入图片描述

  • 节点的构造(struct Node)
    代码:
template<typename T>
struct Node
{
  T data;
  Node<T>* pre;//结构体自引用
  Node<T>* next;

  Node(const T& item, Node* p = NULL, Node* n = NULL) :data(item), pre(p), next(n) {}

};
  • 迭代器(class Iterator)
    代码:
template<typename T>
class Iterator
{
public:
  Node<T>* current;//  因为class List用这个成员也(可以用继承)

};

解释:迭代器的操作对象是一个节点的地址 而不是一个节点放的数据的地址,所以迭代器也能访问其中的数据

  • 链表(class List)
    代码:
template<typename T>
class List
{
  Node<T>* head;
  Node<T>* tail;
  int size;
}

解释:因为链表操作对象是一个个节点Node的对象, 所以需要创造head与tail节点连成链,再把其余的节点插入进来

三.双向链表类模板的思路

3.1 节点
template<typename T>
struct Node
{
 T data;
 Node<T>* pre;
 Node<T>* next;
//之后要用new 创造Node
 Node(const T& item = T(), Node* p = NULL, Node* n = NULL) :data(item), pre(p), next(n) {}

};   
3.2迭代器
template<typename T>
class Iterator
{
public:
 Node<T>* current;

 //构造函数
 Iterator() :current(NULL) {}
 Iterator(Node<T>* c) { current = c; }
 Iterator(const Iterator& itr) :current(itr.current) {}


 //重载函数
 Iterator& operator ++() { current = current->next; return *this; }//前缀++
 Iterator& operator ++(int n)
 {
 	Iterator old = *this;
 	++(*this);
 	return old;
 };//后缀++
 Iterator& operator --() { current = current->pre; return *this; }//前缀--
 Iterator& operator --(int n) { Iterator old = *this; --(*this); return old; }//后缀--
 T& operator *() { return current->data; }
 const T& operator *() const { return current->data; }
 
 //判断
 bool operator ==(const Iterator& itr) { return (current == itr.current); 													
 bool operator !=(const Iterator& itr) { return (current != itr.current); }
};

解释说明

1.不管是++还是–,我们都希望是从一个节点移动到下一个节点。比如++,从 current 移到 current->pre(这里就装着下一个节点的地址)
2.而我们用*,我们希望取出来的是data,所以我们想要这个*current=current->data

3.3链表(重点)

这是全部的代码 看着很头晕。 别着急 可以先不看 后面我会放图片详细解释

template<typename T>
class List
{
 Node<T>* head;
 Node<T>* tail;
 int size;
public:

 typedef Iterator<T>iterator;
 typedef const Iterator<T> const_iterator;
 List()
 {
 	size = 0;
 	head = new Node<T>();
 	tail = new Node<T>();
 	tail->pre = head;
 	head->next = tail;//连起来  变成链表

 };
 ~List()
 {
 	Clear();
 	delete head;
 	delete tail;
 };
 List(const List& l)
 {
 	size = l.size;
 	head = new Node<T>();
 	tail = new Node<T>();
 	tail->pre = head;
 	head->next = tail;
 	for (iterator temp = l.Begin(); temp != l.End(); temp++)
 		push_back(*temp);//一个一个节点复制


 }
 void Clear()
 {
 	iterator temp = Begin();
 	while (temp != End())
 		temp = Erase(temp);
 }

 iterator Begin() { return iterator(head->next); }
 const_iterator  Begin() const { return iterator(head->next); }
 
 iterator End() { return iterator(tail); }
 const_iterator End()const { return iterator(tail); }
 
 T& Front() { return *Begin(); }
 const T& Front()const { return *Begin(); }
 
 T& Back() { return  *(--End()); }
 const T& Back()const { return *(--End()); }

 List& operator =(const List& l)
 {
 	if (this != &l)
 	{
 		Clear();
 		size = l.size;

 		for (iterator temp = l.Begin(); temp != l.End(); temp++)
 			push_back(*temp);
 		return *this;

 	}

 }

 void push_front(const T& item) { Insert(Begin(), item); }
 void push_back(T& item) { Insert(End(), item); }
 
 void pop_front() { Erase(Begin()); }
 void pop_back() { Erase(--End()); }
 
 int Size()const { return size; }
 bool Empty() { return head->next == tail; }

 iterator Insert(const  iterator itr, const T& item)
 {
 	Node<T>* n = itr.current;
 	Node<T>* newnode = new Node<T>(item, n->pre, n);
 	n->pre->next = newnode;
 	n->pre = newnode;
 	size++;

 	return iterator(newnode);
 }

 iterator Erase(iterator itr)
 {
 	Node<T>* temp = itr.current;
 	temp->pre->next = temp->next;
 	temp->next->pre = temp->pre;
 	size--;
 	Node<T>* p = temp->next;
 	delete temp;

 	return iterator(p);
 }

};

我们先讲最难的,因为前面基本的像构造函数需要用到这几个难的函数,所以我们要先了解
1.插入。 itr.current是当前指向的节点的地址 ,我们想把某一个节点插入到itr.current 指向的节点的前面位置 ,item是要插入到这个某个节点data里的数据

 iterator Insert(const  iterator itr, const T& item)
 {
 	Node<T>* n = itr.current;//n是节点
 	Node<T>* newnode = new Node<T>(item, n->pre, n);//插入newnode节点到n节点的前面
 	n->pre->next = newnode;//修改 n前面节点(没插入之前 )的后继
 	n->pre = newnode;//修改 n节点的前驱
 	size++;

 	return iterator(newnode);//返回类型要与函数类型一致
 }

如图:
我们要把 newnode 插入n前面去
在这里插入图片描述2.消除。我们要清除itr的current指向的节点,并重新连接前面,返回值是下一个节点就是(itr变了 向后移了一位)划重点:itr变了 向后移了一位,因为它原来指向的被我们删了

iterator Erase(iterator itr)
{
	Node<T>* temp = itr.current;
	temp->pre->next = temp->next;
	temp->next->pre = temp->pre;
	size--;
	Node<T>* p = temp->next;
	delete temp;

	return iterator(p);
}

和插入的思路其实一样 。不懂评论告诉我,我帮你瞅瞅

3.比较简单的函数名字
Begin() ; // 第一个装有数据的节点的地址(head->next)
End(); //最后一个装有数据节点的下一个节点(tail)
Front();//链表中第一个数据
Back();//链表中最后的一个数据
push_front();//首插
push_back();//尾插
pop_front();//首删
pop_back();//尾删

4.前面的构造函数(都很简单 注释我写在前面好了)
~List()
{
Clear();
delete head;
delete tail;
};
void Clear()
{
iterator temp = Begin();
while (temp != End())
temp = Erase(temp);//这里不用temp++,这里temp自己会往后移动。这是我们老师写的,老师nb;
}

四 测试函数

#include <iostream>
#include"List.h"		//用户头文件
using namespace std;

template<class Iterator>	//在指定范围内输出元素
void display(Iterator first, Iterator last)
{
   for (; first != last; ++first)
   	cout << *first << ' ';
   cout << endl;
}

int main()
{
   List<int> L;
   for (int i = 0; i < 10; i++)
   {
   	L.Insert(L.Begin(), i + 100);
   }
   display(L.Begin(), L.End());
   L.Erase(L.Begin());
   display(L.Begin(), L.End());
   List<int> L2;
   L2 = L;
   display(L2.Begin(), L2.End());
   for (int i = 0; i < 20; i++)
   {
   	L2.Insert(L2.Begin(), i * 2);
   }
   display(L2.Begin(), L2.End());
   L.Erase(L2.Begin());
   display(L2.Begin(), L2.End());

   const List<int> L3(L2);
   display(L2.Begin(), L2.End());

   return 0;
}

结果:
在这里插入图片描述

哪里不懂 或者错误 都可以评论 、私信提出来。
感谢林老师O(∩_∩)~

在这里插入图片描述

  • 18
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值