C语言单链表讲解(上):有表头链表的使用

目录

 

前言

单链表的分类

有表头单链表

一、create()创建链表,后面产生的结构体将放在表头后面

二、newNote()创建节点,读取用户输入的记录,生成结构体数据

三、insert()从表头插入新的数据,可用于添加信息的功能。

四、query()通过接收用户的编号来查找链表中对应的记录,但只能返回第一个编号相同的记录

五、query a record()查找函数,可用于查找信息功能

六、delete()用于删除第一个与编号相同的记录

八、sort()按编号排序,用于信息排序,经典的冒泡排序法

九、display()用于信息显示

十、Reverse()链表逆置

完整代码展示


前言

在我们学习C语言基础的过程中,指针链表一直是新手的两大拦路虎。在大学的程序语言课程设计中,我们在设计系统时可能会用到链表或者结构体数组今天,我想为大家梳理一下关于单链表的使用。本文章需要读者先行理解C语言的结构体和结构体中next的使用。

注:我所使用的是vs2019,部分代码可能在其他编译器中会报错。

单链表的分类

在我理解中,单链表分为两类,一种是有表头单链表,一种是无表头单链表。本章我先来帮助大家理解有表头单链表。

结构体框架

struct Structure //为了方便大家理解char类型和int类型在使用上的区别,我特地分别设置了两个变量
{
	char num[4];//编号
	int score;//分数
	char name[10];//姓名
	int room;//房间号
	struct Structure* next;
};

有表头单链表

有表头单链表,顾名思义,就是创建的链表中表头数据为NULL,next直接指向下一结构体。它包含的基础功能有:添加信息查找信息删除信息信息排序文件操作信息显示。文件操作功能我暂且放在下一章讲解。

下面,我来讲解使用有表头单链表需要使用到的函数:

一、create()创建链表,后面产生的结构体将放在表头后面

struct Structure* create()//创建有表头链表(不存数据)
{
	struct Structure* head = (struct Structure*)malloc(sizeof(struct Structure));//产生变量
	head->next = NULL;//初始化变量
	return head;
}

我们需要在主函数main中编写 struct Structure* head = create();  这样一个链表的表头就形成了。

二、newNote()创建节点,读取用户输入的记录,生成结构体数据

struct Structure* newNote()//创建节点
{
	struct Structure* p = (struct Structure*)malloc(sizeof(struct Structure));
	scanf_s("%s%d%s%d", p->num, 4, &p->score, p->name, 10, &p->room);
	p->next = NULL;
	return p;
}

scanf_s代码解释:              //  scanf_s("%s%d%s%d", p->num, 4, &p->score, p->name, 10, &p->room);

1.代码解读:num和name均为char类型的数组变量,在取址时可以不用添加“&”,后面跟着的4,10为可读取数据的长度。score和room为int类型变量,需添加“&”读取地址

2.这是vs新版本中为了保证系统的安全,添加了检验机制。

3.它与scanf的区别在于,读取字符串时可以确定读取的长度,当输入字符串长度过长时可以直接对该变量赋值为空,且不影响后续数据的读取。若输入数据为123456 78 张三 101,则存储的数据为:num=""   score=78   name="张三"  room=101   。

函数解读,创建一个结构体,并存储用户输入数据,并将该结构体返回。

三、insert()从表头插入新的数据,可用于添加信息的功能。

void insert(struct Structure* head)//调用创建节点导入数据并从表头插入
{
	int n;//用于读取用户输入的记录个数
	printf("请输入你要录入的学生个数:");
	scanf_s("%d", &n);

	for (;  n < 1; n--)
	{
		printf("请输入数据\n");//也可以定义变量i来提示用户当前输入的记录条数
		struct Structure* node = newNote();
		node->next = head->next;
		head->next = node;
	}
}

将用户输入的记录一个个从表头插入链表中。

四、query()通过接收用户的编号来查找链表中对应的记录,但只能返回第一个编号相同的记录

Structure* Query(Structure* head, char* num)//查找编号,有则返回对应结构体,否则返回空链表
{
	Structure* p;
	p = head;
	while (p != NULL)
	{
		if (strcmp(num, p->num) == 0) 
			return p;
		p = p->next;
	}
	return NULL;
}

本函数中使用了strcmp()函数,需使用头文件#include<string.h>。常用于比较两个字符串的大小,当两种相同时则返回数值0。

本函数从表头开始查找,编号相同则返回,直到查找到链表尾步为止,若无对应编号则返回空值。

常被query a record查找函数和delete删除函数调用。

五、query a record()查找函数,可用于查找信息功能

void Query_a_record(Structure* head)//接收查找反馈并展示结果
{

	char s[4];
	Structure* p;
	printf("请输入一个要查找的编号\t");
	scanf_s("%s", s, 4);
	getchar();
	p = Query(head, s);
	if (p != NULL)
	{
		printf("%s %d %s %d", p->num, p->score, p->name, p->room);
		system("pause");
	}
	else
	{
		printf("找不到对应的编号!\t\t");
		system("pause");
	}
}

调用query查找函数,分两种情况,若查找到记录则显示;若返回值为空则链表没有该编号。

缺点:无法判断链表是否存储数据

六、delete()用于删除第一个与编号相同的记录

Telephone* Delete(Telephone* head, char* num)//接收数据,并删除编号相同的记录
{
    Telephone* p1, * p2=NULL;
    p1 = head;
    while ((strcmp(num, p1->num) != 0) && (p1->next != NULL))
    {
        p2 = p1; p1 = p1->next;
    }
    if (strcmp(num, p1->num) == 0)
    {
        if (p1 == head) head = p1->next;
        else p2->next = p1->next;
        free(p1);
    }
    return head;
}

用于接收来自delete a record删除函数传递的编号,然后删除第一个与之编号相同的记录

缺点:只能删除一个

七、delete a record()用于删除信息功能,删除查询的记录

Structure* Delete_a_record(Structure* head)//传递要删除的记录编号,并选择是否删除
{
	char s[4];
	printf("请输入你要删除的编号:\t");
	scanf_s("%s", s, 4);
	getchar();
	if (Query(head, s) != NULL)
	{		
		head = Delete(head, s);
		printf("编号为%s的记录已删除", s);
		system("pause");
	}
	else
	{
		printf("编号为%s的记录未查询到", s);
		system("pause");
	}
	return head;
}

通过调用query查找函数来确定编号是否存在,然后调用delete删除函数来删除记录。

八、sort()按编号排序,用于信息排序,经典的冒泡排序法

void sort(struct Structure* head)//排序
{
	int i, j, n = 0;
	Structure t, * p = head, * p1, * p2;
	while (p != NULL)//统计结构体数量
	{
		n++;
		p = p->next;
	}
	p2 = head;
	for (i = 0; i < n - 1; i++)
	{
		p1 = p2->next;
		p = p2;
		for (j = i + 1; j < n; j++)
		{
			if (strcmp(p1->num, p->num) < 0) 
				p = p1;
			p1 = p1->next;
		}
		if (p != p2)
		{
			strcpy(t.num, p2->num);
			strcpy(t.name, p2->name);
			t.room = p2->room;
			t.score = p2->score;

			strcpy(p2->num, p->num);
			strcpy(p2->name, p->name);
			p2->room = p->room;
			p2->score = p->score;

			strcpy(p->num, t.num);
			strcpy(p->name, t.name);
			p->room = t.room;
			p->score = t.score;
		}
		p2 = p2->next;
	}
	printf("升序排列操作已完成!\t");
	system("pause");
}

t是结构体变量,是交换的载体。而p1,p2是结构体指针。所以在调用结构体成员时,t使用".",p1使用"->"。

九、display()用于信息显示

void Display(Structure* head)//显示记录
{
	Structure* p = head;
	printf("编号\t\t成绩\t\t姓名\t\t教室\n");
    p = p->next;//跳过表头
		while (p != NULL)
		{
			printf("%s\t\t%d\t\t%s\t\t%d\n", p->num, p->score, p->name, p->room);
			p = p->next;
		}
		system("pause");
}

十、Reverse()链表逆置

不知道怎么说,读者可以通过画图自行体会

Structure* Reverse(Structure* head)
{
	Structure* p1, * p2, *p3;
	p3 = head->next;
	p1 = p3->next;
	p3->next = NULL;
	while (p1 != NULL)
	{
		p2 = p1; p1 = p1->next;
		p2->next = p3; p3 = p2;
	}
	head->next = p3;
	return head;
}

完整代码展示

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//为了方便大家理解char类型和int类型在使用上的区别,我特地分别设置了两个变量
struct Structure 
{
	char num[4];//编号
	int score;//分数
	char name[10];//姓名
	int room;//房间号
	struct Structure* next;
};
//创建有表头链表(不存数据)
struct Structure* create()
{
	struct Structure* head = (struct Structure*)malloc(sizeof(struct Structure));//产生变量
	head->next = NULL;//初始化变量
	return head;
}
//创建节点
struct Structure* newNote()
{
	struct Structure* p = (struct Structure*)malloc(sizeof(struct Structure));
	scanf_s("%s%d%s%d", p->num, 4, &p->score, p->name, 10, &p->room);
	p->next = NULL;
	return p;
}
//调用创建节点导入数据并从表头插入
void insert(struct Structure* head)
{
	int n;//用于读取用户输入的记录个数
	printf("请输入你要录入的学生个数:");
	scanf_s("%d", &n);

	for (;  n > 0; n--)
	{
		printf("请输入数据\n");//也可以定义变量i来提示用户当前输入的记录条数
		struct Structure* node = newNote();
		node->next = head->next;
		head->next = node;
	}
}
//查找编号,有则返回对应结构体,否则返回空链表
Structure* Query(Structure* head, char* num)
{
	Structure* p;
	p = head;
	while (p != NULL)
	{
		if (strcmp(num, p->num) == 0) 
			return p;
		p = p->next;
	}
	return NULL;
}
//接收查找反馈并展示结果
void Query_a_record(Structure* head)//接收查找反馈并展示结果
{

	char s[4];
	Structure* p;
	printf("请输入一个要查找的编号\t");
	scanf_s("%s", s, 4);
	getchar();
	p = Query(head, s);
	if (p != NULL)
	{
		printf("%s %d %s %d", p->num, p->score, p->name, p->room);
		system("pause");
	}
	else
	{
		printf("找不到对应的编号!\t\t");
		system("pause");
	}
}
//接收数据,并删除编号相同的记录
Structure* Delete(Structure* head, char* num)
{
	Structure* p1, * p2 = NULL;
	p1 = head;
	while ((strcmp(num, p1->num) != 0) && (p1->next != NULL))
	{
		p2 = p1; p1 = p1->next;
	}
	if (strcmp(num, p1->num) == 0)
	{
		if (p1 == head) head = p1->next;
		else p2->next = p1->next;
		free(p1);
	}
	return head;
}
//传递要删除的记录编号,并选择是否删除
Structure* Delete_a_record(Structure* head)
{
	char s[4];
	printf("请输入你要删除的编号:\t");
	scanf_s("%s", s, 4);
	getchar();
	if (Query(head, s) != NULL)
	{		
		head = Delete(head, s);
		printf("编号为%s的记录已删除", s);
		system("pause");
	}
	else
	{
		printf("编号为%s的记录未查询到", s);
		system("pause");
	}
	return head;
}
//排序
void sort(struct Structure* head)
{
	int i, j, n = 0;
	Structure t, * p = head, * p1, * p2;
	while (p != NULL)//统计结构体数量
	{
		n++;
		p = p->next;
	}
	p2 = head;
	for (i = 0; i < n - 1; i++)
	{
		p1 = p2->next;
		p = p2;
		for (j = i + 1; j < n; j++)
		{
			if (strcmp(p1->num, p->num) < 0) 
				p = p1;
			p1 = p1->next;
		}
		if (p != p2)
		{
			strcpy(t.num, p2->num);
			strcpy(t.name, p2->name);
			t.room = p2->room;
			t.score = p2->score;

			strcpy(p2->num, p->num);
			strcpy(p2->name, p->name);
			p2->room = p->room;
			p2->score = p->score;

			strcpy(p->num, t.num);
			strcpy(p->name, t.name);
			p->room = t.room;
			p->score = t.score;
		}
		p2 = p2->next;
	}
	printf("升序排列操作已完成!\t");
	system("pause");
}
//显示记录
void Display(Structure* head)
{
	Structure* p = head;
	printf("编号\t\t成绩\t\t姓名\t\t教室\n");
	p = p->next;//跳过表头
		while (p != NULL)
		{
			printf("%s\t\t%d\t\t%s\t\t%d\n", p->num, p->score, p->name, p->room);
			p = p->next;
		}
		system("pause");
}
void Display_Main_Menu()
{
	printf("1.添加信息\n");
	printf("2.删除信息\n");
	printf("3.查找信息\n");
	printf("4.排序信息\n");
	printf("5.显示信息\n");
	printf("0.退出系统\n");
}
int main()
{
	int choose, b;
	struct Structure* head = create();
	while (1)
	{
		Display_Main_Menu();
		scanf_s("%d", &choose);
		switch (choose)
		{
		case 1:
			insert(head);
			system("cls");
			break;
		case 2:
			Delete_a_record(head);
			system("cls");
			break;
		case 3:
			Query_a_record(head);
			system("cls");
			break;
		case 4:
			sort(head);
			system("cls");
			break;
		case 5:
			Display(head);
			system("cls");
			break;
		case 0:
			goto loop;
		default:
			printf("\t请输入选项0—9!\n");
			system("pause");
		}
	}
loop:
	printf("感谢本次使用成绩处理系统\n");
	system("pause");
}

 

  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值