
这一篇博客记录我自己在写双向链表list时所犯的一个小错误!(2次了,我还没有完全记住),因此希望通过本篇博客来记录这个错误,日后学习coding过程中希望若再次遇到此类错误能第一时间反应过来或者能避免这种错误!
我们都知道,写双向链表的时候,我们都会用一个ListNode的结点类保存每一个结点所存储的相关信息,再用一个list类来实现一个双向链表的各种操作(这里我们引入头尾哨兵来do)
下面不妨看以下代码:
ListNode.hpp:
#ifndef __LISTNODE__ #define __LISTNODE__ templateclass ListNode { public: ListNode * next;//指向下一节点 ListNode * prev;//指向上一节点 T m_Data;//维护节点的数据data的 public: ListNode():next(nullptr), prev(nullptr),m_Data(0) {} }; #endif
CircularList.hpp:
#ifndef __CIRCULARLIST__ #define __CIRCULARLIST__ #include#include"ListNode.hpp" using namespace std; template class CircularList { private: //设置头尾哨兵,且对于外部来说是看不见的! ListNode * head;//头哨兵指针 ListNode * tail;//尾哨兵指针 int Size;//维护双向循环list的大小 public: CircularList() { //构成双向循环的list 的操作 head->next = tail; head->prev = tail; tail->prev = head; tail->next = head; Size = 0; } ~CircularList() { } int getSize() { return this->Size; } void push_back(const T& tdata);//插入元素到双向循环list中 void push_front(const T& tdata);//插入元素到双向循环list中的back尾部 bool removeElem(const T& tdata);//删除双向循环list中值为tdata的元素 void showList() { ListNode * tempNode = new ListNode ; tempNode = head->next; while (tempNode != tail) { cout << tempNode->m_Data << "t"; tempNode = tempNode->next; } cout << endl; delete tempNode; } }; template void CircularList ::push_back(const T& tdata) //插入元素到双向循环list中 { ListNode * newNode = new ListNode ; newNode->m_Data = tdata; newNode->prev = tail->prev; tail->prev->next = newNode; newNode->next = tail; tail->prev = newNode; this->Size++; } template void CircularList ::push_front(const T& tdata) //插入元素到双向循环list的front头部 { ListNode * newNode = new ListNode ; newNode->m_Data = tdata; newNode->next = head->next; head->next->prev = newNode; head->next = newNode; newNode->prev = head; this->Size++ ; } template bool CircularList ::removeElem(const T& tdata) //删除双向循环list中值为tdata的元素 { ListNode * tempNode = new ListNode ; tempNode = head; while (tempNode != tail)//非空 { if (tempNode->m_Data == tdata) { tempNode->prev->next = tempNode->next; tempNode->next->prev = tempNode->prev; delete tempNode; tempNode = nullptr; tempNode->prev = nullptr; tempNode->next = nullptr; return true; } tempNode = tempNode->next; } return false; } #endif
运行结果:
这里所引发的异常表面,this->head 和 this->tail 都是一个nullptr,我们没有办法对他们所存储的空间的指针元素do事情。
事实上,在类内写:
ListNode* head;//头哨兵指针 ListNode * tail;//尾哨兵指针
这两条成员属性的语句时只是对于head 和 tail指针做了个声明而已,其默认值编译器会给我们置为NULL(也即nullptr),也即没有给他们分配实际的内存空间,因此没办法对他们进行操作(也即没办法用他们来do任何事情,包括赋值啥的),所有我们使用头尾哨兵时,就必须给他们以定义。也即用以下的Constructor的代码来替换才是正确的用法:
CircularList():head(new ListNode), tail(new ListNode ), Size(0) { //注意!这里必须要在堆区heao区分别new一个新的结点给到head 以及tail这两个哨兵结点! //否则你没办法去使用这2个只是声明了的结点指针变量,这里只是声明了而已,默认时head = tail = nullptr的! //头尾哨兵按常规来说是不存储任何数据的,因此不用管(在Node结点类中已经默认给每一个结点的data都赋值为0了 ) //构成双向循环的list 的操作 head->next = tail; head->prev = tail; tail->prev = head; tail->next = head; }
也即将head和tail的初始化语句改成:
this->head = new ListNode; this->tail = new ListNode ;
(此时,都在堆heap区申请了实际的内存空间,因此此时我们就可以做下面构成双向循环list的操作了)
总结:
对于一个指针变量,你只是声明了的话IDE会默认给你复制为NULL(也即nullptr),此时你没办法对这个变量做任何事情任何操作,你只有在堆区给它 new 了空间才能对他do事情!