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函数,从头和从尾部增加节点
- 增加节点的唯一方式;
- 注意此时的新节点都需要手动分配新内存(new),删除时需要手动删除(delete)
- 动态分配一个新地址给这个值为num,next为nullptr的节点;
- 然后加入链表;
此时注意考虑三种情况:
- 链表为空 :head = 新节点 + tail = 新节点
- 链表只有一个节点:从前面push的话就head = 新节点,从后面就设tail = 新节点
- 链表有两个或更多节点:同上
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
}
从头删除节点
- 删除节点的第一种方式
- 记得考虑只有一个节点的情况,不然程序会crash;记得手动删除head & tail并且设置成nullptr(随手设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;
}
}
} // 为什么我的代码长得这么丑......难受
}
拷贝链表 (=)
此时注意需要考虑五种情况:
- 空链表拷贝空链表
- 空链表拷贝非空链表
- 非空链表拷贝空链表
- 非空链表拷贝非空链表
- 自己拷贝自己 (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;
}
这里我犯过两个错误:
- 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;
}
- 非空链表拷贝空链表的时候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