字符串的 scanf 在执行另一个 scanf 后被跳过

scanf for string gets skipped after taking another scanf

提问人:Pratyush 提问时间:9/2/2023 最后编辑:chqrliePratyush 更新时间:9/2/2023 访问量:48

问:

这是一个程序,它从用户那里获取输入,即:roll.no,姓名,他的物理,化学和数学标记并打印它们。

如果用于获取字符串输入,则在此程序中不起作用。%[^\n]s

这个程序工作正常

#include <stdio.h>
#include <string.h>

void main()
{
    int roll_no;
    char student[1000];
    float physics, maths, chemistry;
    printf("Enter your roll no. : ");
    scanf("%d", &roll_no);
    fflush(stdin);
    printf("\nEnter your name : ");
    fflush(stdin);
    scanf("%s", student);
    fflush(stdin);
    printf("\nEnter your physics : ");
    scanf("%f", &physics);
    printf("\nEnter your maths : ");
    scanf("%f", &maths);
    printf("\nEnter your chemistry : ");
    scanf("%f", &chemistry);
    
    printf("Student's Name : %s\nPhysics Marks : %.2f Chemistry Marks : %.2f Maths Marks : %.2f",
           student, physics, chemistry, maths);
}

为了取学生的全名(名字和姓氏),我后来用代替了scanf("%[^\n]s", student);scanf("%s", student);

当我用于学生数组时,程序采用卷号。然后,用户的输入会跳过学生。它直接跳到物理来获取物理分数。 还用于防止在采用多种数据类型时出现故障,但似乎仍然不起作用%[^\n]sscanfscanffflush(stdin)scanf

数组 C 字符串 输入 scanf

评论

2赞 Some programmer dude 9/2/2023
请注意,在 C 规范中明确提到将仅输入流(如 )传递给会导致未定义的行为。对于支持它的编译器(IIRC 实际上只有三个主要编译器中的一个支持它),那么您显示的代码就不需要它了。stdinfflush
2赞 Retired Ninja 9/2/2023
fflush(stdin);可能不会做你所希望的。在尾随 s 不正确。scanf() 将换行符留在缓冲区中,值得一读,以了解处理此问题的正确方法。%[^\n]s
2赞 Some programmer dude 9/2/2023
而且没有格式,只有.请注意,尾随不是格式说明符的一部分。如果您使用它,则需要一个实际的字符作为输入。%[...]s%[...]sscanfs
1赞 Some programmer dude 9/2/2023
对于将来的问题,请向我们展示实际失败的代码,即您有问题并想询问的代码。显示的代码没有您要问的问题,它不是一个最小的可重现示例
1赞 Fe2O3 9/2/2023
防止为字符串指定最大字符数时溢出。例如scanf( "%999[^\n]", name );

答:

3赞 chqrlie 9/2/2023 #1

您的问题存在多个问题和误解:

  • 没有转换规范,尾随不是转换的一部分,它充当必须与输入字符匹配的字符,这不会发生,因为仅在换行符之前或文件末尾停止。%[^\n]sss%[^\n]

  • fflush(stdin)具有未定义的行为。它不会刷新输入流。您可以读取和丢弃输入行中的剩余字符,包括带有循环的换行符:

        int c;
        while ((c = getchar()) != EOF && c !+ '\n')
            continue;
    
  • 和 之间的区别在于空格字符的行为:忽略前导空格(包括换行符),然后读取字符并将其存储到目标数组中,直到读取空格并将其推回输入流。相反,读取和存储除换行符之外的任何字符,换行符被推回。%s%[^\n]%s%[^\n]

  • 上述行为的后果是忽略 by 留下的挂起换行符和任何其他前导空格,然后读取进一步的输入,直到找到下一个空格字符,该字符在输入流中保持挂起状态。 立即停止并返回,因为挂起的换行符不会被忽略并且与规范不匹配。若要跳过此前导空格,应在格式字符串中使用空格:scanf("%s", student)scanf("%d", &roll_no)scanf("%[^\n]", student)0scanfscanf(" %[^\n]", student)

  • 如果输入包含一长串匹配字符,则两者都可以将任意数量的字符存储到目标数组中。这是一个典型的安全漏洞,攻击者可以使用精心构造的输入行来利用它。您应该将要存储的最大字符数传递为 或%s%[^\n]scanf("%999s", student)scanf(" %999[^\n]", student);

  • 在使用仍未初始化的目标变量时,应始终测试 的返回值以检测无效或缺失的输入,并避免未定义的行为。scanf()

  • 没有参数的原型是 。mainint main(void)

这是一个修改后的版本:

#include <stdio.h>

int main(void) {
    int roll_no;
    char student[1000];
    float physics, maths, chemistry;

    printf("Enter your roll no.: ");
    if (scanf("%d", &roll_no) != 1) {
        fprintf(stderr, "invalid input\n");
        return 1;
    }
    printf("\nEnter your name: ");
    if (scanf(" %999[^\n]", student) != 1) {
        fprintf(stderr, "invalid input\n");
        return 1;
    }
    printf("\nEnter your physics: ");
    if (scanf("%f", &physics) != 1) {
        fprintf(stderr, "invalid input\n");
        return 1;
    }
    printf("\nEnter your maths: ");
    if (scanf("%f", &maths) != 1) {
        fprintf(stderr, "invalid input\n");
        return 1;
    }
    printf("\nEnter your chemistry: ");
    if (scanf("%f", &chemistry) != 1) {
        fprintf(stderr, "invalid input\n");
        return 1;
    }

    printf("Student Name: %s\n"
           "Physics Marks: %.2f Chemistry Marks: %.2f Maths Marks: %.2f",
           student, physics, chemistry, maths);
    return 0;
}

您可以尝试避免代码重复,并在出现错误时使用自定义输入函数(如下所示)重新提示用户:

#include <stdio.h>

int get_integer(const char *prompt, int *dest, int min, int max) {
    for (;;) {
        int val, c, res;
        printf("%s: ", prompt);
        res = scanf("%d", &val);
        while ((c = getchar()) != EOF && c != '\n')
            continue;
        if (res == 1) {
            if (val >= min || val <= max) {
                *dest = val;
                return 1;
            }
            printf("value %d out of range [%d..%d]\n", val, min, max);
        } else
        if (res == EOF || c == EOF) {
            printf("unexpected end of file\n");
            return 0;
        } else {
            printf("invalid input\n");
        }
    }
}

int get_float(const char *prompt, float *dest) {
    for (;;) {
        float val;
        int c, res;
        printf("%s: ", prompt);
        res = scanf("%f", &val);
        while ((c = getchar()) != EOF && c != '\n')
            continue;
        if (res == 1) {
            *dest = val;
            return 1;
        } else
        if (res == EOF || c == EOF) {
            printf("unexpected end of file\n");
            *dest = 0;
            return 0;
        } else {
            printf("invalid input\n");
        }
    }
}

int get_string(const char *prompt, char *dest, size_t size) {
    for (;;) {
        int c;
        size_t n = 0, i = 0;
        printf("\n%s: ", prompt);
        while ((c = getchar()) != EOF && c != '\n') {
            if (i + 1 < size)
                dest[i++] = (char)c;
            n++;
        }
        if (i < size) {
            dest[i] = '\0';
        }
        if (n == 0) {
            if (c == EOF) {
                printf("unexpected end of file\n");
                return 0;
            }
            printf("string cannot be empty\n");
        } else {
            return 1;
        }
    }
}

int main(void) {
    int roll_no;
    char student[1000];
    float physics, maths, chemistry;

    if (!get_integer("Enter your roll no.", &roll_no, 0, 10000)
    ||  !get_string("Enter your name", student, sizeof student)
    ||  !get_float("Enter your physics marks", &physics)
    ||  !get_float("Enter your maths marks", &maths)
    ||  !get_float("Enter your chemistry marks", &chemistry)) {
        return 1;
    }

    printf("Student Name: %s\n"
           "Physics Marks: %.2f Chemistry Marks: %.2f Maths Marks: %.2f\n",
           student, physics, chemistry, maths);
    return 0;
}

评论

0赞 Andrew Henle 9/2/2023
您可以尝试避免代码重复,并在出现错误时使用自定义输入函数(例如哈佛课程 cs50 提倡的函数)重新提示用户。只是永远不要使用或重现 CS50 令人困惑的可憎之物的恐怖。像这样混淆 C 字符串处理会造成对许多基本 C 概念的严重误解。typedef char *string
0赞 chqrlie 9/2/2023
@AndrewHenle:我同意你的看法,这种 typedef 具有误导性和毒性。然而,输入包装器很有趣。我将修改答案并提供示例,而不是推广 cs50。