<think>根据题目要求,我们需要实现一个学生成绩管理系统,并且通过菜单驱动用户操作。我们将使用一维数组来存储学号和成绩,并通过函数来实现各个功能。注意:最多不超过30人,具体人数由键盘输入。我们将定义以下函数:1.input_record:录入每个学生的学号和考试成绩2.calculate_total_average:计算课程的总分和平均分3.sort_by_score_desc:按成绩由高到低排序(名次表)4.sort_by_number_asc:按学号由小到大排序(成绩表)5.search_by_number:按学号查询学生排名及其考试成绩(注意:排名是按成绩从高到低排序后的名次)6.statistic_analysis:统计每个类别的人数及百分比7.list_record:输出每个学生的学号、考试成绩(这里要求输出总分和平均分,但注意每个学生的总分和平均分?实际上应该是课程的总分和平均分,但每个学生只有一门课的成绩,所以每个学生输出学号、成绩,最后输出课程总分和平均分?但是第7条要求是输出每个学生的学号、考试成绩、课程总分和平均分。这里注意:每个学生的学号和考试成绩是独立的,而课程总分和平均分是全班的总分和平均分,所以每个学生输出时,总分和平均分是全班的值,所以每个学生输出的总分和平均分是一样的。但这样输出重复了,所以可以这样:先列出每个学生的学号和成绩,然后在最后输出课程总分和平均分。题目要求输出每个学生,所以我们可以这样:每个学生一行,输出学号、成绩,然后最后输出总分和平均分。或者按照题目要求,每个学生输出学号、成绩、总分、平均分,那么总分和平均分对于每个学生都是相同的。我们按照题目要求输出。但是注意,题目要求用一维数组作为函数参数。我们将使用两个一维数组:一个存储学号,一个存储成绩。另外,由于有菜单,我们需要一个主循环,根据用户输入执行不同的操作。由于排序后可能会改变原始顺序,但题目要求的功能中,排序后输出名次表,按学号排序输出成绩表,查询时要求按学号查询排名(排名是按成绩排序后的名次),所以我们需要考虑是否保存原始顺序?或者每次操作都基于原始数据?但是注意,按学号查询排名,这个排名应该是按成绩排序后的名次。因此,我们可以这样:-在输入数据后,我们保存原始数据(两个数组:学号数组和成绩数组)-当进行排序操作时,我们可以不改变原始数据,而是对副本进行排序。或者改变原始数据?题目要求排序后排出名次表,但没有要求改变原始存储顺序。所以我们可以这样:排序操作只用于输出,不改变原始存储顺序。但是查询操作需要知道按成绩排序后的名次,因此我们需要在查询时按成绩排序,然后找到该学号对应的成绩,再在排序后的数组中找到其位置(即名次)。或者,我们可以在每次成绩排序后记录下名次,但这样就需要额外的数组来存储名次。考虑到简单性,我们可以在执行排序操作时,输出排序结果,但不改变原始数据(即原始数据保持不变)。在查询操作时,我们重新按成绩排序(或者使用之前排序好的?)然后找到该学生的排名。但是这样效率较低,因为每次查询都要排序。另一种方法是,在输入数据后,我们维护一个按成绩排序的数组(或索引数组)和一个按学号排序的数组(或索引数组)。但这样会增加复杂度。由于学生数量最多30人,排序的开销很小,我们可以每次查询时按成绩排序,然后查找该学号对应的成绩在排序后的位置。具体实现思路:我们将使用两个全局数组(或者通过参数传递)来存储学号和成绩,然后在各个函数中操作。但是,题目要求模块化设计,我们将使用结构化的方式,不推荐全局变量。因此,我们将在主函数中定义数组,然后通过参数传递给各个函数。另外,注意菜单循环,直到用户选择0退出。步骤:1.定义数组:学号数组(long类型,因为学号可能很长)和成绩数组(float或int,这里用float可以允许小数,但题目没有说明,我们用int也可以,但题目要求是考试成绩,整数即可,所以我们用int)。2.在输入选项之前,我们需要知道当前是否已经录入了数据。所以我们需要一个标志来记录数据是否录入。或者,在每次执行操作前检查,比如除了录入操作,其他操作都需要数据存在。我们设计如下:主循环:显示菜单读取用户选择根据选择调用相应的函数注意:选项1(录入数据)是第一步,其他操作需要先有数据。我们将学生人数n作为全局?不,我们可以在主函数中定义,然后作为参数传递给各个函数。但是有些操作会改变学生人数(只有录入操作),所以我们可以将学生人数n定义为一个变量,在录入时由用户输入,然后录入数据。具体函数设计:1.voidinput_record(longnumbers[],intscores[],int*n);参数:学号数组,成绩数组,以及指向学生人数的指针(因为要改变学生人数)功能:首先输入学生人数(不超过30),然后循环输入每个学生的学号和成绩。2.voidcalculate_total_average(intscores[],intn,int*total,float*average);参数:成绩数组,学生人数,总分的指针,平均分的指针功能:计算总分和平均分,并存储到total和average指向的变量。3.voidsort_by_score_desc(longnumbers[],intscores[],intn);参数:学号数组,成绩数组,学生人数功能:按成绩由高到低排序(降序),同时调整学号数组的顺序(因为成绩和学号要对应)。这里我们使用选择排序或冒泡排序等。注意:排序会改变原始数组。但是题目要求排序后排出名次表,也就是输出排序后的结果。所以这个函数可以输出排序后的名次表。或者,我们在这个函数内部排序并输出,而不改变原始数组?题目没有明确要求是否改变原始数组,但考虑到后续操作(比如按学号排序)应该基于原始数据,所以我们在排序操作时,可以创建一个副本进行排序并输出,不改变原始数据。但是这样会增加内存和复制操作。由于数据量小,我们可以这样做。有两种方式:a)在排序函数内部,对原始数组进行排序,然后输出,这样原始数据顺序被改变。这样后续操作(比如按学号排序)就会基于这个排序后的数组。这不符合要求,因为按学号排序要求按学号升序,它应该基于原始数据(或者重新输入?)所以我们需要保留原始数据。b)在排序函数内,先复制一份数据,然后对复制的数据进行排序,然后输出,这样原始数据不变。我们采用方式b),因为这样不会破坏原始数据,后续操作可以继续使用原始数据。4.voidsort_by_number_asc(longnumbers[],intscores[],intn);参数:学号数组,成绩数组,学生人数功能:按学号由小到大排序(升序),同样,我们复制一份数据,对复制的数据进行排序并输出,不改变原始数据。5.voidsearch_by_number(longnumbers[],intscores[],intn,longsearch_number);参数:学号数组,成绩数组,学生人数,要查询的学号功能:首先按成绩降序排序(这里同样需要复制数据,因为不能改变原始数据),然后根据学号在排序后的数组中查找该学生的成绩和名次(即索引+1)。但是注意:名次是按成绩排序后的位置,如果成绩相同,则名次相同(并列)。题目没有说明相同成绩的名次处理,但通常相同成绩应该并列,然后下一个名次跳过并列人数。例如:100,99,99,98->名次:1,2,2,4。所以,在按成绩排序后,我们需要处理并列情况:遍历排序后的数组,第一个为第1名,如果下一个成绩相同,则名次相同,否则名次为当前下标+1(注意并列后的名次应该是前面的人数+1)。因此,我们可以在排序后的数组中,先给每个学生一个名次(考虑并列),然后根据学号查找该学生的名次和成绩。6.voidstatistic_analysis(intscores[],intn);参数:成绩数组,学生人数功能:统计各个类别的人数及百分比。7.voidlist_record(longnumbers[],intscores[],intn);参数:学号数组,成绩数组,学生人数功能:输出每个学生的学号、成绩,然后输出课程总分和平均分。另外,我们还需要一个辅助函数:复制数组。但是,由于数据量小,我们在需要排序而不改变原始数组的函数中,临时创建数组副本,排序后使用,然后释放(如果是动态分配)或者使用栈上数组(因为最多30人)。具体实现:由于菜单需要多次操作,所以每次操作我们都使用原始数据。我们开始编写代码,注意注释。注意:按学号查询时,我们需要按成绩排序后的名次,所以我们在search_by_number函数中:步骤:1.复制原始数据(学号和成绩)到一个临时数组。2.对临时数组按成绩降序排序(同时保持学号和成绩对应)。3.计算每个学生在排序后数组中的名次(考虑并列)。4.在原始数组(或排序后的数组?)中查找给定学号对应的学生,但是注意,排序后的数组中学号顺序是打乱的。所以我们需要在排序后的数组中同时记录原始索引?或者我们不需要,因为我们要的是该学生在排序后的位置(名次)和成绩。5.遍历排序后的数组,同时记录当前名次(考虑并列),然后检查每个学生的学号是否等于要查询的学号。如果找到,则输出该名次和成绩。但是,这样在排序后的数组中查找学号,我们只能顺序查找。因为排序后的数组按成绩排序,学号无序。不过学生人数少,顺序查找没问题。另一种思路:在查询函数中,我们不按成绩排序整个数组,而是先找到该学号对应的成绩,然后统计比该成绩高的学生人数(即名次-1),然后名次=比该成绩高的人数+1。但是要注意相同成绩:如果有相同成绩,则名次为比该成绩高的人数+1,并且相同成绩的学生都是这个名次。但是这样计算名次需要知道有多少人成绩相同且比该学生先出现?实际上,我们只需要统计大于该学生成绩的人数,那么该学生的名次就是大于该成绩的人数+1,然后相同成绩的学生都拥有相同的名次(即比该成绩高的人数+1)。但是这样计算出的名次可能和排序后分配的名次一致。例如:成绩:100,90,90,80,查询学号为A(成绩90)的学生:大于90的人数为1(100),所以名次为2(即1+1),而另一个90的学生也是名次2。因此,我们可以这样:1.通过学号在原始数组中找到该学生的成绩(假设为scoreX)。2.遍历整个成绩数组,统计成绩大于scoreX的人数(count)。3.该学生的名次为count+1。4.同时,我们也可以输出成绩。但是,这样我们就避免了排序。而且效率更高(O(n))。所以我们可以这样实现查询函数。所以,我们不需要在查询函数中排序。只需要两次遍历:一次找学号对应的成绩,一次统计大于该成绩的人数。但是,这样计算名次,我们无法知道并列的情况(但题目只要求查询一个学生的排名,所以这样计算是合理的)。因此,我们改变之前的想法:查询函数不需要排序,直接计算名次。具体步骤:intsearch_by_number(longnumbers[],intscores[],intn,longsearch_number){inti;intfound=0;intscoreX=-1;//先找到该学号对应的成绩for(i=0;i<n;i++){if(numbers[i]==search_number){found=1;scoreX=scores[i];break;}}if(!found){printf("学号不存在!\n");return;}//统计成绩大于scoreX的人数intcount=0;for(i=0;i<n;i++){if(scores[i]>scoreX){count++;}}intrank=count+1;//名次printf("学号%ld的成绩为%d,排名为%d\n",search_number,scoreX,rank);}注意:这样计算的名次,如果有并列,则并列的学生名次相同。例如,两个90分,那么第一个90分的学生,我们统计大于90分的人数(假设为1),则名次为2;另一个90分的学生,同样名次为2。所以这样计算是正确的。因此,我们不需要在查询函数中排序了。现在,我们开始写代码。注意,排序函数(按成绩和按学号)我们使用副本,并输出副本,不改变原始数组。我们使用选择排序算法(简单)。代码结构:主函数:定义数组:学号数组numbers,成绩数组scores,最大容量MAX=30学生人数n=0循环显示菜单,直到用户选择0退出注意:在录入数据之前,除了选项0和1,其他选项应该提示先录入数据。我们将用一个变量flag来标记数据是否已经录入(或者用n>0判断)。由于题目要求增量测试,我们逐步实现各个函数。下面我们给出完整代码,并注释。</think>以下是一个完整的学生成绩管理系统实现,使用C语言编写,包含菜单驱动和所有要求的功能。代码遵循模块化设计原则,使用一维数组作为函数参数实现排序、查找和统计分析。
```c
#include <stdio.h>
#define MAX_STUDENTS 30
// 函数声明
void input_record(long numbers[], int scores[], int *n);
void calculate_total_average(int scores[], int n, int *total, float *average);
void sort_by_score_desc(long numbers[], int scores[], int n);
void sort_by_number_asc(long numbers[], int scores[], int n);
void search_by_number(long numbers[], int scores[], int n);
void statistic_analysis(int scores[], int n);
void list_record(long numbers[], int scores[], int n);
int main() {
long numbers[MAX_STUDENTS];
int scores[MAX_STUDENTS];
int n = 0; // 学生人数
int choice;
int total;
float average;
while (1) {
printf("\n1.Input record\n");
printf("2.Caculate total and average score\n");
printf("3.Sort in descending order by score\n");
printf("4.Sort in ascending order by number\n");
printf("5.Search by number\n");
printf("6.Statistic analysis\n");
printf("7.List record\n");
printf("0.Exit\n");
printf("Please enter your choice: ");
scanf("%d", &choice);
switch (choice) {
case 1:
input_record(numbers, scores, &n);
break;
case 2:
if (n == 0) {
printf("No data! Please input records first.\n");
break;
}
calculate_total_average(scores, n, &total, &average);
printf("Total: %d, Average: %.2f\n", total, average);
break;
case 3:
if (n == 0) {
printf("No data! Please input records first.\n");
break;
}
sort_by_score_desc(numbers, scores, n);
break;
case 4:
if (n == 0) {
printf("No data! Please input records first.\n");
break;
}
sort_by_number_asc(numbers, scores, n);
break;
case 5:
if (n == 0) {
printf("No data! Please input records first.\n");
break;
}
search_by_number(numbers, scores, n);
break;
case 6:
if (n == 0) {
printf("No data! Please input records first.\n");
break;
}
statistic_analysis(scores, n);
break;
case 7:
if (n == 0) {
printf("No data! Please input records first.\n");
break;
}
list_record(numbers, scores, n);
break;
case 0:
return 0;
default:
printf("Invalid choice! Please try again.\n");
}
}
return 0;
}
// 1. 录入学生学号和成绩
void input_record(long numbers[], int scores[], int *n) {
printf("Enter number of students (max %d): ", MAX_STUDENTS);
scanf("%d", n);
if (*n > MAX_STUDENTS || *n <= 0) {
printf("Invalid number! Must be 1-%d\n", MAX_STUDENTS);
*n = 0;
return;
}
for (int i = 0; i < *n; i++) {
printf("Enter student %d's number and score: ", i + 1);
scanf("%ld %d", &numbers[i], &scores[i]);
}
printf("Input successful!\n");
}
// 2. 计算总分和平均分
void calculate_total_average(int scores[], int n, int *total, float *average) {
*total = 0;
for (int i = 0; i < n; i++) {
*total += scores[i];
}
*average = (float)(*total) / n;
}
// 3. 按成绩降序排序(选择排序)
void sort_by_score_desc(long numbers[], int scores[], int n) {
// 创建副本以免修改原始数据
long num_copy[MAX_STUDENTS];
int score_copy[MAX_STUDENTS];
for (int i = 0; i < n; i++) {
num_copy[i] = numbers[i];
score_copy[i] = scores[i];
}
// 选择排序
for (int i = 0; i < n - 1; i++) {
int max_idx = i;
for (int j = i + 1; j < n; j++) {
if (score_copy[j] > score_copy[max_idx]) {
max_idx = j;
}
}
// 交换成绩
int temp_score = score_copy[i];
score_copy[i] = score_copy[max_idx];
score_copy[max_idx] = temp_score;
// 交换学号
long temp_num = num_copy[i];
num_copy[i] = num_copy[max_idx];
num_copy[max_idx] = temp_num;
}
// 输出结果
printf("Ranking List (Score Descending):\n");
printf("Rank\tNumber\tScore\n");
for (int i = 0; i < n; i++) {
printf("%d\t%ld\t%d\n", i + 1, num_copy[i], score_copy[i]);
}
}
// 4. 按学号升序排序(冒泡排序)
void sort_by_number_asc(long numbers[], int scores[], int n) {
// 创建副本
long num_copy[MAX_STUDENTS];
int score_copy[MAX_STUDENTS];
for (int i = 0; i < n; i++) {
num_copy[i] = numbers[i];
score_copy[i] = scores[i];
}
// 冒泡排序
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (num_copy[j] > num_copy[j + 1]) {
// 交换学号
long temp_num = num_copy[j];
num_copy[j] = num_copy[j + 1];
num_copy[j + 1] = temp_num;
// 交换成绩
int temp_score = score_copy[j];
score_copy[j] = score_copy[j + 1];
score_copy[j + 1] = temp_score;
}
}
}
// 输出结果
printf("Score List (Number Ascending):\n");
printf("Number\tScore\n");
for (int i = 0; i < n; i++) {
printf("%ld\t%d\n", num_copy[i], score_copy[i]);
}
}
// 5. 按学号查询学生
void search_by_number(long numbers[], int scores[], int n) {
long target;
printf("Enter student number to search: ");
scanf("%ld", &target);
int rank = 1;
// 计算排名:统计成绩更高的人数
for (int i = 0; i < n; i++) {
if (scores[i] > scores[target]) {
rank++;
}
}
// 查找学号
for (int i = 0; i < n; i++) {
if (numbers[i] == target) {
printf("Student found!\n");
printf("Number: %ld, Score: %d, Rank: %d\n", numbers[i], scores[i], rank);
return;
}
}
printf("Student not found!\n");
}
// 6. 成绩统计分析
void statistic_analysis(int scores[], int n) {
int categories[5] = {0}; // 不及格,及格,中等,良好,优秀
for (int i = 0; i < n; i++) {
if (scores[i] >= 90) categories[4]++; // 优秀
else if (scores[i] >= 80) categories[3]++; // 良好
else if (scores[i] >= 70) categories[2]++; // 中等
else if (scores[i] >= 60) categories[1]++; // 及格
else categories[0]++; // 不及格
}
// 输出统计结果
char *levels[5] = {"Fail", "Pass", "Medium", "Good", "Excellent"};
printf("Grade Distribution:\n");
for (int i = 0; i < 5; i++) {
float percentage = (float)categories[i] / n * 100;
printf("%-10s: %d students (%.2f%%)\n",
levels[i], categories[i], percentage);
}
}
// 7. 列出所有记录
void list_record(long numbers[], int scores[], int n) {
int total;
float average;
calculate_total_average(scores, n, &total, &average);
printf("All Student Records:\n");
printf("Number\tScore\n");
for (int i = 0; i < n; i++) {
printf("%ld\t%d\n", numbers[i], scores[i]);
}
printf("Total: %d, Average: %.2f\n", total, average);
}
```
### 代码解释:
1. **全局定义**:
- `MAX_STUDENTS`:最大学生数(30人)
2. **主函数**:
- 使用菜单驱动界面,通过`switch-case`处理用户选择
- 检查数据是否存在(`n>0`)再执行操作
- 循环显示菜单直到选择退出
3. **核心函数**:
- `input_record()`:录入学号和成绩,验证输入有效性
- `calculate_total_average()`:计算总分和平均分(使用指针返回结果)
- `sort_by_score_desc()`:选择排序实现成绩降序(使用副本保护原始数据)
- `sort_by_number_asc()`:冒泡排序实现学号升序(使用副本)
- `search_by_number()`:先计算排名(统计更高分人数),再查找学号
- `statistic_analysis()`:统计5个等级的人数和百分比
- `list_record()`:显示所有记录并输出总分和平均分
4. **关键算法**:
- **排序**:选择排序(成绩降序)和冒泡排序(学号升序)
- **查找**:线性搜索结合排名计算
- **统计**:使用分类计数器计算百分比
5. **数据保护**:
- 排序操作使用数组副本,避免修改原始数据
- 输入验证确保学生人数在有效范围内
### 使用说明:
1. 编译运行程序后显示菜单
2. 选择1录入学生数据(先输入人数,再输入学号和成绩)
3. 选择2-7执行相应功能
4. 选择0退出程序
5. 所有操作均有数据存在性检查
### 思考题答案:
程序已按照要求修改为菜单驱动模式:
1. 显示包含8个选项的菜单
2. 根据用户输入执行对应操作
3. 添加了数据存在性检查
4. 使用`switch-case`结构处理用户选择