第一次写博客,今天学习了C语言链表的相关知识,自己实现了一个很简单的学生成绩管理系统,同时也温习了一下多文件编程,想和大家分享一下自己从中的一些经验和感受。
头文件
//List.h 包含结构体的定义
#ifndef M //使用条件编译来避免重复包含
#define M
struct Student
{
char name[50];//学生的姓名
double score;//学生的成绩
struct Student *pnext;//存储下一个节点的地址
};
#endif
typedef struct Student ST;//简化结构体的声明
//function.h,包含函数的定义
#define _CRT_SECURE_NO_WARNINGS//关闭安全检查
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"List.h"
void add(ST **head, char *name, double iscore);//增加学生的成绩信息
void del(ST **head, char *name, double iscore);//删除学生的成绩信息
void get(const ST *head, char *name);//查询学生的成绩信息
void change(ST *head, char *name, double score, double sscore);//改变学生的成绩信息
void sort(const ST *head);//对学生的成绩进行排序
ST *rev(ST *head);//逆转所有学生的成绩信息
void insert(ST **head, char *name, double iscore);//插入学生的成绩信息
void print(const ST *head);//打印学生的成绩信息
ST *delall(ST *head);//删除所有学生的信息
//各个函数的实现
#include"function.h"
//实现add函数
void add(ST **head, char *name, double iscore)//增加学生的成绩信息
{
if (*head == NULL)//整个链表为空
{
ST *phead = (ST *)malloc(sizeof(ST));//动态分配内存空间
strcpy(phead->name, name);//字符串拷贝
phead->score = iscore;
phead->pnext = NULL;//最后一个节点的指针域为NULL
*head = phead;//指向头节点
}
else//链表为非空
{
ST *p = *head;//存储首节点的地址
while (p->pnext)//循环到最后一个节点,并且不改变传入的头节点指针的指向
{
p = p->pnext;
}
ST *pnew = (ST *)malloc(sizeof(ST));
strcpy(pnew->name, name);
pnew->score = iscore;
pnew->pnext = NULL;//最后一个指针域为空
p->pnext = pnew;//将节点连接起来
}
}
由于函数的副本机制,所以改变一个指针的指向需要使用二级指针
#include"function.h"
//实现del函数
void del(ST **head, char *name, double iscore)//删除学生的成绩信息
{
ST *p = *head;
if ((strcmp(p->name, name) == 0) && (p->score == iscore))//删除的为首节点
{
*head = p->pnext;//指向下一个节点
free(p);//释放内存
}
else//删除的不是首节点
{
ST *p1 = p;//记录上一个节点的地址
while (p->pnext)
{
if ((strcmp(p->name, name) == 0) && (p->score == iscore))//找到了待删除的节点
{
p1->pnext = p->pnext;//删除节点p
free(p);
break;//跳出循环
}
p1 = p;
p = p->pnext;//下一次的循环
}
}
}
删除链表的一个节点只需让上一个节点存储该节点的指针域即可
#include"function.h"
//实现get函数
void get(const ST *head, char *name)
{
ST *p = head;
while (p->pnext)
{
if (strcmp(p->name, name) == 0)
{
printf("%s的成绩为%lf\n", name, p->score);
}
p = p->pnext;
}
}
#include"function.h"
//实现change函数
void change(ST *head, char *name, double score, double sscore)
{
ST *p = head;
while (p->pnext)
{
if ((strcmp(p->name, name) == 0) && (p->score == score))
{
p->score = sscore;//函数的副本机制,必须通过指针才能改变原来的值
break;
}
p = p->pnext;
}
}
#include"function.h"
//实现sort函数
void sort(const ST *head)
{
//选择排序法
for (ST *pi = head; pi; pi = pi->pnext)
{
for (ST *pj = pi->pnext; pj; pj = pj->pnext)
{
if ((pi->score) > (pj->score))
{
//交换成绩
double temp = pi->score;
pi->score = pj->score;
pj->score = temp;
//交换姓名
char str[50];
strcpy(str, pi->name);
strcpy(pi->name, pj->name);
strcpy(pj->name, str);
}
}
}
}
#include"function.h"
//实现rev函数
ST * rev(ST *head)//链表的逆转
{
if (head == NULL || head->pnext == NULL)//链表为空或者只有一个节点
{
return head;
}
ST *p1 = head, *p2 = NULL, *p3 = NULL;
p2 = p1->pnext;//指向下一个节点
while (p2)//p2为NULL时循环结束
{
p3 = p2->pnext;
p2->pnext = p1;//存储前一个节点的值
p1 = p2;
p2 = p3;//指针依次向后移动一位
}
head->pnext = NULL;//最后一个指针域为NULL
head = p1;//指向头节点
return head;//返回头节点,由于函数有副本机制,因此只有通过返回值
//并且在main函数中赋值才能在不使用二级指针的情况下改变头节点
}
链表的逆转就是让每一个节点依次存储前一个节点的地址,最后再让头指针指向之前的最后一个节点即可。
#include"function.h"
//实现insert函数
static void inserth(ST **head, char *name, double iscore)//在链表的头部插入
{
ST *p = (ST *)malloc(sizeof(ST));
strcpy(p->name, name);
p->score = iscore;
p->pnext = *head;//将节点连接起来
*head = p;//指向头节点
}
static void insertc(const ST *head, ST *phead, ST *pback, char *name, double iscore)//在链表的中间插入
{
ST *p = (ST *)malloc(sizeof(ST));
strcpy(p->name, name);
p->score = iscore;
phead->pnext = p;
p->pnext = pback;//将链表连接起来
}
void insert(ST **head, char *name, double iscore)//实现节点的插入
{
ST *p = *head;
if (iscore < (p->score))//比第一个成绩还小,则在首节点前面插入
{
inserth(head, name, iscore);
}
else//在中间或者尾部插入
{
ST *pc = NULL;//记录当前节点的位置
//定位到待插入点的位置
while (((p->score) < iscore) && p->pnext)
{
pc = p;
p = p->pnext;
}
if (p->pnext == NULL)//尾部插入
{
add(head, name, iscore);
}
else//中间插入
{
insertc(*head, pc, pc->pnext, name, iscore);
}
}
}
链表在插入之前需要先进行排序操作
#include"function.h"
//实现print函数
void print(const ST *head)//输出所有节点
{
ST *p = head;
while (p)
{
printf("学生%s的成绩为%lf\n", p->name, p->score);
p = p->pnext;
}
}
#include"function.h"
//实现delall函数
ST *delall(ST *head)//删除所有节点
{
ST *p1 = NULL;
while (head->pnext)
{
p1 = head->pnext;
head->pnext = p1->pnext;//删除节点p2
free(p1);//释放p1的内存
//printf("\n删除后的链表为:\n\n");
//print(head);//输出删除后的结果
}
free(head);//删除第一个节点
return NULL;//返回空指针
}
删除整个链表时,保持头节点的位置不变,依次删除下一个节点,最后再删除头节点即可。
#include"function.h"
void main()
{
ST *head = NULL;
int num;
printf("请输入学生的人数:\n");
scanf("%d", &num);//从键盘获取num的输入
int count = 1;//计数
while (num)
{
char name[50];
double score = 0;
printf("请输入第%d个学生的姓名:\n", count);
scanf("%s", name);//从键盘获取输入
printf("请输入第%d个学生的成绩:\n", count);
scanf("%lf", &score);
add(&head, name, score);
count++;
num--;
}
sort(head);
printf("\n\n学生的成绩为:\n");
print(head);
system("pause");
}
最后是main函数中进行调用,由于已经直接包含了头文件,所以此时没必要再用extern进行外部函数声明。
通过这个简单的小项目,也进一步巩固了自己对链表的增删查改,逆转等操作的熟悉,同时也复习了函数副本以及二级指针的相关知识!