发布的代码在main() 中定义了StudentScan()。但是 C 中不允许嵌套函数定义。这应该会产生编译器警告,例如:
警告:ISO C 禁止嵌套函数 [-Wpedantic]
void StudentScan(int i, struct student list[])
注意所有编译器警告并修复它们。如果编译此代码时未看到警告,请调高编译器警告级别。在 gcc 上,我建议至少使用gcc -Wall -Wextra,并且我总是添加-Wpedantic。 gcc 需要-Wpedantic 才能看到警告。一些编译器,gcc 就是其中之一,do 支持嵌套函数定义作为编译器扩展。不过,这个功能是非标准的,最好不要依赖它。
解决方法很简单:将StudentScan() 的定义移出main():
#include <stdio.h>
#include <stdlib.h>
struct student {
char firstName[20];
char AverageNum[2];
};
void StudentScan(int, struct student[]);
void StudentPrint(int, struct student[]);
int main(void) {
int i;
int length;
struct student *studentp;
printf ("\nEnter the host of students: ");
scanf ("%d ", &length);
struct student list[length];
studentp=malloc(length*sizeof(struct student));
if (studentp==NULL)
{
printf("Out of memory!");
return 0;
}
for(i = 0; i < length; i++) {
StudentScan(i,studentp);
printf("\nEnter average number: ");
scanf("%s", list[i].AverageNum);
}
free (studentp);
return 0;
}
void StudentScan(int i, struct student list[])
{ printf("\nEnter first name : ");
scanf("%s", list[i].firstName);
printf("\nEnter average number: ");
scanf("%s", list[i].AverageNum);
}
还请注意,使用scanf() 系列函数和%s 或%[] 读取字符串时,应始终指定最大宽度以避免缓冲区溢出。例如:
scanf("%19s", list[i].firstName);
请注意,使用 19,即使 firstName 字段是 20 个 char 值的数组。请记住,必须为\0 终止符保留一个空格。由于您使用%s 将字符串读入AverageNum 字段,因此您还应该:
scanf("%1s", list[i].AverageNum);
也就是说,这个字段只能保存一位数字。如果打算保留两位数,则必须将 struct 内的此字段更改为:char AverageNum[3]。
当我们讨论scanf() 时,请注意此函数返回函数调用期间成功分配的次数。如果没有分配,则返回 0。这个返回值应该总是被检查。考虑:如果用户在预期数字时错误地输入了字母,则预期变量中不会存储任何内容。这可能会导致未定义的行为。您可以尝试这样的方法来验证数字输入:
printf ("\nEnter the host of students: ");
while (scanf ("%d ", &length) < 1) {
puts("Please enter a number");
int c;
while ((c = getchar()) != '\n' && c != EOF) {
continue;
}
}
如果未按预期输入数字,则此代码要求用户再次输入。请注意,如果用户确实输入了非数字,则此字符将保留在输入流中,并且必须在尝试处理更多用户输入之前将其清除。 while 循环是完成此任务的典型结构。
编辑
基于 OP 制作的 cmets,这里是发布代码的修改版本。此版本对struct 的AverageNum 字段使用float 值而不是字符数组。对于存储平均值,浮点类型可能比整数类型更有用。通常最好将double 用于浮点值,但在这种情况下,AverageNum 似乎几乎不需要精度(char 数组原本打算只保存两位数); float 可能足以满足此用途。如果需要不同的类型,只需修改下面的代码即可。
实现了一些输入验证,但请注意,还可以做更多工作。当在预期输入数字的地方发现非数字输入时,提示用户输入数字。在这样的输入错误之后,输入流被while 循环构造清理;例如,最好将此代码删除到名为 clear_input() 的单独函数中。
如果用户从键盘发出文件结束信号,scanf() 将返回EOF;在这种情况下,下面的代码选择退出并显示错误消息,而不是继续输入格式错误。这也可能发生在从文件重定向的输入中,如果需要这样的输入,则可能需要以不同的方式处理这种情况。
填充list[] 数组的循环似乎运行效率低下,每次通过两次请求AverageNum。这已被简化。
请注意,对malloc() 的调用可以重写为:
studentp = malloc(length * sizeof *studentp);
这是编写此类分配的一种非常惯用的方式。这里,不是使用显式类型作为sizeof 的操作数,也就是说,不是sizeof (struct student),而是使用保存分配地址的变量。 sizeof 只使用了表达式*studentp 的类型,所以这里没有取消引用这个变量。在代码的维护生命周期中,当类型发生变化时,这种编码方式不易出错且更易于维护。
然而,尚不清楚为什么首先为studentp 分配内存。在发布的代码中,firstName 和AverageNum 字段都在循环调用StudentScan() 时为动态分配的studentp 的成员填充;相同的循环用不同的输入填充list[](structs 的不同数组)成员的AverageNum 字段。似乎不需要studentstructs 的这些数组之一;我已经注释掉了动态分配的数组以支持静态分配的版本。
这里是修改后的代码:
#include <stdio.h>
#include <stdlib.h>
struct student {
char firstName[20];
float AverageNum;
};
void StudentScan(int, struct student[]);
void StudentPrint(int, struct student[]);
int main(void) {
int i;
int length;
// struct student *studentp;
printf ("\nEnter the host of students: ");
while (scanf ("%d", &length) < 1) {
puts("Please enter a number");
int c;
while ((c = getchar()) != '\n' && c != EOF) {
continue;
}
}
struct student list[length];
/* This is fine */
// studentp = malloc(length * sizeof (struct student));
/* But this is better */
// studentp = malloc(length * sizeof *studentp);
// if (studentp == NULL)
// {
/* Not wrong, but... */
// printf("Out of memory!");
// return 0;
// fprintf(stderr, "Allocation failure\n");
// exit(EXIT_FAILURE);
// }
for(i = 0; i < length; i++) {
StudentScan(i, list);
}
/* Code to display results here */
// free (studentp);
return 0;
}
void StudentScan(int i, struct student list[])
{
putchar('\n');
printf("Enter first name: ");
if (scanf("%19s", list[i].firstName) != 1) {
puts("Input error");
exit(EXIT_FAILURE);
}
printf("Enter average number: ");
while (scanf("%f", &list[i].AverageNum) < 1) {
puts("Please enter a number");
int c;
while ((c = getchar()) != '\n' && c != EOF) {
continue;
}
}
}