C语言中链表的使用
什么是链表?
在C语言中,我们无法使用向C++中的STL标准库中的vector等动态数组,但是我们可以用maclloc不断地申请在空间上不连续的内存段供我们使用,我们想“如果将这些零零散散的内存段通过一定方式连接起来,是不是可以当作连续的存储空间使用呢?”,此时“链表”的概念就问世了,我们所用的数组在内存空间上是连续的:
这种内存空间的申请方式非常有利于我们顺序访问,可问题来了“哪有那么多连续的存储空间供我们使用呢?”,因此,我们的链表采用如下方式来将零零散散的内存段整合为“可以连续访问的在空间上不连续的‘高仿数组’”:
以此类推,我们可以整合无限大的内存空间。
链表的使用流程
① 构建头节点,我们必须像数组一样直到内存空间的首地址我们才可以进行后续访问,俗话说的好“好的开头是成功的一半”;
② 构建尾节点,尾节点的作用是帮助我们找到“何时结束遍历内存空间”,我们对于内存空间的申请肯定是连续的,也就是内存申请有头有尾,因此遍历结束标志就是我们尾节点;
③ 构建当前节点,我们用这个节点不断在堆区中申请我们想要的内存空间;
④ 更新尾节点,这样我们才可以将我们在前面申请到的内存不断地添加到我们的链表当中,但是切记头节点千万不可以改变它是我们找到并开始遍历链表的唯一唯一的方式;
⑤ 我们讲善始善终,因此我们申请的堆区内存最终还是要释放的,这就是我们要做的结尾工作。
链表示例
#include <iostream>
using namespace std;
struct Node
{
int data;
struct Node* ptr;
};
struct Node* DynamicSpaceApply() // 注意这里一定要返回Node类型的指针
{
struct Node *head, *current, *tail;
int i = 0;
head = new Node;
cin >> head->data;
current = head;
tail = head;
while (1)
{
current = new Node;
cin >> current->data;
tail->ptr = current;
tail = current;
cout << "是否结束:";
cin >> i;
if (i)
{
break;
}
}
tail->ptr = nullptr;
return head;
}
void Print_LinkedList(struct Node* head)
{
struct Node* current = head;
int i = 0;
if (current->ptr == nullptr)
{
cout << "Linked List is NULL!";
}
else
{
cout << "数据:" << head->data << endl;
while (current->ptr != nullptr)
{
current = current->ptr;
cout << "数据:" << current->data << endl;
}
}
}
void FreeDynamicSpace(struct Node* head)
{
struct Node *current = head, *next;
if (current->ptr == nullptr)
{
cout << "Linked List is NULL!" << endl;
return;
}
while (current->ptr != nullptr)
{
next = current->ptr;
free(current);
current = next;
}
}
int main()
{
struct Node *head;
head = DynamicSpaceApply();
Print_LinkedList(head);
FreeDynamicSpace(head);
}
这里我们的程序主要包含三个主要方面:
① 堆区内存的申请;
② 链表内容的读取;
③ 堆区内存的释放。
堆区内存的申请
struct Node* DynamicSpaceApply() // 注意这里一定要返回Node类型的指针
{
struct Node *head, *current, *tail;
int i = 0;
head = new Node;
cin >> head->data;
current = head;
tail = head;
while (1)
{
current = new Node;
cin >> current->data;
tail->ptr = current;
tail = current;
cout << "是否结束:";
cin >> i;
if (i)
{
break;
}
}
tail->ptr = nullptr;
return head; // 非常重要!
}
这一段代码特别要注意:函数的返回值一定是Node类型的指针,代表着动态申请内存的首地址。
链表数据的打印输出
void Print_LinkedList(struct Node* head)
{
struct Node* current = head;
int i = 0;
if (current->ptr == nullptr)
{
cout << "Linked List is NULL!";
}
else
{
cout << "数据:" << head->data << endl;
while (current->ptr != nullptr)
{
current = current->ptr;
cout << "数据:" << current->data << endl;
}
}
}
这里,我的逻辑是:先讲首地址的数据读出,然后判断下一个地址是否非空,如果非空,那么读出下一个地址对应的数据。
堆区内存的释放
void FreeDynamicSpace(struct Node* head)
{
struct Node *current = head, *next;
if (current->ptr == nullptr)
{
cout << "Linked List is NULL!" << endl;
return;
}
while (current->ptr != nullptr)
{
next = current->ptr; // 接收下一个数据的地址
free(current); // 释放当前数据占用的内存
current = next;
}
}
这里,我们特别要注意的是:我们必须用两个Node类型的指针来进行堆区内存的释放,其中,我们必须在释放当前内存之前将下一个数据的地址记录下来,否则释放完当前数据后,我们根本无法找到下一个数据所在的地址。