编程语言
首页 > 编程语言> > C++ 关于 single linked list 链表的创建和执行的源代码基础部分(有头尾节点,考虑动态存储和memory leak等问题)

C++ 关于 single linked list 链表的创建和执行的源代码基础部分(有头尾节点,考虑动态存储和memory leak等问题)

作者:互联网

这是C++ programming II这门课布置的一个小作业,核心是关于动态存储和指针的理解(尤其是new和delete的使用);花了不少时间,中途来csdn找资料也没有看到相关内容的经验分享,所以来写篇博文记录一下成果和心得。
代码旁有简单的注释,中英夹杂;不重要的我没有翻译,有问题的话欢迎评论留言。

首先声明一些习惯性用词:
head:头节点
tail:尾节点/尾部

创建(头文件)

LinkedList.h:

#ifndef INTLIST_H
#define INTLIST_H

#include <iostream>
using namespace std;

struct IntNode {
	int value;
	IntNode *next;
	IntNode(int value) : value(value), next(nullptr) {}
};

class IntList {
 private:
	IntNode *head;
	IntNode *tail;
 public:
	IntList();
	IntList(const IntList &cpy);
	~IntList();
	void push_front(int);
	void pop_front();
	bool empty() const;
	const int & front() const;
	const int & back() const;
	IntList& operator=(const IntList &rhs);
	void push_back(int);
	void clear();
	void selection_sort();
	void insert_ordered(int);
	void remove_duplicates();
	friend ostream & operator<<(ostream &, const IntList &rhs);
};

#endif

LinkedList.cpp里的构造函数:

IntList::IntList(): head(nullptr), tail(nullptr) {}

析构函数

最关键的来了,不注意的话从这一步开始就已经产生memory leak和dangling pointer了

IntList::~IntList(){
   if (head != nullptr){
     // 1. go to head 
     // 2. delete head and set it to null
     // 3. use the next of head to go to the next node in the IntList
     // 4. delete that node and set it to null
     // 5. repeat 3 and 4 until it reaches tail
     while(head != nullptr){
         IntNode *tmp = head->next;  // 从头节点的next一直走到nullptr
         delete head;
         head = tmp; // 终止时头节点一定是nullptr
     }
   }
  
}

两个push函数,从头和从尾部增加节点

此时注意考虑三种情况:

  1. 链表为空 :head = 新节点 + tail = 新节点
  2. 链表只有一个节点:从前面push的话就head = 新节点,从后面就设tail = 新节点
  3. 链表有两个或更多节点:同上

push_front(int)

void IntList::push_front(int num){  
    IntNode* node = new IntNode(num); 
    if (empty()){ 
    //consider three different senarios: 1. empty; 2. only one node(head == tail == nullptr); 3. two or more than two
        tail = node; 
        head = node;
    }else{
        node->next = head;
        head = node;
        }
    } // 为什么我的代码这么丑?难受
}

push_back(int)

void IntList::push_back(int value_){
    IntNode* node = new IntNode(value_);
    if (head == nullptr){
        push_front(value_);
    }
    else{
        tail->next = node;
        tail = node;
    }
}

返回头和尾节点的值

front() & back()

const int & IntList::front() const{
    return head->value;
}    
const int & IntList::back() const{
    return tail->value;
}

empty()

bool IntList::empty() const{
    return (head == nullptr); // if checking both nodes, when deleting, set  to null
}

从头删除节点

pop_front()

void IntList::pop_front(){
    // the same three senarios
    if (!empty()){ 
        if (head != nullptr){
            IntNode* node = head->next;
            delete head;
            head = node;
        }
        else if(head == tail){
            delete head;
            delete tail;
            head = nullptr;
            tail = nullptr; // avoid the dangling pointer
        }
    }
}

删除所有节点

clear()

	void IntList::clear(){
    if (head != nullptr){
     // 1. delete head and set it to null
     // 2. use the next of head to go to the next node in the IntList
     // 3. delete that node and set it to null
     // 4. repeat 2 and 3 until it reaches tail
     while(head != nullptr){
         IntNode *tmp = head->next;
         delete head;
         head = tmp;
     }
   }
}

使用 << 进行链表内容的打印

注意这里我是用节点的next是否为nullptr来判断是否已经到了尾部(因为我在下一部分的的remove_duplicates()函数里不知只剩一个节点时tail如何进行处理,检查节点是否== tail时会打印出错)

friend function: operator<<

ostream & operator<<(ostream &out, const IntList &list){
    IntNode* node = list.head;
    while(node != nullptr){
        out << node->value;
        if (node->next == nullptr){
         // test deleting tail success or not; since I cannot handle the positon of tail node properly, this is a good practice
            break;
        }
        out << " ";
        node = node->next;
    }
    return out;
}

拷贝构造函数

要进行 deep copy哦,需要手动一个个增加节点

IntList::IntList(const IntList &cpy){
    head = nullptr; // !
    tail = nullptr; // !
    if (this != &cpy){
        if (!cpy.empty()){
            IntNode* tmp = cpy.head;
            while(tmp != nullptr){
                push_back(tmp->value);
                tmp = tmp->next;
            }
        }
    }  // 为什么我的代码长得这么丑......难受
}

拷贝链表 (=)

此时注意需要考虑五种情况:

  1. 空链表拷贝空链表
  2. 空链表拷贝非空链表
  3. 非空链表拷贝空链表
  4. 非空链表拷贝非空链表
  5. 自己拷贝自己 (self-assignment)
IntList& IntList::operator=(const IntList &rhs){
    if (rhs.empty()){
        head = nullptr;
        tail = nullptr;
    }
    else if (&rhs != this){
        if (head != nullptr){
            clear();
            head = nullptr;
            tail = nullptr; // clear() does not set head and tail to null which can be used later
        }
        IntNode* tmp = rhs.head;
        while(tmp != nullptr){
            push_back(tmp->value);
            tmp = tmp->next;
        }
    }else if (&rhs == this){
        return *this;
    }
    return *this;
}

这里我犯过两个错误:

  1. segmentation fault
       IntNode* tmp = rhs.head;
       // do not assign head with rhs's head, the push_back function does the same
        // 如果加上 head = rhs.head;, 会产生segmentation fault (访问dangling pointer时产生;
        // dangling pointer: deallocates the data the pointer pointing to without modifying the value of the pointer)
        while(tmp != nullptr){
            push_back(tmp->value);
            tmp = tmp->next;
        }
  1. 非空链表拷贝空链表的时候clear()结束没有随手把tail设nullptr(因为clear()只从head开始delete,最后把head设null)

剩下的还有selection_sort(), insert_ordered(int), remove_duplicates()以及写作业时程序报过的所有error;今天作业太多了,有时间再写在下一篇里。

附上复习后面三个函数的实现和报错分析的 Notion 笔记链接,全英文阅读无障碍的朋友可以来看看:
https://www.notion.so/lavendershuo/Review-Linked-List-19911524ae42403b8cab305ff36c1943

标签:head,nullptr,next,链表,tail,memory,IntList,源代码,节点
来源: https://blog.csdn.net/lishuo0204/article/details/114110486