任务详情
任务描述
编写一个管理学生信息的小程序。
目的
掌握结构体的定义、初始化及成员访问(普通变量 + 指针方式);
熟练使用指针操作结构体数组,实现数组遍历、排序;
理解地址传递的本质,通过指针修改结构体成员(成绩转等级);
掌握自定义排序规则(按姓名字典序、按成绩降序)的实现方法。
功能要求
定义学生结构体 STU,包含以下成员:
姓名:char name[20](最多 19 个字符);
成绩:float score(百分制,0~100);
等级:char grade(等级制,A/B/C/D/F);
typedef struct {
char name[NAME_LEN]; // 姓名
...... //自行补充成绩、等级
} STU;实现函数 void scoreToGrade(STU *stuArr, int n)
参数:结构体数组指针、学生人数;
功能:遍历结构体数组,将每个学生的百分制成绩转换为等级制并写入grade成员;
转换规则:≥90→'A',80~89→'B',70~79→'C',60~69→'D',<60→'F';
void scoreToGrade(STU *stuArr, int n) {
STU *p = stuArr; // 指针指向数组首元素
for (int i = 0; i < n; i++, p++) { // 指针遍历数组
if (p->score >= 90) {
p->grade = 'A';
}
...... // 自行补充
}
}
实现函数 void sortByName(STU *stuArr, int n)
参数:结构体数组指针、学生人数
功能:用指针操作实现结构体数组按姓名字典序升序排序
void sortByName(STU *stuArr, int n) {
STU temp; // 临时结构体,用于交换
STU *p1, *p2; // 遍历指针
for (int i = 0; i < n - 1; i++) {
p1 = stuArr + i; // 指向第 i 个元素
for (int j = i + 1; j < n; j++) {
p2 = stuArr + j; // 指向第 j 个元素
// 字典序:前 > 后 则交换
...... // 自行补充
}
}
}
实现函数 void sortByScore(STU *stuArr, int n)
参数:结构体数组指针、学生人数
功能:用指针操作实现结构体数组按成绩降序排序
void sortByScore(STU *stuArr, int n) {
...... // 可模仿上一个自行补充完成
}实现函数 void printStuInfo(STU *stuArr, int n)
参数:结构体数组指针、学生人数;
功能:遍历并打印所有学生的姓名、成绩、等级信息,格式清晰;
主函数要求
输入学生人数(最多 50 人)和每个学生的姓名、成绩;
调用scoreToGrade转换等级;
分别调用sortByName和sortByScore排序,并打印排序后的结果;
所有函数参数均使用指针传递,禁止使用全局变量。
输入输出要求
输入
首先输入学生人数n(1≤n≤50),若人数不符合要求,输出输入非法!请输入1~%d之间的数;
依次输入n个学生的姓名(不含空格)、成绩(浮点数)(假设输入的数据无误);
输入样例
张三 85.5
输出
原始学生信息(含转换后的等级);
按姓名排序后的学生信息;
按成绩降序排序后的学生信息;
输出样例
张三 85.5 B 李四 92.0 A
编程要求
所有结构体数组的操作必须通过指针实现(禁止使用数组下标[]遍历,仅允许初始化 / 输入时用下标);
排序函数中交换结构体元素时,需通过结构体指针完成值拷贝;
对输入的成绩做合法性校验(0≤score≤100),若输入非法成绩,提示并要求重新输入;
代码注释清晰,关键函数、关键逻辑需添加注释。
实验提示
结构体指针访问成员:stuPtr->name 等价于 (*stuPtr).name;
字符串比较用strcmp函数(需包含<string.h>),字典序升序排序规则:strcmp(p1->name, p2->name) > 0 时交换;
冒泡排序中,遍历数组时用STU *p = stuArr + i 指向第i个元素;
成绩合法性校验:输入后判断score < 0 || score > 100,若非法则重新输入该学生信息。
代码示例
#include <stdio.h>// 引入标准输入输出库,为了使用 printf 和 scanf
#include <string.h> // 引入字符串处理库,为了使用 strcmp 比较姓名
#include <stdlib.h> // 引入标准库(虽然这里没用到动态分配,但习惯性引入也没错)
#define MAX_STU 50 // 宏定义:规定这个系统最多能装 50 个学生。方便以后一键修改上限
#define NAME_LEN 20 // 宏定义:规定每个学生姓名的最大长度为 20 个字符
// ==========================================
// 1. 定义学生结构体 (图纸)
// ==========================================
// 使用 typedef 给匿名的 struct 起个别名叫 STU,以后写代码就不需要每次都写 struct STU 了
typedef struct {
char name[NAME_LEN]; // 字符数组,用来存放学生的姓名,最多 19 个可见字符 + 1个结束符 '\0'
float score; // 单精度浮点数,用来存放百分制成绩(如 85.5)
char grade; // 单个字符,用来存放计算出来的等级(如 'A', 'B')
} STU;
// ==========================================
// 2. 成绩转等级函数
// ==========================================
/**
功能:百分制成绩转等级制
@param stuArr 学生结构体数组指针(也就是数组的首地址)
@param n 学生人数
*/
void scoreToGrade(STU *stuArr, int n) {
STU *p = stuArr; // 定义一个指针 p,让它一开始指向队伍里的第一个学生
// 循环遍历队伍。i 控制循环次数;p++ 让指针每次往后移动一个学生的位置
for(int i = 0; i < n; i++, p++) {
// 使用 p->score 提取当前学生的成绩,然后进行判断
if(p->score >= 90){
p->grade = 'A'; // 如果大于等于 90,就把字母 'A' 塞进当前学生的 grade 格子里
}
else if(p->score >= 80){
p->grade = 'B';
}
else if(p->score >= 70){
p->grade = 'C';
}
else if(p->score >= 60){
p->grade = 'D';
}
else {
p->grade = 'F'; // 低于 60 分不及格,给 'F'
}
}
}
// ==========================================
// 3. 按姓名升序排序函数 (冒泡排序)
// ==========================================
/**
功能:按姓名字典序升序排序(冒泡排序)
@param stuArr 学生结构体数组指针
@param n 学生人数
*/
void sortByName(STU *stuArr, int n) {
STU temp; // 定义一个临时的结构体变量,作为交换两个学生数据时的“中转站”
STU *p1, *p2; // 定义两个指针,分别用来指向要进行比较的两个学生
// 外层循环:控制比较的轮数,n 个人只需要比 n-1 轮
for (int i = 0; i < n - 1; i++) {
p1 = stuArr + i; // p1 指向当前轮次确定的基础学生(通过基地址 + 偏移量 i 计算得出)
// 内层循环:让 p1 指向的学生,跟排在他后面的所有学生逐一打招呼比较
for (int j = i + 1; j < n; j++) {
p2 = stuArr + j; // p2 指向 p1 后面的某个学生
// strcmp 比较两个名字。如果 p1 的名字在字典中排在 p2 的后面(返回值 > 0)
// 说明顺序反了(我们要升序),需要把他们俩换位置
if(strcmp(p1->name, p2->name) > 0){
// 核心交换逻辑:解引用(*)后,连名带分把整个结构体的数据完整互换
temp = *p1; // 把 p1 学生的所有数据暂存到 temp
*p1 = *p2; // 把 p2 学生的所有数据覆盖掉 p1 所在位置的数据
*p2 = temp; // 把 temp 里暂存的原 p1 数据放到 p2 所在位置
}
}
}
}
// ==========================================
// 4. 按成绩降序排序函数 (冒泡排序)
// ==========================================
/**
功能:按成绩降序排序(冒泡排序)
@param stuArr 学生结构体数组指针
@param n 学生人数
*/
void sortByScore(STU *stuArr, int n) {
STU temp; // 同样的中转站
STU *p1, *p2; // 同样的两根比较指针
// 嵌套循环逻辑与姓名排序完全一致
for (int i = 0; i < n - 1; i++) {
p1 = stuArr + i;
for (int j = i + 1; j < n; j++) {
p2 = stuArr + j;
// 区别在这里:比较的是成绩 (score)。
// 因为要求降序(高分在前),所以如果前面的分数小于后面的分数,就必须交换。
if(p1->score < p2->score){
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
}
}
}
// ==========================================
// 5. 打印信息函数
// ==========================================
/**
功能:打印学生信息
@param stuArr 学生结构体数组指针
@param n 学生人数
*/
void printStuInfo(STU *stuArr, int n) {
STU *p = stuArr; // 让指针 p 指向队伍开头
// 遍历队伍
for(int i = 0; i < n; i++, p++){
// %s 对应姓名(字符串),%.1f 对应成绩(浮点数保留1位),%c 对应等级(字符),\n 负责换行
printf("%s %.1f %c\n", p->name, p->score, p->grade);
}
}
// ==========================================
// 6. 主函数
// ==========================================
int main() {
STU stuArr[MAX_STU]; // 在内存里真正划出一块地盘,打造 50 个学生的储物柜
int n; // 定义变量 n,用来存储实际要录入的学生人数
// ---------- 第 1 步:输入并校验人数 ----------
// 读取用户输入的人数(注意加了 & 符取地址)
scanf("%d", &n);
// 如果输入的人数不合规(小于1或大于最大容量),程序提前结束
if( n < 1 || n > 50 ){
// 提示错误
printf("输入非法!请输入1~50之间的数\n");
return 0;
}
// ---------- 第 2 步:输入学生姓名和成绩 ----------
STU *p = stuArr; // 定义指针 p 指向第一个学生的柜子
// 循环录入 n 个学生的信息
for(int i = 0; i < n; i++, p++){
// 输入姓名。p->name 是字符数组,本身就是地址,所以不需要加 &
scanf("%s", p->name);
// 使用内部循环校验成绩输入的合法性
do {
// 输入成绩。score 是浮点数,必须加 & 获取其内存地址
scanf("%f", &(p->score));
// 校验:成绩不能是负数,也不能超过 100 分
if((p->score < 0) || (p->score > 100)){
printf("输入非法!请重新输入成绩\n");
}
} while((p->score < 0) || (p->score > 100)); // 只要成绩非法,就要求重输
}
// ---------- 第 3 步:处理和输出 ----------
// 1. 调用成绩转等级函数。直接传数组名 stuArr(即首地址),函数内部就能顺着地址改写 grade
scoreToGrade(stuArr, n);
// 2. 打印刚转换完等级的原始顺序信息
printStuInfo(stuArr, n);
// 3. 调用按姓名排序函数,原数组的物理顺序会被打乱并重新排列
sortByName(stuArr, n);
// 打印按姓名重新排列后的信息
printStuInfo(stuArr, n);
// 4. 调用按成绩排序函数,原数组再次被重新排列
sortByScore(stuArr, n);
// 打印按成绩降序排列后的最终信息
printStuInfo(stuArr, n);
// 程序顺利结束
return 0;
}