一、工程文件的创建
1、main
在根目录下新建一个名为main的文件夹,里面包含Makefile文件和src源码文件夹。
其中Makefile如下所示代码编写:
include ../scripts/Makefile
all : $(Objs)
clean :
rm -rf $(Objs)
在src里面新建一个main.c,里面写上包括但不限于,链表创建,链表释放,链表插入,链表删除,链表遍历,链表逆序的功能。
代码等测试的时候一个个贴出来。
2、link
在根目录下面新建一个名为link的文件夹,里面包含Makefile文件和src源码文件夹。
其中Makefile和main中的一样。
在src里面新建一个link.c,代码为链表基本操作的定义,源码如下:
#include <stdio.h>
#include <stdlib.h>
#include "../../include/myhead.h"
/* 函数名:Create_link
* 参数:链表(结构体)指针,
* 返回值:无
* 调用函数:无
* 被调函数:main()
* 概述:将头指针指向NULL,创建一个空的链表
*/
void Create_link(Link *head)
{
*head = NULL; //将空链表指向NULL,防止野指针
}
/* 函数名:Insert_node_head
* 参数:链表的头指针,新节点的指针
* 返回值:无
* 调用函数:无
* 被调函数:main()
* 概述:将新节点的指针指向原来的头节点,将头指针指向新节点
*/
void Insert_node_head(Link * head,Link new_node)
{
new_node->next = *head; //新节点指向越来的头指针
*head = new_node; //头指针指向新节点
}
/* 函数名:Insert_node_tail()
* 参数:链表的头指针,新节点的指针
* 返回值:无
* 调用函数:无
* 被调函数:
* 概述:将链表的最后一个节点的指针指向新节点,新节点的指针指向NULL,如果链表为空,则让头指针指向它,它的指针指向NULL
*/
void Insert_node_tail(Link * head,Link new_node)
{
Link p; //定义一个结构体指针,用来移动链表,保护头指针
p = *head;
if (p == NULL) //如果链表是空链表。则将新节点作为头节点
{
new_node->next = NULL; //新节点的指针指向NULL
*head = new_node; //头指针指向新节点
}
else //否则找到链表的尾节点
{
while (p->next != NULL) //判断是否到尾节点
{
p = p->next; //节点向后移动
}
new_node->next = NULL; //移动到尾节点之后,让新节点指向NULL
p->next = new_node; //原来的尾节点指向新节点
}
}
/* 函数名:Insert_node()
* 参数:头指针,要插入链表的数据
* 返回值:无
* 调用函数:无
* 被调函数:main()
* 概述:将一个数据插入链表,如果链表为空,则将它作为头节点,否则,升序插入
*/
void Insert_node(Link * head,int num)
{
Link new_node,p; //定义一个新节点指针和一个结构体指针(移动链表,保护头指针)
new_node = (Link)malloc(sizeof(Node));
new_node->num = num; //为新节点分配内存,把数据存入新节点
p = *head;
if (*head == NULL) //如果是空链表,把新节点作为链表
{
*head = new_node; //头指针指向新节点
new_node->next = NULL; //新节点指向NULL
}
else //不是空链表
{
if (num < (*head)->num) //数据比头节点数据还小,将新节点作为新的头节点
{
*head = new_node; //头指针指向新节点
new_node->next = p; //新节点指向原来的头节点
return; //插入结束,返回
}
while (p->next != NULL) //p还没有移动到尾节点
{
if (p->next->num > num) //如果下一个节点的数比要插入的数大
{
new_node->next = p->next;
p->next = new_node; //就插入,新节点指向下一个节点,这个节点指向新节点;
return; //插入结束,返回
}
p = p->next; //没有返回,就将链表节点向后移
}
p->next = new_node; //单节点和不是单节点的最后一个数据都比较过了,不满足插在之前的条件
new_node->next =NULL; //这个节点指向新节点,新节点指向NULL
}
}
/* 函数:Delete_node()
* 参数:头指针,具体数值
* 返回值:无
* 调用函数:无
* 被调函数:main()
* 概述:将指定数值的值从链表删除
*/
void Delete_node(Link * head,int num)
{
Link p,q; //定义两个结构体指针,保护链表,和将删除节点的链表连接在一起
int flag = 1;
if (*head == NULL) //如果链表是空链表,无法删除,直接返回
{
printf("Link is empty!\n");
return;
}
else //如果不是空链表
{
while (*head != NULL && (*head)->num == num)
//如果要删除的值和头指针一样,则将头指针后移,首先链表(头节点)不能为空
{
p = *head; //将头节点存入p
*head = (*head)->next; //将头指针指向下一个节点
free(p); //将头节点释放
flag = 0; //清0删除开关量
}
p = q = *head; //把最新的头节点给p,q
while (p != NULL) //p不指向NULL
{
if (p->num != num) //P指向的数据不等于要删除的数据
{
q = p; //将q指向p
p = p->next; //p指向下一个下一个节点
}
else //p指向的数据等于要删除的数据
{
q->next = p->next; //将q指向p的下一个
free(p); //将p指向的空间释放
p = q->next; //将下一个节点给p,用以继续比较
flag = 0; //清0删除开关量
}
}
if (flag) //删除开关量为1
{
printf("No such num!\n");
} //输出链表里没有那个数
}
}
/* 函数名:Display()
* 参数:链表的头指针
* 返回值:无
* 调用函数:无
* 被调函数:main()
* 概述:由头指针指向的头节点开始,将链表所有数据输出
*/
void Display(Link head)
{
Link p;
p = head; //将头指针赋值给别人,保护头指针
if (head == NULL) //链表为空
{
printf("Link is empty!\n"); //输出链表为空
}
while (p != NULL) //当p指针不指向空
{
printf("num = %d\n",p->num);//将p指向的数据输出
p = p->next; //p指向下一个节点
}
}
/* 函数名:Release()
* 参数:链表的头指针
* 返回值:无
* 调用函数:无
* 被调函数:main()
* 概述:释放整个链表
*/
void Release(Link * head)
{
Link p; //保护头节点
while (*head != NULL) //头节点不指向空
{
p = *head; //存一下头节点
*head = (*head)->next; //头节点后移
free(p); //释放p指向的节点
}
}
/* 函数名:Reverse_link()
* 参数:结构体指针,指向节点的结构体
* 返回值:结构体指针
* 调用函数:Reverse_link()
* 被调函数:main()
* 概述:递归调用,将头指针指向尾节点,让下一个节点指向自己,让自己指向NULL
*/
Link Reverse_link(Link node)
{
Link head; //定义一个指针用以存放新的头节点
if( node == NULL || node->next == NULL)
{
return node; //如果节点为空或者节点指向空,就返回这个节点
}
else
{
head = Reverse_link(node->next);//将返回的节点作为头指针
node->next->next = node; //下一个节点指向自己
node->next = NULL; //自己指向空
return head; //返回头指针
}
}
写好备用。
3、include
在根目录下新建一个include文件夹,里面新建一个myhead.h文件,写上链表结构体的定义,转命名,和函数的声明。
源码如下:
struct node // 创建链表节点
{
int num; //成员1为数据
struct node * next; //成员2为和自己同类型的指针
};
typedef struct node Node; //将链表节点重命名为Node
typedef struct node * Link; //将链表节点指针重命名为Link
//typedef Node * Link;
/* 函数名:Create_link
* 参数:链表(结构体)指针,
* 返回值:无
* 调用函数:无
* 被调函数:main()
* 概述:将头指针指向NULL,创建一个空的链表
*/
void Create_link(Link *head);
/* 函数名:Insert_node_head
* 参数:链表的头指针,新节点的指针
* 返回值:无
* 调用函数:无
* 被调函数:main()
* 概述:将新节点的指针指向原来的头节点,将头指针指向新节点
*/
void Insert_node_head(Link * head,Link new_node);
/* 函数名NULL* 参数:链表的头指针,新节点的指针
* 返回值:无
* 调用函数:无
* 被调函数:
* 概述:将链表的最后一个节点的指针指向新节点,新节点的指针指向NULL,如果链表为空,则让头指针指向它,它的指针指向NULL
*/
void Insert_node_tail(Link * head,Link new_node);
/* 函数:Delete_node()
* 参数:头指针,具体数值
* 返回值:无
* 调用函数:无
* 被调函数:main()
* 概述:将指定数值的值从链表删除
*/
void Delete_node(Link * head,int num);
/* 函数名:Display()
* 参数:链表的头指针
* 返回值:无
* 调用函数:无
* 被调函数:main()
* 概述:由头指针指向的头节点开始,将链表所有数据输出
*/
void Display(Link head);
/* 函数名:Release()
* 参数:链表的头指针
* 返回值:无
* 调用函数:无
* 被调函数:main()
* 概述:释放整个链表
*/
void Release(Link * head);
/* 函数名:Reverse_link()
* 参数:结构体指针,指向节点的结构体
* 返回值:结构体指针
* 调用函数:Reverse_link()
* 被调函数:main()
* 概述:递归调用,将头指针指向尾节点,让下一个节点指向自己,让自己指向NULL
*/
Link Reverse_link(Link node);
/* 函数名:Insert_node()
* 参数:头指针,要插入链表的数据
* 返回值:无
* 调用函数:无
* 被调函数:main()
* 概述:将一个数据插入链表,如果链表为空,则将它作为头节点,否则,升序插入
*/
void Insert_node(Link * head,int num);
4、scripts
在根目录下新建一个scripts文件夹,里面新建一个Makefile文件,源码如下所示:
CC := gcc
CFLAGS := -Wall -O3
Libs = -lpthread
Target := base_link
Source := $(wildcard src/*.c)
Objs := $(patsubst %.c,%.o,$(Source))
Modules += main link
AllObjs := $(addsuffix /src/*.o,$(Modules))
留着备用。
5、Makefile
在根目录下新建一个Makefile文件,源码如下:
include scripts/Makefile
modules_make = $(MAKE) -C $(1);
modules_clean = $(MAKE) clean -C $(1);
.PHONY: all mm mc clean
all: $(Target)
mm:
@ $(foreach n,$(Modules),$(call modules_make,$(n)))
mc:
@ $(foreach n,$(Modules),$(call modules_clean,$(n)))
$(Target) : mm
$(CC) $(CFLAGS) -o $(Target) $(AllObjs) $(Libs)
@ echo $(Target) make done!
clean : mc
rm -rf $(Target)
@ echo clean done!
以上五步完成之后,工程文件就建立好了。
就可以在根目录下输入make命令(Linux环境下)。
执行效果如下:
其中base_link为可执行文件。
二、链表的基本操作
具体如何实现link.c都有注释。
1、链表的创建与遍历。
链表创建之后我们无法的知是否创建,可以通过遍历链表来操作。
main()源码如下:
#include <stdio.h>
#include <stdlib.h> //调用malloc函数,返回值为void
#include "../../include/myhead.h"
/* 函数名:main()
* 参数:无
* 返回值:整型(无意义)
* 调用函数:Create_link(),Insert_node_head(),Display()
* 被调函数:无
* 概述:生成链表并遍历
*/
int main()
{
Link head;
Create_link(&head); // 创建链表, 将地址传过去,带回地址
Display(head); // 遍历链表
}
执行效果如下:
成功创建了一个新链表。
2、链表的创建与节点插入
(1)、头插
我们把数据插在链表前面,再通过遍历显示。
main()源码如下:
#include <stdio.h>
#include <stdlib.h> //调用malloc函数,返回值为void
#include "../../include/myhead.h"
/* 函数名:main()
* 参数:无
* 返回值:整型(无意义)
* 调用函数:Create_link(),Insert_node_head(),Display()
* 被调函数:无
* 概述:调用函数新建一个链表,头插进十个数据,并遍历链表
*/
int main()
{
Link head;
Link new_node;
int i;
Create_link(&head); // 创建链表, 将地址传过去,带回地址
for (i = 0;i < 10; i++)
{
new_node = (Link)malloc(sizeof(Node));
new_node->num = (i + 1) / 2;
Insert_node_head(&head,new_node);
}
Display(head);
return 0;
}
执行效果如下:
成功的创建链表,头插,遍历。
(2)、尾插
同样的把数据插在尾部,并且遍历显示。
main()源码如下:
#include <stdio.h>
#include <stdlib.h> //调用malloc函数,返回值为void
#include "../../include/myhead.h"
/* 函数名:main()
* 参数:无
* 返回值:整型(无意义)
* 调用函数:Create_link(),Insert_node_head(),Display()
* 被调函数:无
* 概述:调用函数新建一个链表,尾插进十个数据,并遍历链表
*/
int main()
{
Link head;
Link new_node;
int i;
Create_link(&head); // 创建链表, 将地址传过去,带回地址
for (i = 0;i < 10; i++)
{
new_node = (Link)malloc(sizeof(Node));
new_node->num = (i + 1) / 2;
Insert_node_tail(&head,new_node);
}
Display(head);
return 0;
}
执行效果如下:
成功的创建链表,尾插,遍历。
(3)插入(升序)
把数据插入到指定要求的地方,这里是升序插入。
main()源码如下:
#include <stdio.h>
#include <stdlib.h> //调用malloc函数,返回值为void
#include "../../include/myhead.h"
/* 函数名:main()
* 参数:无
* 返回值:整型(无意义)
* 调用函数:Create_link(),Insert_node_head(),Display()
* 被调函数:无
* 概述:调用函数新建一个链表,插入你输入的10个数据,每次输入都遍历链表
*/
int main()
{
Link head;
Link new_node;
int i;
int num;
Create_link(&head); // 创建链表, 将地址传过去,带回地址
Display(head);
for (i = 0;i < 10; i++)
{
printf("please enter num to be add!");
scanf("%d",&num);
Insert_node(&head,num);
Display(head);
}
return 0;
}
执行效果如下:
成功的创建链表,插入数据,遍历链表,对链表的插入有,插入空链表,插入链表头,插入链表尾,插入链表中四种,执行效果如预期。
3、链表的释放
为了不造成内存泄漏,链表使用完成之后一定要释放。
对空链表的释放毫无意义,甚至会报错。所以我们就新建一个头插的链表来进行释放,同时通过遍历来判断是否释放。
main()源码如下:
#include <stdio.h>
#include <stdlib.h> //调用malloc函数,返回值为void
#include "../../include/myhead.h"
/* 函数名:main()
* 参数:无
* 返回值:整型(无意义)
* 调用函数:Create_link(),Insert_node_head(),Display()
* 被调函数:无
* 概述:调用函数新建一个链表,头插进数个节点,遍历,释放链表,再遍历
*/
int main()
{
Link head;
Link new_node;
int i;
Create_link(&head); // 创建链表, 将地址传过去,带回地址
for (i = 0;i < 10; i++)
{
new_node = (Link)malloc(sizeof(Node));
new_node->num = (i + 1) / 2;
Insert_node_head(&head,new_node);
}
Display(head);
Release(&head);
Display(head);
return 0;
}
执行效果如下:
最后可见,链表被释放为空链表。
4、链表的删除
链表删除有很多种情况,我写的是删除指定元素。那么就有以下几种情况。
(1)、删除空链表里面的数据
main()源码如下:
#include <stdio.h>
#include <stdlib.h> //调用malloc函数,返回值为void
#include "../../include/myhead.h"
/* 函数名:main()
* 参数:无
* 返回值:整型(无意义)
* 调用函数:Create_link(),Insert_node_head(),Display()
* 被调函数:无
* 概述:调用函数新建一个链表,遍历,删除全部链表数据
*/
int main()
{
Link head;
Create_link(&head); // 创建链表, 将地址传过去,带回地址
Display(head);
printf("please input a number to be deleted!\n");
scanf("%d",&num);
Delete_node(&head,num);
return 0;
}
执行效果如下:
(2)、删除链表的全部数据
main()源码如下:
#include <stdio.h>
#include <stdlib.h> //调用malloc函数,返回值为void
#include "../../include/myhead.h"
/* 函数名:main()
* 参数:无
* 返回值:整型(无意义)
* 调用函数:Create_link(),Insert_node_head(),Display()
* 被调函数:无
* 概述:调用函数新建一个链表,插入数据,遍历,删光数据,遍历
*/
int main()
{
Link head;
int num;
Create_link(&head); // 创建链表, 将地址传过去,带回地址
printf("please enter num to be add!");
scanf("%d",&num);
Insert_node(&head,num);
Display(head);
printf("please input a number to be deleted!\n");
scanf("%d",&num);
Delete_node(&head,num);
Display(head);
return 0;
}
执行效果如下:
(3)、删除尾部数据(和删除中间数据一起演示,同时删除多个数据)
main()源码如下:
#include <stdio.h>
#include <stdlib.h> //调用malloc函数,返回值为void
#include "../../include/myhead.h"
/* 函数名:main()
* 参数:无
* 返回值:整型(无意义)
* 调用函数:Create_link(),Insert_node_head(),Display()
* 被调函数:无
* 概述:调用函数新建一个链表,插入十个数据,遍历,删除部分,遍历
*/
int main()
{
Link head;
int i;
int num;
Create_link(&head); // 创建链表, 将地址传过去,带回地址
for (i = 0;i < 10; i++)
{
printf("please enter num to be add!");
scanf("%d",&num);
Insert_node(&head,num);
}
Display(head);
printf("please input a number to be deleted!\n");
scanf("%d",&num);
Delete_node(&head,num);
Display(head);
return 0;
}
执行效果如下图所示:
5、链表的逆序
我这边逆序是用的递归的方法,用起来比较简单。
main()源码如下:
#include <stdio.h>
#include <stdlib.h> //调用malloc函数,返回值为void
#include "../../include/myhead.h"
/* 函数名:main()
* 参数:无
* 返回值:整型(无意义)
* 调用函数:Create_link(),Insert_node_head(),Display()
* 被调函数:无
* 概述:调用函数新建一个链表,头插10数据,遍历,逆序,遍历。
*/
int main()
{
Link head;
Link new_node;
int i;
Create_link(&head); // 创建链表, 将地址传过去,带回地址
for (i = 0;i < 10; i++)
{
new_node = (Link)malloc(sizeof(Node));
new_node->num = (i + 1) / 2;
Insert_node_head(&head,new_node);
}
Display(head);
head = Reverse_link(head);
Display(head);
return 0;
}
执行效果如下图:
链表的基本操作大致就是这些。
---------------------
作者:不会水的鸟
来源:优快云
原文:https://blog.youkuaiyun.com/weixin_42425796/article/details/86741819
版权声明:本文为博主原创文章,转载请附上博文链接!