C语言之链表学习

28 篇文章 0 订阅
26 篇文章 0 订阅

C语言之链表

一.引言

假设需要建立一个学生信息表,学生人数无法估计,而且学生人数经常发生变化,应该如何实现?

一般我们会想到用数组来存储,但是数组属于静态存储分配,必须事先确定容量,故无法实现;

因此引出链表这个概念,链表属于动态存储分配,在运行时才分配空间,这种存储方式解决了这种问题。

二.链表的概述

链表是一种物理存储单元上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

链表是由一个个结点组成的。

单链表的结点有两个部分:数据域和指针域

数据域:存储的是当前结点的数据(int,double,struct等等)。

指针域:存储的是下一个结点的地址。

一般单链表的结点结构:可以用一个结构体来表示

typedef struct node
{
	DataType data;  //数据域
	struct node *next;  //指针域
}Node,*Link;

说明:

Node st;//等价于 struct node st;

Link p;//等价于 struct node *p;  指向这种(struct node)结构体的指针。
p = &st;//p指向Node类型的变量st的地址

思考:如何申请一个结点?

p=(Link)malloc(sizeof(Node));

这句代码的意思是:给一个结点变量申请了sizeof(Node)个字节的存储空间。即给st申请了sizeof(Node)个字节的存储空间。

思考:如何引用数据元素?

p -> data;//相当于(*p).data

思考:如何引用指针域?

p -> next;

三.链表的分类

  1. 空表(没有数据)
  2. 非空表(有数据)

如何将空表与非空表统一起来?

引入头结点。(头结点:在单链表的第一个元素结点之前附设一个类型相同的结点,以便空表和非空表处理统一。)

引入头结点后的格式如下:

空表
head–>|冗余值 |null|

非空表
head–>|冗余值 |next|–>第一个数据 下一个数据的地址…

四.链表的练习

使用链表完成学生信息管理

  1. 头文件内容(studentList.h)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

//一些警告的处理
#pragma warning(disable:4996)  //可使用非安全函数
#pragma warning(disable:6031)  //有些函数的返回值不用定义,比如getchar()
#pragma warning(disable:6011) //警告	C6011	取消对 NULL 指针“head”的引用。

#define NUM_MAX 20
#define NAME_MAX 20

typedef struct studentInfo
{
	char stu_num[NUM_MAX];
	char stu_name[NAME_MAX];
}Stu;

typedef struct nodeInfo 
{
	Stu stu_data;
	struct nodeInfo* next;
}Node, * Link;

void myMenu(void);//功能菜单
Link createHead(void);//创建头结点
void inputNode(Link p);//输入学生信息
bool addNode(Link head);//新增学生记录
void displayNode(Link head);//遍历所有学生记录
int countNode(Link head);//统计学生人数
void inputStudentNo(char s[], char no[]);//输入相应的学号和具体的操作
bool queryNode(Link head);//查询学生记录
void insertNode(Link head, Link newNode);//插入一个学生记录
bool modifyNode(Link head);//修改学生记录
bool deleteNode(Link head);//删除学生记录
void clearLink(Link head);//清除链表
  1. studentList.c文件内容
#include "studentList.h"

void myMenu(void) {
	printf("\n");
	printf(" * * * * * * * * * 菜     单 * * * * * * * * * *\n");
	printf("     1 增加学生记录            2 删除学生记录       \n");
	printf("     3 查找学生记录            4 修改学生记录       \n");
	printf("     5 统计学生人数            6 显示学生记录       \n");
	printf("     7 退出系统                                  \n");
	printf(" * * * * * * * * * * * * * * * * * * * * * * * *\n");
}


Link createHead(void)
{
	Link head = (Link)malloc(sizeof(Node));
	head->next = NULL;//这句代码会引起警告C6011,取消对 NULL 指针“head”的引用,因此头文件中需忽略此警告
	return head;
}

void inputNode(Link p)
{
	printf("请输入学生的学号:");
	scanf("%s", p->stu_data.stu_num);
	getchar();
	printf("请输入学生的姓名:");
	scanf("%s", p->stu_data.stu_name);
	getchar();
	p->next = NULL;
}

bool addNode(Link head)
{
	Link p, q;
	Link newNode;
	bool flag = false;

	p = head;
	q = head->next;
	newNode = (Link)malloc(sizeof(Node));

	inputNode(newNode);

	if (head->next == NULL)
	{
		head->next = newNode;
		flag = true;
	}
	else
	{
		while (q != NULL)
		{
			if (strcmp(newNode->stu_data.stu_num, q->stu_data.stu_num) < 0)
			{
				p->next = newNode;
				newNode->next = q;
				flag = true;
				break;
			}
			else
			{
				p = q;
				q = q->next;
			}
		}
	}
	//目前p->next=NULL,q已经在链表外了,只能将新结点添加到p结点的后面
	if (q == NULL && flag == false)
	{
		p->next = newNode;
		flag = true;
	}

	return flag;

}

//结点遍历的实质,从头结点的下一个结点开始一直遍历到最后一个,最后一个结点的next域为NULL。
void displayNode(Link head)
{
	printf("\n* * * * * * * * * 学 生 列  表 * * * * * * * * * *\n");
	printf("\t学号\t\t\t\t姓名\n");
	Link p = head->next;
	while (p != NULL)//这里的p!=NULL和p->next == NULL不相同,p!=NULL包含了p->next==NULL这个情况
	{
		printf("\t%s\t\t\t\t%s\n", p->stu_data.stu_num, p->stu_data.stu_name);
		p = p->next;
	}
}

int countNode(Link head)
{
	Link p = head->next;
	int count = 0;
	while (p != NULL)
	{
		count++;
		p = p->next;
	}
	return count;
}

void inputStudentNo(char s[], char no[])
{
	printf("请输入要%s的学生学号:", s);
	scanf("%s", no);
}

bool queryNode(Link head)
{
	char no[NUM_MAX];
	strcpy(no, "");
	inputStudentNo("查询", no);
	bool flag = false;
	Link p = head->next;
	while (p != NULL)
	{
		if (!strcmp(p->stu_data.stu_num, no))
		{
			printf("%s的姓名是%s\n", no, p->stu_data.stu_name);
			flag = true;
			break;
		}
		else
		{
			p = p->next;
		}
	}
	return flag;
}

void insertNode(Link head, Link newNode)
{
	Link p, q;
	bool flag = false; //是否插入成功
	p = head;
	q = head->next;
	//如果是空链表
	if (head->next == NULL)
	{
		head->next = newNode;
		flag = true;
	}
	else
	{
		//不是空链表
		while (q != NULL)
		{
			if (strcmp(newNode->stu_data.stu_num, q->stu_data.stu_num) < 0)
			{
				p->next = newNode;
				newNode->next = q;
				flag = true;
				break;
			}
			else
			{
				p = q;
				q = q->next;
			}
		}
	}
	if (q == NULL && flag == false)
	{
		p->next = newNode;
	}
}

bool modifyNode(Link head)
{
	char no[NUM_MAX];
	char new_no[NUM_MAX];
	char new_name[NAME_MAX];
	int flag = false;

	strcpy(no, "");
	strcpy(new_no, "");
	strcpy(new_name, "");

	int select;                 //功能选择
	Link p, q;
	inputStudentNo("修改", no);
	printf("请输入需要修改的项目: 1 学号 2 姓名\n");
	scanf("%d", &select);
	getchar();

	p = head;
	q = head->next;
	while (q != NULL)
	{
		if (!strcmp(q->stu_data.stu_num, no))
		{
			if (select == 1)
			{
				p->next = q->next;
				q->next = NULL;//一般一个新的结点没有下一个结点,故新结点的next域为NULL
				printf("请输入该学生的新学号:");
				scanf("%s", new_no);
				getchar();
				strcpy(q->stu_data.stu_num, new_no);
				insertNode(head, q);
			}
			else
			{
				printf("请输入该学生的新姓名:");
				scanf("%s", new_name);
				strcpy(q->stu_data.stu_name, new_name);
			}
			flag = true;
		}
		else
		{
			p = q;
			q = q->next;
		}
	}
	return flag;
}

bool deleteNode(Link head)
{
	char no[NUM_MAX];
	strcpy(no, "");
	inputStudentNo("删除", no);

	int flag = false;
	Link p, q;
	p = head;
	q = head->next;
	while (q != NULL)
	{
		if (!strcmp(q->stu_data.stu_num, no))
		{
			p->next = q->next;
			free(q);
			q = p->next;//如果不写这段代码会报警告C6001,使用未初始化的内存 q
			flag = true;
		}
		else
		{
			p = q;
			q = q->next;
		}
	}

	return flag;
}

void clearLink(Link head)
{
	Link p, q;
	p = head;
	q = head->next;
	while (q != NULL)//不包括头结点
	{
		free(p);
		p = q;
		q = q->next;
	}
	free(p);//清除头结点
	puts("\n\n~~~~~~~~~~~~~~~~~《程序退出,欢迎您再次使用》~~~~~~~~~~~~~~~~~~~~~~~");
}

  1. 测试内容(test.c)
#include "studentList.h"

int main(void)
{
    int select = 0; //功能选项代码
    int count = 0;  //学生人数

    //链表头
    Link head = createHead();

    while (1)
    {
        myMenu();
        printf("\n请输入你的选择(1-7):");
        scanf("%d", &select);
        switch (select)
        {
        case 1:
            if (addNode(head))
            {
                printf("成功增加一个学生记录。\n");
            }
            break;
        case 2:
            if (deleteNode(head))
            {
                printf("成功删除一个学生记录。\n");
            }
            else
            {
                printf("没有找到要删除的学生记录。\n");
            }
            break;
        case 3:
            if (queryNode(head))
            {
                printf("成功找到学生记录。\n");
            }
            else
            {
                printf("没有找到要查询的学生记录。\n");
            }
            break;
        case 4:
            if (modifyNode(head))
            {
                printf("成功修改一个学生记录。\n");
            }
            else
            {
                printf("没有找到要修改的学生记录。\n");
            }
            break;
        case 5:
            count = countNode(head);
            printf("学生人数为:%d\n", count);
            break;
        case 6:
            displayNode(head);
            break;
        case 7:
            clearLink(head);
            system("pause");
            return 0;
        default:
            printf("输入不正确,请输入1-7之间的数。\n");
            break;
        }
    }
}

五.完结

总结:关于链表的学习,一定要自己通过画图来理解。

代码是根据逻辑顺序完成的,因此测试的时候,先添加,然后遍历学生,然后统计,然后查询,然后修改,然后删除,最后退出。

认真理解,多多练习才能真正掌握!!!
本篇代码只用到了单向链表。
双向链表:结点有三个部分,指针域1(前一个数据的地址),数据域,指针域2(后一个数据的地址),思路和单向链表相同。
循环链表:使得最后一个结点指向头结点,思路也和单向链表相同。

《完结撒花》

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值