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:变量定义与基本类型
题目:定义 int、float、double、char 四种类型的变量并赋值,使用 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/9和5.0/9.0的结果差异 - 理解隐式类型转换
- 输出保留 2 位小数
第三章:运算符与表达式
实验 3-1:算术与赋值运算符
题目:输入一个三位整数,分别输出它的百位、十位、个位数字,以及反转后的数字。
输入示例:
请输入一个三位整数:357
输出示例:
百位:3
十位:5
个位:7
反转后:753
要求:
- 使用
/和%运算符提取各位数字 - 不能使用循环
实验 3-2:位运算符练习
题目:输入一个整数,分别输出它左移 1 位、右移 1 位、按位取反的结果,并以二进制格式验证(可选,使用计算器辅助)。
输入示例:
请输入一个整数:5
输出示例:
5 << 1 = 10
5 >> 1 = 2
~5 = -6
要求:
- 理解
<<,>>,~,&,|,^运算符 - 体会左移 = 乘以 2,右移 = 除以 2
实验 3-3:复合赋值与自增自减
题目:阅读以下代码,写出输出结果,然后上机验证。体会 ++i 与 i++ 的区别。
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);
要求:
- 解释
++i和i++的执行顺序差异 - 再写一段含
+=,-=,*=,/=的代码并输出结果
第四章:选择结构
实验 4-1:成绩等级判定
题目:输入一个百分制成绩(0-100),输出对应的等级:
- 90-100:A
- 80-89:B
- 70-79:C
- 60-69:D
- 0-59:E
- 其他:输入错误
分别用 if-else 和 switch-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 <time.h>) - 使用
do-while保证至少猜一次 - 记录并输出猜测次数
实验 5-3:素数判定与统计
题目:输入一个正整数 n,输出 2 到 n 之间的所有素数,并统计个数。分别用 for、while 实现。
输出示例:
请输入 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 个数学工具函数,放在独立的源文件中,通过头文件调用。测试每个函数:
|
函数 |
功能 |
|---|---|
|
|
返回两数最大值 |
|
|
返回三数最大值(调用 max2) |
|
|
返回 x 的 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:字符串基础操作
题目:不使用 <string.h> 中的函数,手动实现 4 个字符串工具函数:
|
函数 |
功能 |
|---|---|
|
|
计算字符串长度 |
|
|
字符串复制 |
|
|
字符串拼接 |
|
|
字符串比较 |
要求:
- 用指针遍历字符串,遇到
\0结束 - 不能使用数组下标,只用指针操作
- 在每个函数中打印中间过程,帮助理解
实验 9-2:字符串统计与处理
题目:输入一行英文句子,统计并输出:
- 字母个数、数字个数、空格个数、其他字符个数
- 大写字母全部转为小写后输出
- 单词个数(以空格分隔)
输入示例:
Hello World 2024! Welcome to C Language.
输出示例:
字母:28 数字:4 空格:6 其他:3
转换为小写:hello world 2024! welcome to c language.
单词个数:7
要求:
- 使用
fgets读取含空格的字符串 - 用
<ctype.h>中的isalpha、isdigit、isspace、tolower等函数
实验 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))- 用
->运算符访问结构体成员 - 封装以下函数:
inputStudent、calcScore、printStudent、sortStudents - 所有函数接受结构体指针参数
实验 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_SET、SEEK_CUR、SEEK_END的区别 - 对比文本文件和二进制文件的优缺点
第十二章:链表
实验 12-1:单链表基础操作
题目:定义单链表节点,实现以下基本操作:
typedef struct Node {
int data;
struct Node *next;
} Node;
|
操作 |
函数原型 |
|---|---|
|
创建节点 |
|
|
头插法 |
|
|
尾插法 |
|
|
按值查找 |
|
|
删除节点 |
|
|
打印链表 |
|
|
释放链表 |
|
要求:
- 所有函数测试通过
- 注意
deleteNode要考虑删除头节点的情况 - 理解二级指针
Node **head的作用(修改链表头指针)
实验 12-2:链表进阶操作
题目:在实验 12-1 的基础上,增加以下功能:
|
操作 |
说明 |
|---|---|
|
反转链表 |
就地反转(不用额外空间) |
|
合并有序链表 |
两个升序链表合并为一个升序链表 |
|
找中间节点 |
快慢指针法 |
|
判断是否有环 |
快慢指针法 |
反转链表 — 三种方法:
- 迭代法(三指针 prev/curr/next)
- 递归法
- 头插法重建
要求:
- 反转链表为高频面试题,写出完整代码,画图辅助理解
- 快慢指针:
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 数据存储
|
功能 |
说明 |
|---|---|
|
保存到文件 |
退出时将所有链表数据保存到 |
|
从文件加载 |
启动时自动从 |
二、数据结构设计
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分)
进阶要求(30分)
strstr)加分项(10分)
六、完整参考代码
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 |
双向链表 |
☐ |
|
综 |
学生成绩管理系统 |
☐ |