发布的代码已定义StudentScan()
within main()
。但是 C 中不允许嵌套函数定义。这应该会生成编译器警告,例如:
警告:ISO C 禁止嵌套函数 [-Wpedantic]
void StudentScan(int i, 结构学生列表[])
注意所有编译器警告并修复它们。如果编译此代码时没有看到警告,请调高编译器警告的级别。在 gcc 上,我建议始终至少使用gcc -Wall -Wextra
,我总是加上-Wpedantic
. The -Wpedantic
需要 gcc 才能看到对此的警告。一些编译器,gcc 就是其中之一,do支持嵌套函数定义作为编译器扩展。不过,此功能是非标准的,最好不要依赖它。
修复方法很简单:移动 的定义StudentScan()
out of 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
or %[]
以避免缓冲区溢出。例如:
scanf("%19s", list[i].firstName);
请注意,尽管使用了 19firstName
字段是一个 20 的数组char
价值观。请记住,必须为以下内容保留一个空间\0
终结者。既然你正在使用%s
将字符串读入AverageNum
字段,您还应该有:
scanf("%1s", list[i].AverageNum);
也就是说,该字段只能保存一位数字。如果打算保留两位数字,则必须在以下时间内更改此字段struct
to: char AverageNum[3]
.
当我们正在讨论时scanf()
,请注意,此函数返回函数调用期间成功分配的数量。如果未进行分配,则返回 0。这个返回值应该always被检查。考虑一下:如果用户在应该输入数字时错误地输入了字母,则预期变量中不会存储任何内容。这可能会导致未定义的行为。您可以尝试这样的方法来验证数字输入:
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;
}
}
如果未按预期输入数字,此代码会要求用户再次输入。请注意,如果用户does如果输入非数字,该字符将保留在输入流中,并且必须在尝试处理更多用户输入之前清除。这while
循环是完成此任务的典型结构。
Edit
根据 OP 的评论,这里是已发布代码的修改版本。这个版本使用了一个float
值而不是字符数组AverageNum
领域的struct
。对于存储平均值,浮点类型可能比整数类型更有用。通常最好使用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
and AverageNum
为动态分配的成员填充字段studentp
在致电StudentScan()
循环中;相同的循环填充AverageNum
成员领域list[]
(不同的数组struct
s) 使用不同的输入。似乎不需要这些数组之一student
struct
s;我已注释掉动态分配的数组,以支持静态分配的版本。
这是修改后的代码:
#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;
}
}
}