C语言学生信息管理系统 --- 知识点详解
一、结构体(struct)
1.1 知识点概述
结构体是C语言中最重要的自定义数据类型之一,它允许我们将多个不同类型的数据组合在一起,形成一个整体。在学生信息管理系统中,每个学生具有姓名、学号、成绩等多种属性,这些属性类型各不相同,非常适合用结构体来表示。
1.2 结构体的定义
c
struct 结构体名 {
数据类型 成员名1;
数据类型 成员名2;
数据类型 成员名3;
...
}; /* 注意:此处的分号不能省略 */
语法要点:
struct是定义结构体的关键字- 结构体名是自定义的标识符
- 大括号内是结构体的成员列表
- 每个成员可以是任意合法的数据类型(包括基本类型、数组、指针等)
- 定义末尾必须有分号
; - 定义结构体只是创建了一个"模板",并不会分配内存
1.3 结构体的实际应用
在学生信息管理系统中,通常定义如下结构体:
c
struct Student {
char id[20]; /* 学号,使用字符数组存储 */
char name[20]; /* 姓名,使用字符数组存储 */
float score[3]; /* 三门课程的成绩,使用浮点型数组存储 */
float average; /* 平均成绩 */
float total; /* 总分 */
};
1.4 使用 typedef 简化结构体
c
#include <stdio.h>
#include <string.h>
/* 使用typedef为结构体起别名 */
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 姓名 */
float score[3]; /* 三科成绩 */
float total; /* 总分 */
float average; /* 平均分 */
} STU; /* STU就是这个结构体的别名 */
int main() {
STU stu1; /* 直接用STU声明变量,无需写struct */
strcpy(stu1.id, "2024001"); /* 用strcpy赋值字符串成员 */
strcpy(stu1.name, "张三"); /* 用strcpy赋值字符串成员 */
stu1.score[0] = 90.0; /* 第一科成绩赋值 */
stu1.score[1] = 85.5; /* 第二科成绩赋值 */
stu1.score[2] = 92.0; /* 第三科成绩赋值 */
/* 计算总分 */
stu1.total = stu1.score[0] + stu1.score[1] + stu1.score[2];
/* 计算平均分 */
stu1.average = stu1.total / 3.0;
/* 输出学生信息 */
printf("学号: %s\n", stu1.id); /* 输出学号 */
printf("姓名: %s\n", stu1.name); /* 输出姓名 */
printf("总分: %.1f\n", stu1.total); /* 输出总分,保留1位小数 */
printf("平均分: %.1f\n", stu1.average); /* 输出平均分 */
return 0; /* 程序正常结束 */
}
运行结果:
学号: 2024001
姓名: 张三
总分: 267.5
平均分: 89.2
1.5 结构体变量的三种初始化方式
c
/* 方式1:定义时初始化 */
STU stu1 = {"2024001", "张三", {90, 85, 92}, 267, 89.0};
/* 方式2:逐个成员赋值 */
STU stu2;
strcpy(stu2.id, "2024002"); /* 字符串成员用strcpy */
strcpy(stu2.name, "李四");
stu2.score[0] = 88.0; /* 数值成员直接赋值 */
stu2.score[1] = 76.5;
stu2.score[2] = 91.0;
stu2.total = stu2.score[0] + stu2.score[1] + stu2.score[2];
stu2.average = stu2.total / 3.0;
/* 方式3:指定成员初始化(C99标准) */
STU stu3 = {
.id = "2024003",
.name = "王五",
.score = {95, 88, 90}
};
1.6 结构体数组
在管理系统中,需要存储多个学生的信息,因此使用结构体数组:
c
#include <stdio.h>
#include <string.h>
/* 定义学生结构体 */
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 姓名 */
float score[3]; /* 三科成绩 */
float total; /* 总分 */
float average; /* 平均分 */
} STU;
int main() {
/* 定义结构体数组,最多存储100个学生 */
STU students[100];
int count = 0; /* 记录当前学生数量 */
/* 录入第一个学生信息 */
strcpy(students[0].id, "2024001");
strcpy(students[0].name, "张三");
students[0].score[0] = 90.0;
students[0].score[1] = 85.5;
students[0].score[2] = 92.0;
students[0].total = students[0].score[0] + students[0].score[1] + students[0].score[2];
students[0].average = students[0].total / 3.0;
count++; /* 学生数量加1 */
/* 录入第二个学生信息 */
strcpy(students[1].id, "2024002");
strcpy(students[1].name, "李四");
students[1].score[0] = 88.0;
students[1].score[1] = 76.5;
students[1].score[2] = 91.0;
students[1].total = students[1].score[0] + students[1].score[1] + students[1].score[2];
students[1].average = students[1].total / 3.0;
count++; /* 学生数量加1 */
/* 遍历并输出所有学生信息 */
printf("%-12s %-10s %-8s %-8s %-8s %-8s %-8s\n",
"学号", "姓名", "成绩1", "成绩2", "成绩3", "总分", "平均分");
printf("-----------------------------------------------------------\n");
int i; /* 循环变量,单独声明以兼容C89 */
for (i = 0; i < count; i++) {
printf("%-12s %-10s %-8.1f %-8.1f %-8.1f %-8.1f %-8.1f\n",
students[i].id, /* 第i个学生的学号 */
students[i].name, /* 第i个学生的姓名 */
students[i].score[0], /* 第i个学生的第1科成绩 */
students[i].score[1], /* 第i个学生的第2科成绩 */
students[i].score[2], /* 第i个学生的第3科成绩 */
students[i].total, /* 第i个学生的总分 */
students[i].average); /* 第i个学生的平均分 */
}
return 0; /* 程序正常结束 */
}
运行结果:
学号 姓名 成绩1 成绩2 成绩3 总分 平均分
-----------------------------------------------------------
2024001 张三 90.0 85.5 92.0 267.5 89.2
2024002 李四 88.0 76.5 91.0 255.5 85.2
1.7 结构体指针
c
#include <stdio.h>
#include <string.h>
/* 定义学生结构体 */
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 姓名 */
float score; /* 成绩 */
} STU;
int main() {
STU stu1; /* 定义结构体变量 */
STU *p = &stu1; /* 定义结构体指针,指向stu1 */
/* 通过结构体变量访问成员,使用 "." 运算符 */
strcpy(stu1.id, "2024001");
strcpy(stu1.name, "张三");
stu1.score = 95.5;
/* 通过结构体指针访问成员,使用 "->" 运算符 */
printf("学号: %s\n", p->id); /* 等价于 (*p).id */
printf("姓名: %s\n", p->name); /* 等价于 (*p).name */
printf("成绩: %.1f\n", p->score); /* 等价于 (*p).score */
return 0; /* 程序正常结束 */
}
要点总结:
- 用
.运算符通过结构体变量访问成员 - 用
->运算符通过结构体指针访问成员 p->member等价于(*p).member,但更简洁常用
二、预处理指令
2.1 知识点概述
预处理指令是C语言编译过程的第一步,在编译之前 由预处理器处理。预处理指令都以 # 开头,行末不需要分号。在学生信息管理系统中,常用的预处理指令包括文件包含 #include 和宏定义 #define。
2.2 文件包含指令 #include
c
/* 方式1:用尖括号包含系统头文件
编译器在系统目录中查找头文件 */
#include <stdio.h> /* 标准输入输出头文件 */
#include <stdlib.h> /* 标准库头文件,包含exit()、system()等 */
#include <string.h> /* 字符串操作头文件,包含strcpy()、strcmp()等 */
/* 方式2:用双引号包含自定义头文件
编译器先在当前目录查找,找不到再到系统目录查找 */
#include "student.h" /* 包含自定义的学生信息头文件 */
两者区别:
| 形式 | 查找路径 | 适用场景 |
|---|---|---|
<文件名> |
系统头文件目录 | 标准库头文件 |
"文件名" |
先当前目录,再系统目录 | 用户自定义头文件 |
2.3 宏定义 #define
2.3.1 无参数的宏定义
c
/* 无参数宏定义:用一个标识符代表一个字符串 */
#define PI 3.14159 /* 定义圆周率 */
#define MAX_STU 100 /* 定义最大学生数 */
#define MAX_NAME 20 /* 定义姓名最大长度 */
#define MAX_ID 20 /* 定义学号最大长度 */
#define COURSE_NUM 3 /* 定义课程数量 */
#define FILENAME "student.dat" /* 定义数据文件名 */
使用示例:
c
#include <stdio.h>
/* 宏定义常量 */
#define MAX_STU 100 /* 最大学生数量 */
#define COURSE_NUM 3 /* 课程数量 */
int main() {
/* 使用宏定义的常量来声明数组大小 */
float scores[COURSE_NUM]; /* 数组大小由宏定义决定 */
int count = 0; /* 当前学生计数 */
/* 当学生数量不超过最大限制时,允许录入 */
if (count < MAX_STU) {
printf("可以继续录入学生信息\n");
printf("最多可录入 %d 名学生\n", MAX_STU); /* 宏在预处理时被替换 */
printf("每人 %d 门课程\n", COURSE_NUM); /* 宏在预处理时被替换 */
}
return 0; /* 程序正常结束 */
}
运行结果:
可以继续录入学生信息
最多可录入 100 名学生
每人 3 门课程
2.3.2 带参数的宏定义
c
/* 带参数的宏定义 */
#define MAX(a, b) ((a) > (b) ? (a) : (b)) /* 求两个数中的较大值 */
#define MIN(a, b) ((a) < (b) ? (a) : (b)) /* 求两个数中的较小值 */
#define SWAP(a, b) { float t = a; a = b; b = t; } /* 交换两个float变量的值 */
使用示例:
c
#include <stdio.h>
/* 带参数宏定义:计算两个数中的最大值 */
/* 注意:每个参数和整个表达式都要用括号括起来,避免运算符优先级问题 */
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
float score1 = 85.5; /* 第一门课成绩 */
float score2 = 92.0; /* 第二门课成绩 */
/* 用宏求两门课的最高分 */
float best = MAX(score1, score2);
printf("最高分: %.1f\n", best); /* 输出92.0 */
/* 宏也可以直接用在表达式中 */
printf("两门课分差: %.1f\n", MAX(score1, score2) - MIN(score1, score2));
return 0; /* 程序正常结束 */
}
运行结果:
最高分: 92.0
两门课分差: 6.5
2.4 条件编译
c
/* 条件编译:根据条件决定编译哪些代码 */
#ifndef STUDENT_H /* 如果没有定义STUDENT_H */
#define STUDENT_H /* 则定义STUDENT_H(防止头文件被重复包含) */
/* 头文件的内容写在这里 */
typedef struct {
char id[20];
char name[20];
float score[3];
} STU;
#endif /* 条件编译结束 */
三种条件编译形式:
c
/* 形式1:#ifdef ------ 如果定义了某个宏 */
#ifdef DEBUG
printf("调试信息:当前学生数量为 %d\n", count); /* 只在DEBUG模式下输出 */
#endif
/* 形式2:#ifndef ------ 如果没有定义某个宏(常用于头文件保护) */
#ifndef MAX_STU
#define MAX_STU 100 /* 如果未定义,则定义它 */
#endif
/* 形式3:#if ------ 根据常量表达式 */
#if COURSE_NUM > 3
printf("课程数量较多\n"); /* COURSE_NUM大于3时编译此行 */
#else
printf("课程数量正常\n"); /* 否则编译此行 */
#endif
2.5 预处理模块综合示例
c
#include <stdio.h> /* 标准输入输出 */
#include <stdlib.h> /* 标准库函数 */
#include <string.h> /* 字符串操作函数 */
/* 宏定义:系统配置参数 */
#define MAX_STU 100 /* 最大学生数量 */
#define MAX_NAME 20 /* 姓名最大长度 */
#define MAX_ID 20 /* 学号最大长度 */
#define COURSE_NUM 3 /* 课程门数 */
#define FILENAME "student.dat" /* 数据存储文件名 */
/* 带参数宏 */
#define TOTAL(s) (s[0] + s[1] + s[2]) /* 计算总分 */
#define AVERAGE(s) (TOTAL(s) / COURSE_NUM) /* 计算平均分 */
/* 结构体定义 */
typedef struct {
char id[MAX_ID]; /* 学号 */
char name[MAX_NAME]; /* 姓名 */
float score[COURSE_NUM]; /* 各科成绩 */
float total; /* 总分 */
float average; /* 平均分 */
} STU;
int main() {
STU stu = {"2024001", "张三", {90, 85, 92}}; /* 初始化结构体 */
/* 使用宏计算总分和平均分 */
stu.total = TOTAL(stu.score); /* 展开后为:stu.score[0]+stu.score[1]+stu.score[2] */
stu.average = AVERAGE(stu.score); /* 展开后为:TOTAL(stu.score)/3 */
printf("学生信息管理系统 v1.0\n");
printf("最多管理 %d 名学生,每人 %d 门课程\n", MAX_STU, COURSE_NUM);
printf("数据保存文件: %s\n\n", FILENAME);
printf("学号: %s\n", stu.id);
printf("姓名: %s\n", stu.name);
printf("总分: %.1f\n", stu.total);
printf("平均分: %.1f\n", stu.average);
return 0; /* 程序正常结束 */
}
运行结果:
学生信息管理系统 v1.0
最多管理 100 名学生,每人 3 门课程
数据保存文件: student.dat
学号: 2024001
姓名: 张三
总分: 267.0
平均分: 89.0
三、文件操作
3.1 知识点概述
文件操作是学生信息管理系统的核心 功能。系统需要将学生数据持久化保存到文件中,以便程序关闭后数据不会丢失。C语言通过 <stdio.h> 中提供的文件操作函数来实现读写。
3.2 FILE 指针
c
/* FILE 是C语言中定义的结构体类型,用于描述文件的各种信息 */
FILE *fp; /* 定义FILE类型的指针变量 */
所有文件操作都需要通过 FILE 指针进行,它是文件操作的"句柄"。
3.3 文件打开与关闭
c
/* fopen() 打开文件
参数1:文件路径(字符串)
参数2:打开模式(字符串)
返回值:成功返回FILE指针,失败返回NULL */
FILE *fp = fopen("student.dat", "rb");
/* fclose() 关闭文件
参数:FILE指针
返回值:成功返回0,失败返回EOF(-1) */
int result = fclose(fp);
常用文件打开模式:
| 模式 | 含义 | 文件不存在时 |
|---|---|---|
"r" |
只读(文本模式) | 打开失败 |
"w" |
只写(文本模式),清空原内容 | 创建新文件 |
"a" |
追加写(文本模式) | 创建新文件 |
"rb" |
只读(二进制模式) | 打开失败 |
"wb" |
只写(二进制模式),清空原内容 | 创建新文件 |
"ab" |
追加写(二进制模式) | 创建新文件 |
"r+" |
读写(文本模式) | 打开失败 |
"rb+" |
读写(二进制模式) | 打开失败 |
"wb+" |
读写(二进制模式),清空原内容 | 创建新文件 |
3.4 判断文件是否成功打开
c
#include <stdio.h>
#include <stdlib.h> /* 包含exit()函数 */
int main() {
/* 以只读方式打开文件 */
FILE *fp = fopen("student.dat", "rb");
/* 判断文件是否成功打开 */
if (fp == NULL) {
printf("文件打开失败!\n");
exit(1); /* 异常退出程序,返回状态码1 */
}
printf("文件打开成功!\n");
/* 操作完成后关闭文件 */
fclose(fp); /* 关闭文件,释放资源 */
return 0; /* 程序正常结束 */
}
3.5 文本文件读写函数
3.5.1 fprintf 和 fscanf(格式化读写)
c
#include <stdio.h>
#include <string.h>
/* 定义学生结构体 */
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 姓名 */
float score; /* 成绩 */
} STU;
int main() {
STU stu_write = {"2024001", "张三", 95.5}; /* 要写入的学生 */
STU stu_read; /* 用于读取的学生 */
FILE *fp; /* 文件指针 */
/* ---- 写入文件 ---- */
fp = fopen("test.txt", "w"); /* 以写模式打开文本文件 */
if (fp == NULL) {
printf("文件打开失败!\n");
return 1; /* 返回非0值表示异常 */
}
/* fprintf: 向文件中按格式写入数据,用法类似printf */
fprintf(fp, "%s %s %.1f\n",
stu_write.id, /* 写入学号 */
stu_write.name, /* 写入姓名 */
stu_write.score); /* 写入成绩 */
fclose(fp); /* 关闭文件 */
/* ---- 从文件读取 ---- */
fp = fopen("test.txt", "r"); /* 以读模式打开文本文件 */
if (fp == NULL) {
printf("文件打开失败!\n");
return 1;
}
/* fscanf: 从文件中按格式读取数据,用法类似scanf */
fscanf(fp, "%s %s %f",
stu_read.id, /* 读取到学号 */
stu_read.name, /* 读取到姓名 */
&stu_read.score); /* 读取到成绩,注意取地址符& */
fclose(fp); /* 关闭文件 */
/* 输出读取到的数据 */
printf("学号: %s\n", stu_read.id);
printf("姓名: %s\n", stu_read.name);
printf("成绩: %.1f\n", stu_read.score);
return 0; /* 程序正常结束 */
}
运行结果:
学号: 2024001
姓名: 张三
成绩: 95.5
3.6 二进制文件读写函数
3.6.1 fwrite(二进制写入)
c
/* fwrite 函数原型 */
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
/* 参数说明:
ptr ------ 要写入的数据的起始地址
size ------ 每个数据元素的字节数(通常用sizeof获得)
count ------ 要写入的数据元素个数
stream ------ FILE指针
返回值 ------ 成功写入的元素个数 */
3.6.2 fread(二进制读取)
c
/* fread 函数原型 */
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
/* 参数说明:
ptr ------ 存放读取数据的目标地址
size ------ 每个数据元素的字节数
count ------ 要读取的数据元素个数
stream ------ FILE指针
返回值 ------ 成功读取的元素个数 */
3.6.3 二进制读写完整示例
c
#include <stdio.h>
#include <string.h>
/* 定义学生结构体 */
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 姓名 */
float score; /* 成绩 */
} STU;
int main() {
STU students[3]; /* 学生数组 */
STU read_buf[3]; /* 读取缓冲区 */
int count = 3; /* 学生数量 */
FILE *fp; /* 文件指针 */
int i; /* 循环变量 */
/* 初始化3个学生数据 */
strcpy(students[0].id, "2024001");
strcpy(students[0].name, "张三");
students[0].score = 90.5;
strcpy(students[1].id, "2024002");
strcpy(students[1].name, "李四");
students[1].score = 85.0;
strcpy(students[2].id, "2024003");
strcpy(students[2].name, "王五");
students[2].score = 92.0;
/* ---- 二进制方式写入文件 ---- */
fp = fopen("student.dat", "wb"); /* 以二进制写模式打开文件 */
if (fp == NULL) {
printf("文件打开失败!\n");
return 1;
}
/* 将整个结构体数组一次性写入文件 */
fwrite(students, sizeof(STU), count, fp);
/* 参数说明:
students ------ 数组首地址
sizeof(STU) ------ 每个元素占的字节数
count ------ 写入3个元素
fp ------ 目标文件 */
fclose(fp); /* 关闭文件 */
printf("写入 %d 条学生记录到文件\n", count);
/* ---- 二进制方式从文件读取 ---- */
fp = fopen("student.dat", "rb"); /* 以二进制读模式打开文件 */
if (fp == NULL) {
printf("文件打开失败!\n");
return 1;
}
/* 从文件中读取数据到结构体数组 */
int read_count = fread(read_buf, sizeof(STU), count, fp);
/* 参数说明:
read_buf ------ 存放读取数据的目标数组
sizeof(STU) ------ 每个元素占的字节数
count ------ 最多读取3个元素
fp ------ 源文件
返回值 ------ 实际读取到的元素个数 */
fclose(fp); /* 关闭文件 */
/* 输出读取到的所有学生信息 */
printf("从文件读取到 %d 条记录:\n", read_count);
for (i = 0; i < read_count; i++) {
printf("学号:%-10s 姓名:%-8s 成绩:%.1f\n",
read_buf[i].id,
read_buf[i].name,
read_buf[i].score);
}
return 0; /* 程序正常结束 */
}
运行结果:
写入 3 条学生记录到文件
从文件读取到 3 条记录:
学号:2024001 姓名:张三 成绩:90.5
学号:2024002 姓名:李四 成绩:85.0
学号:2024003 姓名:王五 成绩:92.0
3.7 其他常用文件操作函数
3.7.1 rewind ------ 将文件指针重置到文件开头
c
rewind(fp);
/* 等价于 fseek(fp, 0, SEEK_SET); */
/* 将文件内部位置指针重新指向文件起始位置 */
3.7.2 fseek ------ 移动文件指针到指定位置
c
/* fseek 函数原型 */
int fseek(FILE *stream, long offset, int whence);
/* 参数说明:
stream ------ FILE指针
offset ------ 偏移量(字节数)
whence ------ 起始位置,有三个取值:
SEEK_SET(值为0)------ 从文件开头
SEEK_CUR(值为1)------ 从当前位置
SEEK_END(值为2)------ 从文件末尾
返回值 ------ 成功返回0,失败返回非0 */
使用示例:
c
/* 将文件指针移动到第2个学生记录的位置(从0开始计数,即第3个) */
fseek(fp, 2 * sizeof(STU), SEEK_SET);
/* 将文件指针移动到文件末尾 */
fseek(fp, 0, SEEK_END);
/* 获取文件大小(字节数) */
long file_size = ftell(fp);
printf("文件大小: %ld 字节\n", file_size);
/* 计算文件中存储了多少条学生记录 */
long record_count = file_size / sizeof(STU);
printf("文件中有 %ld 条学生记录\n", record_count);
3.7.3 ftell ------ 获取当前文件指针位置
c
long pos = ftell(fp);
/* 返回文件指针的当前位置(相对于文件开头的字节数) */
3.8 文件操作综合示例:判断文件是否有内容
c
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp;
/* 尝试以读写模式打开文件 */
fp = fopen("student.dat", "rb");
if (fp == NULL) {
/* 文件不存在,这是首次使用系统 */
printf("文件不存在,将创建新文件\n");
fp = fopen("student.dat", "wb"); /* 创建新文件 */
if (fp == NULL) {
printf("创建文件失败!\n");
exit(1); /* 创建失败则退出 */
}
printf("文件创建成功!\n");
fclose(fp); /* 关闭文件 */
} else {
/* 文件存在,检查是否有内容 */
fseek(fp, 0, SEEK_END); /* 将指针移到文件末尾 */
long size = ftell(fp); /* 获取文件大小 */
if (size == 0) {
printf("文件存在但没有内容\n");
} else {
printf("文件存在,大小为 %ld 字节\n", size);
printf("文件中有数据可供读取\n");
}
fclose(fp); /* 关闭文件 */
}
return 0; /* 程序正常结束 */
}
四、字符串操作函数
4.1 知识点概述
在学生信息管理系统中,学号和姓名都是字符串类型,需要用到 <string.h> 中的各种字符串处理函数。
4.2 常用字符串函数详解
4.2.1 strcpy ------ 字符串复制
c
/* 函数原型 */
char *strcpy(char *dest, const char *src);
/* 将src指向的字符串复制到dest指向的内存空间
注意:dest空间必须足够大,否则会溢出 */
c
char name[20];
strcpy(name, "张三"); /* 将"张三"复制到name数组中 */
printf("%s\n", name); /* 输出:张三 */
4.2.2 strcmp ------ 字符串比较
c
/* 函数原型 */
int strcmp(const char *s1, const char *s2);
/* 比较规则:逐字符比较ASCII码值
返回值:
< 0 ------ s1 < s2(s1排在s2前面)
= 0 ------ s1 == s2(两个字符串完全相同)
> 0 ------ s1 > s2(s1排在s2后面) */
c
char id1[20] = "2024001";
char id2[20] = "2024002";
int result = strcmp(id1, id2); /* 比较两个学号 */
if (result == 0) {
printf("学号相同\n");
} else if (result < 0) {
printf("id1 排在 id2 前面\n");
} else {
printf("id1 排在 id2 后面\n");
}
4.2.3 strcat ------ 字符串连接
c
/* 函数原型 */
char *strcat(char *dest, const char *src);
/* 将src字符串追加到dest字符串末尾 */
c
char greeting[50] = "你好,";
strcat(greeting, "欢迎使用学生管理系统!");
printf("%s\n", greeting); /* 输出:你好,欢迎使用学生管理系统! */
4.2.4 strlen ------ 求字符串长度
c
/* 函数原型 */
size_t strlen(const char *s);
/* 返回字符串中字符的个数(不含结束符'\0') */
c
char name[20] = "张三";
int len = strlen(name); /* 返回的是字符个数,不是字节数 */
printf("名字长度: %d\n", len); /* 输出:名字长度: 2 */
4.2.5 strstr ------ 字符串查找
c
/* 函数原型 */
char *strstr(const char *haystack, const char *needle);
/* 在haystack中查找needle子串
找到则返回首次匹配的位置指针,找不到返回NULL */
c
char name[20] = "张三丰";
char *pos = strstr(name, "张三"); /* 在"张三丰"中查找"张三" */
if (pos != NULL) {
printf("找到了!\n"); /* "张三丰"中包含"张三" */
}
五、输入输出与缓冲区处理
5.1 知识点概述
在学生信息管理系统中,需要频繁地从键盘读取用户输入。C语言的输入函数需要注意缓冲区残留数据的问题。
5.2 scanf 与缓冲区
c
#include <stdio.h>
int main() {
int num;
char name[20];
printf("请输入一个整数: ");
scanf("%d", &num); /* 读取整数 */
/*
* 问题:scanf("%d") 读取数字后,回车键'\n'残留在缓冲区
* 如果接下来用gets()或scanf("%s")读取,会直接读到'\n'
* 解决方案:用getchar()吃掉残留的'\n'
*/
getchar(); /* 消耗掉缓冲区中的'\n' */
printf("请输入姓名: ");
scanf("%s", name); /* 现在可以正确读取姓名了 */
printf("整数: %d, 姓名: %s\n", num, name);
return 0;
}
5.3 清空输入缓冲区
c
/* 方法:循环读取并丢弃缓冲区中的所有字符,直到遇到换行符或EOF */
void clear_buffer() {
int c; /* 用int类型存储字符,因为EOF是-1 */
while ((c = getchar()) != '\n' && c != EOF) {
/* 逐个读取并丢弃,不做任何处理 */
}
}
5.4 gets 和 fgets
c
#include <stdio.h>
#include <string.h>
int main() {
char name[20];
printf("请输入姓名: ");
/* fgets 从标准输入读取一行
参数1:存放结果的字符数组
参数2:最多读取的字符数(含'\0')
参数3:输入流(stdin表示键盘)
注意:fgets会把换行符'\n'也读进来 */
fgets(name, sizeof(name), stdin);
/* 去除fgets读入的换行符 */
int len = strlen(name); /* 获取字符串长度 */
if (len > 0 && name[len - 1] == '\n') {
name[len - 1] = '\0'; /* 将末尾的换行符替换为结束符 */
}
printf("你输入的姓名是: %s\n", name);
return 0;
}
六、菜单界面设计(switch-case)
6.1 知识点概述
学生信息管理系统采用菜单驱动 的交互方式,用户通过输入数字选择功能。这需要用到循环结构和 switch-case 分支结构。
6.2 switch-case 语句详解
c
switch (表达式) {
case 常量1:
语句1;
break; /* 跳出switch */
case 常量2:
语句2;
break;
case 常量3:
case 常量4:
语句3; /* case 3和case 4执行相同代码 */
break;
default:
默认语句; /* 所有case都不匹配时执行 */
break;
}
语法要点:
switch后面的表达式必须是整型或字符型case后面必须是常量表达式,不能是变量- 每个
case末尾的break不能省略,否则会"穿透"到下一个case default是可选的,用于处理所有case都不匹配的情况
6.3 完整的菜单界面示例
c
#include <stdio.h>
#include <stdlib.h>
/* 函数声明 */
void menu(void); /* 显示主菜单 */
int main() {
int choice; /* 用户选择的功能编号 */
while (1) { /* 无限循环,直到用户选择退出 */
menu(); /* 显示菜单 */
printf("\n请输入您的选择(0-8): ");
scanf("%d", &choice); /* 读取用户选择 */
getchar(); /* 消耗回车符 */
switch (choice) {
case 1:
printf(">> 录入学生信息\n\n");
/* 调用录入函数(此处为示意) */
break; /* 跳出switch */
case 2:
printf(">> 查询学生信息\n\n");
/* 调用查询函数 */
break;
case 3:
printf(">> 删除学生信息\n\n");
/* 调用删除函数 */
break;
case 4:
printf(">> 修改学生信息\n\n");
/* 调用修改函数 */
break;
case 5:
printf(">> 插入学生信息\n\n");
/* 调用插入函数 */
break;
case 6:
printf(">> 学生成绩排序\n\n");
/* 调用排序函数 */
break;
case 7:
printf(">> 显示所有学生信息\n\n");
/* 调用显示函数 */
break;
case 8:
printf(">> 保存到文件\n\n");
/* 调用保存函数 */
break;
case 0:
printf("感谢使用,再见!\n");
exit(0); /* 退出程序 */
default:
printf("输入错误,请重新选择!\n\n");
break; /* 处理无效输入 */
}
}
return 0;
}
/* 显示主菜单函数 */
void menu(void) {
printf("===================================\n");
printf(" 学生信息管理系统 v1.0\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(" 8. 保存到文件\n");
printf(" 0. 退出系统\n");
printf("===================================\n");
}
七、函数的定义与调用
7.1 知识点概述
学生信息管理系统采用模块化设计,将每个功能封装为独立的函数,使代码结构清晰、易于维护。
7.2 函数的定义
c
/* 语法格式 */
返回值类型 函数名(参数列表) {
函数体;
return 返回值; /* void类型函数可省略 */
}
7.3 函数声明(函数原型)
c
/* 当函数定义在调用之后,需要在文件开头声明函数原型 */
void inputStudent(STU students[], int *count); /* 录入学生信息 */
void displayAll(STU students[], int count); /* 显示所有学生 */
int searchById(STU students[], int count, char *id); /* 按学号查找 */
void deleteStudent(STU students[], int *count, int index); /* 删除学生 */
void sort(STU students[], int count); /* 排序 */
7.4 向函数传递结构体数组
c
#include <stdio.h>
#include <string.h>
/* 结构体定义 */
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 姓名 */
float score; /* 成绩 */
} STU;
/* 函数声明 */
void inputStudent(STU students[], int *count); /* 录入函数 */
void displayAll(STU students[], int count); /* 显示函数 */
int main() {
STU students[100]; /* 学生数组,最多100人 */
int count = 0; /* 当前学生数量 */
inputStudent(students, &count); /* 录入学生,传入数组和计数的地址 */
displayAll(students, count); /* 显示所有学生 */
return 0;
}
/* 录入一个学生信息
参数1:学生数组
参数2:学生数量的指针(通过指针修改实参的值) */
void inputStudent(STU students[], int *count) {
printf("请输入学号: ");
scanf("%s", students[*count].id); /* 读取学号到当前空位 */
printf("请输入姓名: ");
scanf("%s", students[*count].name); /* 读取姓名 */
printf("请输入成绩: ");
scanf("%f", &students[*count].score); /* 读取成绩 */
(*count)++; /* 学生数量加1(通过指针修改外部变量) */
printf("录入成功!当前共 %d 名学生\n", *count);
}
/* 显示所有学生信息
参数1:学生数组(只读,不修改)
参数2:当前学生数量 */
void displayAll(STU students[], int count) {
int i;
printf("\n%-12s %-10s %-8s\n", "学号", "姓名", "成绩");
printf("--------------------------------\n");
for (i = 0; i < count; i++) {
printf("%-12s %-10s %-8.1f\n",
students[i].id,
students[i].name,
students[i].score);
}
}
运行结果:
请输入学号: 2024001
请输入姓名: 张三
请输入成绩: 95.5
录入成功!当前共 1 名学生
学号 姓名 成绩
--------------------------------
2024001 张三 95.5
八、录入学生信息模块
8.1 知识点概述
录入模块是系统的基础功能,负责从键盘获取学生信息并存储到结构体数组中。需要处理两种情况:文件中无数据(首次使用)和文件中已有数据(追加录入)。
8.2 录入时文件中无内容
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_STU 100 /* 最大学生数量 */
#define FILENAME "student.dat" /* 数据文件名 */
/* 学生结构体 */
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 姓名 */
float score[3]; /* 三科成绩 */
float total; /* 总分 */
float average; /* 平均分 */
} STU;
int main() {
STU students[MAX_STU]; /* 学生数组 */
int count = 0; /* 当前学生数量 */
FILE *fp; /* 文件指针 */
/* 检查文件是否存在且有内容 */
fp = fopen(FILENAME, "rb"); /* 尝试以二进制读模式打开 */
if (fp == NULL) {
/* 文件不存在 ------ 首次使用系统 */
printf("欢迎首次使用,数据文件将自动创建\n");
} else {
/* 文件存在,检查是否有内容 */
fseek(fp, 0, SEEK_END); /* 移动到文件末尾 */
long size = ftell(fp); /* 获取文件大小 */
if (size == 0) {
printf("数据文件为空,请开始录入学生信息\n");
} else {
/* 文件有内容,读取已有数据 */
rewind(fp); /* 将文件指针重置到开头 */
count = fread(students, sizeof(STU), MAX_STU, fp);
printf("已加载 %d 条学生记录\n", count);
}
fclose(fp); /* 关闭文件 */
}
/* 录入新学生信息 */
char continue_input = 'y'; /* 是否继续录入的标志 */
while (continue_input == 'y' || continue_input == 'Y') {
if (count >= MAX_STU) { /* 检查是否超出容量 */
printf("学生数量已达上限 %d!\n", MAX_STU);
break; /* 跳出录入循环 */
}
printf("\n--- 录入第 %d 个学生 ---\n", count + 1);
printf("请输入学号: ");
scanf("%s", students[count].id); /* 录入学号 */
printf("请输入姓名: ");
scanf("%s", students[count].name); /* 录入姓名 */
int i;
students[count].total = 0; /* 初始化总分 */
for (i = 0; i < 3; i++) { /* 循环录入3科成绩 */
printf("请输入第%d科成绩: ", i + 1);
scanf("%f", &students[count].score[i]);
students[count].total += students[count].score[i]; /* 累加总分 */
}
students[count].average = students[count].total / 3.0; /* 计算平均分 */
count++; /* 学生数量加1 */
printf("录入成功!当前共 %d 名学生\n", count);
printf("是否继续录入?(y/n): ");
getchar(); /* 消耗回车 */
scanf("%c", &continue_input); /* 读取用户选择 */
}
/* 保存数据到文件 */
fp = fopen(FILENAME, "wb"); /* 以二进制写模式打开(覆盖原内容) */
if (fp == NULL) {
printf("保存失败!\n");
return 1;
}
fwrite(students, sizeof(STU), count, fp); /* 写入所有学生数据 */
fclose(fp); /* 关闭文件 */
printf("\n数据已保存到文件 %s\n", FILENAME);
return 0; /* 程序正常结束 */
}
8.3 录入时文件中有内容(追加)
c
/* 追加录入的核心逻辑 */
void appendStudent(STU students[], int *count) {
FILE *fp;
/* 先从文件加载已有数据 */
fp = fopen(FILENAME, "rb");
if (fp != NULL) {
/* 读取文件中的所有记录 */
*count = fread(students, sizeof(STU), MAX_STU, fp);
fclose(fp);
}
/* 在已有数据后面追加新记录 */
if (*count < MAX_STU) {
printf("请输入新学生的学号: ");
scanf("%s", students[*count].id);
printf("请输入新学生的姓名: ");
scanf("%s", students[*count].name);
int i;
students[*count].total = 0;
for (i = 0; i < 3; i++) {
printf("请输入第%d科成绩: ", i + 1);
scanf("%f", &students[*count].score[i]);
students[*count].total += students[*count].score[i];
}
students[*count].average = students[*count].total / 3.0;
(*count)++; /* 数量加1 */
/* 保存全部数据到文件(覆盖写入) */
fp = fopen(FILENAME, "wb");
if (fp != NULL) {
fwrite(students, sizeof(STU), *count, fp);
fclose(fp);
printf("追加成功!\n");
}
} else {
printf("学生数量已达上限!\n");
}
}
九、查询学生信息模块
9.1 知识点概述
查询模块支持按学号或姓名查找学生。核心是遍历数组 并使用 strcmp 进行字符串匹配。
9.2 按学号查询
c
#include <stdio.h>
#include <string.h>
#define MAX_STU 100
/* 学生结构体 */
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 姓名 */
float score[3]; /* 三科成绩 */
float total; /* 总分 */
float average; /* 平均分 */
} STU;
/* 函数:按学号查找学生
参数1:学生数组
参数2:当前学生数量
参数3:要查找的学号
返回值:找到返回数组下标,找不到返回-1 */
int searchById(STU students[], int count, char *id) {
int i; /* 循环变量 */
for (i = 0; i < count; i++) {
/* strcmp返回0表示两个字符串相同 */
if (strcmp(students[i].id, id) == 0) {
return i; /* 找到了,返回下标 */
}
}
return -1; /* 遍历完没找到,返回-1 */
}
/* 函数:显示一个学生的完整信息
参数:学生结构体变量 */
void displayOne(STU stu) {
int i;
printf("\n---- 学生详细信息 ----\n");
printf("学号: %s\n", stu.id);
printf("姓名: %s\n", stu.name);
for (i = 0; i < 3; i++) {
printf("第%d科成绩: %.1f\n", i + 1, stu.score[i]);
}
printf("总分: %.1f\n", stu.total);
printf("平均分: %.1f\n", stu.average);
printf("---------------------\n");
}
int main() {
STU students[MAX_STU]; /* 学生数组 */
int count = 0; /* 学生数量 */
char search_id[20]; /* 要查找的学号 */
int index; /* 查找结果的下标 */
/* 为了演示,手动设置一些测试数据 */
strcpy(students[0].id, "2024001");
strcpy(students[0].name, "张三");
students[0].score[0] = 90; students[0].score[1] = 85; students[0].score[2] = 92;
students[0].total = 267; students[0].average = 89.0;
count++;
strcpy(students[1].id, "2024002");
strcpy(students[1].name, "李四");
students[1].score[0] = 78; students[1].score[1] = 92; students[1].score[2] = 88;
students[1].total = 258; students[1].average = 86.0;
count++;
/* 查询操作 */
printf("请输入要查询的学号: ");
scanf("%s", search_id); /* 读取要查找的学号 */
index = searchById(students, count, search_id); /* 执行查找 */
if (index != -1) {
printf("找到了!该学生是第 %d 条记录\n", index + 1);
displayOne(students[index]); /* 显示该学生信息 */
} else {
printf("未找到学号为 %s 的学生\n", search_id);
}
return 0; /* 程序正常结束 */
}
运行结果:
请输入要查询的学号: 2024001
找到了!该学生是第 1 条记录
---- 学生详细信息 ----
学号: 2024001
姓名: 张三
第1科成绩: 90.0
第2科成绩: 85.0
第3科成绩: 92.0
总分: 267.0
平均分: 89.0
---------------------
9.3 按姓名查询(模糊查找)
c
/* 函数:按姓名查找(支持模糊匹配)
参数1:学生数组
参数2:当前学生数量
参数3:要查找的姓名关键字
返回值:找到的记录数量 */
int searchByName(STU students[], int count, char *keyword) {
int i;
int found = 0; /* 找到的记录计数 */
printf("\n%-12s %-10s %-8s %-8s %-8s\n",
"学号", "姓名", "总分", "平均分", "排名");
printf("----------------------------------------------\n");
for (i = 0; i < count; i++) {
/* strstr在students[i].name中查找keyword子串
如果找到,返回非NULL指针 */
if (strstr(students[i].name, keyword) != NULL) {
printf("%-12s %-10s %-8.1f %-8.1f\n",
students[i].id,
students[i].name,
students[i].total,
students[i].average);
found++; /* 匹配记录数加1 */
}
}
return found;
}
十、删除学生信息模块
10.1 知识点概述
删除操作的原理是:找到目标学生后,将其后面的所有元素依次前移一位,覆盖被删除的元素,同时将总数量减1。
10.2 删除算法图解
删除前(删除下标为1的"李四"):
下标: 0 1 2 3
数据: 张三 李四 王五 赵六
↑ 后面的元素前移
删除后:
下标: 0 1 2
数据: 张三 王五 赵六
10.3 完整删除示例
c
#include <stdio.h>
#include <string.h>
#define MAX_STU 100
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 姓名 */
float score; /* 成绩 */
} STU;
/* 显示所有学生 */
void displayAll(STU students[], int count) {
int i;
printf("\n%-5s %-12s %-10s %-8s\n", "序号", "学号", "姓名", "成绩");
printf("-------------------------------------\n");
for (i = 0; i < count; i++) {
printf("%-5d %-12s %-10s %-8.1f\n",
i + 1,
students[i].id,
students[i].name,
students[i].score);
}
printf("-------------------------------------\n");
printf("共 %d 名学生\n\n", count);
}
/* 删除指定下标的学生
参数1:学生数组
参数2:学生数量的指针
参数3:要删除的学生下标 */
void deleteStudent(STU students[], int *count, int index) {
int i;
/* 边界检查 */
if (index < 0 || index >= *count) {
printf("删除失败:下标越界!\n");
return; /* 直接返回,不执行删除 */
}
/* 核心操作:将index之后的元素依次前移一位 */
for (i = index; i < *count - 1; i++) {
students[i] = students[i + 1]; /* 结构体整体赋值 */
}
(*count)--; /* 学生数量减1 */
printf("删除成功!\n");
}
int main() {
STU students[MAX_STU] = {
{"2024001", "张三", 90.0},
{"2024002", "李四", 85.0},
{"2024003", "王五", 92.0},
{"2024004", "赵六", 78.0}
};
int count = 4; /* 当前4名学生 */
char del_id[20]; /* 要删除的学号 */
int index = -1; /* 查找结果 */
int i;
printf("删除前:\n");
displayAll(students, count);
printf("请输入要删除的学生学号: ");
scanf("%s", del_id);
/* 查找要删除的学生 */
for (i = 0; i < count; i++) {
if (strcmp(students[i].id, del_id) == 0) {
index = i; /* 找到了,记录下标 */
break; /* 跳出查找循环 */
}
}
if (index != -1) {
printf("找到学生: %s (%s)\n", students[index].name, students[index].id);
deleteStudent(students, &count, index); /* 执行删除 */
printf("\n删除后:\n");
displayAll(students, count); /* 显示删除后的列表 */
} else {
printf("未找到学号为 %s 的学生\n", del_id);
}
return 0;
}
运行结果:
删除前:
序号 学号 姓名 成绩
-------------------------------------
1 2024001 张三 90.0
2 2024002 李四 85.0
3 2024003 王五 92.0
4 2024004 赵六 78.0
-------------------------------------
共 4 名学生
请输入要删除的学生学号: 2024002
找到学生: 李四 (2024002)
删除成功!
删除后:
序号 学号 姓名 成绩
-------------------------------------
1 2024001 张三 90.0
2 2024003 王五 92.0
3 2024004 赵六 78.0
-------------------------------------
共 3 名学生
十一、修改学生信息模块
11.1 知识点概述
修改操作的流程:先查找目标学生,找到后让用户输入新的信息覆盖原有数据。
11.2 完整修改示例
c
#include <stdio.h>
#include <string.h>
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 姓名 */
float score[3]; /* 三科成绩 */
float total; /* 总分 */
float average; /* 平均分 */
} STU;
/* 函数:修改指定学生的成绩
参数1:学生数组
参数2:学生数量
参数3:要修改的学生学号 */
void modifyStudent(STU students[], int count, char *target_id) {
int i;
int found = 0; /* 是否找到的标志 */
for (i = 0; i < count; i++) {
if (strcmp(students[i].id, target_id) == 0) {
/* 找到了目标学生 */
found = 1;
printf("找到学生: %s (%s)\n", students[i].name, students[i].id);
printf("当前成绩: %.1f, %.1f, %.1f\n",
students[i].score[0],
students[i].score[1],
students[i].score[2]);
/* 选择修改项目 */
printf("\n请选择修改项目:\n");
printf("1. 修改姓名\n");
printf("2. 修改成绩\n");
printf("3. 修改全部信息\n");
printf("请输入选择: ");
int choice;
scanf("%d", &choice);
switch (choice) {
case 1:
printf("请输入新姓名: ");
scanf("%s", students[i].name); /* 直接覆盖旧姓名 */
break;
case 2: {
int j;
students[i].total = 0; /* 重置总分 */
for (j = 0; j < 3; j++) {
printf("请输入第%d科新成绩: ", j + 1);
scanf("%f", &students[i].score[j]);
students[i].total += students[i].score[j];
}
students[i].average = students[i].total / 3.0;
break;
}
case 3:
printf("请输入新姓名: ");
scanf("%s", students[i].name);
{
int j;
students[i].total = 0;
for (j = 0; j < 3; j++) {
printf("请输入第%d科新成绩: ", j + 1);
scanf("%f", &students[i].score[j]);
students[i].total += students[i].score[j];
}
students[i].average = students[i].total / 3.0;
}
break;
default:
printf("无效选择!\n");
return;
}
printf("\n修改成功!更新后的信息:\n");
printf("学号: %s\n", students[i].id);
printf("姓名: %s\n", students[i].name);
printf("总分: %.1f\n", students[i].total);
printf("平均分: %.1f\n", students[i].average);
break; /* 找到并修改后,跳出查找循环 */
}
}
if (!found) {
printf("未找到学号为 %s 的学生\n", target_id);
}
}
int main() {
STU students[100] = {
{"2024001", "张三", {90, 85, 92}, 267, 89.0},
{"2024002", "李四", {78, 92, 88}, 258, 86.0}
};
int count = 2;
char target_id[20];
printf("请输入要修改的学生学号: ");
scanf("%s", target_id);
modifyStudent(students, count, target_id); /* 调用修改函数 */
return 0;
}
十二、插入学生信息模块
12.1 知识点概述
插入操作是指在指定位置(如按学号顺序插入)添加新学生。插入的核心是将插入位置之后的所有元素后移一位,腾出空间给新元素。
12.2 插入算法图解
插入前(在下标1处插入"钱七"):
下标: 0 1 2
数据: 张三 李四 王五
↑ 这里开始后移
后移过程:
下标: 0 1 2 3
数据: 张三 ??? 李四 王五
插入后:
下标: 0 1 2 3
数据: 张三 钱七 李四 王五
12.3 完整插入示例
c
#include <stdio.h>
#include <string.h>
#define MAX_STU 100
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 始名 */
float score; /* 成绩 */
} STU;
/* 显示所有学生 */
void displayAll(STU students[], int count) {
int i;
printf("\n%-5s %-12s %-10s %-8s\n", "序号", "学号", "姓名", "成绩");
printf("-------------------------------------\n");
for (i = 0; i < count; i++) {
printf("%-5d %-12s %-10s %-8.1f\n",
i + 1, students[i].id, students[i].name, students[i].score);
}
printf("-------------------------------------\n\n");
}
/* 函数:在指定位置插入学生
参数1:学生数组
参数2:学生数量的指针
参数3:要插入的位置(下标) */
void insertStudent(STU students[], int *count, int pos) {
int i;
/* 边界检查 */
if (*count >= MAX_STU) {
printf("学生数量已达上限,无法插入!\n");
return;
}
if (pos < 0 || pos > *count) {
printf("插入位置无效!有效范围: 0 ~ %d\n", *count);
return;
}
/* 核心操作:将pos及之后的元素后移一位
注意:必须从最后一个元素开始后移,否则会覆盖数据 */
for (i = *count - 1; i >= pos; i--) {
students[i + 1] = students[i]; /* 结构体整体赋值 */
}
/* 在腾出的位置录入新学生信息 */
printf("请输入新学生的学号: ");
scanf("%s", students[pos].id);
printf("请输入新学生的姓名: ");
scanf("%s", students[pos].name);
printf("请输入新学生的成绩: ");
scanf("%f", &students[pos].score);
(*count)++; /* 学生数量加1 */
printf("插入成功!\n");
}
/* 函数:按学号顺序找到插入位置
参数1:学生数组
参数2:当前学生数量
参数3:新学生的学号
返回值:应该插入的位置(下标) */
int findInsertPos(STU students[], int count, char *new_id) {
int i;
for (i = 0; i < count; i++) {
/* 如果新学号比当前学生的学号小,就插入到当前位置 */
if (strcmp(new_id, students[i].id) < 0) {
return i; /* 返回插入位置 */
}
}
return count; /* 新学号最大,插在末尾 */
}
int main() {
STU students[MAX_STU] = {
{"2024001", "张三", 90.0},
{"2024003", "王五", 92.0},
{"2024004", "赵六", 78.0}
};
int count = 3;
STU new_stu;
printf("插入前:\n");
displayAll(students, count);
/* 先获取新学生学号,用于确定插入位置 */
printf("请输入新学生的学号: ");
scanf("%s", new_stu.id);
/* 按学号顺序找到插入位置 */
int pos = findInsertPos(students, count, new_stu.id);
printf("将插入到位置 %d\n", pos);
/* 在该位置插入新学生 */
/* 由于findInsertPos已经读取了学号,这里手动填入 */
/* 为演示完整流程,这里重新实现插入 */
int i;
for (i = count - 1; i >= pos; i--) {
students[i + 1] = students[i]; /* 后移 */
}
strcpy(students[pos].id, new_stu.id); /* 填入学号 */
printf("请输入新学生的姓名: ");
scanf("%s", students[pos].name);
printf("请输入新学生的成绩: ");
scanf("%f", &students[pos].score);
count++;
printf("\n插入后:\n");
displayAll(students, count);
return 0;
}
运行结果(假设输入学号2024002,姓名钱七,成绩88):
插入前:
序号 学号 姓名 成绩
-------------------------------------
1 2024001 张三 90.0
2 2024003 王五 92.0
3 2024004 赵六 78.0
-------------------------------------
请输入新学生的学号: 2024002
将插入到位置 1
请输入新学生的姓名: 钱七
请输入新学生的成绩: 88
插入后:
序号 学号 姓名 成绩
-------------------------------------
1 2024001 张三 90.0
2 2024002 钱七 88.0
3 2024003 王五 92.0
4 2024004 赵六 78.0
-------------------------------------
十三、学生成绩排序模块
13.1 知识点概述
排序模块对学生按成绩进行排列,最常用的是冒泡排序。在管理系统中,排序的对象是结构体数组,每次"交换"实际上是交换整个结构体。
13.2 冒泡排序算法
基本思想: 相邻元素两两比较,如果顺序错误就交换,每一轮将一个最值"冒泡"到正确位置。
13.3 冒泡排序图解
原始数据: [90, 78, 85, 92, 88]
第1轮:比较相邻元素,将最大值冒泡到末尾
[90,78] → 交换 → [78,90,85,92,88]
[90,85] → 交换 → [78,85,90,92,88]
[90,92] → 不换 → [78,85,90,92,88]
[92,88] → 不换 → [78,85,90,88,92] ← 92到位
第2轮:
[78,85] → 不换 → [78,85,90,88,92]
[85,90] → 不换 → [78,85,90,88,92]
[90,88] → 交换 → [78,85,88,90,92] ← 90到位
第3轮:
[78,85] → 不换 → [78,85,88,90,92]
[85,88] → 不换 → [78,85,88,90,92] ← 88到位
第4轮:
[78,85] → 不换 → [78,85,88,90,92] ← 排序完成
13.4 完整排序示例
c
#include <stdio.h>
#include <string.h>
#define MAX_STU 100
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 姓名 */
float score[3]; /* 三科成绩 */
float total; /* 总分 */
float average; /* 平均分 */
} STU;
/* 显示所有学生 */
void displayAll(STU students[], int count) {
int i;
printf("\n%-5s %-12s %-10s %-8s %-8s\n",
"排名", "学号", "姓名", "总分", "平均分");
printf("----------------------------------------------\n");
for (i = 0; i < count; i++) {
printf("%-5d %-12s %-10s %-8.1f %-8.1f\n",
i + 1,
students[i].id,
students[i].name,
students[i].total,
students[i].average);
}
printf("----------------------------------------------\n\n");
}
/* 冒泡排序 - 按总分降序排列
参数1:学生数组
参数2:学生数量
参数3:排序方式 1=升序 0=降序 */
void bubbleSort(STU students[], int count, int order) {
int i, j; /* 循环变量 */
STU temp; /* 临时变量,用于交换 */
/* 外层循环:控制比较的轮数,共需要count-1轮 */
for (i = 0; i < count - 1; i++) {
/* 内层循环:每轮比较的次数
第i轮需要比较 count-1-i 次
因为每轮结束后,末尾已经排好了一个元素 */
for (j = 0; j < count - 1 - i; j++) {
/* 比较相邻两个学生的总分 */
if (order == 1) {
/* 升序:如果前面的总分大于后面的,交换 */
if (students[j].total > students[j + 1].total) {
temp = students[j]; /* 暂存第j个元素 */
students[j] = students[j + 1]; /* 第j个 = 第j+1个 */
students[j + 1] = temp; /* 第j+1个 = 暂存值 */
}
} else {
/* 降序:如果前面的总分小于后面的,交换 */
if (students[j].total < students[j + 1].total) {
temp = students[j];
students[j] = students[j + 1];
students[j + 1] = temp;
}
}
}
}
}
int main() {
STU students[MAX_STU] = {
{"2024003", "王五", {95, 88, 90}, 273, 91.0},
{"2024001", "张三", {90, 85, 92}, 267, 89.0},
{"2024004", "赵六", {88, 92, 85}, 265, 88.3},
{"2024002", "李四", {78, 92, 88}, 258, 86.0}
};
int count = 4;
int choice;
printf("排序前:\n");
displayAll(students, count);
printf("请选择排序方式:\n");
printf("1. 按总分升序排列(从低到高)\n");
printf("2. 按总分降序排列(从高到低)\n");
printf("请输入选择: ");
scanf("%d", &choice);
if (choice == 1) {
bubbleSort(students, count, 1); /* 升序排序 */
printf("\n按总分升序排列:\n");
} else {
bubbleSort(students, count, 0); /* 降序排序 */
printf("\n按总分降序排列:\n");
}
displayAll(students, count); /* 显示排序结果 */
return 0;
}
运行结果:
排序前:
排名 学号 姓名 总分 平均分
----------------------------------------------
1 2024003 王五 273.0 91.0
2 2024001 张三 267.0 89.0
3 2024004 赵六 265.0 88.3
4 2024002 李四 258.0 86.0
----------------------------------------------
请选择排序方式:
1. 按总分升序排列(从低到高)
2. 按总分降序排列(从高到低)
请输入选择: 2
按总分降序排列:
排名 学号 姓名 总分 平均分
----------------------------------------------
1 2024003 王五 273.0 91.0
2 2024001 张三 267.0 89.0
3 2024004 赵六 265.0 88.3
4 2024002 李四 258.0 86.0
----------------------------------------------
13.5 选择排序(补充)
c
/* 选择排序:每次选出最小(大)的元素放到前面
参数1:学生数组
参数2:学生数量 */
void selectionSort(STU students[], int count) {
int i, j;
int min_idx; /* 当前轮次中最小元素的下标 */
STU temp;
/* 外层循环:从第0个位置到倒数第2个位置 */
for (i = 0; i < count - 1; i++) {
min_idx = i; /* 假设当前位置的元素最小 */
/* 内层循环:在未排序部分中找到真正的最小值 */
for (j = i + 1; j < count; j++) {
if (students[j].total < students[min_idx].total) {
min_idx = j; /* 更新最小值下标 */
}
}
/* 如果最小值不在当前位置,则交换 */
if (min_idx != i) {
temp = students[i];
students[i] = students[min_idx];
students[min_idx] = temp;
}
}
}
十四、显示所有学生信息模块
14.1 知识点概述
显示模块遍历结构体数组,以表格形式输出所有学生的完整信息。需要用到格式化输出控制。
14.2 printf 格式化输出详解
| 格式符 | 含义 | 示例 |
|---|---|---|
%s |
字符串 | printf("%s", "张三") |
%d |
十进制整数 | printf("%d", 42) |
%f |
浮点数 | printf("%f", 3.14) |
%-10s |
左对齐,宽度10 | printf("%-10s", "张三") |
%8d |
右对齐,宽度8 | printf("%8d", 100) |
%.1f |
保留1位小数 | printf("%.1f", 92.5) |
%8.1f |
宽度8,保留1位小数 | printf("%8.1f", 92.5) |
对齐方式说明:
%-10s:负号表示左对齐,宽度为10个字符,不足的右边补空格%10s:正号(默认)表示右对齐,宽度为10个字符,不足的左边补空格
14.3 完整显示示例
c
#include <stdio.h>
#include <string.h>
#define MAX_STU 100
#define COURSE_NUM 3
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 姓名 */
float score[COURSE_NUM]; /* 各科成绩 */
float total; /* 总分 */
float average; /* 平均分 */
} STU;
/* 显示表头 */
void displayHeader() {
printf("\n");
printf("%-4s %-12s %-8s", "序号", "学号", "姓名");
int i;
for (i = 0; i < COURSE_NUM; i++) {
printf(" %-6s", "成绩"); /* 动态输出课程列 */
}
printf(" %-8s %-8s\n", "总分", "平均分");
/* 打印分隔线 */
printf("------------------------------------------------------------------\n");
}
/* 显示所有学生信息
参数1:学生数组
参数2:当前学生数量 */
void displayAll(STU students[], int count) {
int i, j;
if (count == 0) {
printf("\n当前没有学生记录!\n\n");
return;
}
displayHeader(); /* 先显示表头 */
/* 遍历并显示每个学生的信息 */
for (i = 0; i < count; i++) {
printf("%-4d %-12s %-8s",
i + 1, /* 序号(从1开始) */
students[i].id, /* 学号 */
students[i].name); /* 姓名 */
/* 显示各科成绩 */
for (j = 0; j < COURSE_NUM; j++) {
printf(" %-6.1f", students[i].score[j]);
}
/* 显示总分和平均分 */
printf(" %-8.1f %-8.1f\n",
students[i].total,
students[i].average);
}
printf("------------------------------------------------------------------\n");
printf("共 %d 名学生\n\n", count);
}
int main() {
STU students[MAX_STU] = {
{"2024001", "张三", {90, 85, 92}, 267, 89.0},
{"2024002", "李四", {78, 92, 88}, 258, 86.0},
{"2024003", "王五", {95, 88, 90}, 273, 91.0}
};
int count = 3;
displayAll(students, count);
return 0;
}
运行结果:
序号 学号 姓名 成绩 成绩 成绩 总分 平均分
------------------------------------------------------------------
1 2024001 张三 90.0 85.0 92.0 267.0 89.0
2 2024002 李四 78.0 92.0 88.0 258.0 86.0
3 2024003 王五 95.0 88.0 90.0 273.0 91.0
------------------------------------------------------------------
共 3 名学生
十五、主函数设计与程序整合
15.1 知识点概述
主函数是整个程序的入口点,负责协调各个模块。完整的主函数需要在启动时加载文件数据,运行时根据用户选择调用各功能模块,退出前保存数据。
15.2 完整的主函数框架
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ==================== 宏定义 ==================== */
#define MAX_STU 100 /* 最大学生数量 */
#define COURSE_NUM 3 /* 课程门数 */
#define FILENAME "student.dat" /* 数据文件名 */
/* ==================== 结构体定义 ==================== */
typedef struct {
char id[20]; /* 学号 */
char name[20]; /* 姓名 */
float score[COURSE_NUM]; /* 各科成绩 */
float total; /* 总分 */
float average; /* 平均分 */
} STU;
/* ==================== 全局变量 ==================== */
STU students[MAX_STU]; /* 全局学生数组 */
int count = 0; /* 当前学生数量 */
/* ==================== 函数声明 ==================== */
void menu(void); /* 显示主菜单 */
void loadData(void); /* 从文件加载数据 */
void saveData(void); /* 保存数据到文件 */
void inputStudent(void); /* 录入学生信息 */
void searchStudent(void); /* 查询学生信息 */
void deleteStudent(void); /* 删除学生信息 */
void modifyStudent(void); /* 修改学生信息 */
void insertStudent(void); /* 插入学生信息 */
void sortStudents(void); /* 学生成绩排序 */
void displayAll(void); /* 显示所有学生 */
void clearInput(void); /* 清空输入缓冲区 */
/* ==================== 主函数 ==================== */
int main() {
int choice; /* 用户菜单选择 */
loadData(); /* 程序启动时从文件加载数据 */
while (1) { /* 主循环 */
menu(); /* 显示菜单 */
printf("请输入您的选择(0-8): ");
scanf("%d", &choice);
clearInput(); /* 清空输入缓冲区 */
switch (choice) {
case 1: inputStudent(); break; /* 录入 */
case 2: searchStudent(); break; /* 查询 */
case 3: deleteStudent(); break; /* 删除 */
case 4: modifyStudent(); break; /* 修改 */
case 5: insertStudent(); break; /* 插入 */
case 6: sortStudents(); break; /* 排序 */
case 7: displayAll(); break; /* 显示 */
case 8: saveData(); break; /* 保存 */
case 0:
saveData(); /* 退出前自动保存 */
printf("\n数据已保存,感谢使用,再见!\n");
exit(0); /* 退出程序 */
default:
printf("\n输入无效,请重新选择!\n\n");
break;
}
}
return 0;
}
/* ==================== 功能函数实现 ==================== */
/* 显示主菜单 */
void menu(void) {
printf("╔═══════════════════════════════════╗\n");
printf("║ 学生信息管理系统 v1.0 ║\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("║ 8. 保存到文件 ║\n");
printf("║ 0. 退出系统 ║\n");
printf("╚═══════════════════════════════════╝\n");
}
/* 从文件加载数据 */
void loadData(void) {
FILE *fp = fopen(FILENAME, "rb"); /* 以二进制读模式打开 */
if (fp != NULL) {
/* 读取文件中的所有学生记录 */
count = fread(students, sizeof(STU), MAX_STU, fp);
fclose(fp);
printf("已从文件加载 %d 条学生记录\n\n", count);
} else {
printf("数据文件不存在,将创建新文件\n\n");
}
}
/* 保存数据到文件 */
void saveData(void) {
FILE *fp = fopen(FILENAME, "wb"); /* 以二进制写模式打开(覆盖) */
if (fp != NULL) {
fwrite(students, sizeof(STU), count, fp); /* 写入全部数据 */
fclose(fp);
printf("数据已保存到 %s\n", FILENAME);
} else {
printf("保存失败!\n");
}
}
/* 清空输入缓冲区 */
void clearInput(void) {
int c;
while ((c = getchar()) != '\n' && c != EOF) {
/* 丢弃缓冲区中的所有字符 */
}
}
/* 以下是各功能函数的具体实现
(此处为框架示意,完整实现在前面各模块中已详细讲解)
*/
/* 录入学生信息 */
void inputStudent(void) {
/* 实现代码参见第八节 */
}
/* 查询学生信息 */
void searchStudent(void) {
/* 实现代码参见第九节 */
}
/* 删除学生信息 */
void deleteStudent(void) {
/* 实现代码参见第十节 */
}
/* 修改学生信息 */
void modifyStudent(void) {
/* 实现代码参见第十一节 */
}
/* 插入学生信息 */
void insertStudent(void) {
/* 实现代码参见第十二节 */
}
/* 学生成绩排序 */
void sortStudents(void) {
/* 实现代码参见第十三节 */
}
/* 显示所有学生信息 */
void displayAll(void) {
/* 实现代码参见第十四节 */
}
十六、知识点总结
| 知识点 | 核心内容 | 对应模块 |
|---|---|---|
| 结构体(struct) | 自定义数据类型,typedef别名,结构体数组,结构体指针 | 所有模块 |
| 预处理指令 | #include文件包含,#define宏定义,条件编译#ifndef | 预处理模块 |
| 文件操作 | fopen/fclose打开关闭,fread/fwrite二进制读写,fprintf/fscanf文本读写,fseek/ftell/rewind | 录入、保存、加载 |
| 字符串函数 | strcpy复制,strcmp比较,strstr查找,strlen长度 | 查询、删除、修改 |
| 输入输出 | scanf格式输入,printf格式输出,getchar缓冲区处理 | 所有模块 |
| switch-case | 多分支选择,break穿透,default默认 | 主菜单 |
| 函数设计 | 函数定义、声明、调用,参数传递(数组、指针) | 所有模块 |
| 冒泡排序 | 相邻比较、交换,升序/降序控制 | 排序模块 |
| 数组操作 | 元素前移(删除),元素后移(插入),遍历查找 | 删除、插入、查询 |
| 循环控制 | while(1)无限循环,for遍历,break/continue | 所有模块 |
以上就是学生信息管理系统涉及的全部核心知识点。每个知识点都配有入门级的代码示例和逐行注释,从基础的结构体定义到完整的文件读写操作,循序渐进地覆盖了项目所需的全部C语言语法和编程技巧。