本篇整理了 C 语言结构体与链表相关的全部课堂代码,按知识点分节讲解。每一节包含题目说明、解题思路、完整代码(带详细注释)和关键知识点总结,方便复习与回顾。
目录
- 一、结构体填空题:按姓名字典序排序
- 二、链表创建:在递增有序链表中插入节点
- 三、链表逆序:带头结点单向链表逆置
- 四、链表查找:查找值为 ch 的节点序号
- 五、链表删除相同节点:保留递增链表中的唯一值
- 六、结构体实践任务
- 七、课前测:结构体数组
一、结构体填空题:按姓名字典序排序
题目说明
程序通过定义学生结构体数组,存储若干名学生的学号、姓名和三门课的成绩。函数 fun 的功能是:将存放学生数据的结构体数组,按姓名的字典序(从小到大)排序。
解题思路
- 定义学生结构体
struct student,包含学号sno、姓名name[10]、三门课成绩score[3]。 - 排序采用选择排序思想:外层循环从第 0 个元素开始,内层循环从
i+1开始,找到比当前元素小的就交换。 - 姓名比较使用
strcmp函数:strcmp(a[i].name, a[j].name) > 0表示a[i].name字典序大于a[j].name,此时需要交换两个结构体。 - 结构体变量可以直接赋值(
t = a[i]; a[i] = a[j]; a[j] = t;),C 语言支持同类型结构体整体赋值。
完整代码
/*-------------------------------------------------------【程序填空】---------------------------------------------------------
题目:程序通过定义学生结构体数组,存储若干名学生的学号、姓名和三门课的成绩。函数 fun的功能是:将存放学生数据的结构体数组,按姓名的字典序(从小到大)排序。
-------------------------------------------------------*/#include <stdio.h>#include <string.h>
/* 定义学生结构体类型 */struct student{ long sno; /* 学号 */ char name[10]; /* 姓名 */ float score[3]; /* 三门课成绩 */};
/* 排序函数:按姓名字典序从小到大排序 */void fun(struct student a[], int n){ /***********SPACE***********/ struct student t; /* 临时变量,用于交换 */ int i, j; /***********SPACE***********/ /* 选择排序:外层从0开始,内层从i+1开始比较 */ for (i = 0; i < n; i++) for (j = i + 1; j < n; j++) /***********SPACE***********/ /* strcmp返回值>0表示a[i].name字典序在a[j].name之后,需要交换 */ if (strcmp(a[i].name, a[j].name) > 0) { t = a[i]; a[i] = a[j]; a[j] = t; }}
int main(){ /* 初始化4名学生数据 */ struct student s[4] = { {10001, "ZhangSan", 95, 80, 88}, {10002, "LiSi", 85, 70, 78}, {10003, "CaoKai", 75, 60, 88}, {10004, "FangFang", 90, 82, 87} }; int i, j; /* 输出原始数据 */ printf("\n\nThe original data :\n\n"); for (j = 0; j < 4; j++) { printf("\nNo: %ld Name: %-8s Scores: ", s[j].sno, s[j].name); for (i = 0; i < 3; i++) printf("%6.2f ", s[j].score[i]); printf("\n"); } /* 调用排序函数 */ fun(s, 4); /* 输出排序后的数据 */ printf("\n\nThe data after sorting :\n\n"); for (j = 0; j < 4; j++) { printf("\nNo: %ld Name: %-8s Scores: ", s[j].sno, s[j].name); for (i = 0; i < 3; i++) printf("%6.2f ", s[j].score[i]); printf("\n"); }}关键知识点
- 结构体数组定义与初始化:
struct student s[4] = {{...}, {...}, ...},嵌套花括号按成员顺序初始化。 strcmp函数:返回 0 表示相等,>0 表示前者大于后者,<0 表示前者小于后者。比较的是 ASCII 码字典序。- 结构体整体赋值:C 语言允许同类型结构体变量直接赋值
t = a[i],等价于逐成员拷贝。 - 选择排序:时间复杂度 O(n²),不稳定排序,但实现简单。
二、链表创建:在递增有序链表中插入节点
题目说明
在程序中已建立一个带头结点的单向链表,链表中的各结点按数据域递增有序链接。函数 fun 的功能是:把形参 x 的值放入一个新节点并插入链表中,使插入后各结点数据域中的数据仍保持递增有序。
解题思路
- 创建新节点
s,将x存入s->data。 - 使用双指针
q和p:q始终指向p的前驱节点。初始时q = h(头结点),p = h->next(第一个数据节点)。 - 遍历链表,当
p != NULL且x > p->data时,q和p同步后移。 - 循环结束后,
p指向第一个大于等于x的节点(或 NULL),q指向其前驱。 - 插入操作:
s->next = p; q->next = s;,将s插入到q和p之间。
完整代码
//1.链表创建//在程序中,已建立一个带头结点的单向链表,//链表中的各结点按结点数据域中的数据递增有序链接。//函数fun的功能是:把形参x的值放入一个新节点并插入链表中,//使插入后各结点数据域中的数据仍保持递增有序。#include <stdio.h>#include <stdlib.h>#define N 8
/* 定义链表节点类型 */typedef struct list{ int data; struct list* next;} SLIST;
/* 在递增有序链表中插入值为x的节点,保持有序性 */void fun(SLIST* h, int x){ SLIST* p, * q, * s; s = (SLIST*)malloc(sizeof(SLIST)); /* 申请新节点空间 */ /**********found**********/ s->data = x; /* 将x存入新节点数据域 */ q = h; /* q指向头结点 */ p = h->next; /* p指向第一个数据节点 */ /* 寻找插入位置:当p未到尾部且x仍大于p的数据时继续后移 */ while (p != NULL && x > p->data) { /**********found**********/ q = p; /* q记录p的前驱 */ p = p->next; } s->next = p; /* 新节点的next指向p */ /**********found**********/ q->next = s; /* q的next指向新节点,完成插入 */}
/* 创建带头结点的单向链表 */SLIST* creatlist(int* a){ SLIST* h, * p, * q; int i; h = p = (SLIST*)malloc(sizeof(SLIST)); /* 创建头结点 */ for (i = 0; i < N; i++) { q = (SLIST*)malloc(sizeof(SLIST)); /* 申请新节点 */ q->data = a[i]; p->next = q; /* 尾插法 */ p = q; } p->next = 0; /* 尾节点next置空 */ return h;}
/* 输出链表 */void outlist(SLIST* h){ SLIST* p; p = h->next; if (p == NULL) printf("\nThe list is NULL!\n"); else { printf("\nHead"); do { printf("->%d", p->data); p = p->next; } while (p != NULL); printf("->End\n"); }}
void main(){ SLIST* head; int x; /* 初始链表数据已递增有序 */ int a[N] = { 11, 12, 15, 18, 19, 22, 25, 29 }; head = creatlist(a); printf("\nThe list before inserting:\n"); outlist(head); printf("\nEnter a number : "); scanf("%d", &x); fun(head, x); /* 插入x */ printf("\nThe list after inserting:\n"); outlist(head);}关键知识点
- 带头结点的链表:头结点不存储数据,
h->next才是第一个数据节点,简化插入/删除的边界处理。 typedef定义链表类型:typedef struct list { int data; struct list* next; } SLIST;让后续代码更简洁。- 动态内存分配:
malloc(sizeof(SLIST))返回void*,需强制转换,使用后应free释放。 - 有序插入的双指针技巧:
q跟随p的前驱,便于在q和p之间插入新节点。
三、链表逆序:带头结点单向链表逆置
题目说明
fun 函数的功能是将带头结点的单向链表逆置。若原链表中从头至尾数据域依次为 2, 4, 6, 8, 10,逆置后从头至尾结点数据域依次为 10, 8, 6, 4, 2。
解题思路
- 经典的”头插法”逆置:将原链表节点逐个摘下,插入到头结点之后。
- 使用三个指针
p、q、r:p指向当前待处理节点(初始为第一个数据节点h->next)。q指向p的下一个节点。r用于暂存q的下一个节点,防止断链。
- 先将
p->next = NULL(原第一个节点变为新链表的尾节点)。 - 循环中:
r = q->next(保存下一节点),q->next = p(将q指向前面的p),然后p = q; q = r同步前移。 - 循环结束后,
p指向原链表的最后一个节点,令h->next = p完成逆置。
完整代码
//fun函数的功能是将带头结点的单向链表逆置,若原链表中从头至尾//数据域依次为2,4,6,8,10,逆置后,从头至尾结点数据域依次为10,8,6,4,2.#include <stdio.h>#include <stdlib.h>#define N 5
/* 定义链表节点类型 */typedef struct node{ int data; struct node* next;} NODE;
/* 链表逆置函数 */void fun(NODE* h){ NODE* p, * q, * r; /**********found**********/ p = h->next; /* p指向第一个数据节点 */ /**********found**********/ if (p == NULL) return; /* 空链表直接返回 */ q = p->next; p->next = NULL; /* 原第一个节点成为新链表的尾节点,next置空 */ while (q) { r = q->next; /* 暂存q的下一个节点,防止断链 */ q->next = p; /* q指向前一个节点p,实现反转 */ /**********found**********/ p = q; /* p后移 */ q = r; /* q后移 */ } h->next = p; /* 头结点指向新的第一个节点(原尾节点) */}
/* 创建带头结点的单向链表 */NODE* creatlist(int a[]){ NODE* h, * p, * q; int i; h = (NODE*)malloc(sizeof(NODE)); h->next = NULL; for (i = 0; i < N; i++)
{ q = (NODE*)malloc(sizeof(NODE)); q->data = a[i]; q->next = NULL; if (h->next == NULL) h->next = p = q; else { p->next = q; p = q; } } return h;}
/* 输出链表 */void outlist(NODE* h){ NODE* p; p = h->next; if (p == NULL) printf("The list is NULL!\n"); else { printf("\nHead "); do { printf("->%d", p->data); p = p->next; } while (p != NULL); printf("->End\n"); }}
void main(){ NODE* head; int a[N] = { 2, 4, 6, 8, 10 }; head = creatlist(a); printf("\nThe original list:\n"); outlist(head); fun(head); printf("\nThe list after inverting :\n"); outlist(head);}关键知识点
- 三指针逆置法:
p、q、r配合,r用于保存下一节点防止断链,是链表逆置的标准写法。 - 头插法思想:本质上是把每个节点依次”摘下”插到头部,时间复杂度 O(n),空间复杂度 O(1)。
- 空链表判断:
if (p == NULL) return;防止对空链表操作导致崩溃。 - 头结点的作用:逆置后只需修改
h->next即可,无需返回新头指针。
四、链表查找:查找值为 ch 的节点序号
题目说明
函数 fun 的功能是:在带头结点的单向链表中,查找数据域中值为 ch 的结点。找到后通过函数值返回该节点在链表中所处的顺序号;若不存在值为 ch 的结点,函数返回 0 值。
注:文件名为”求最大值”,但实际代码功能是按值查找节点位置,以下按代码实际功能讲解。
解题思路
- 从第一个数据节点
p = h->next开始遍历。 - 使用计数器
n,每访问一个节点n++,记录当前节点是第几个。 - 若
p->data == ch,立即返回当前n值(顺序号从 1 开始)。 - 否则
p = p->next继续向后查找。 - 若遍历完整个链表仍未找到,返回 0。
完整代码
//函数fun的功能是:在带头结点的单向链表中,查找数据域中//值为ch的结点。找到后通过函数值返回该节点在链表中所处的顺序号;//若不存在值为ch的结点,函数返回0值。#include <stdio.h>#include <stdlib.h>#define N 8
typedef struct list{ int data; struct list* next;} SLIST;SLIST* creatlist(char*);void outlist(SLIST*);
/* 查找值为ch的节点,返回顺序号;未找到返回0 */int fun(SLIST* h, char ch){ SLIST* p; int n = 0; p = h->next; /**********found**********/ while (p != NULL) /* 遍历链表直到末尾 */ { n++; /* 节点序号加1 */ /**********found**********/ if (p->data == ch) /* 找到匹配的节点 */ return n; /* 返回当前序号 */ else p = p->next; /* 否则继续后移 */ } return 0; /* 未找到返回0 */}
void main(){ SLIST* head; int k; char ch; char a[N] = { 'm', 'p', 'g', 'a', 'w', 'x', 'r', 'd' }; head = creatlist(a); outlist(head); printf("Enter a letter:"); scanf("%c", &ch); /**********found**********/ k = fun(head, ch); /* 接收查找结果 */ if (k == 0) printf("\nNot found!\n"); else printf("The sequence number is : %d\n", k);}
/* 创建带头结点的单向链表(数据为字符) */SLIST* creatlist(char* a){ SLIST* h, * p, * q; int i; h = p = (SLIST*)malloc(sizeof(SLIST)); for (i = 0; i < N; i++) { q = (SLIST*)malloc(sizeof(SLIST)); q->data = a[i]; p->next = q; p = q; } p->next = 0; return h;}
/* 输出链表(字符型) */void outlist(SLIST* h){ SLIST* p; p = h->next; if (p == NULL) printf("\nThe list is NULL!\n"); else { printf("\nHead"); do { printf("->%c", p->data); p = p->next; } while (p != NULL); printf("->End\n"); }}关键知识点
- 链表遍历模板:
p = h->next; while (p != NULL) { ...; p = p->next; }是链表遍历的标准模式。 - 顺序号计数:从 1 开始计数(
n++在判断前),未找到返回 0 以区分”找到第 0 个”和”未找到”。 - 字符型链表:
data为char类型,输出用%c,比较直接用==。 - 函数返回值传递信息:用返回值同时表达”是否找到”和”位置”两种信息。
五、链表删除相同节点:保留递增链表中的唯一值
题目说明
已建立一个带头结点的单向链表,链表中的各个节点按数据域递增有序连接。函数 fun 的功能是:删除链表中数据域值相同的结点,使之只保留一个。
解题思路
- 由于链表已递增有序,相同值的节点必然相邻。
- 使用双指针
p和q:p指向当前保留的节点,q始终指向p的下一个节点。 - 比较
p->data和q->data:- 若相等,说明
q是重复节点:p->next = q->next跳过q,free(q)释放内存,然后q = p->next取下一个待比较节点。 - 若不等,说明
q不是p的重复:p = q后移,q = q->next后移。
- 若相等,说明
- 循环直到
q == NULL。
完整代码
//已建立一个带头结点的单向链表,链表中的各个节点按数据域//递增有序连接。函数fun的功能是:删除链表中数据域值相同的结点,//使之只保留一个。#include <stdio.h>#include <stdlib.h>#define N 8
typedef struct list{ int data; struct list* next;} SLIST;
/* 删除递增有序链表中数据域相同的重复节点 */void fun(SLIST* h){ SLIST* p, * q; p = h->next; /* p指向第一个数据节点 */ if (p != NULL) /* 链表非空才处理 */ { q = p->next; /* q指向p的下一个节点 */ while (q != NULL) { if (p->data == q->data) /* 发现重复节点 */ { p->next = q->next; /* 跳过q节点 */ /**********found**********/ free(q); /* 释放q节点内存 */ /**********found**********/ q = p->next; /* q指向新的下一个节点,继续与p比较 */ } else /* 不重复,p和q同步后移 */ { p = q; /**********found**********/ q = q->next; } } }}
/* 创建带头结点的单向链表 */SLIST* creatlist(int* a){ SLIST* h, * p, * q; int i; h = p = (SLIST*)malloc(sizeof(SLIST)); for (i = 0; i < N; i++) { q = (SLIST*)malloc(sizeof(SLIST)); q->data = a[i]; p->next = q; p = q; } p->next = 0; return h;}
/* 输出链表 */void outlist(SLIST* h){ SLIST* p; p = h->next; if (p == NULL) printf("\nThe list is NULL!\n"); else { printf("\nHead"); do { printf("->%d", p->data); p = p->next; } while (p != NULL); printf("->End\n"); }}
void main(){ SLIST* head; /* 注意:含重复数据2,2和4,4,4 */ int a[N] = { 1, 2, 2, 3, 4, 4, 4, 5 }; head = creatlist(a); printf("\nThe list before deleting :\n"); outlist(head); fun(head); printf("\nThe list after deleting :\n"); outlist(head);}关键知识点
- 利用有序性简化去重:有序链表中重复节点必相邻,只需一次遍历 O(n) 即可去重;无序链表去重需 O(n²) 或借助哈希。
- 删除节点的三步骤:① 跳过节点(修改前驱的
next);②free释放内存;③ 更新指针指向新的下一节点。 free的重要性:删除节点后必须free,否则造成内存泄漏。- 双指针遍历:
p和q一前一后,是链表操作的经典模式。
六、结构体实践任务
任务 1-1:计算学生 8 门课的平均成绩
题目说明
定义学生结构体 STREC,包含学号 num[10]、8 门课成绩 s[N] 和平均成绩 ave。编写函数 fun,计算该学生 8 门课的平均成绩并存入 ave 字段。
解题思路
- 结构体成员
s[N]存储 8 门课成绩,ave存储平均分。 - 函数接收结构体指针
STREC *a,先累加a->s[i]到a->ave,最后除以N得到平均值。 - 注意初始化
a->ave = 0.0,避免累加随机值。
完整代码
/*作者:wyl时间:2026-07-07 08:19:42
输出:GA00585.5 76.0 69.5 85.0 91.0 72.0 64.5 87.5 78.875*/#include<stdio.h>#define N 8
/* 学生结构体:学号、8门课成绩、平均分 */typedef struct stu{ char num[10]; //学号 double s[N]; //8门课成绩 double ave; //平均成绩}STREC;
/* 计算平均成绩,通过指针修改结构体成员 */void fun(STREC *a){ int i=0; a->ave=0.0; /* 初始化累加和为0 */ for(i=0;i<N;i++) { a->ave+=a->s[i]; /* 累加8门课成绩 */ } a->ave/=N; /* 求平均值 */}
int main(){ /* 初始化一个学生:学号GA005,8门课成绩 */ STREC s ={"GA005", 85.5,76,69.5, 85,91,72,64.5,87.5}; int i; fun(&s); /* 传地址,函数内修改ave */ printf("%s \n", s.num); for(i=0;i<N;i++) { printf("%4.1f ", s.s[i]); /* 输出8门课成绩,保留1位小数 */ } printf("%7.3f ", s.ave); /* 输出平均分,保留3位小数 */ return 0;}关键知识点
- 结构体指针访问成员:使用
->运算符,等价于(*a).ave。 - 传地址修改实参:函数形参为
STREC *a,实参传&s,函数内对a->ave的修改直接影响主函数的s。 - 浮点数格式化输出:
%4.1f表示总宽 4 位、1 位小数;%7.3f表示总宽 7 位、3 位小数。
任务 1-2:找出总分最高的学生
题目说明
输入 n 个学生信息(学号、姓名、3 门课成绩),编程找出总分最高的学生,并输出其学号、姓名和总分。如果有多个相同最高分,则输出所有最高分学生的信息。
解题思路
- 定义结构体
STREC,含学号、姓名、3 门成绩、总分sum。 - 读入每个学生数据时,同步计算
sum = score[0]+score[1]+score[2]。 - 编写
findMaxScore函数遍历数组,返回最高总分。 - 再次遍历数组,输出所有
sum == max的学生信息(处理并列最高分的情况)。
完整代码
/*作者:wyl时间:2026-07-07 08:19:42题目:
输入样例:编程找出总分最高的学生,并输出其学号、姓名和总分。如果有多个相同最高分,则输出所有最高分学生的信息。
【输入样例】52109001 HuangJie 78 83 792109002 Liuhaipeng 79 80 772109003 Wangqiang 87 86 762109004 Liangfeng 92 89 792109005 Chengmeng 80 82 75【输出样例】2109004 Liangfeng 260*/#include<stdio.h>#define N 10
/* 学生结构体:学号、姓名、3门成绩、总分 */typedef struct{ char num[10]; //学号 char name[20]; //姓名 int score[3]; //分数 int sum; //总分}STREC;
/* 找最高分的学生总分,返回最高分值 */int findMaxScore(STREC st[], int n){ int i,j; int max=st[0].sum; /* 假设第一个学生总分最高 */ for(i=1;i<n;i++) { if(max<st[i].sum) max=st[i].sum; /* 更新最高分 */ }
return max;}
int main(){ STREC stu[N]; int n=0, max=0; scanf("%d", &n); /* 读入学生人数 */
for(int i=0;i<n;i++) { scanf("%s %s %d %d %d", &stu[i].num, &stu[i].name, &stu[i].score[0], &stu[i].score[1], &stu[i].score[2]); /* 同步计算总分 */ stu[i].sum = stu[i].score[0]+stu[i].score[1]+stu[i].score[2];
}
max=findMaxScore(stu, n); /* 找到最高分 */ /* 输出所有总分等于最高分的学生(处理并列情况) */ for(int i=0;i<n;i++) { if(stu[i].sum==max) printf("%s %s %d", stu[i].num,stu[i].name, stu[i].sum); }
return 0;}关键知识点
- 结构体数组:
STREC stu[N]一次性存储多个学生记录。 - 求最值的通用模式:假设第一个为最值,遍历比较更新。
- 处理并列情况:先求出最值,再遍历一次输出所有等于最值的元素,避免遗漏。
scanf读入字符串:%s遇空格结束,适合读入无空格的学号和姓名。
任务 2:筛选高于等于平均分的学生
题目说明
学生的记录由学号和成绩组成,N 名学生的数据已放入主函数的结构体数组 s 中。编写函数 fun,把高于等于平均分的学生数据放在 b 所指的数组中,高于等于平均分的学生人数通过形参 n 传回,平均分通过函数值返回。
注:原题描述为”高于等于”,但参考答案代码中使用
a[i].s > avg(严格大于),以下以答案代码为准。
解题思路
- 第一遍遍历:累加所有学生成绩,求出平均分
avg。 - 第二遍遍历:将成绩
> avg的学生拷贝到数组b,同时通过(*n)++计数。 - 函数返回平均分
avg。 - 注意指针参数
int *n用于”带回”人数,需要用*n解引用。
完整代码
/** * * 把高于等于平均分的学生数据放在b所指的数组中, * 高于等于平均分的学生人数通过形参n传回, * 平均分通过函数值返回。 * * 输出样例: The 6 student data which is higher than 77.583: GA05 85 GA04 85 GA01 91 GA06 87 GA11 79 GA10 90 */#include <stdio.h>#include <string.h>#define N 12
/* 学生结构体:学号、成绩 */typedef struct{ char num[10]; int s;} STREC;
/* 筛选高于平均分的学生,返回平均分;人数通过指针n带回 */double fun(STREC *a, STREC *b, int *n){ /**********Program**********/ float avg=0.0; /* 第一遍:求总分 */ for(int i=0;i<N;i++) { avg+=a[i].s; } avg/=N; /* 求平均分 */
/* 第二遍:筛选高于平均分的学生 */ for(int i=0, j=0;i<N;i++) { if(a[i].s>avg) /* 严格大于平均分 */ { b[j++]=a[i]; /* 结构体整体赋值拷贝到b */ (*n)++; /* 人数加1,通过指针带回主函数 */ } }
return avg; /* 平均分通过函数值返回 */ /********** End **********/}
void main(){ STREC s[N] = {{"GA05", 85}, {"GA03", 76}, {"GA02", 69}, {"GA04", 85}, {"GA01", 91}, {"GA07", 72}, {"GA08", 64}, {"GA06", 87}, {"GA09", 60}, {"GA11", 79}, {"GA12", 73}, {"GA10", 90}}; STREC h[N]; int i, n; double ave; ave = fun(s, h, &n); /* 传s和h数组,n传地址 */ printf("The %d student data which is higher than %7.3f:\n", n, ave); for (i = 0; i < n; i++) printf("%s %d\n", h[i].num, h[i].s); printf("\n");}关键知识点
- 指针参数带回多个结果:C 语言函数只能返回一个值,需”带回”的额外结果通过指针参数实现(如
int *n)。 - 两遍遍历策略:第一遍求统计量(平均分),第二遍根据统计量筛选,思路清晰不易出错。
- 结构体数组拷贝:
b[j++] = a[i]直接整体赋值,等价于memcpy逐字节拷贝。 - 函数返回值与指针参数的配合:平均分用
return,人数用*n,是 C 语言常见的”多结果返回”模式。
任务 3:按分数降序排列学生记录
题目说明
学生的记录由学号和成绩组成,N 名学生的数据已放入主函数中的结构体数组 s 中。请编写函数 fun,其功能是:按分数降序排列学生的记录,高分在前,低分在后。
解题思路
- 采用冒泡排序:相邻元素两两比较,若前者分数小于后者则交换。
- 外层循环
i从 0 到N-2,内层循环j从 0 到N-i-2。 - 比较条件
a[j].s < a[j+1].s:前小后大则交换,实现降序(大的前移)。 - 结构体整体交换
temp = a[j]; a[j] = a[j+1]; a[j+1] = temp;。
完整代码
/** * 学生的记录由学号和成绩组成,N 名学生的数据已放入主函数中的结构体数组 s 中。 请编写函数 fun,其功能是:按分数降序排列学生的记录,高分在前,低分在后。
The data after sorted : GA001 91 GA013 91 GA014 91 GA006 87 GA005 85 GA004 85 GA015 85 GA003 76 GA007 72 GA016 72 GA002 69 GA011 66 GA008 64 GA012 64 GA017 64 GA018 64 */#include <stdio.h>#define N 16typedef struct{ char num[10]; int s;} STREC;
/* 冒泡排序:按分数降序排列 */void fun(STREC a[]){ /**********Program**********/ int i,j; STREC temp; for(i=0;i<N-1;i++) { for(j=0;j<N-i-1;j++) { if(a[j].s<a[j+1].s) /* 前者分数小于后者,交换使大的前移 */ { temp=a[j]; a[j]=a[j+1]; a[j+1]=temp; } /********** End **********/ } }}
void main(){ STREC s[N] = {{"GA005", 85}, {"GA003", 76}, {"GA002", 69}, {"GA004", 85}, {"GA001", 91}, {"GA007", 72}, {"GA008", 64}, {"GA006", 87}, {"GA015", 85}, {"GA013", 91}, {"GA012", 64}, {"GA014", 91}, {"GA011", 66}, {"GA017", 64}, {"GA018", 64}, {"GA016", 72}}; int i; fun(s); printf("The data after sorted :\n"); for (i = 0; i < N; i++) { if ((i) % 4 == 0) printf("\n"); /* 每4个换行 */ printf("%s %4d ", s[i].num, s[i].s); } printf("\n");}关键知识点
- 冒泡排序:相邻元素两两比较交换,每轮把最小(或最大)元素”沉”到末尾。时间复杂度 O(n²),稳定排序。
- 降序 vs 升序:降序用
a[j] < a[j+1](前小后大则交换),升序用a[j] > a[j+1]。 - 冒泡排序内层循环边界:
j < N-i-1,因为每轮结束后末尾i个元素已就位,无需再比较。 - 格式化输出:
%4d表示占 4 位右对齐,if (i % 4 == 0) printf("\n")实现每行 4 个。
任务 4:动态链表的创建、插入、删除
题目说明
实现动态链表的完整操作:创建链表、按值有序插入节点、删除指定值节点、遍历输出、销毁整个链表。
解题思路
- 创建链表
createLink:遍历数组,逐个malloc新节点,尾插法链接。需维护head(头指针)和qr(尾指针)。 - 插入节点
insertNode:按数据值有序插入(升序)。需处理三种情况:- 空链表:新节点直接作为头节点。
- 插入到表头:新节点的
next指向原头节点,更新头指针。 - 插入到中间或末尾:找到合适位置后插入。
- 删除节点
DeleteNode:遍历找到目标节点,分”删除头节点”和”删除中间/末尾节点”两种情况处理。删除后free释放内存。 - 销毁链表
DeleteLink:遍历逐个free所有节点,防止内存泄漏。
完整代码
/*创建动态链表*/#include<stdio.h>#include<stdlib.h>#define N 5typedef struct Node{ int data; struct Node * next;}NODE;
/* 创建链表:尾插法,返回头指针 */NODE* createLink(int x[], int n){ int i=0; NODE * p=NULL, *head=NULL, * qr=NULL;
for(i=0; i<n; i++) { p=(NODE *)malloc(sizeof(NODE)); /* 申请新节点 */ p->data=x[i]; p->next=NULL; if(head==NULL) /* 第一个节点,设为头 */ { head=p; qr=p; } else /* 后续节点,尾插 */ { qr->next = p; qr=qr->next; }
} return head;}
/* 有序插入节点(升序),返回头指针 */NODE * insertNode(NODE* head,int data){ NODE * pr=head, * temp=NULL; NODE * p =(NODE *)malloc(sizeof(NODE)); if (p==NULL) { printf("No enough memory!\n"); exit(0); } p->next =NULL; p->data=data; if (head==NULL) /* 空链表,新节点为头 */ { head=p; } else { /* 寻找插入位置:pr->data < data 时继续后移 */ while(pr->data <data && pr->next !=NULL) { temp = pr; /* temp记录pr的前驱 */ pr = pr->next; }//找到数据 if( pr->data >= data) /* 插入到pr之前 */ { if(pr==head) /* 插入到表头 */ { p->next = head; head=p; } else /* 插入到中间 */ { pr=temp; p->next = pr->next; pr->next =p;
} } else /* 插入到末尾 */ { pr->next=p; } } return head;}
/* 删除指定data的结点,返回head */NODE * DeleteNode(NODE *head,int data){ NODE * p=head, *pr=head; if(head==NULL) return head; else { /* 寻找待删除节点 */ while(p->data !=data && p->next !=NULL) { pr = p; /* pr记录p的前驱 */ p = p->next; }//找到数据 if(data==p->data) /* 找到目标节点 */ { if(p==head) /* 删除头节点 */ head=p->next; else /* 删除中间/末尾节点 */ pr->next=p->next; free(p); /* 释放内存 */ } else printf("Not found\n"); } return head;}
/* 遍历输出链表 */void printNode(NODE *head){ NODE *p;
for(p=head;p!=NULL;p=p->next) { printf("%d,", p->data); } printf("\n");}
/* 销毁整个链表,释放所有节点内存 */void DeleteLink(NODE *head){ NODE * p=head, *pr=NULL; while(p) { pr=p; p=p->next; free(pr); /* 逐个释放 */ }}int main(){ NODE *head, *p; int dataArry[5]={1,3,5,7,9}; head = createLink(dataArry, N); /* 创建:1,3,5,7,9 */ printNode(head);
head = insertNode(head, 10); /* 插入10:1,3,5,7,9,10 */ printNode(head);
head = DeleteNode(head, 3); /* 删除3:1,5,7,9,10 */ printNode(head); return 0;}关键知识点
- 不带头结点的链表:与前面例子不同,此链表无头结点,
head直接指向第一个数据节点,因此插入/删除头节点时需特殊处理并更新head。 - 尾插法建表:维护尾指针
qr,每次新节点链接到qr->next,然后qr后移。 - 有序插入的边界处理:需分别考虑空表、表头插入、中间插入、表尾插入四种情况。
- 删除节点的边界处理:需分别考虑删除头节点和删除中间/末尾节点。
- 内存管理:
malloc申请的内存必须free释放;DeleteLink销毁整个链表防止内存泄漏。 - 函数返回头指针:插入/删除可能改变头指针,因此函数返回新的
head,调用方需接收返回值。
七、课前测:结构体数组
课前测 1:职工实发工资计算
题目说明
输入一个整数 n(3 ≤ n ≤ 10),然后输入 n 个职工的信息(姓名、基本工资、津贴、奖金、支出),要求计算并输出每位职工的实发工资。实发工资 = 基本工资 + 津贴 + 奖金 - 支出。
解题思路
- 定义结构体
struct emp,包含姓名name[10]、基本工资jbgz、津贴jt、奖金jj、支出zc。 - 定义结构体数组
s[10],循环读入n个职工数据。 - 循环计算并输出每个职工的实发工资:
jbgz + jt + jj - zc。
注意:原代码中实发工资公式写为
s[i].jbgz + s[i].jt + s[i].jt - s[i].zc,津贴jt被加了两次,漏加了奖金jj。这看起来是一个 bug,但由于期望输出与该公式吻合,可能是题目本身的设计。正确公式应为jbgz + jt + jj - zc。下方代码保留原写法,并在注释中标注。
完整代码
/*输入一个整数n(3<=n<=10),然后输入n个职工的信息要求计算每位职工的实发工资,实发工资=基本工资+津贴+奖金-支出
输入:n=3Zhao 3450 2500 3000 895Qian 3680 3000 4000 1068Wang 4350 3500 5000 1275
输出:Zhao:7555.00Qian:8612.00Wang:10075.00*/
#include <stdio.h>int main (void ){ /* 定义职工结构体 */ struct emp{ char name[10]; double jbgz, jt, jj, zc; /* 基本工资、津贴、奖金、支出 */ }; struct emp s[10]; int i, n;
printf("n="); scanf("%d", &n); /* 读入n个职工数据 */ for (i = 0; i < n; i++){ scanf("%s%lf%lf%lf%lf", s[i].name, &s[i].jbgz, &s[i].jt, &s[i].jj, &s[i].zc); } /* 计算并输出实发工资 */ for (i = 0; i < n; i++){ /* 计算时设置断点调试 */ /* 注意:原代码为 jbgz+jt+jt-zc(jt加两次),与期望输出吻合; 按题目描述正确公式应为 jbgz+jt+jj-zc,此处保留原写法 */ printf ("%s:%.2f\n", s[i].name, s[i].jbgz+s[i].jt+ s[i].jt -s[i].zc); }
return 0;}关键知识点
- 结构体定义在函数内部:
struct emp定义在main函数内,作用域局限于该函数。 scanf读入double:必须用%lf,不能用%f(%f用于float)。printf输出double:用%f或%.2f(保留 2 位小数)即可,double和float在printf中都用%f。- 数组名作为
scanf参数:s[i].name是数组名,本身就是地址,无需加&;而&s[i].jbgz等普通变量需加&。
课前测 2:找出总分最高的学生
题目说明
建立一个有 n(3 ≤ n ≤ 10)个学生成绩的结构记录,包括学号、姓名和 3 门成绩,输出总分最高学生的姓名和总分。
解题思路
- 定义结构体
struct students,含学号number、姓名name[20]、3 门成绩score[3]、总分sum。 - 循环读入
n个学生数据,读入时同步累加计算sum。 - 遍历数组找总分最大的学生,用
index记录其下标。 - 输出
student[index]的姓名和总分。
完整代码
/*找出总分最高的学生:建立一个有n(3<=n<=10)个学生成绩的结构记录,包括学号、姓名和3门成绩,输出总分最高学生的姓名和总分。
输入:n=51 Huang 78 83 752 Wang 76 80 773 Shen 87 83 764 Zhang 92 88 785 Liu 80 82 75
输出:Zhang, 258*/#include <stdio.h>int main (void){ int i, index, j, n, max=0; /* 定义学生结构体 */ struct students{ int number; char name[20]; int score[3]; int sum; }student[10];
printf("n="); scanf("%d", &n); /* 读入n个学生数据,同步计算总分 */ for (i = 0; i < n; i++){ scanf("%d%s", &student[i].number, student[i].name); student[i].sum=0; for(j = 0; j < 3; j++){ scanf("%d", &student[i].score[j]); student[i].sum += student[i].score[j]; /* 累加3门成绩 */ } } /* 查找总分最高的学生 */ index = 0; max = student[0].sum; /* 假设第一个总分最高 */ for(i = 1; i < n; i++){ if(max < student[i].sum) { index = i; /* 更新最高分学生下标 */ max=student[i].sum; } } /* 输出最高分学生的姓名和总分 */ printf("%s, %d\n", student[index].name, student[index].sum);
return 0;}关键知识点
- 结构体数组定义与变量同时声明:
struct students {...} student[10];在定义类型的同时声明数组变量。 - 边读入边计算:读入成绩时同步累加
sum,避免再写一个循环。 - 记录下标而非记录整个结构体:用
index记录最高分学生的下标,比拷贝整个结构体更高效。 - 求最值的标准模式:假设第一个为最值,遍历比较更新,与任务 1-2 的
findMaxScore函数思路一致。
总结
本篇涵盖了结构体与链表的核心操作:
| 知识点 | 涉及章节 | 核心技巧 |
|---|---|---|
| 结构体定义与初始化 | 填空题、所有实践任务 | struct、typedef、嵌套初始化 |
| 结构体数组排序 | 填空题、任务 3 | 选择排序、冒泡排序、strcmp |
| 结构体指针与传参 | 任务 1-1、任务 2 | -> 运算符、传地址修改实参 |
| 链表创建 | 链表创建、任务 4 | 头插法、尾插法、头结点 |
| 链表遍历与查找 | 链表查找 | while(p!=NULL) 模板 |
| 链表插入 | 链表创建、任务 4 | 双指针、有序插入、边界处理 |
| 链表删除 | 链表删除相同节点、任务 4 | free 释放、双指针 |
| 链表逆置 | 链表逆序 | 三指针法、头插法思想 |
| 内存管理 | 所有链表章节 | malloc/free 配对、防内存泄漏 |
掌握这些基础操作后,可以进一步学习双向链表、循环链表、链表合并、链表环检测等进阶内容。
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时












