完成比完美更重要
实验7 结构与联合实验
7.1实验目的
1.通过实验,熟悉和掌握结构的说明和引用、结构的指针、结构数组、以及函数中使用结构的方法。
2.通过实验,掌握动态储存分配函数的用法,掌握自引用结构,单向链表的创建、遍历、结点的增删、查找等操作。
3.了解字段结构和联合的用法。
7.2实验题目及要求
1.表达式求值的程序验证题
设有说明:
char u[]="UVWXYZ";
char v[]="xyz";
struct T{
int x;
char c;
char *t;
}a[]={{11,ˊAˊ,u},{100, ˊBˊ,v}},*p=a;
请先自己计算下面表达式的值,然后通过编程计算来加以验证。(各表达式相互无关)
序号 | 表达式 | 计算值 | 验证值 |
1 | (++p)->x | ||
2 | p++,p->c | ||
3 | *p++->t,*p->t | ||
4 | *(++p)->t | ||
5 | *++p->t | ||
6 | ++*p->t |
解答:
- 计算值从上至下依次为:
100
B
U,x
x
V
V
- 用以验证计算值的源程序清单:
- #include<stdio.h>
- #include<string.h>
- structT{
- intx;
- charc;
- char* t;
- };
- charu_data[] ="UVWXYZ";
- charv_data[] ="xyz";
- constcharu_backup[] ="UVWXYZ";
- constcharv_backup[] ="xyz";
- structTa_data[] = {
- {11,'A', u_data},
- {100,'B', v_data}
- };
- structT* p = a_data;
- voidreset_p() {
- p = a_data;
- memcpy(u_data, u_backup,sizeof(u_backup));
- memcpy(v_data, v_backup,sizeof(v_backup));
- a_data[0].t = u_data;
- a_data[1].t = v_data;
- }
- intmain() {
- intval_int;
- charval_char;
- charval_char1, val_char2;
- printf("验证值:\n");
- // 1. (++p)->x
- reset_p();
- val_int = (++p)->x;
- printf("1: %d\n", val_int);
- // 2. p++, p->c
- reset_p();
- val_char = (p++, p->c);
- printf("2: %c\n", val_char);
- // 3. *p++->t, *p->t
- reset_p();
- val_char1 = *p++->t;
- val_char2 = *p->t;
- printf("3: %c, %c\n", val_char1, val_char2);
- // 4. *(++p)->t
- reset_p();
- val_char = *(++p)->t;
- printf("4: %c\n", val_char);
- // 5. *++p->t
- reset_p();
- val_char = *++p->t;
- printf("5: %c\n", val_char);
- // 6. ++*p->t
- reset_p();
- val_char = ++ * p->t;
- printf("6: %c\n", val_char);
- return0;
- }
(3)验证程序的运行截图:
图7-1程序验证题对应程序的运行截图
2.源程序修改替换题
给定一批整数,以0作为结束标志且不作为结点,将其建成一个先进先出的链表,先进先出链表的指头指针始终指向最先创建的结点(链头),先建结点指向后建结点,后建结点始终是尾结点。
- 源程序中存在什么样的错误(先观察执行结果)?对程序进行修改、调试,使之能够正确完成指定任务。
源程序如下:
#include "stdio.h"
#include "stdlib.h"
struct s_list{
int data; /* 数据域 */
struct s_list *next; /* 指针域 */
} ;
void create_list (struct s_list *headp,int *p);
void main(void)
{
struct s_list *head=NULL,*p;
int s[]={1,2,3,4,5,6,7,8,0}; /* 0为结束标记 */
create_list(head,s); /* 创建新链表 */
p=head; /*遍历指针p指向链头 */
while(p){
printf("%d\t",p->data); /* 输出数据域的值 */
p=p->next; /*遍历指针p指向下一结点 */
}
printf("\n");
}
void create_list(struct s_list *headp,int *p)
{
struct s_list * loc_head=NULL,*tail;
if(p[0]==0) /* 相当于*p==0 */
;
else { /* loc_head指向动态分配的第一个结点 */
loc_head=(struct s_list *)malloc(sizeof(struct s_list));
loc_head->data=*p++; /* 对数据域赋值 */
tail=loc_head; /* tail指向第一个结点 */
while(*p){ /* tail所指结点的指针域指向动态创建的结点 */
tail->next=(struct s_list *)malloc(sizeof(struct s_list));
tail=tail->next; /* tail指向新创建的结点 */
tail->data=*p++; /* 向新创建的结点的数据域赋值 */
}
tail->next=NULL; /* 对指针域赋NULL值 */
}
headp=loc_head; /* 使头指针headp指向新创建的链表 */
}
解答:
- 错误修改:
- main中对notes的调用错误
- notes的声明和定义错误,应使用二级指针
- 修改后的源程序清单:
#include <stdio.h>
#include <stdlib.h>
struct s_list {
int data;
struct s_list *next;
};
// 接受二级指针:struct s_list **
void create_list(struct s_list **headpp, int *p);
void main(void)
{
struct s_list *head = NULL, *p;
int s[] = {1, 2, 3, 4, 5, 6, 7, 8, 0};
// 传递 head 的地址
create_list(&head, s);
p = head;
printf("--- FIFO 链表输出 ---\n");
while(p) {
printf("%d\t", p->data);
p = p->next;
}
printf("\n");
}
// 修改后的 create_list 函数(FIFO)
void create_list(struct s_list **headpp, int *p)
{
struct s_list *loc_head = NULL, *tail;
if (*p == 0)
; // 数组为空,loc_head 保持 NULL
else {
// 1. 创建第一个结点
loc_head = (struct s_list *)malloc(sizeof(struct s_list));
if (loc_head == NULL) return; // 检查内存分配
loc_head->data = *p++;
tail = loc_head;
// 2. 依次创建后续结点 (尾部插入)
while (*p) {
tail->next = (struct s_list *)malloc(sizeof(struct s_list));
if (tail->next == NULL) break; // 检查内存分配
tail = tail->next;
tail->data = *p++;
}
tail->next = NULL;
}
// 3. 关键修正:通过二级指针修改 main 函数中 head 的值
*headpp = loc_head;
}
- 对应程序的运行截图:
图7-2程序修改后的运行截图
- 修改替换create_list函数,将其建成一个后进先出的链表,后进先出链表的头指针始终指向最后创建的结点(链头),后建结点指向先建结点,先建结点始终是尾结点。
解答:
- 替换后的源程序清单:
// 修改后的 create_list 函数(LIFO - 头部插入)
void create_list_lifo(struct s_list **headpp, int *p)
{
struct s_list *new_node = NULL; // 用于指向每次新创建的结点
// 初始化 headpp 指向的头指针为 NULL
*headpp = NULL;
while (*p != 0) {
// 1. 创建新结点 (new_node)
new_node = (struct s_list *)malloc(sizeof(struct s_list));
if (new_node == NULL) {
printf("内存分配失败!\n");
return;
}
// 2. 填充数据域
new_node->data = *p;
p++; // 移动到下一个整数
// 3. 核心 LIFO 逻辑:头部插入
// 新结点的 next 指向当前的链头 (*headpp)
new_node->next = *headpp;
// 4. 更新链头:将新结点设置为新的链头
*headpp = new_node;
}
// 循环结束后,*headpp 就是指向最后创建的结点 (链头)
}
- 对应程序的运行截图:
图7-3程序替换题的运行截图
3程序设计
以下(1)至(3)题对应Educoder 教学平台“C语言实验”课程,实验7,第17关实验7-1、第18关实验7-2,以及第19关实验7-3。
(1)本关任务:用单向链表建立一张班级成绩单,包括每个学生的学号、姓名、英语、高等数学、普通物理、C语言程序设计四门课程的成绩。用菜单实现下列功能:
① 输入每个学生的各项信息。
② 输出每个学生的各项信息。
③ 修改指定学生的指定数据项的内容。
④ 统计每个同学的平均成绩(保留2位小数)。
⑤ 输出各位同学的学号、姓名、四门课程的总成绩和平均成绩。
解答:
- 解题思路:
数据结构设计
定义 struct student 结构体,包含学号、姓名、四门课程成绩及平均成绩字段,并用 next 指针构成链表。
建立链表
动态申请内存(malloc),按输入顺序将新节点尾插到链表中,构成学生信息链表。
遍历与输出
从头指针开始遍历链表即可输出所有学生信息。
修改功能
根据学号查找节点,选择要修改的成绩字段并更新其值。
计算平均成绩与输出
遍历链表计算每个学生的平均分并存入节点,再次遍历输出总分与平均分。
- 源程序清单:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct student {
char id[20];
char name[20];
float eng, math, phy, cprog;
float avg;
struct student *next;
} Stu;
Stu* create(); //① 输入
void print(Stu *head); //② 输出
void modify(Stu *head);//③ 修改
void calcAvg(Stu *head);//④ 统计平均
void printAvg(Stu *head);//⑤ 输出总分和平均分
int menu();
int main() {
Stu *head = NULL;
int choice;
while(1) {
choice = menu();
switch(choice) {
case 1: head = create(); break;
case 2: print(head); break;
case 3: modify(head); break;
case 4: calcAvg(head); break;
case 5: printAvg(head); break;
case 0: exit(0);
default: printf("输入错误!\n");
}
}
return 0;
}
int menu() {
printf("\n===== 学生成绩管理系统 =====\n");
printf("1. 输入学生信息\n");
printf("2. 输出学生信息\n");
printf("3. 修改指定学生信息\n");
printf("4. 计算每位学生平均成绩\n");
printf("5. 输出总分与平均分\n");
printf("0. 退出\n");
printf("请选择:");
int c; scanf("%d", &c);
return c;
}
Stu* create() {
Stu *head = NULL, *p, *tail = NULL;
int n;
printf("请输入学生人数: ");
scanf("%d", &n);
for(int i = 0; i < n; i++) {
p = (Stu*)malloc(sizeof(Stu));
printf("输入 学号 姓名 英语 高数 物理 C语言 成绩:\n");
scanf("%s %s %f %f %f %f", p->id, p->name, &p->eng, &p->math, &p->phy, &p->cprog);
p->avg = 0;
p->next = NULL;
if(head == NULL) head = p;
else tail->next = p;
tail = p;
}
return head;
}
void print(Stu *head) {
Stu *p = head;
printf("\n学号\t姓名\t英语\t高数\t物理\tC语言\n");
while(p) {
printf("%s\t%s\t%.2f\t%.2f\t%.2f\t%.2f\n",
p->id, p->name, p->eng, p->math, p->phy, p->cprog);
p = p->next;
}
}
void modify(Stu *head) {
char id[20];
printf("输入要修改的学生学号: ");
scanf("%s", id);
Stu *p = head;
while(p && strcmp(p->id, id) != 0) p = p->next;
if(!p) { printf("未找到该学生!\n"); return; }
printf("选择要修改的科目:1英语 2高数 3物理 4C语言\n");
int c; scanf("%d", &c);
float score;
printf("输入新成绩: "); scanf("%f", &score);
if(c == 1) p->eng = score;
else if(c == 2) p->math = score;
else if(c == 3) p->phy = score;
else if(c == 4) p->cprog = score;
else printf("输入无效\n");
}
void calcAvg(Stu *head) {
Stu *p = head;
while(p) {
p->avg = (p->eng + p->math + p->phy + p->cprog) / 4.0;
p = p->next;
}
printf("平均分计算完成!\n");
}
void printAvg(Stu *head) {
Stu *p = head;
printf("\n学号\t姓名\t总分\t平均分\n");
while(p) {
float sum = p->eng + p->math + p->phy + p->cprog;
printf("%s\t%s\t%.2f\t%.2f\n", p->id, p->name, sum, p->avg);
p = p->next;
}
}
- 测试:
- 各项功能的运行截图:
图7-4程序设计题(1)的运行截图
(2)本关任务:对程序设计题第(1)题的程序,⑥增加按照平均成绩进行升序排序的函数,写出用交换结点数据域的方法升序排序的函数,排序可用选择法或冒泡法。
解答:
- 解题思路:
前提条件
平均成绩已计算完成(任务一的基础)。
排序策略
可使用冒泡法或选择法本质都是比较两个节点的 avg 值。
交换方式
不改变节点之间指针关系,而是直接交换结构体中的数据项(包括姓名、分数等字段)。
- 源程序清单:“
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct student {
char id[20];
char name[20];
float eng, math, phy, cprog;
float avg;
struct student *next;
} Stu;
Stu* create(); //① 输入
void print(Stu *head); //② 输出
void modify(Stu *head);//③ 修改
void calcAvg(Stu *head);//④ 统计平均
void printAvg(Stu *head);//⑤ 输出总分和平均分
void sortDataSwap(Stu *head);
int menu();
int main() {
Stu *head = NULL;
int choice;
while(1) {
choice = menu();
switch(choice) {
case 1: head = create(); break;
case 2: print(head); break;
case 3: modify(head); break;
case 4: calcAvg(head); break;
case 5: printAvg(head); break;
case 6: sortDataSwap(head); break;
case 0: exit(0);
default: printf("输入错误!\n");
}
}
return 0;
}
int menu() {
printf("\n===== 学生成绩管理系统 =====\n");
printf("1. 输入学生信息\n");
printf("2. 输出学生信息\n");
printf("3. 修改指定学生信息\n");
printf("4. 计算每位学生平均成绩\n");
printf("5. 输出总分与平均分\n");
printf("6. 按平均成绩排序(交换数据域)\n");
printf("0. 退出\n");
printf("请选择:");
int c; scanf("%d", &c);
return c;
}
Stu* create() {
Stu *head = NULL, *p, *tail = NULL;
int n;
printf("请输入学生人数: ");
scanf("%d", &n);
for(int i = 0; i < n; i++) {
p = (Stu*)malloc(sizeof(Stu));
printf("输入 学号 姓名 英语 高数 物理 C语言 成绩:\n");
scanf("%s %s %f %f %f %f", p->id, p->name, &p->eng, &p->math, &p->phy, &p->cprog);
p->avg = 0;
p->next = NULL;
if(head == NULL) head = p;
else tail->next = p;
tail = p;
}
return head;
}
void print(Stu *head) {
Stu *p = head;
printf("\n学号\t姓名\t英语\t高数\t物理\tC语言\n");
while(p) {
printf("%s\t%s\t%.2f\t%.2f\t%.2f\t%.2f\n",
p->id, p->name, p->eng, p->math, p->phy, p->cprog);
p = p->next;
}
}
void modify(Stu *head) {
char id[20];
printf("输入要修改的学生学号: ");
scanf("%s", id);
Stu *p = head;
while(p && strcmp(p->id, id) != 0) p = p->next;
if(!p) { printf("未找到该学生!\n"); return; }
printf("选择要修改的科目:1英语 2高数 3物理 4C语言\n");
int c; scanf("%d", &c);
float score;
printf("输入新成绩: "); scanf("%f", &score);
if(c == 1) p->eng = score;
else if(c == 2) p->math = score;
else if(c == 3) p->phy = score;
else if(c == 4) p->cprog = score;
else printf("输入无效\n");
}
void calcAvg(Stu *head) {
Stu *p = head;
while(p) {
p->avg = (p->eng + p->math + p->phy + p->cprog) / 4.0;
p = p->next;
}
printf("平均分计算完成!\n");
}
void printAvg(Stu *head) {
Stu *p = head;
printf("\n学号\t姓名\t总分\t平均分\n");
while(p) {
float sum = p->eng + p->math + p->phy + p->cprog;
printf("%s\t%s\t%.2f\t%.2f\n", p->id, p->name, sum, p->avg);
p = p->next;
}
}
void sortDataSwap(Stu *head) {
Stu *p, *q;
Stu temp;
for(p = head; p && p->next; p = p->next) {
for(q = p->next; q; q = q->next) {
if(p->avg > q->avg) { // 升序
temp = *p; *p = *q; *q = temp;
// 修正 next 指针被交换的问题
Stu *t = p->next;
p->next = q->next;
q->next = t;
}
}
}
printf("已按平均成绩升序排序(数据域交换)!\n");
}
- 测试:
- 各项功能的运行截图:
图7-5程序设计题(2)的运行截图
- 本关任务:对程序设计题第(2)题,进一步写出用交换结点指针域的方法升序排序的函数。
- 解题思路:
指针操作代替数据交换
找出当前循环中平均分最小的节点,用指针操作将其移动到正确位置。
排序方法
通常采用选择排序思想:每轮从剩余链表中找出最小 avg 节点,并将指针调整到指定位置
- 源程序清单:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedefstructstudent{
charid[20];
charname[20];
floateng, math, phy, cprog;
floatavg;
structstudent* next;
}Stu;
Stu* create();//①输入
voidprint(Stu*head);//②输出
voidmodify(Stu*head);//③修改
voidcalcAvg(Stu*head);//④统计平均
voidprintAvg(Stu*head);//⑤输出总分和平均分
voidsortDataSwap(Stu*head);
Stu* sortPointerSwap(Stu*head);
intmenu();
intmain() {
Stu* head =NULL;
intchoice;
while(1) {
choice = menu();
switch(choice) {
case1: head = create();break;
case2: print(head);break;
case3: modify(head);break;
case4: calcAvg(head);break;
case5: printAvg(head);break;
case6: sortDataSwap(head);break;
case7: head = sortPointerSwap(head);break;
case0: exit(0);
default: printf("输入错误!\n");
}
}
return0;
}
intmenu() {
printf("\n=====学生成绩管理系统=====\n");
printf("1.输入学生信息\n");
printf("2.输出学生信息\n");
printf("3.修改指定学生信息\n");
printf("4.计算每位学生平均成绩\n");
printf("5.输出总分与平均分\n");
printf("6.按平均成绩排序(交换数据域)\n");
printf("7.按平均成绩排序(交换指针域)\n");
printf("0.退出\n");
printf("请选择:");
intc; scanf("%d", &c);
returnc;
}
Stu* create() {
Stu* head =NULL, * p, * tail =NULL;
intn;
printf("请输入学生人数: ");
scanf("%d", &n);
for(inti = 0; i < n; i++) {
p = (Stu*)malloc(sizeof(Stu));
printf("输入学号姓名英语高数物理C语言成绩:\n");
scanf("%s %s %f %f %f %f", p->id, p->name, &p->eng, &p->math, &p->phy, &p->cprog);
p->avg = 0;
p->next =NULL;
if(head ==NULL) head = p;
elsetail->next = p;
tail = p;
}
returnhead;
}
voidprint(Stu*head) {
Stu* p =head;
printf("\n学号\t姓名\t英语\t高数\t物理\tC语言\n");
while(p) {
printf("%s\t%s\t%.2f\t%.2f\t%.2f\t%.2f\n",
p->id, p->name, p->eng, p->math, p->phy, p->cprog);
p = p->next;
}
}
voidmodify(Stu*head) {
charid[20];
printf("输入要修改的学生学号: ");
scanf("%s", id);
Stu* p =head;
while(p && strcmp(p->id, id) != 0) p = p->next;
if(!p) { printf("未找到该学生!\n");return; }
printf("选择要修改的科目:1英语2高数3物理4C语言\n");
intc; scanf("%d", &c);
floatscore;
printf("输入新成绩: "); scanf("%f", &score);
if(c == 1) p->eng = score;
elseif(c == 2) p->math = score;
elseif(c == 3) p->phy = score;
elseif(c == 4) p->cprog = score;
elseprintf("输入无效\n");
}
voidcalcAvg(Stu*head) {
Stu* p =head;
while(p) {
p->avg = (p->eng + p->math + p->phy + p->cprog) / 4.0;
p = p->next;
}
printf("平均分计算完成!\n");
}
voidprintAvg(Stu*head) {
Stu* p =head;
printf("\n学号\t姓名\t总分\t平均分\n");
while(p) {
floatsum = p->eng + p->math + p->phy + p->cprog;
printf("%s\t%s\t%.2f\t%.2f\n", p->id, p->name, sum, p->avg);
p = p->next;
}
}
voidsortDataSwap(Stu*head) {
Stu* p, * q;
Stutemp;
for(p =head; p && p->next; p = p->next) {
for(q = p->next; q; q = q->next) {
if(p->avg > q->avg) {//升序
temp = *p; *p = *q; *q = temp;
//修正next指针被交换的问题
Stu* t = p->next;
p->next = q->next;
q->next = t;
}
}
}
printf("已按平均成绩升序排序(数据域交换)!\n");
}
Stu* sortPointerSwap(Stu*head) {
if(!head)returnhead;
Studummy, * p, * q, * minPrev, * prev;
dummy.next =head;
prev = &dummy;
while(prev->next) {
minPrev = prev;
q = prev->next;
while(q->next) {
if(q->next->avg < minPrev->next->avg)
minPrev = q;
q = q->next;
}
if(minPrev != prev) {//交换节点指针
Stu* minNode = minPrev->next;
minPrev->next = minNode->next;
minNode->next = prev->next;
prev->next = minNode;
}
prev = prev->next;
}
printf("已按平均成绩升序排序(指针域交换)!\n");
returndummy.next;
}
- 测试:
- 各项功能的运行截图:
图7-6程序设计题(3)的运行截图
7.3实验小结
通过本次链表成绩管理系统的实现,我不仅掌握了单向链表的建立、遍历和修改等基本操作,还深入理解了结构体在数据组织中的作用;在完成平均成绩计算和排序功能的过程中,我体会到不同排序方法在链表结构中的实现差异,尤其是交换数据域与交换指针域的逻辑思想,使我对链表在动态数据管理中的优势有了更加直观而深刻的认识,也提升了我分析问题和模块化设计程序的能力。
实验7 结构与联合实验
7.1实验目的
1.通过实验,熟悉和掌握结构的说明和引用、结构的指针、结构数组、以及函数中使用结构的方法。
2.通过实验,掌握动态储存分配函数的用法,掌握自引用结构,单向链表的创建、遍历、结点的增删、查找等操作。
3.了解字段结构和联合的用法。
7.2实验题目及要求
1.表达式求值的程序验证题
设有说明:
char u[]="UVWXYZ";
char v[]="xyz";
struct T{
int x;
char c;
char *t;
}a[]={{11,ˊAˊ,u},{100, ˊBˊ,v}},*p=a;
请先自己计算下面表达式的值,然后通过编程计算来加以验证。(各表达式相互无关)
序号 | 表达式 | 计算值 | 验证值 |
1 | (++p)->x | ||
2 | p++,p->c | ||
3 | *p++->t,*p->t | ||
4 | *(++p)->t | ||
5 | *++p->t | ||
6 | ++*p->t |
解答:
- 计算值从上至下依次为:
100
B
U,x
x
V
V
- 用以验证计算值的源程序清单:
- #include<stdio.h>
- #include<string.h>
- structT{
- intx;
- charc;
- char* t;
- };
- charu_data[] ="UVWXYZ";
- charv_data[] ="xyz";
- constcharu_backup[] ="UVWXYZ";
- constcharv_backup[] ="xyz";
- structTa_data[] = {
- {11,'A', u_data},
- {100,'B', v_data}
- };
- structT* p = a_data;
- voidreset_p() {
- p = a_data;
- memcpy(u_data, u_backup,sizeof(u_backup));
- memcpy(v_data, v_backup,sizeof(v_backup));
- a_data[0].t = u_data;
- a_data[1].t = v_data;
- }
- intmain() {
- intval_int;
- charval_char;
- charval_char1, val_char2;
- printf("验证值:\n");
- // 1. (++p)->x
- reset_p();
- val_int = (++p)->x;
- printf("1: %d\n", val_int);
- // 2. p++, p->c
- reset_p();
- val_char = (p++, p->c);
- printf("2: %c\n", val_char);
- // 3. *p++->t, *p->t
- reset_p();
- val_char1 = *p++->t;
- val_char2 = *p->t;
- printf("3: %c, %c\n", val_char1, val_char2);
- // 4. *(++p)->t
- reset_p();
- val_char = *(++p)->t;
- printf("4: %c\n", val_char);
- // 5. *++p->t
- reset_p();
- val_char = *++p->t;
- printf("5: %c\n", val_char);
- // 6. ++*p->t
- reset_p();
- val_char = ++ * p->t;
- printf("6: %c\n", val_char);
- return0;
- }
(3)验证程序的运行截图:
图7-1程序验证题对应程序的运行截图
2.源程序修改替换题
给定一批整数,以0作为结束标志且不作为结点,将其建成一个先进先出的链表,先进先出链表的指头指针始终指向最先创建的结点(链头),先建结点指向后建结点,后建结点始终是尾结点。
- 源程序中存在什么样的错误(先观察执行结果)?对程序进行修改、调试,使之能够正确完成指定任务。
源程序如下:
#include "stdio.h"
#include "stdlib.h"
struct s_list{
int data; /* 数据域 */
struct s_list *next; /* 指针域 */
} ;
void create_list (struct s_list *headp,int *p);
void main(void)
{
struct s_list *head=NULL,*p;
int s[]={1,2,3,4,5,6,7,8,0}; /* 0为结束标记 */
create_list(head,s); /* 创建新链表 */
p=head; /*遍历指针p指向链头 */
while(p){
printf("%d\t",p->data); /* 输出数据域的值 */
p=p->next; /*遍历指针p指向下一结点 */
}
printf("\n");
}
void create_list(struct s_list *headp,int *p)
{
struct s_list * loc_head=NULL,*tail;
if(p[0]==0) /* 相当于*p==0 */
;
else { /* loc_head指向动态分配的第一个结点 */
loc_head=(struct s_list *)malloc(sizeof(struct s_list));
loc_head->data=*p++; /* 对数据域赋值 */
tail=loc_head; /* tail指向第一个结点 */
while(*p){ /* tail所指结点的指针域指向动态创建的结点 */
tail->next=(struct s_list *)malloc(sizeof(struct s_list));
tail=tail->next; /* tail指向新创建的结点 */
tail->data=*p++; /* 向新创建的结点的数据域赋值 */
}
tail->next=NULL; /* 对指针域赋NULL值 */
}
headp=loc_head; /* 使头指针headp指向新创建的链表 */
}
解答:
- 错误修改:
- main中对notes的调用错误
- notes的声明和定义错误,应使用二级指针
- 修改后的源程序清单:
#include <stdio.h>
#include <stdlib.h>
struct s_list {
int data;
struct s_list *next;
};
// 接受二级指针:struct s_list **
void create_list(struct s_list **headpp, int *p);
void main(void)
{
struct s_list *head = NULL, *p;
int s[] = {1, 2, 3, 4, 5, 6, 7, 8, 0};
// 传递 head 的地址
create_list(&head, s);
p = head;
printf("--- FIFO 链表输出 ---\n");
while(p) {
printf("%d\t", p->data);
p = p->next;
}
printf("\n");
}
// 修改后的 create_list 函数(FIFO)
void create_list(struct s_list **headpp, int *p)
{
struct s_list *loc_head = NULL, *tail;
if (*p == 0)
; // 数组为空,loc_head 保持 NULL
else {
// 1. 创建第一个结点
loc_head = (struct s_list *)malloc(sizeof(struct s_list));
if (loc_head == NULL) return; // 检查内存分配
loc_head->data = *p++;
tail = loc_head;
// 2. 依次创建后续结点 (尾部插入)
while (*p) {
tail->next = (struct s_list *)malloc(sizeof(struct s_list));
if (tail->next == NULL) break; // 检查内存分配
tail = tail->next;
tail->data = *p++;
}
tail->next = NULL;
}
// 3. 关键修正:通过二级指针修改 main 函数中 head 的值
*headpp = loc_head;
}
- 对应程序的运行截图:
图7-2程序修改后的运行截图
- 修改替换create_list函数,将其建成一个后进先出的链表,后进先出链表的头指针始终指向最后创建的结点(链头),后建结点指向先建结点,先建结点始终是尾结点。
解答:
- 替换后的源程序清单:
// 修改后的 create_list 函数(LIFO - 头部插入)
void create_list_lifo(struct s_list **headpp, int *p)
{
struct s_list *new_node = NULL; // 用于指向每次新创建的结点
// 初始化 headpp 指向的头指针为 NULL
*headpp = NULL;
while (*p != 0) {
// 1. 创建新结点 (new_node)
new_node = (struct s_list *)malloc(sizeof(struct s_list));
if (new_node == NULL) {
printf("内存分配失败!\n");
return;
}
// 2. 填充数据域
new_node->data = *p;
p++; // 移动到下一个整数
// 3. 核心 LIFO 逻辑:头部插入
// 新结点的 next 指向当前的链头 (*headpp)
new_node->next = *headpp;
// 4. 更新链头:将新结点设置为新的链头
*headpp = new_node;
}
// 循环结束后,*headpp 就是指向最后创建的结点 (链头)
}
- 对应程序的运行截图:
图7-3程序替换题的运行截图
3程序设计
以下(1)至(3)题对应Educoder 教学平台“C语言实验”课程,实验7,第17关实验7-1、第18关实验7-2,以及第19关实验7-3。
(1)本关任务:用单向链表建立一张班级成绩单,包括每个学生的学号、姓名、英语、高等数学、普通物理、C语言程序设计四门课程的成绩。用菜单实现下列功能:
① 输入每个学生的各项信息。
② 输出每个学生的各项信息。
③ 修改指定学生的指定数据项的内容。
④ 统计每个同学的平均成绩(保留2位小数)。
⑤ 输出各位同学的学号、姓名、四门课程的总成绩和平均成绩。
解答:
- 解题思路:
数据结构设计
定义 struct student 结构体,包含学号、姓名、四门课程成绩及平均成绩字段,并用 next 指针构成链表。
建立链表
动态申请内存(malloc),按输入顺序将新节点尾插到链表中,构成学生信息链表。
遍历与输出
从头指针开始遍历链表即可输出所有学生信息。
修改功能
根据学号查找节点,选择要修改的成绩字段并更新其值。
计算平均成绩与输出
遍历链表计算每个学生的平均分并存入节点,再次遍历输出总分与平均分。
- 源程序清单:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct student {
char id[20];
char name[20];
float eng, math, phy, cprog;
float avg;
struct student *next;
} Stu;
Stu* create(); //① 输入
void print(Stu *head); //② 输出
void modify(Stu *head);//③ 修改
void calcAvg(Stu *head);//④ 统计平均
void printAvg(Stu *head);//⑤ 输出总分和平均分
int menu();
int main() {
Stu *head = NULL;
int choice;
while(1) {
choice = menu();
switch(choice) {
case 1: head = create(); break;
case 2: print(head); break;
case 3: modify(head); break;
case 4: calcAvg(head); break;
case 5: printAvg(head); break;
case 0: exit(0);
default: printf("输入错误!\n");
}
}
return 0;
}
int menu() {
printf("\n===== 学生成绩管理系统 =====\n");
printf("1. 输入学生信息\n");
printf("2. 输出学生信息\n");
printf("3. 修改指定学生信息\n");
printf("4. 计算每位学生平均成绩\n");
printf("5. 输出总分与平均分\n");
printf("0. 退出\n");
printf("请选择:");
int c; scanf("%d", &c);
return c;
}
Stu* create() {
Stu *head = NULL, *p, *tail = NULL;
int n;
printf("请输入学生人数: ");
scanf("%d", &n);
for(int i = 0; i < n; i++) {
p = (Stu*)malloc(sizeof(Stu));
printf("输入 学号 姓名 英语 高数 物理 C语言 成绩:\n");
scanf("%s %s %f %f %f %f", p->id, p->name, &p->eng, &p->math, &p->phy, &p->cprog);
p->avg = 0;
p->next = NULL;
if(head == NULL) head = p;
else tail->next = p;
tail = p;
}
return head;
}
void print(Stu *head) {
Stu *p = head;
printf("\n学号\t姓名\t英语\t高数\t物理\tC语言\n");
while(p) {
printf("%s\t%s\t%.2f\t%.2f\t%.2f\t%.2f\n",
p->id, p->name, p->eng, p->math, p->phy, p->cprog);
p = p->next;
}
}
void modify(Stu *head) {
char id[20];
printf("输入要修改的学生学号: ");
scanf("%s", id);
Stu *p = head;
while(p && strcmp(p->id, id) != 0) p = p->next;
if(!p) { printf("未找到该学生!\n"); return; }
printf("选择要修改的科目:1英语 2高数 3物理 4C语言\n");
int c; scanf("%d", &c);
float score;
printf("输入新成绩: "); scanf("%f", &score);
if(c == 1) p->eng = score;
else if(c == 2) p->math = score;
else if(c == 3) p->phy = score;
else if(c == 4) p->cprog = score;
else printf("输入无效\n");
}
void calcAvg(Stu *head) {
Stu *p = head;
while(p) {
p->avg = (p->eng + p->math + p->phy + p->cprog) / 4.0;
p = p->next;
}
printf("平均分计算完成!\n");
}
void printAvg(Stu *head) {
Stu *p = head;
printf("\n学号\t姓名\t总分\t平均分\n");
while(p) {
float sum = p->eng + p->math + p->phy + p->cprog;
printf("%s\t%s\t%.2f\t%.2f\n", p->id, p->name, sum, p->avg);
p = p->next;
}
}
- 测试:
- 各项功能的运行截图:
图7-4程序设计题(1)的运行截图
(2)本关任务:对程序设计题第(1)题的程序,⑥增加按照平均成绩进行升序排序的函数,写出用交换结点数据域的方法升序排序的函数,排序可用选择法或冒泡法。
解答:
- 解题思路:
前提条件
平均成绩已计算完成(任务一的基础)。
排序策略
可使用冒泡法或选择法本质都是比较两个节点的 avg 值。
交换方式
不改变节点之间指针关系,而是直接交换结构体中的数据项(包括姓名、分数等字段)。
- 源程序清单:“
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct student {
char id[20];
char name[20];
float eng, math, phy, cprog;
float avg;
struct student *next;
} Stu;
Stu* create(); //① 输入
void print(Stu *head); //② 输出
void modify(Stu *head);//③ 修改
void calcAvg(Stu *head);//④ 统计平均
void printAvg(Stu *head);//⑤ 输出总分和平均分
void sortDataSwap(Stu *head);
int menu();
int main() {
Stu *head = NULL;
int choice;
while(1) {
choice = menu();
switch(choice) {
case 1: head = create(); break;
case 2: print(head); break;
case 3: modify(head); break;
case 4: calcAvg(head); break;
case 5: printAvg(head); break;
case 6: sortDataSwap(head); break;
case 0: exit(0);
default: printf("输入错误!\n");
}
}
return 0;
}
int menu() {
printf("\n===== 学生成绩管理系统 =====\n");
printf("1. 输入学生信息\n");
printf("2. 输出学生信息\n");
printf("3. 修改指定学生信息\n");
printf("4. 计算每位学生平均成绩\n");
printf("5. 输出总分与平均分\n");
printf("6. 按平均成绩排序(交换数据域)\n");
printf("0. 退出\n");
printf("请选择:");
int c; scanf("%d", &c);
return c;
}
Stu* create() {
Stu *head = NULL, *p, *tail = NULL;
int n;
printf("请输入学生人数: ");
scanf("%d", &n);
for(int i = 0; i < n; i++) {
p = (Stu*)malloc(sizeof(Stu));
printf("输入 学号 姓名 英语 高数 物理 C语言 成绩:\n");
scanf("%s %s %f %f %f %f", p->id, p->name, &p->eng, &p->math, &p->phy, &p->cprog);
p->avg = 0;
p->next = NULL;
if(head == NULL) head = p;
else tail->next = p;
tail = p;
}
return head;
}
void print(Stu *head) {
Stu *p = head;
printf("\n学号\t姓名\t英语\t高数\t物理\tC语言\n");
while(p) {
printf("%s\t%s\t%.2f\t%.2f\t%.2f\t%.2f\n",
p->id, p->name, p->eng, p->math, p->phy, p->cprog);
p = p->next;
}
}
void modify(Stu *head) {
char id[20];
printf("输入要修改的学生学号: ");
scanf("%s", id);
Stu *p = head;
while(p && strcmp(p->id, id) != 0) p = p->next;
if(!p) { printf("未找到该学生!\n"); return; }
printf("选择要修改的科目:1英语 2高数 3物理 4C语言\n");
int c; scanf("%d", &c);
float score;
printf("输入新成绩: "); scanf("%f", &score);
if(c == 1) p->eng = score;
else if(c == 2) p->math = score;
else if(c == 3) p->phy = score;
else if(c == 4) p->cprog = score;
else printf("输入无效\n");
}
void calcAvg(Stu *head) {
Stu *p = head;
while(p) {
p->avg = (p->eng + p->math + p->phy + p->cprog) / 4.0;
p = p->next;
}
printf("平均分计算完成!\n");
}
void printAvg(Stu *head) {
Stu *p = head;
printf("\n学号\t姓名\t总分\t平均分\n");
while(p) {
float sum = p->eng + p->math + p->phy + p->cprog;
printf("%s\t%s\t%.2f\t%.2f\n", p->id, p->name, sum, p->avg);
p = p->next;
}
}
void sortDataSwap(Stu *head) {
Stu *p, *q;
Stu temp;
for(p = head; p && p->next; p = p->next) {
for(q = p->next; q; q = q->next) {
if(p->avg > q->avg) { // 升序
temp = *p; *p = *q; *q = temp;
// 修正 next 指针被交换的问题
Stu *t = p->next;
p->next = q->next;
q->next = t;
}
}
}
printf("已按平均成绩升序排序(数据域交换)!\n");
}
- 测试:
- 各项功能的运行截图:
图7-5程序设计题(2)的运行截图
- 本关任务:对程序设计题第(2)题,进一步写出用交换结点指针域的方法升序排序的函数。
- 解题思路:
指针操作代替数据交换
找出当前循环中平均分最小的节点,用指针操作将其移动到正确位置。
排序方法
通常采用选择排序思想:每轮从剩余链表中找出最小 avg 节点,并将指针调整到指定位置
- 源程序清单:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedefstructstudent{
charid[20];
charname[20];
floateng, math, phy, cprog;
floatavg;
structstudent* next;
}Stu;
Stu* create();//①输入
voidprint(Stu*head);//②输出
voidmodify(Stu*head);//③修改
voidcalcAvg(Stu*head);//④统计平均
voidprintAvg(Stu*head);//⑤输出总分和平均分
voidsortDataSwap(Stu*head);
Stu* sortPointerSwap(Stu*head);
intmenu();
intmain() {
Stu* head =NULL;
intchoice;
while(1) {
choice = menu();
switch(choice) {
case1: head = create();break;
case2: print(head);break;
case3: modify(head);break;
case4: calcAvg(head);break;
case5: printAvg(head);break;
case6: sortDataSwap(head);break;
case7: head = sortPointerSwap(head);break;
case0: exit(0);
default: printf("输入错误!\n");
}
}
return0;
}
intmenu() {
printf("\n=====学生成绩管理系统=====\n");
printf("1.输入学生信息\n");
printf("2.输出学生信息\n");
printf("3.修改指定学生信息\n");
printf("4.计算每位学生平均成绩\n");
printf("5.输出总分与平均分\n");
printf("6.按平均成绩排序(交换数据域)\n");
printf("7.按平均成绩排序(交换指针域)\n");
printf("0.退出\n");
printf("请选择:");
intc; scanf("%d", &c);
returnc;
}
Stu* create() {
Stu* head =NULL, * p, * tail =NULL;
intn;
printf("请输入学生人数: ");
scanf("%d", &n);
for(inti = 0; i < n; i++) {
p = (Stu*)malloc(sizeof(Stu));
printf("输入学号姓名英语高数物理C语言成绩:\n");
scanf("%s %s %f %f %f %f", p->id, p->name, &p->eng, &p->math, &p->phy, &p->cprog);
p->avg = 0;
p->next =NULL;
if(head ==NULL) head = p;
elsetail->next = p;
tail = p;
}
returnhead;
}
voidprint(Stu*head) {
Stu* p =head;
printf("\n学号\t姓名\t英语\t高数\t物理\tC语言\n");
while(p) {
printf("%s\t%s\t%.2f\t%.2f\t%.2f\t%.2f\n",
p->id, p->name, p->eng, p->math, p->phy, p->cprog);
p = p->next;
}
}
voidmodify(Stu*head) {
charid[20];
printf("输入要修改的学生学号: ");
scanf("%s", id);
Stu* p =head;
while(p && strcmp(p->id, id) != 0) p = p->next;
if(!p) { printf("未找到该学生!\n");return; }
printf("选择要修改的科目:1英语2高数3物理4C语言\n");
intc; scanf("%d", &c);
floatscore;
printf("输入新成绩: "); scanf("%f", &score);
if(c == 1) p->eng = score;
elseif(c == 2) p->math = score;
elseif(c == 3) p->phy = score;
elseif(c == 4) p->cprog = score;
elseprintf("输入无效\n");
}
voidcalcAvg(Stu*head) {
Stu* p =head;
while(p) {
p->avg = (p->eng + p->math + p->phy + p->cprog) / 4.0;
p = p->next;
}
printf("平均分计算完成!\n");
}
voidprintAvg(Stu*head) {
Stu* p =head;
printf("\n学号\t姓名\t总分\t平均分\n");
while(p) {
floatsum = p->eng + p->math + p->phy + p->cprog;
printf("%s\t%s\t%.2f\t%.2f\n", p->id, p->name, sum, p->avg);
p = p->next;
}
}
voidsortDataSwap(Stu*head) {
Stu* p, * q;
Stutemp;
for(p =head; p && p->next; p = p->next) {
for(q = p->next; q; q = q->next) {
if(p->avg > q->avg) {//升序
temp = *p; *p = *q; *q = temp;
//修正next指针被交换的问题
Stu* t = p->next;
p->next = q->next;
q->next = t;
}
}
}
printf("已按平均成绩升序排序(数据域交换)!\n");
}
Stu* sortPointerSwap(Stu*head) {
if(!head)returnhead;
Studummy, * p, * q, * minPrev, * prev;
dummy.next =head;
prev = &dummy;
while(prev->next) {
minPrev = prev;
q = prev->next;
while(q->next) {
if(q->next->avg < minPrev->next->avg)
minPrev = q;
q = q->next;
}
if(minPrev != prev) {//交换节点指针
Stu* minNode = minPrev->next;
minPrev->next = minNode->next;
minNode->next = prev->next;
prev->next = minNode;
}
prev = prev->next;
}
printf("已按平均成绩升序排序(指针域交换)!\n");
returndummy.next;
}
- 测试:
- 各项功能的运行截图:
图7-6程序设计题(3)的运行截图
7.3实验小结
通过本次链表成绩管理系统的实现,我不仅掌握了单向链表的建立、遍历和修改等基本操作,还深入理解了结构体在数据组织中的作用;在完成平均成绩计算和排序功能的过程中,我体会到不同排序方法在链表结构中的实现差异,尤其是交换数据域与交换指针域的逻辑思想,使我对链表在动态数据管理中的优势有了更加直观而深刻的认识,也提升了我分析问题和模块化设计程序的能力。