跳转到内容

C语言实验题目

第一章:C 语言概述与开发环境

实验 1-1:Hello World 与编译运行

题目:编写第一个 C 程序,在屏幕上输出 "Hello, C Language!",熟悉开发环境(gcc / VS Code / Dev-C++)的编译运行流程。

要求

  • 正确写出 main 函数和 #include <stdio.h>
  • 使用 gcc hello.c -o hello && ./hello 完成编译运行
  • 了解 printf 的基本用法
#include <stdio.h>
int main() {
    printf("Hello, C Language!\n");
    return 0;
}

实验 1-2:多行图形输出

题目:使用 printf 在屏幕上输出如下图案(用 * 绘制一个三角形):

  *
 ***
*****

要求

  • 每一行一个 printf(不能用循环)
  • 注意转义字符 \n 的使用

实验 1-3:注释与代码规范

题目:编写一个程序,包含你的姓名、学号和当前日期。分别使用单行注释 // 和多行注释 /* */ 为代码添加说明。

要求

  • // 注释姓名和学号
  • /* */ 注释一段程序说明
  • 输出格式清晰,体现代码规范意识

第二章:数据类型与输入输出

实验 2-1:变量定义与基本类型

题目:定义 intfloatdoublechar 四种类型的变量并赋值,使用 printf 输出它们的值和 sizeof 大小。

输出示例

int:    value=100,   size=4 bytes
float:  value=3.14,  size=4 bytes
double: value=3.1415926535, size=8 bytes
char:   value='A',   size=1 byte

要求

  • 使用 sizeof 运算符获取每种类型的大小
  • 控制浮点数的小数位数(%.2f%.10f

实验 2-2:格式化输入输出

题目:编写一个简单的个人信息录入程序。从键盘输入姓名(字符串)、年龄(整数)、身高(浮点数)、性别(字符),然后格式化输出。

输入示例

请输入姓名:张三
请输入年龄:20
请输入身高(cm):175.5
请输入性别(M/F):M

输出示例

========== 个人信息 ==========
姓名:张三
年龄:20 岁
身高:175.5 cm
性别:M
=============================

要求

  • 使用 scanf 读取不同类型的数据
  • 注意 scanf 读取 char 时缓冲区残留问题

实验 2-3:数据类型转换

题目:输入一个华氏温度 F,将其转换为摄氏温度 C。公式:C = 5.0 / 9.0 * (F - 32)。分别演示整除(5/9)和浮点除(5.0/9.0)的区别。

要求

  • 对比 5/95.0/9.0 的结果差异
  • 理解隐式类型转换
  • 输出保留 2 位小数

第三章:运算符与表达式

实验 3-1:算术与赋值运算符

题目:输入一个三位整数,分别输出它的百位、十位、个位数字,以及反转后的数字。

输入示例

请输入一个三位整数:357

输出示例

百位:3
十位:5
个位:7
反转后:753

要求

  • 使用 /% 运算符提取各位数字
  • 不能使用循环

实验 3-2:位运算符练习

题目:输入一个整数,分别输出它左移 1 位、右移 1 位、按位取反的结果,并以二进制格式验证(可选,使用计算器辅助)。

输入示例

请输入一个整数:5

输出示例

5 << 1 = 10
5 >> 1 = 2
~5     = -6

要求

  • 理解 &lt;&lt;, &gt;&gt;, ~, &, |, ^ 运算符
  • 体会左移 = 乘以 2,右移 = 除以 2

实验 3-3:复合赋值与自增自减

题目:阅读以下代码,写出输出结果,然后上机验证。体会 ++ii++ 的区别。

int a = 5, b = 5;
int x = ++a;   // a 先加 1,再赋值给 x
int y = b++;   // b 先赋值给 y,再加 1
printf("a=%d, x=%d\n", a, x);
printf("b=%d, y=%d\n", b, y);

要求

  • 解释 ++ii++ 的执行顺序差异
  • 再写一段含 +=, -=, *=, /= 的代码并输出结果

第四章:选择结构

实验 4-1:成绩等级判定

题目:输入一个百分制成绩(0-100),输出对应的等级:

  • 90-100:A
  • 80-89:B
  • 70-79:C
  • 60-69:D
  • 0-59:E
  • 其他:输入错误

分别用 if-elseswitch-case 两种方式实现。

要求

  • if-else 版本用多分支结构
  • switch-case 版本利用整除 /10 简化分支(s/10 得到 0-10)
  • 处理非法输入

实验 4-2:简易计算器

题目:设计一个简易计算器。输入两个操作数和一个运算符(+, -, *, /),输出计算结果。除法时需要判断除数是否为零。

输入示例

请输入表达式(如 3 + 5):10 / 3

输出示例

10 / 3 = 3.33

要求

  • 使用 switch-case 判断运算符
  • 除法结果保留 2 位小数
  • 除数为 0 时给出错误提示
  • 运算符不合法时给出提示

实验 4-3:闰年判断

题目:输入一个年份,判断它是否为闰年。闰年条件:(1) 能被 4 整除但不能被 100 整除,或 (2) 能被 400 整除。

要求

  • 用逻辑运算符 &&|| 组合条件
  • 分别用嵌套 if 和单条复合条件两种方式实现
  • 理解闰年规则的由来(能被 100 整除的世纪年需能被 400 整除才是闰年)

第五章:循环结构

实验 5-1:乘法口诀表

题目:使用嵌套循环输出 9×9 乘法口诀表,要求对齐格式。

输出示例

1×1= 1  1×2= 2  1×3= 3  ...  1×9= 9
2×2= 4  2×3= 6  ...          2×9=18
...
9×9=81

要求

  • 使用 for 嵌套循环
  • 使用 %2d 控制对齐
  • 思考:只输出下三角

实验 5-2:猜数字游戏

题目:程序随机生成一个 1-100 之间的整数(用 rand()),用户输入猜测值,程序提示"大了"或"小了",直到猜对为止,统计猜测次数。

输出示例

我已经想好了一个 1-100 之间的数字,请猜:
50 → 大了
25 → 小了
37 → 猜对了!你共猜了 3 次。

要求

  • 使用 srand(time(NULL)) 初始化随机种子(需要 #include &lt;time.h&gt;
  • 使用 do-while 保证至少猜一次
  • 记录并输出猜测次数

实验 5-3:素数判定与统计

题目:输入一个正整数 n,输出 2 到 n 之间的所有素数,并统计个数。分别用 forwhile 实现。

输出示例

请输入 n:50
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
共有 15 个素数。

要求

  • 素数判定:只能被 1 和自身整除
  • 优化:只需检测 2 到 sqrt(n)
  • 输出每行 10 个

第六章:数组

实验 6-1:一维数组 — 成绩统计

题目:输入 10 个学生的成绩(0-100),计算并输出:最高分、最低分、平均分、及格人数、各等级人数(A/B/C/D/E)。

要求

  • 使用一维数组存储成绩
  • 遍历数组完成统计
  • 格式化输出统计结果
========== 成绩统计 ==========
最高分:98
最低分:42
平均分:73.5
及格人数:8(80.0%)
A(90-100):2 人
B(80-89) :3 人
C(70-79) :2 人
D(60-69) :1 人
E(0-59)  :2 人
=============================

实验 6-2:二维数组 — 矩阵运算

题目:输入两个 3×3 矩阵 A 和 B,分别计算并输出 A+B、A×B(矩阵乘法)。

要求

  • 使用二维数组存储矩阵
  • 矩阵加法:对应位置相加
  • 矩阵乘法:三重循环,C[i][j] = Σ A[i][k] * B[k][j]
  • 验证矩阵乘法的不可交换性(A×B ≠ B×A)

实验 6-3:数组排序算法

题目:输入 n 个整数存入数组(n 由用户输入,最大 100),分别用冒泡排序选择排序对数组进行升序排列,输出排序前后的数组,并比较两种排序的交换次数。

要求

  • 冒泡排序:相邻元素两两比较,大的往后沉
  • 选择排序:每次找到最小元素,与当前位置交换
  • 统计交换次数
  • 所有输入和输出数组每行 10 个元素

第七章:函数

实验 7-1:自定义函数库

题目:创建 4 个数学工具函数,放在独立的源文件中,通过头文件调用。测试每个函数:

函数

功能

int max2(int a, int b)

返回两数最大值

int max3(int a, int b, int c)

返回三数最大值(调用 max2)

double power(double x, int n)

返回 x 的 n 次方

int factorial(int n)

返回 n 的阶乘

要求

  • 创建 mymath.h(函数声明)和 mymath.c(函数实现)
  • 主函数 #include "mymath.h" 调用
  • 体会函数封装和模块化编程

实验 7-2:递归与迭代对比

题目:分别用递归和迭代(循环)实现斐波那契数列第 n 项的计算。输入 n,输出 F(n) 和两种方式分别的计算次数(函数调用次数或循环次数)。

斐波那契数列:F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)

要求

  • 递归版:int fib_rec(int n)
  • 迭代版:int fib_iter(int n)
  • 对比 n=40 时两种方法的耗时(体会递归的性能问题)
  • 思考:如何优化递归(记忆化搜索 / 尾递归)

实验 7-3:函数指针应用

题目:编写 sort 函数,可以按升序或降序排列数组。升序和降序的逻辑通过函数指针传入。

// 函数指针类型
typedef int (*cmp_func)(int, int);

// 排序函数
void sort(int arr[], int n, cmp_func cmp);

// 比较函数
int asc(int a, int b);   // 升序:a<b 返回负数
int desc(int a, int b);  // 降序:a>b 返回负数

要求

  • 定义函数指针类型,实现 sort 函数
  • 用同一套排序代码实现升序和降序
  • 体会函数指针的"策略模式"思想

第八章:指针

实验 8-1:指针基础 — 值交换

题目:编写 swap 函数,用指针实现两个整数的值交换。对比传值和传址的区别。

// 错误示范:传值,不改变实参
void swap_wrong(int a, int b);

// 正确做法:传地址
void swap(int *a, int *b);

要求

  • 在主函数中打印交换前后的变量地址和值
  • 理解"指针 = 地址"的概念
  • 画出内存示意图

实验 8-2:指针与数组

题目:用三种方式遍历数组并输出元素,证明 arr[i]*(arr+i)*(p+i) 等价。

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;

// 方式1:下标
for (int i = 0; i < 5; i++) printf("%d ", arr[i]);

// 方式2:指针偏移
for (int i = 0; i < 5; i++) printf("%d ", *(arr + i));

// 方式3:指针变量
for (int i = 0; i < 5; i++) printf("%d ", *(p + i));

// 方式4:指针移动
for (int *q = arr; q < arr + 5; q++) printf("%d ", *q);

要求

  • 理解 arr 本身是数组首元素地址
  • 理解 arr[i]*(arr+i) 的语法糖
  • 体会指针遍历的高效性

实验 8-3:动态内存分配

题目:编写程序,让用户输入学生人数 n,然后用 malloc 动态分配数组存储 n 个学生的成绩。计算平均分、最高分、最低分后,用 free 释放内存。

要求

  • 使用 malloc 分配内存(int *scores = (int *)malloc(n * sizeof(int))
  • 检查 malloc 返回值是否为 NULL(内存不足时)
  • 使用完毕后 free(scores) 释放内存
  • 理解堆内存和栈内存的区别

第九章:字符串

实验 9-1:字符串基础操作

题目:不使用 &lt;string.h&gt; 中的函数,手动实现 4 个字符串工具函数:

函数

功能

int my_strlen(const char *s)

计算字符串长度

void my_strcpy(char *dest, const char *src)

字符串复制

void my_strcat(char *dest, const char *src)

字符串拼接

int my_strcmp(const char *a, const char *b)

字符串比较

要求

  • 用指针遍历字符串,遇到 \0 结束
  • 不能使用数组下标,只用指针操作
  • 在每个函数中打印中间过程,帮助理解

实验 9-2:字符串统计与处理

题目:输入一行英文句子,统计并输出:

  • 字母个数、数字个数、空格个数、其他字符个数
  • 大写字母全部转为小写后输出
  • 单词个数(以空格分隔)

输入示例

Hello World 2024! Welcome to C Language.

输出示例

字母:28  数字:4  空格:6  其他:3
转换为小写:hello world 2024! welcome to c language.
单词个数:7

要求

  • 使用 fgets 读取含空格的字符串
  • &lt;ctype.h&gt; 中的 isalphaisdigitisspacetolower 等函数

实验 9-3:回文串判断

题目:输入一个字符串,判断它是否回文(正读反读都一样)。忽略大小写和非字母字符。

示例

"A man, a plan, a canal: Panama"  → 是回文
"race a car"                      → 不是回文
"上海自来水来自海上"               → 是回文

要求

  • 双指针法:一头一尾向中间移动比较
  • 跳过非字母/非数字字符
  • 忽略大小写差异

第十章:结构体与共用体

实验 10-1:结构体定义与使用

题目:定义 Student 结构体,包含学号、姓名、三门课成绩。输入 5 个学生的信息,计算每个学生的总分和平均分,按总分从高到低排序输出。

typedef struct {
    int id;              // 学号
    char name[20];       // 姓名
    float scores[3];     // 三门课成绩
    float total;         // 总分
    float avg;           // 平均分
} Student;

要求

  • 使用结构体数组
  • qsort 或自己写冒泡排序
  • 输出格式对齐,保留 2 位小数

实验 10-2:结构体指针

题目:将实验 10-1 改为动态分配内存存储学生数据(n 由用户输入),所有操作使用结构体指针完成。

要求

  • Student *students = (Student *)malloc(n * sizeof(Student))
  • -&gt; 运算符访问结构体成员
  • 封装以下函数:inputStudentcalcScoreprintStudentsortStudents
  • 所有函数接受结构体指针参数

实验 10-3:共用体与枚举

题目:定义一个 Shape 结构体,用枚举区分形状类型(圆形/矩形/三角形),共用体存储不同形状的参数。计算并输出每种形状的面积。

typedef enum { CIRCLE, RECTANGLE, TRIANGLE } ShapeType;

typedef union {
    double radius;                    // 圆形:半径
    struct { double w, h; } rect;    // 矩形:宽和高
    struct { double a, b, c; } tri;  // 三角形:三边长
} ShapeData;

typedef struct {
    ShapeType type;
    ShapeData data;
} Shape;

要求

  • 理解共用体所有成员共享同一段内存
  • 输入 3 种不同类型的形状并计算面积
  • 注意三角形面积使用海伦公式

第十一章:文件操作

实验 11-1:文件读写基础

题目:编程生成 100 个 1-1000 的随机整数,写入文件 numbers.txt(每行一个)。然后从该文件读取数据,统计最大值、最小值、平均值,将结果追加写入文件末尾。

要求

  • fopen("numbers.txt", "w") 写入
  • fopen("numbers.txt", "r") 读取
  • fopen("numbers.txt", "a") 追加统计结果
  • 每次操作后检查文件是否成功打开

实验 11-2:CSV 文件处理

题目:创建一个学生成绩 CSV 文件 students.csv,包含学号、姓名、语文、数学、英语 5 列。编程读取该 CSV 文件,计算每个学生的总分,输出到新文件 students_with_total.csv

输入文件 students.csv

1001,张三,85,92,78
1002,李四,90,88,95
1003,王五,76,84,70

输出文件 students_with_total.csv

学号,姓名,语文,数学,英语,总分
1001,张三,85,92,78,255
1002,李四,90,88,95,273
1003,王五,76,84,70,230

要求

  • 使用 fgets 逐行读取
  • 使用 sscanf 解析 CSV 格式
  • 使用 fprintf 写入输出文件
  • 容错:跳过空行和格式错误的行

实验 11-3:二进制文件与随机访问

题目:使用结构体存储学生信息,以二进制方式写入 students.dat。实现按记录号随机读写(fseek + fread/fwrite),支持按学号查询和修改学生信息。

要求

  • 使用 fwrite 写入二进制结构体数据
  • 使用 fseek 定位到指定记录
  • 使用 fread 读取指定记录
  • 理解 SEEK_SETSEEK_CURSEEK_END 的区别
  • 对比文本文件和二进制文件的优缺点

第十二章:链表

实验 12-1:单链表基础操作

题目:定义单链表节点,实现以下基本操作:

typedef struct Node {
    int data;
    struct Node *next;
} Node;

操作

函数原型

创建节点

Node *createNode(int data)

头插法

void insertHead(Node **head, int data)

尾插法

void insertTail(Node **head, int data)

按值查找

Node *find(Node *head, int data)

删除节点

int deleteNode(Node **head, int data)

打印链表

void printList(Node *head)

释放链表

void freeList(Node **head)

要求

  • 所有函数测试通过
  • 注意 deleteNode 要考虑删除头节点的情况
  • 理解二级指针 Node **head 的作用(修改链表头指针)

实验 12-2:链表进阶操作

题目:在实验 12-1 的基础上,增加以下功能:

操作

说明

反转链表

就地反转(不用额外空间)

合并有序链表

两个升序链表合并为一个升序链表

找中间节点

快慢指针法

判断是否有环

快慢指针法

反转链表 — 三种方法:

  1. 迭代法(三指针 prev/curr/next)
  2. 递归法
  3. 头插法重建

要求

  • 反转链表为高频面试题,写出完整代码,画图辅助理解
  • 快慢指针:fast 每次走两步,slow 每次走一步
  • 测试合并链表的边界情况(空链表、单节点链表)

实验 12-3:双向链表

题目:实现双向链表,在实验 12-1 所有功能的基础上增加 prev 指针,实现双向遍历、在指定节点前后插入。

typedef struct DNode {
    int data;
    struct DNode *prev;
    struct DNode *next;
} DNode;

要求

  • 实现头插、尾插、按值删除
  • 实现正向遍历和反向遍历
  • 实现 insertBefore(DNode *node, int data)insertAfter(DNode *node, int data)
  • 对比单链表和双向链表的优缺点

综合项目:学生成绩管理系统

项目定位:综合运用 C 语言全部核心知识点,实现一个完整的命令行交互式学生成绩管理系统。 核心知识点覆盖:结构体、链表、文件操作、指针、动态内存、字符串处理、排序算法、模块化编程。

一、功能需求

1.1 核心功能 (CRUD)

功能

说明

添加学生

输入学号(自动查重)、姓名、语文/数学/英语成绩,自动计算总分和平均分

删除学生

根据学号删除学生记录,需确认

修改学生

根据学号修改姓名或成绩,自动重算总分和平均分

查找学生

支持按学号精确查找、按姓名模糊查找

显示全部

以表格形式显示所有学生,按总分排名

统计信息

各科平均分、最高分、最低分、及格率;各等级人数分布

1.2 数据存储

功能

说明

保存到文件

退出时将所有链表数据保存到 students.dat(二进制文件)

从文件加载

启动时自动从 students.dat 加载已有数据

二、数据结构设计

2.1 学生结构体

typedef struct {
    int id;                 // 学号(唯一,范围 1000-9999)
    char name[32];          // 姓名
    float chinese;          // 语文成绩 (0-100)
    float math;             // 数学成绩 (0-100)
    float english;          // 英语成绩 (0-100)
    float total;            // 总分
    float average;          // 平均分
    char grade;             // 等级 (A/B/C/D/E)
} Student;

2.2 链表节点

typedef struct StudentNode {
    Student data;
    struct StudentNode *next;
} StudentNode;

2.3 链表结构体(带管理信息)

typedef struct {
    StudentNode *head;      // 链表头指针
    int count;              // 学生总数
} StudentList;

三、函数设计(模块化)

main.c            — 主程序入口,菜单交互
student.h         — 数据结构定义
list.h / list.c   — 链表操作(增删改查)
file.h / file.c   — 文件读写
ui.h / ui.c       — 用户界面(菜单、输入输出)
utils.h / utils.c — 工具函数(排序、统计、校验)

完整函数列表

// ========== list.c — 链表操作 ==========
StudentList*  createList();                                  // 创建空链表
void          freeList(StudentList *list);                   // 释放链表
StudentNode*  findByID(StudentList *list, int id);           // 按学号查找
void          findByName(StudentList *list, const char *name); // 按姓名查找
int           addStudent(StudentList *list, Student *stu);   // 添加学生(查重)
int           deleteStudent(StudentList *list, int id);      // 删除学生
int           updateStudent(StudentList *list, int id);      // 修改学生
void          printAll(StudentList *list);                   // 显示所有(表格形式)
void          sortByTotal(StudentList *list);                // 按总分排序(冒泡)
void          statistics(StudentList *list);                 // 统计信息

// ========== file.c — 文件操作 ==========
int           saveToFile(StudentList *list, const char *filename);   // 保存
int           loadFromFile(StudentList *list, const char *filename); // 加载

// ========== ui.c — 界面 ==========
void         showMenu();                           // 显示主菜单
int          getMenuChoice();                       // 获取用户选择
void         inputStudent(Student *stu);            // 输入学生信息
void         printTableHeader();                    // 打印表格头
void         printStudentRow(Student *stu);         // 打印一行
char         calcGrade(float avg);                  // 计算等级
void         clearScreen();                         // 清屏

// ========== utils.c — 工具 ==========
void         bubbleSort(StudentNode *head);         // 冒泡排序
float        getMax(StudentList *list, int subject); // 科目最高分
float        getMin(StudentList *list, int subject); // 科目最低分
float        getAvg(StudentList *list, int subject); // 科目平均分
int          getPassCount(StudentList *list, int subject); // 及格人数
int          isIDExist(StudentList *list, int id);  // 学号查重

四、主程序交互流程

程序启动
  └→ loadFromFile("students.dat")  // 加载已有数据

主循环:
  ┌──────────────────────────────────────────┐
  │  ===== 学生成绩管理系统 =====               │
  │  1. 添加学生                              │
  │  2. 删除学生                              │
  │  3. 修改学生信息                          │
  │  4. 查找学生(按学号/按姓名)               │
  │  5. 显示全部学生(按总分排名)              │
  │  6. 统计信息                              │
  │  7. 保存数据                              │
  │  0. 退出(自动保存)                       │
  │  ================================         │
  │  请选择:                                 │
  └──────────────────────────────────────────┘

程序退出
  └→ saveToFile("students.dat")  // 自动保存
  └→ freeList()                  // 释放内存

五、要求与评分标准

基本要求(60分)

程序能正常编译运行,无明显 bug
基础 CRUD 功能完整可用
链表操作使用指针,不允许用数组模拟
退出时数据保存到文件,启动时从文件加载
输入有基本校验(成绩 0-100、学号 4 位数范围)
学号不能重复

进阶要求(30分)

按总分自动排序显示(链表原地排序,不拷贝到数组)
支持按姓名模糊查找(子串匹配,用 strstr
统计信息完善(各科平均分、最高/低分、及格率、等级分布)
删除/修改前有确认提示
界面友好,表格对齐,清屏刷新
代码模块化(按功能拆分 .h 和 .c 文件),有 Makefile 或编译脚本

加分项(10分)

使用函数指针统一排序规则(可切换按学号/总分/单科排序)
实现撤销删除(软删除标记 + 恢复功能)
支持导出为 CSV 格式(可用 Excel 打开)
增加密码登录功能

六、完整参考代码

6.1 主程序 main.c

#include <stdio.h>
#include <stdlib.h>
#include "student.h"
#include "list.h"
#include "file.h"
#include "ui.h"

#define DATA_FILE "students.dat"

int main() {
    StudentList *list = createList();

    // 加载已有数据
    if (loadFromFile(list, DATA_FILE)) {
        printf("已加载 %d 条学生记录。\n", list->count);
    }

    int choice;
    do {
        showMenu();
        choice = getMenuChoice();
        printf("\n");

        switch (choice) {
            case 1: { // 添加学生
                Student stu;
                inputStudent(&stu);
                if (isIDExist(list, stu.id)) {
                    printf("× 学号 %d 已存在!\n", stu.id);
                    break;
                }
                stu.total = stu.chinese + stu.math + stu.english;
                stu.average = stu.total / 3.0;
                stu.grade = calcGrade(stu.average);
                if (addStudent(list, &stu)) {
                    printf("√ 添加成功!\n");
                }
                break;
            }
            case 2: { // 删除学生
                int id;
                printf("请输入要删除的学号:");
                scanf("%d", &id);
                StudentNode *node = findByID(list, id);
                if (!node) {
                    printf("× 未找到学号 %d 的学生。\n", id);
                    break;
                }
                printf("确认删除 %s (学号:%d)?(y/n):", node->data.name, id);
                char confirm;
                scanf(" %c", &confirm);
                if (confirm == 'y' || confirm == 'Y') {
                    deleteStudent(list, id);
                    printf("√ 删除成功!\n");
                }
                break;
            }
            case 3: { // 修改学生
                int id;
                printf("请输入要修改的学号:");
                scanf("%d", &id);
                if (updateStudent(list, id)) {
                    printf("√ 修改成功!\n");
                } else {
                    printf("× 未找到学号 %d 的学生。\n", id);
                }
                break;
            }
            case 4: { // 查找学生
                printf("查找方式:1-按学号  2-按姓名\n请选择:");
                int sub;
                scanf("%d", &sub);
                if (sub == 1) {
                    int id;
                    printf("请输入学号:");
                    scanf("%d", &id);
                    StudentNode *node = findByID(list, id);
                    if (node) {
                        printTableHeader();
                        printStudentRow(&node->data);
                    } else {
                        printf("× 未找到。\n");
                    }
                } else if (sub == 2) {
                    char name[32];
                    printf("请输入姓名(支持模糊搜索):");
                    scanf("%s", name);
                    findByName(list, name);
                }
                break;
            }
            case 5: // 显示全部
                sortByTotal(list);
                printAll(list);
                break;
            case 6: // 统计信息
                statistics(list);
                break;
            case 7: // 保存数据
                if (saveToFile(list, DATA_FILE)) {
                    printf("√ 数据已保存到 %s\n", DATA_FILE);
                } else {
                    printf("× 保存失败!\n");
                }
                break;
            case 0:
                saveToFile(list, DATA_FILE);
                printf("数据已保存,再见!\n");
                break;
            default:
                printf("无效选择,请重新输入。\n");
        }

        if (choice != 0) {
            printf("\n按回车键继续...");
            getchar(); getchar(); // 暂停
        }
    } while (choice != 0);

    freeList(list);
    return 0;
}

6.2 student.h

#ifndef STUDENT_H
#define STUDENT_H

typedef struct {
    int id;
    char name[32];
    float chinese;
    float math;
    float english;
    float total;
    float average;
    char grade;
} Student;

typedef struct StudentNode {
    Student data;
    struct StudentNode *next;
} StudentNode;

typedef struct {
    StudentNode *head;
    int count;
} StudentList;

#endif

6.3 list.h

#ifndef LIST_H
#define LIST_H
#include "student.h"

StudentList*  createList();
void          freeList(StudentList *list);
StudentNode*  findByID(StudentList *list, int id);
void          findByName(StudentList *list, const char *name);
int           addStudent(StudentList *list, Student *stu);
int           deleteStudent(StudentList *list, int id);
int           updateStudent(StudentList *list, int id);
void          printAll(StudentList *list);
void          sortByTotal(StudentList *list);
void          statistics(StudentList *list);
int           isIDExist(StudentList *list, int id);

#endif

6.4 list.c — 核心链表操作

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"
#include "ui.h"

StudentList* createList() {
    StudentList *list = (StudentList *)malloc(sizeof(StudentList));
    list->head = NULL;
    list->count = 0;
    return list;
}

void freeList(StudentList *list) {
    StudentNode *curr = list->head;
    while (curr) {
        StudentNode *next = curr->next;
        free(curr);
        curr = next;
    }
    free(list);
}

int addStudent(StudentList *list, Student *stu) {
    StudentNode *node = (StudentNode *)malloc(sizeof(StudentNode));
    node->data = *stu;
    // 头插法(简单高效)
    node->next = list->head;
    list->head = node;
    list->count++;
    return 1;
}

StudentNode* findByID(StudentList *list, int id) {
    StudentNode *curr = list->head;
    while (curr) {
        if (curr->data.id == id) return curr;
        curr = curr->next;
    }
    return NULL;
}

int isIDExist(StudentList *list, int id) {
    return findByID(list, id) != NULL;
}

void findByName(StudentList *list, const char *name) {
    int found = 0;
    StudentNode *curr = list->head;
    while (curr) {
        if (strstr(curr->data.name, name)) {  // 子串匹配
            if (!found) printTableHeader();
            printStudentRow(&curr->data);
            found++;
        }
        curr = curr->next;
    }
    if (!found) printf("× 未找到包含\"%s\"的学生。\n", name);
    else printf("共找到 %d 条记录。\n", found);
}

int deleteStudent(StudentList *list, int id) {
    StudentNode *curr = list->head, *prev = NULL;
    while (curr) {
        if (curr->data.id == id) {
            if (prev) prev->next = curr->next;
            else list->head = curr->next;  // 删除头节点
            free(curr);
            list->count--;
            return 1;
        }
        prev = curr;
        curr = curr->next;
    }
    return 0;
}

int updateStudent(StudentList *list, int id) {
    StudentNode *node = findByID(list, id);
    if (!node) return 0;

    printf("当前学生:%s 语文=%.0f 数学=%.0f 英语=%.0f\n",
           node->data.name, node->data.chinese,
           node->data.math, node->data.english);
    printf("请输入新姓名(输入 - 跳过):");
    char name[32];
    scanf("%s", name);
    if (strcmp(name, "-") != 0) strcpy(node->data.name, name);

    printf("请输入新语文成绩(输入 -1 跳过):");
    float score;
    scanf("%f", &score);
    if (score >= 0) node->data.chinese = score;

    printf("请输入新数学成绩(输入 -1 跳过):");
    scanf("%f", &score);
    if (score >= 0) node->data.math = score;

    printf("请输入新英语成绩(输入 -1 跳过):");
    scanf("%f", &score);
    if (score >= 0) node->data.english = score;

    node->data.total = node->data.chinese + node->data.math + node->data.english;
    node->data.average = node->data.total / 3.0;
    node->data.grade = calcGrade(node->data.average);
    return 1;
}

void sortByTotal(StudentList *list) {
    if (!list->head || !list->head->next) return;

    // 链表冒泡排序(交换节点数据,不改变指向)
    for (StudentNode *i = list->head; i->next; i = i->next) {
        for (StudentNode *j = list->head; j->next; j = j->next) {
            if (j->data.total < j->next->data.total) {  // 降序
                Student temp = j->data;
                j->data = j->next->data;
                j->next->data = temp;
            }
        }
    }
}

void printAll(StudentList *list) {
    if (!list->head) {
        printf("暂无学生记录。\n");
        return;
    }
    printTableHeader();
    StudentNode *curr = list->head;
    while (curr) {
        printStudentRow(&curr->data);
        curr = curr->next;
    }
    printf("\n共 %d 条记录。\n", list->count);
}

void statistics(StudentList *list) {
    if (!list->head) { printf("暂无数据。\n"); return; }

    float sum_ch = 0, sum_ma = 0, sum_en = 0;
    float max_ch = 0, max_ma = 0, max_en = 0;
    float min_ch = 100, min_ma = 100, min_en = 100;
    int pass_ch = 0, pass_ma = 0, pass_en = 0;
    int grade_count[5] = {0};  // A B C D E
    char *grade_names[] = {"A(90-100)", "B(80-89)", "C(70-79)", "D(60-69)", "E(0-59)"};

    StudentNode *curr = list->head;
    while (curr) {
        Student *s = &curr->data;
        sum_ch += s->chinese; sum_ma += s->math; sum_en += s->english;

        if (s->chinese > max_ch) max_ch = s->chinese;
        if (s->math > max_ma) max_ma = s->math;
        if (s->english > max_en) max_en = s->english;

        if (s->chinese < min_ch) min_ch = s->chinese;
        if (s->math < min_ma) min_ma = s->math;
        if (s->english < min_en) min_en = s->english;

        if (s->chinese >= 60) pass_ch++;
        if (s->math >= 60) pass_ma++;
        if (s->english >= 60) pass_en++;

        grade_count[s->grade - 'A']++;
        curr = curr->next;
    }

    int n = list->count;
    printf("\n╔═════════════════ 成绩统计 ═════════════════╗\n");
    printf("║ 学生总数:%d                               ║\n", n);
    printf("╠══════════╦══════╦══════╦══════╦══════╦══════╣\n");
    printf("║   科目    ║ 平均分 ║ 最高分 ║ 最低分 ║ 及格人数║ 及格率 ║\n");
    printf("╠══════════╬══════╬══════╬══════╬══════╬══════╣\n");
    printf("║  语文    ║ %5.1f ║ %5.1f ║ %5.1f ║  %3d   ║ %4.1f%% ║\n",
           sum_ch/n, max_ch, min_ch, pass_ch, 100.0*pass_ch/n);
    printf("║  数学    ║ %5.1f ║ %5.1f ║ %5.1f ║  %3d   ║ %4.1f%% ║\n",
           sum_ma/n, max_ma, min_ma, pass_ma, 100.0*pass_ma/n);
    printf("║  英语    ║ %5.1f ║ %5.1f ║ %5.1f ║  %3d   ║ %4.1f%% ║\n",
           sum_en/n, max_en, min_en, pass_en, 100.0*pass_en/n);
    printf("╚══════════╩══════╩══════╩══════╩══════╩══════╝\n");

    printf("\n等级分布:\n");
    for (int i = 0; i < 5; i++) {
        printf("  %s:%d 人 (%.1f%%)\n", grade_names[i],
               grade_count[i], 100.0 * grade_count[i] / n);
    }
}

6.5 file.c — 文件读写

#include <stdio.h>
#include <stdlib.h>
#include "list.h"

int saveToFile(StudentList *list, const char *filename) {
    FILE *fp = fopen(filename, "wb");
    if (!fp) return 0;

    // 先写入学生总数
    fwrite(&list->count, sizeof(int), 1, fp);

    // 再写入每个学生数据
    StudentNode *curr = list->head;
    while (curr) {
        fwrite(&curr->data, sizeof(Student), 1, fp);
        curr = curr->next;
    }

    fclose(fp);
    return 1;
}

int loadFromFile(StudentList *list, const char *filename) {
    FILE *fp = fopen(filename, "rb");
    if (!fp) return 0;  // 文件不存在,正常情况

    int count;
    if (fread(&count, sizeof(int), 1, fp) != 1) {
        fclose(fp);
        return 0;
    }

    for (int i = 0; i < count; i++) {
        Student stu;
        if (fread(&stu, sizeof(Student), 1, fp) != 1) break;
        addStudent(list, &stu);
    }

    fclose(fp);
    return 1;
}

6.6 ui.c — 用户界面

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "student.h"
#include "list.h"

void showMenu() {
#ifdef _WIN32
    system("cls");
#else
    system("clear");
#endif
    printf("\n");
    printf("╔══════════════════════════════════════════╗\n");
    printf("║       学 生 成 绩 管 理 系 统            ║\n");
    printf("╠══════════════════════════════════════════╣\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("╚══════════════════════════════════════════╝\n");
    printf("请选择 [0-7]:");
}

int getMenuChoice() {
    int choice;
    scanf("%d", &choice);
    return choice;
}

void inputStudent(Student *stu) {
    printf("学号(4位整数):");
    scanf("%d", &stu->id);
    printf("姓名:");
    scanf("%s", stu->name);
    printf("语文成绩:");
    scanf("%f", &stu->chinese);
    printf("数学成绩:");
    scanf("%f", &stu->math);
    printf("英语成绩:");
    scanf("%f", &stu->english);
}

void printTableHeader() {
    printf("\n┌──────┬──────────────────┬────┬────┬────┬──────┬──────┬────┐\n");
    printf("│ 学号  │      姓名        │ 语文│ 数学│ 英语│ 总分  │ 平均分 │等级│\n");
    printf("├──────┼──────────────────┼────┼────┼────┼──────┼──────┼────┤\n");
}

void printStudentRow(Student *s) {
    printf("│%6d│%-18s│%4.0f│%4.0f│%4.0f│%6.1f│%6.1f│ %c  │\n",
           s->id, s->name, s->chinese, s->math, s->english,
           s->total, s->average, s->grade);
    printf("├──────┼──────────────────┼────┼────┼────┼──────┼──────┼────┤\n");
}

char calcGrade(float avg) {
    if (avg >= 90) return 'A';
    if (avg >= 80) return 'B';
    if (avg >= 70) return 'C';
    if (avg >= 60) return 'D';
    return 'E';
}

6.7 ui.h

#ifndef UI_H
#define UI_H
#include "student.h"

void showMenu();
int  getMenuChoice();
void inputStudent(Student *stu);
void printTableHeader();
void printStudentRow(Student *stu);
char calcGrade(float avg);

#endif

6.8 file.h

#ifndef FILE_H
#define FILE_H
#include "list.h"

int saveToFile(StudentList *list, const char *filename);
int loadFromFile(StudentList *list, const char *filename);

#endif

6.9 编译脚本 (Makefile)

CC = gcc
CFLAGS = -Wall -Wextra -std=c11
TARGET = student_system
OBJS = main.o list.o file.o ui.o

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)

main.o: main.c student.h list.h file.h ui.h
	$(CC) $(CFLAGS) -c main.c

list.o: list.c list.h student.h ui.h
	$(CC) $(CFLAGS) -c list.c

file.o: file.c file.h list.h
	$(CC) $(CFLAGS) -c file.c

ui.o: ui.c ui.h student.h
	$(CC) $(CFLAGS) -c ui.c

clean:
	rm -f $(OBJS) $(TARGET) students.dat

run:
	./$(TARGET)

.PHONY: clean run

6.10 Windows 编译脚本 (build.bat)

@echo off
gcc -Wall -std=c11 -c main.c
gcc -Wall -std=c11 -c list.c
gcc -Wall -std=c11 -c file.c
gcc -Wall -std=c11 -c ui.c
gcc -o student_system.exe main.o list.o file.o ui.o
echo 编译完成!运行 student_system.exe

七、进度检查清单

序号

实验题目

状态

1-1

Hello World 与编译运行

1-2

多行图形输出

1-3

注释与代码规范

2-1

变量定义与基本类型

2-2

格式化输入输出

2-3

数据类型转换

3-1

算术与赋值运算符

3-2

位运算符练习

3-3

复合赋值与自增自减

4-1

成绩等级判定

4-2

简易计算器

4-3

闰年判断

5-1

乘法口诀表

5-2

猜数字游戏

5-3

素数判定与统计

6-1

一维数组 — 成绩统计

6-2

二维数组 — 矩阵运算

6-3

数组排序算法

7-1

自定义函数库

7-2

递归与迭代对比

7-3

函数指针应用

8-1

指针基础 — 值交换

8-2

指针与数组

8-3

动态内存分配

9-1

字符串基础操作

9-2

字符串统计与处理

9-3

回文串判断

10-1

结构体定义与使用

10-2

结构体指针

10-3

共用体与枚举

11-1

文件读写基础

11-2

CSV 文件处理

11-3

二进制文件与随机访问

12-1

单链表基础操作

12-2

链表进阶操作

12-3

双向链表

学生成绩管理系统