C语言单向链表生成
我是通过bilibili 学习的链表知识,视频的地址如下:
https://www.bilibili.com/video/BV1BE411h7kG
-
链表的作用
我们平常使用存储数据都是使用的数组居多,那么为啥还要用链表呢?
使用数组的时候,我们往往需要对需要存储的数据的大小进行评估,否则容易造成空间的浪费,显得比较麻烦。而链表则
完全不需要考虑这个问题,我们可以存储任意大小的数据而不需要对它进行预估。
参考博文:
https://blog.csdn.net/kevin_yzlong/article/details/65628753 -
链表的组成
如上图所示,链表里面存放的是头结点的指针,尾结点的指针,以及结点的个数。那么,结点又是什么呢? -
结点
结点包含着我们要存储的数据,并且包含着指向下一个结点的指针
如上图所示,结点是一个接着一个的,有点类似于数组,但是不同的是他们之间的地址并不是连续的。结点分为两部分组成:
一个是我们要存储的数据,一个是指向下一个结点的指针。
图片来源:https://www.jianshu.com/p/73d56c3d228c -
生成链表的思路
- 首先,声明要存储的数据类型,一般可以采用结构体产生。
点击查看代码
typedef struct DATA
{
int x;
int y;
}Data;
- 其次,声明结点和链表
点击查看代码
//结点
typedef struct NODE
{
Data data;
struct NODE *pnext;
}Node;
//链表
typedef struct LIST
{
Node* pfront; /* 第一个结点指针*/
Node* prear; /* 最后一个结点指针*/
int count; /* 结点的个数*/
}List;
- 生成链表
生成链表的过程用到了一个二级指针,通过二级指针为链表开辟空间
开辟成功后对之进行初始化
int ListInit(List** pplist)
使用ListInit函数对链表进行初始化。
4.对链表进行操作
包括添加结点、遍历、显示等。
- 注意
在视频的讲解中并没有涉及到内存的释放,但是有malloc的地方必然会有free。
那么如何释放呢?这个问题困扰了我一天,开始没有意识到链表的释放有它的流程,因此,浪费了很多时间。
我当时的疑问在于:对Node *pnode申请了空间但是并没有对应释放,这样会造成内存泄露。
查了资料以后我认为是这样:假如一个指针p,一个指针q都指向一个空间,那么平常所谓的free就是free掉这片空间,
因此,free(p)或者free(q)都是可行的。在这里,Node *pnode 指向的是结点空间,链表中包含了指向结点空间的指针,
因此在链表中释放掉这部分空间就可以了。
for (int i = 0; i < 6; i++)
{
Node *pnode = (Node *)calloc(1, sizeof(Node)); /*制造结点*/
pnode->data.x = rand() % 10;
pnode->data.y = rand() % 10;
pnode->pnext = NULL; /*结点不指向下一个结点*/
InserList(pList, pnode);/*将产生的结点插入到列表中*/
}
那么,如何释放结点空间呢?
链表结点空间的释放,需要引入一个中间指针变量,利用这个变量来存储每个结点的指针(“顺藤摸瓜”过程中每个结点的指针)
释放的时候将这个中间指针释放掉即可。
点击查看代码
void FreeList(Node *pnode) {
//************************************
// Method: FreeList
// FullName: FreeList
// Access: public
// Returns: void
// Qualifier: 用以释放列表中各个结点所占据的内存
// Parameter: List * plist 指向列表的指针
//************************************
Node *pfreenode;
while (pnode != NULL)
{
pfreenode = pnode;
pnode = pnode->pnext;
free(pfreenode);
}
}
还有一点需要注意的是,在初始化过程中用到二级指针为输入的指针变量申请空间,也是需要释放的。
由于这个空间是列表变量(List *pList)需要一直使用,因此,在主函数的后面将之释放掉。
最后
这是我第一次写博文,也是刚刚学习到链表的知识,可能在理解上以及讲述上有问题,希望和大家一起交流,共同进步!!!
整体代码如下
头文件:list.h
点击查看代码
#pragma once //保证只被编译一次
#include <stdlib.h>
#include <stdio.h>
//数据
typedef struct DATA
{
int x;
int y;
}Data;
//结点
typedef struct NODE
{
Data data;
struct NODE *pnext;
}Node;
//链表
typedef struct LIST
{
Node* pfront; /* 第一个结点指针*/
Node* prear; /* 最后一个结点指针*/
int count; /* 结点的个数*/
}List;
int ListInit(List **pplist);
void TraverList(List *plist, void(*Traver)(Node* pnode));
int IsEmpty(List* plist);
void InserList(List* plist, Node* pnode);
void ShowData(Node * pnode);
void FreeList(Node *pnode);
源文件:list.cpp
点击查看代码
#include "List.h"
int ListInit(List** pplist)
{
//************************************
// Method: ListInit
// FullName: ListInit
// Access: public
// Returns: int
// Qualifier: 初始化链表,成功返回1 否则返回 0
// 具体做法:通过二级指针为一级*pplist(主函数中声明)申请一个空间,用于存放列表。然后对产生的空间做初始化处理
// 主要就是申请了一个空间,用了二级指针申请。
// Parameter: List * * pplist
//************************************
*pplist = (List *)calloc(1, sizeof(List)); /*这里使通过二级指针为指针(*pplist)申请一个空间*/
if (NULL == *pplist)
{
printf("申请空间失败");
printf("请按任意键退出");
getchar();
return 0;
}
else
{
(*pplist)->pfront = NULL; /*让链表里的指向结点的指针指向空*/
(*pplist)->prear = NULL;
(*pplist)->count = 0;
}
return 1;
}
void TraverList(List *plist,void(*Traver)(Node* pnode))
{
//************************************
// Method: TraverList
// FullName: TraverList
// Access: public
// Returns: void
// Qualifier: 用于遍历参数并且对链表做操作
// Parameter: List * plist 指向链表的指针
// Parameter: void
// Parameter: * Traver 指向函数的指针 指针:Traver (利用函数名对其赋值) 参数:Node*类型 返回值:void
// Parameter: Node * pnode
//************************************
Node *ptemp = plist->pfront;
int listsize = plist->count;
while (listsize)
{
Traver(ptemp); /*用的时候直接用指针表示函数*/
ptemp = ptemp->pnext;
listsize--;
}
}
int IsEmpty(List* plist)
{
if (plist->count == 0)
return 1;
else
return 0;
}
void InserList(List* plist, Node* pnode)
{
//************************************
// Method: InserList
// FullName: InserList
// Access: public
// Returns: void
// Qualifier: 对链表进行插入,将pnode结点插入plist链表
// Parameter: List * plist 指向链表的结点
// Parameter: Node * pnode 待插入的结点
//************************************
if (IsEmpty(plist)) /*链表是空的*/
plist->pfront = pnode;
else
plist->prear->pnext = pnode; /*尾插法*/
plist->prear = pnode;
plist->count++;
}
void ShowData(Node * pnode)
{
printf("x = %d\t y = %d\n", pnode->data.x, pnode->data.y);
}
void FreeList(Node *pnode) {
//************************************
// Method: FreeList
// FullName: FreeList
// Access: public
// Returns: void
// Qualifier: 用以释放列表中各个结点所占据的内存
// Parameter: List * plist 指向列表的指针
//************************************
Node *pfreenode;
while (pnode != NULL)
{
pfreenode = pnode;
pnode = pnode->pnext;
free(pfreenode);
}
}
主函数:main.cpp
点击查看代码
#include "List.h"
#include <time.h>
int main()
{
List *pList; /*定义一个指针*/
ListInit(&pList); /*对链表进行初始化*/
srand((unsigned)time(NULL));
for (int i = 0; i < 6; i++)
{
Node *pnode = (Node *)calloc(1, sizeof(Node)); /*制造结点*/
pnode->data.x = rand() % 10;
pnode->data.y = rand() % 10;
pnode->pnext = NULL; /*结点不指向下一个结点*/
InserList(pList, pnode);/*将产生的结点插入到列表中*/
}
TraverList(pList, ShowData);/*对链表进行遍历,并将结点中的数据表现出来*/
Node *pfree;
pfree = pList->pfront;
FreeList(pfree); /*将列表生成过程中开辟的空间释放掉*/
free(pList);
getchar();
}