结构和指针卡住
Posted
技术标签:
【中文标题】结构和指针卡住【英文标题】:Struct and Pointers Stuck 【发布时间】:2021-03-23 04:36:04 【问题描述】:我在我的 C 赋值程序中遇到了一些问题:
在选项 #4 中,成绩仅按降序排序,而在选项 #5 中,成绩不会改变,只是交换学生的姓名和分数。
在选项 #8,从文件输入的字符串和浮点数不会显示,我希望选项 8 灵活(通过选项 #7 输入文件时从文件显示或仅显示来自 #1 的输入菜单选项)。这是文件的示例:
80.64 John
90.40 Jane
78.00 Jake
代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Studata
float min, max;
int many;
char *max1, *min1, gx, gn;
studata;
struct Student
char name[100], grade;
float score[100];
;
float average(struct Student student[100])
float sum;
for(int i=0; i<student.many; i++)
sum += studata[i].score;
return sum/(float)student.many;
void MM(struct Student student[100])
int i;
studata.min = 0;
studata.max = 100;
for (i=0; i<studata.many; i++)
if(*student[i].score > studata.min)
studata.min = student[i].score;
studata.min1 = student[i].name;
studata.gn = student[i].grade;
for (i=0; i<studata.many; i++)
if(student[i].score < studata.min)
studata.max = student[i].score;
studata.max1 = student[i].name;
studata.gx = student[i].grade;
void swapname(char *a, char *b)
char z[100];
strcpy(z, a);
strcpy(a, b);
strcpy(b, z);
void swapscore(float a, float b)
float temporary = a;
a = b;
b = temporary;
void swapgrade(char A1, char B1)
char C1 = A1;
A1 = B1;
B1 = C1;
void Bubblesort(int mode, struct Student student[100])
int i, j;
if(mode == 1)
for (i=0; i<studata.many; i++)
for (j=i+1; j<studata.many; j++)
if(student[j].score > student[i].score)
swapname(student[i].name, student[j].name);
swapscore(student[i].score, student[j].score);
swapgrade(student[i].grade, student[j].grade);
else if(mode == 0)
for(i=0; i<studata.many; i++)
for(j=i+1; j<studata.many; j++)
if(student[j].score < student[i].score)
swapname(student[i].name, student[j].name);
swapscore(student[i].score, student[j].score);
swapgrade(student[i].grade, student[j].grade);
int main()
struct Student student[100];
int selection=1;
FILE *file;
while (selection <= 8 && selection >= 1)
printf("\n\n\t-------MENU-------\n\n");
printf("0. Enter Data of Students\n");
printf("1. Calculate the Average\n");
printf("2. Show Maximum and Minimum\n");
printf("3. Sort Score Ascending\n");
printf("4. Sort Score Descending\n");
printf("5. Save Scores\n");
printf("6. Load Scores from File\n");
printf("7. Load All Data\n");
printf("Choice (Other than 1-8 to Exit): ");
scanf("%d", &selection);
if(selection == 1)
printf("=============================\n");
printf("\nHow many students would you like to input: ");
scanf(" %d", &studata.many);
for (int i=0; i<studata.many; i++)
printf("\nStudent-%d Name\t: ", i+1);
scanf(" %[^\n]s", student[i].name);
printf("Student-%d Score\t: ", i+1);
scanf(" %f", &student[i].score);
while(student[i].score > 100 || student[i].score < 0)
printf("Hey, wrong input, please input correctly, okay?");
printf("\nStudent-%d Score\t: ", i+1);
scanf(" %f",&student[i].score);
if (student[i].score <= 100 && student[i].score >= 90 )
student[i].grade= 'A';
else if (student[i].score < 90 && student[i].score >= 80)
student[i].grade= 'B';
else if (student[i].score < 80 && student[i].score >=70)
student[i].grade= 'C';
else if (student[i].score < 70 && student[i].score >=60)
student[i].grade= 'D';
else if (student[i].score < 60 && student[i].score >=50)
student[i].grade= 'E';
else
student[i].grade = 'F';
else if(selection == 2)
printf("=============================\n");
printf("Average of Score is %.2f", average(student));
else if(selection == 3)
MM(student);
printf("=============================\n");
printf("Minimum\t: %s || %4.2f || %c\n", studata.max1, studata.max, studata.gx);
printf("Maximum\t: %s || %4.2f || %c\n", studata.min1, studata.min, studata.gn);
else if(selection == 4)
printf("=============================\n");
Bubblesort(0,student);
for(int i=0; i<studata.many; i++)
printf(" %s : %5.2f --> %c\n", student[i].name, student[i].score, student[i].grade);
else if(selection == 5)
printf("=============================\n");
Bubblesort(1,student);
for(int i=0; i<studata.many; i++)
printf(" %s : %5.2f --> %c\n", student[i].name, student[i].score, student[i].grade);
else if(selection == 6)
char filename[100];
printf("=============================\n");
printf("Name of the file (with ext.): ");
scanf(" %[^\n]s", filename);
file = fopen(filename, "w");
for(int i=0; i<studata.many; i++)
fprintf(file,"%.2f %s\n", student[i].score, student[i].name);
fclose(file);
else if(selection == 7)
char filename[100];
char sub_ch;
int i;
printf("Enter name of file you want to open (with extension): ");
scanf(" %[^\n]s", filename);
file = fopen(filename, "r");
while (file == NULL)
printf("I'm Error! Reinput? (Y/n): ");
scanf("%c", &sub_ch);
if(sub_ch == 'Y')
printf("Enter name of file you want to open (with extension): ");
scanf(" %[^\n]s", filename);
file = fopen(filename, "r");
if(sub_ch == 'n')
exit(1);
printf("=============================\n");
fscanf(file, "%f %s", &student[i].score, student[i].name);
while (!feof(file))
if (student[i].score <= 100 && student[i].score >= 90 )
student[i].grade= 'A';
else if (student[i].score < 90 && student[i].score >= 80)
student[i].grade= 'B';
else if (student[i].score < 80 && student[i].score >=70)
student[i].grade= 'C';
else if (student[i].score < 70 && student[i].score >=60)
student[i].grade= 'D';
else if (student[i].score < 60 && student[i].score >=50)
student[i].grade= 'E';
else
student[i].grade= 'F';
printf("%s %8.2f --> %c\n", student[i].name, student[i].score, student[i].grade);
fscanf(file, "%f %s", &student[i].score, student[i].name);
fclose(file);
else if(selection == 8)
printf("=============================\n");
for (int i=0; i<studata.many; i++)
printf("Name || Score || Grade\t: %s || %3.2f || %c\n", student[i].name, student[i].score, student[i].grade);
return 0;
在我尝试为每个可能的变量提供指针后,我不知道该怎么做。
【问题讨论】:
为什么score
是一个数组(例如float score[100];
)而不是一个标量(例如float score;
)?正如您所拥有的,它被 视为 为标量,因为您这样做:*studata[i].score
无处不在。那只看score[0]
。
如果我没记错,你就是在比较错误。它必须是 studata[j].score[index]
数组指针的基指针。作为@CraigEstey,你为什么使用数组而不是浮点数。
您将name
放入Studata
[这是学生的姓名]。为什么不改用Student
呢?这两个结构的含义是什么?例如,通常情况下,我会有一个描述学生的结构Student
。 Studata
是关于学生在他们参加的各种课程中获得的成绩和考试成绩。所以,我会澄清你想如何组织数据。
对不起,我没有提前计划结构的名称。不过,我现在已经更改了它,并且我已经删除了所有指针。谢谢@CraigEstey 剩下的所有问题都是降序升序交换,他们只改变了第二行和第三行。加载文件也会导致分段错误
【参考方案1】:
扩展上面的提示,使您的代码如此难以阅读(和维护)的原因是您忽略了上面注释中的 (2)。您的实现(应用于数据的逻辑)与您的界面(您如何与用户交互)并不分离。取而代之的是,您的代码在一个跨屏幕的菜单中混杂在一起。
将您的实现与接口分开保持每个单独和可读的逻辑。以下是您可以改进接口代码和实现的一些其他方面:
不要用不必要的重复输出函数调用来干扰实现
在编译期间,编译时连接所有相邻的字符串文字,仅由空格分隔。这意味着您不需要 10 次单独调用 printf()
来输出您的菜单。实际上,由于您显示的菜单中不需要进行任何转换,因此您根本不需要调用 variadic printf()
函数,更不用说调用 10 次了。由于您需要行尾控制,因此只需调用 fputs()
即可,例如
fputs ("\n\n\t-----------MENU-----------\n\n"
" 0. Enter Data of Students\n"
" 1. Calculate the Average\n"
" 2. Show Maximum and Minimum\n"
" 3. Sort Score Ascending\n"
" 4. Sort Score Descending\n"
" 5. Save Scores\n"
" 6. Load Scores from File\n"
" 7. Load All Data\n\n"
"Choice (Other than 1-8 to Exit): ", stdout);
如果你想在最后输出'\n'
,那么只需使用puts()
就可以了。一次调用使您的菜单可读。
创建逻辑函数来实现您的界面
您无需在代码主体的 while
循环中包含 10 行菜单。如果您创建一个简短的函数来显示菜单,它会使事情变得更加可读和可维护,例如:
void show_menu (void)
fputs ("\n\n\t-----------MENU-----------\n\n"
" 0. Enter Data of Students\n"
" 1. Calculate the Average\n"
" 2. Show Maximum and Minimum\n"
" 3. Sort Score Ascending\n"
" 4. Sort Score Descending\n"
" 5. Save Scores\n"
" 6. Load Scores from File\n"
" 7. Load All Data\n\n"
"Choice (Other than 1-8 to Exit): ", stdout);
然后你的主循环可以保持可读,例如
while (selection <= 8 && selection >= 1)
show_menu();
这比主循环开始时的 10 个 printf()
函数更具可读性和更易于维护。
验证程序的每个输入
您必须验证对程序的输入和每次转换。如果用户滑到'4'
而不是按'e'
来选择菜单会发生什么?试试看。
scanf("%d", &selection);
在没有验证的情况下,当selection
(现在拥有一个不确定的值)在if(selection == 1)
中被访问时,您调用未定义的行为。至少您必须捕获错误并退出,例如
if (scanf("%d", &selection) != 1)
fputs ("error: invalid integer input.\n", stderr);
exit (EXIT_FAILURE);
为了优雅地处理错误,您需要知道,对于 scanf()
,当发生 匹配失败 时,从输入流中提取字符会在该点停止,留下有问题的字符导致输入流中的未读失败。您必须在下一次尝试输入之前清除违规字符,否则输入将再次失败,原因相同,在许多情况下会导致无限循环。这个网站上有数百个答案展示了如何正确使用scanf()
。
更好的解释是您不使用scanf()
接受用户输入,而是使用fgets()
和足够大小的缓冲区(字符数组),然后使用sscanf()
从缓冲区中检索您需要的任何值 - - 这消除了输入流中未读字符的任何可能性。
不要让您的界面与您的实施混淆
确定字母等级的if .. else if .. else if ...
逻辑没有理由位于程序循环的中间。就像上面的 show_menu()
一样,该实现应该在一个可以从主循环调用的短函数中,例如
char get_ltrgrade (float f) /* return letter grade given float score */
if (f >= 90) return 'A';
else if (f >= 80) return 'B';
else if (f >= 70) return 'C';
else if (f >= 60) return 'D';
else return 'F';
然后,您无需在if(selection == 1)
之后执行十几行实现,只需短时间调用get_ltrgrade();
将您的数据结构与您拥有的数据相匹配
您对struct Student
和struct Studata
的使用不符合您显示的输入数据。您显示的输入数据为每个学生一个分数。为了匹配您的数据,仅struct Student
就可以了。现在,如果您有很多班级的学生,那么struct Studata
开始变得更有意义。 float score[100];
将允许每个学生获得多个成绩,但这与您显示的数据不匹配,并且您在struct Student
中没有计数器来跟踪该学生的有效成绩数量。从您的显示来看,float score:
更有意义。
使用qsort()
对数组进行排序
C 提供qsort()
来处理任何类型的排序数组。 qsort()
函数将比您编写的任何类型的效率高出几个数量级,并且已经过数十年的测试。新的 C 程序员通常避免使用 qsort()
,因为它需要编写一个简短的 compare()
函数,该函数通常少于 5-10 行代码,告诉 qsort()
如何比较数组的元素。其实很简单。
有关qsort
使用的compare
函数的解释以及如何使用它,请参阅How to sort an array of a structure using qsort? 和How can I stop taking inputs from user when I press the key 'q'? 中的更多详细信息。
下面将您的实现部分放在一起,从文件中读取学生数据,为分数分配字母等级并将所有数据存储在结构数组中。然后程序调用qsort()
对struct数组进行排序并输出数据。由于您包含char grade;
(下面的char ltrgrade;
),因此在从文件中读取数据时会分配成绩,从而无需在实施过程中计算成绩。
#include <stdio.h>
#include <stdlib.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define MAXN 128
#define MAXS 256
typedef struct /* typedef allows use of student_t as type */
char name[MAXN],
ltrgrade;
float score;
student_t;
char get_ltrgrade (float f) /* return letter grade given float score */
if (f >= 90) return 'A';
else if (f >= 80) return 'B';
else if (f >= 70) return 'C';
else if (f >= 60) return 'D';
else return 'F';
/* qsort compare by student_t->scoore (descending),
* change to >, < for ascending sort.
*/
int compare_score (const void *a, const void *b)
student_t *sa = (student_t*)a,
*sb = (student_t*)b;
return (sa->score < sb->score) - (sa->score > sb->score);
int main (int argc, char **argv)
char buf[MAXC]; /* buffer to hold each line read from file */
student_t student[MAXS]; /* array of student_t (MAXS of them) */
size_t n = 0; /* array index (counter) */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) /* validate file open for reading */
perror ("file open failed");
return 1;
/* while array not full, read each line into buf, VALIDATE read */
while (n < MAXS && fgets (buf, MAXC, fp))
/* split line into score and name, VALIDATE conversion */
if (sscanf (buf, "%f %127[^\n]", &student[n].score, student[n].name) == 2)
/* get letter grade based on score */
student[n].ltrgrade = get_ltrgrade (student[n].score);
n += 1; /* increment index only on valid converison */
else /* handle error */
fprintf (stderr, "error: invalid line format, student[%zu].\n", n);
if (fp != stdin) /* close file if not stdin */
fclose (fp);
qsort (student, n, sizeof *student, compare_score); /* sort by score */
for (size_t i = 0; i < n; i++) /* output result */
printf ("%-12s %6.2f %c\n",
student[i].name, student[i].score, student[i].ltrgrade);
return 0;
注意:在这种情况下,compare()
函数需要一整行 2 行代码(为了便于阅读,分成 3 行)。代替条件(a < b) - (a > b)
进行降序排序,您可以使用b - a
,但是当b
是一个大的正值而a
是一个大的负值(反之亦然)时,这很容易溢出。条件的差异用于返回-1, 0, 1
for (-1
- a
排在b
之前), (0
- a
和b
相等), 和 (1
- b
排在 a
) 之前,这消除了潜在的下溢/溢出。
另请注意,qsort
ing 任何数组,无论多么复杂,都只需要 1 行代码:
qsort (student, n, sizeof *student, compare_score); /* sort by score */
使用/输出示例
给定文件dat/studata.txt
中的样本数据,程序运行和输出为:
$ ./bin/studata dat/studata.txt
Jane 90.40 A
John 80.64 B
Jake 78.00 C
每当您编写包含用户界面的代码时,都要努力将界面和实现分开(尽可能地)。理论上,您的界面应该是一个完整的独立程序,不依赖于任何给定的实现。您应该即将操作和运行您的菜单系统,而不必担心任何其他数据(在需要时使用虚拟数据)。这不仅使您的代码具有可读性和可维护性,还允许您在不依赖于任何给定接口的小型可测试单元中创建实现逻辑。这使您的实现保持简短、可读和可测试。总有一些小的重叠区域,但您的目标是尽量减少和消除除绝对必要之外的所有区域。
这里有很多内容,请花点时间阅读,如果您还有其他问题,请告诉我。
【讨论】:
以上是关于结构和指针卡住的主要内容,如果未能解决你的问题,请参考以下文章