Scanf和手动功能,以便在它们协同工作时获取字符串

时间:2015-04-04 04:45:53

标签: c scanf

我在使用scanf和手动函数时遇到麻烦,无法在输入中输入字符串。

这是我的手动函数,用于在输入中获取一行字符串(我也得到[nl]字符):

void getln(char *a) {
    int i,c;
    i=0;
    do {
        c=getchar();
        a[i]=(char)c;
        i++;
    } while(c!='\n');
}

然后,我这样使用它(char hs.school [40]; char hs.pc [20]; int hs.age;):

printf("Import age: ");
scanf("%d",&hs.age);
printf("Import personal code: ");
getln(hs.pc);
printf("Import school: ");
getln(hs.school);

输出:

Import age: 18
Import personal code: Import school: Vo Thi Sau

忽略 scanf 调用后 getln 调用的原因是什么? (但下一个 getln 效果很好) 你能解释一下细节,并建议我如何修复这个bug。谢谢!

编辑: 这是我的完整代码接受用户输入并将输入输出回屏幕,这在我做了一个小技巧后运行良好,但我决定提出一个问题,主要是为了扩展我的知识^ _ ^谢谢你的回答。

#include<stdio.h>

void getln(char *);
void putstr(char *);

int main(void) {
    struct Student {
        struct Fullname {
            char first[10],middle[20],last[10];
        }fu;
        struct Native {
            char social[30],district[30],province[30];
        }na;
        struct Score {
            double maths,physics,chemistry;
        }sc;
        char pc[20],school[40];
        int age;
    }hs;
    printf("Import stage:\n");
    printf("- Import full name:\n");
    printf("++ First name: ");
    getln(hs.fu.first);
    printf("++ Middle name: ");
    getln(hs.fu.middle);
    printf("++ Last name: ");
    getln(hs.fu.last);
    printf("- Import native living place:\n");
    printf("++ Social: ");
    getln(hs.na.social);
    printf("++ District: ");
    getln(hs.na.district);
    printf("++ Province: ");
    getln(hs.na.province);
    printf("- Import school: ");
    getln(hs.school);
    printf("- Import personal code: "); // I have done a little trick
    getln(hs.pc);                       // before I post the question,
    printf("- Import age: ");           // which swaped these two stage,
    scanf("%d",&hs.age);                // but it's works like a charm ^_^
    printf("- Import scores:\n");
    printf("++ Mathematics: ");
    scanf("%lf",&hs.sc.maths);
    printf("++ Physics: ");
    scanf("%lf",&hs.sc.physics);
    printf("++ Chemistry: ");
    scanf("%lf",&hs.sc.chemistry);
    printf("\nExport stage:\n");
    printf("- Full name: ");
    putstr(hs.fu.first);
    printf(" ");
    putstr(hs.fu.middle);
    printf(" ");
    putstr(hs.fu.last);
    printf(".\n");
    printf("- Native living place: ");
    putstr(hs.na.social);
    printf(", ");
    putstr(hs.na.district);
    printf(", ");
    putstr(hs.na.province);
    printf(".\n");
    printf("- School: ");
    putstr(hs.school);
    printf(".\n");
    printf("- Personal code: ");
    putstr(hs.pc);
    printf(".\n");
    printf("- Age: %d.\n",hs.age);
    printf("- Scores (Mathematics, Physics, Chemistry): %.2lf, %.2lf, %.2lf.\n",hs.sc.maths,hs.sc.physics,hs.sc.chemistry);
    return 0;
}

void getln(char *a) {
    int i,c;
    i=0;
    do {
        c=getchar();
        a[i]=(char)c;
        i++;
    } while(c!='\n');
}
void putstr(char *a) {
    int i;
    i=0;
    while(a[i]!='\n') {
        putchar(a[i]);
        i++;
    }
}

4 个答案:

答案 0 :(得分:1)

您没有清除输入缓冲区。所以在这个换行符将放在给scanf的第一个输入之后。所以getchar会将新行作为输入。所以循环将退出。

scanf

之后使用此行
int c;
if ( scanf("%d",&hs.age) != 1 ) { 
       printf("Invalid Input\n");retrun 0; }

while((c=getchar()) != '\n' && c != EOF );

它将清除输入缓冲区。然后它将询问用户的第二个输入。

答案 1 :(得分:1)

在输入 hs.age 后,您按了输入,这是一个 \ n 字符。所以你的getln()被调用但是循环在一次迭代后被破坏,因为 c 包含&#39; \ n&#39; 。如果您打印 hs.pc ,输出屏幕中会有一个新行。

答案 2 :(得分:1)

未跳过getln来电,它会将newline(输入缓冲区)中的stdin字符作为输入,并将'\n'读为{ {1}},将其分配给c,检查a[i]是否为c字符并退出。

要解决初始问题,您需要在调用'\n'之前清除输入缓冲区。您可以使用其他答案中建议的getln循环执行此操作,也可以为while创建一个正确的格式字符串,该字符串将使用换行符,清空缓冲区。 (不是万无一失),但替代scanf将是:

scanf

哪个会在数字前跳过所有空格(包括任何换行符),读取小数值,然后读取并丢弃换行符。 注意:这仅适用于没有尾随字符的数字。输入scanf(" %d%*c",&hs.age); 会在13abc中留下bc\n。在这种情况下,while循环更灵活,因为它会读取所有字符,直到遇到换行符并且可能是更好的选择:

stdin

至于您的scanf(" %d",&hs.age); while ((c = getchar()) != '\n' && c != EOF); 功能,只需将每个字符读入getln即可。并非真正需要a[i]。您还需要对输入进行相同的检查,以免留下换行符。您还需要检查c的最大长度i减去1.我建议a表示您输入的最大字符串长度。这将允许某些东西测试#define MAXS 128以防止写入超出字符串的结尾。

以下是i的替代方案。 注意:类型为getln,允许它返回读取行的长度,以便您可以确定如果已达到int该怎么做(因为仍然会有字符在那时MAXS)。作为一般规则,如果您在可能存在错误的情况下在函数中执行某些操作,则最好返回指示成功/失败/问题的值:

stdin

答案 3 :(得分:0)

  

你能解释一下细节......

我会尽量不使用诸如 buffer 等令人困惑的术语。

您可能已经知道"%d"对应于一组十进制数字字符,这些字符将转换为int。当您按其他人的建议按“Enter”时,'\n'字符将通过stdin传输。 '\n'不是十进制数字字符,因此它会被放回到您的getln函数的流中,以便稍后发现...

实际上,您的“在scanf调用后立即调用getln”可能不会被忽略;只是阅读尾随的'\n'并看到一个空行

这是假设另一个问题没有发挥作用。 getln无法看到a指向的字节数,因此它无法判断何时溢出,因此不会尝试阻止缓冲区溢出...您基本上已重写{ {1}}。如果您的输入足够长,那么我认为这也可能导致您的问题......缓冲区溢出是未定义的行为,使用未定义行为的后果是未定义

关于未定义行为的主题,因为gets在技术上并不产生字符串,所以我希望你以后不会将它用作标准字符串函数的输入...

同样关于未定义行为的主题,如果用户输入的内容不是一组十进制数字,您认为可能会发生什么? getln通过返回值传达输入错误...因此永远不会忽略返回值。您可以(并且应该在某个时候)在the scanf manual中找到有关此内容的更多信息。


  

...并建议我如何修复此错误。

放弃用户输入并没有多大意义,但不幸的是,你不能指望一个能够带来更好的用户体验的解决方案,而不会使你的代码大小(以及这种解释)大大超出比例。


您可以使用scanf来删除该行的其余部分(可能只是'\n'),如下所示:scanf ...跟随{{ 1}} scanf("%*[^\n]"); getchar();调用,当然......您甚至可以将两者合并在一起,如下所示:

"%d"

不幸的是,如果您的用户使用空格键而不是输入,他或她可能不会发现有关这方面的问题,直到它太晚了......


关于缓冲区溢出问题,我建议使用scanf而不是if (scanf("%d%*[^\n]", &hs.age) != 1) { puts("ERROR: EOF or file access error."); exit(0); } getchar(); fgets也有故障模式,它们通过返回值和数组内容传递。返回值用于传达gets和文件访问错误,并且返回值中fgets的存在(或缺少)用于表示输入行太大而无法存储在数组中。我们可以通知用户溢出(我相信他们会很感激)并使用先前使用的相同EOF技巧丢弃多余的...

'\n'

我认为将这些解决方案包装到函数中是有意义的,除了这些函数会促进丢弃用户输入。尽管如此,后者足够冗长,你最有可能从抽象中获益......

scanf

...现在您可以这样使用:if (fgets(hs.pc, sizeof hs.pc, stdin) == NULL) { puts("ERROR: EOF or file access error."); exit(0); } size_t size = strcspn(hs.pc, "\n"); if (hs.pc[size] != '\n') { printf("WARNING: MAXIMUM SIZE OF %zu EXCEEDED! LINE TRUNCATED.\n", sizeof hs.pc - 1); scanf("%*[^\n]"); getchar(); } hs.pc[size] = '\0';