让 scanf 在读取换行符时停止读取?

7
如果我在终端输入5 5,按下回车键,再次按下回车键,我想退出循环。
int readCoefficents(double complex *c){
    int i = 0;
    double real;
    double img;
    while(scanf("%f %f", &real, &img) == 2)
        c[i++] = real + img * I;


    c[i++] = 1 + 0*I; // most significant coefficient is assumed to be 1
    return i;
}

显然,那段代码对我来说没有用(是的,我知道缓冲区溢出随时可能发生)。

scanf 除非我输入一个字母(或一些非数字、非空格的字符串),否则不会停止。我该如何让 scanf 在读取空行后退出?


如果你需要更多理由避免使用 scanf:http://c-faq.com/stdio/scanfprobs.html - jamesdlin
5个回答

11

使用fgets函数读取控制台输入:

   int res = 2;
   while (res == 2) {
       char buf[100];
       fgets(buf, sizeof(buf), stdin);
       res = sscanf(buf, "%f %f", &real, &img);
       if (res == 2)
           c[i++] = real + img * I;
   }
   c[i++] = 1 + 0*I; // most significant coefficient is assumed to be 1
   return i;

因为您先提到了fgets和sscanf,所以应该归功于您。 - Matt
谢谢,我刚意识到在 while 循环内声明缓冲区可能不被 C 语言允许,但概念是一样的。 - Matthew Smith
2
那个声明完全没问题。 - jamesdlin

10
您遇到的问题是,scanf格式字符串为%f,将跳过空格(包括换行符),直到找到要扫描的实际字符。根据c99标准:

执行转换说明的步骤如下:
- 除非规范包括'[''c''n'说明符,否则将跳过输入的空格字符(由isspace函数指定)。

在其他地方,描述了isspace()

标准的空格字符如下:空格' '、换页符'\f'、换行符'\n'、回车符'\r'、水平制表符'\t'和垂直制表符'\v'

您最好使用fgets获取行(这可以很容易地防止缓冲区溢出),然后对结果行使用sscanfscanf函数是那些应该非常谨慎看待的函数之一。以下代码片段是我经常用来处理行输入的:
#include <stdio.h>
#include <string.h>

#define OK       0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Get line with buffer overrun protection.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    if (buff[strlen(buff)-1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[strlen(buff)-1] = '\0';
    return OK;
}

 

// Test program for getLine().

int main (void) {
    int rc;
    char buff[10];

    rc = getLine ("Enter string> ", buff, sizeof(buff));
    if (rc == NO_INPUT) {
        // Extra NL since my system doesn't output that on EOF.
        printf ("\nNo input\n");
        return 1;
    }

    if (rc == TOO_LONG) {
        printf ("Input too long [%s]\n", buff);
        return 1;
    }

    printf ("OK [%s]\n", buff);

    return 0;
}

使用各种组合进行测试:

pax> ./prog
Enter string>[CTRL-D]
No input

pax> ./prog
Enter string> a
OK [a]

pax> ./prog
Enter string> hello
OK [hello]

pax> ./prog
Enter string> hello there
Input too long [hello the]

pax> ./prog
Enter string> i am pax
OK [i am pax]

我会使用这个函数安全地获取一行,然后简单地使用以下代码:
sscanf (buffer, "%f %f", &real, &img)

获取实际值(并检查计数)。


事实上,这是一个完整的程序,更接近于您想要的:

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

#define OK       0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Get line with buffer overrun protection.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    if (buff[strlen(buff)-1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[strlen(buff)-1] = '\0';
    return OK;
}

 

int main (void) {
    int i = 1, rc;
    char prompt[50], buff[50];
    float real, imag;

    while (1) {
        sprintf (prompt, "\nEnter real and imaginary for #%3d: ", i);
        rc = getLine (prompt, buff, sizeof(buff));
        if (rc == NO_INPUT) break;
        if (*buff == '\0') break;

        if (rc == TOO_LONG) {
            printf ("** Input too long [%s]...\n", buff);
        }

        if (sscanf (buff, "%f %f", &real, &imag) == 2) {
            printf ("Values were %f and %f\n", real, imag);
            i++;
        } else {
            printf ("** Invalid input [%s]\n", buff);
        }
    }

    return 0;
}

伴随着一次测试运行:

pax> ./testprog

Enter real and imaginary for #  1: hello
** Invalid input [hello]

Enter real and imaginary for #  1: hello there
** Invalid input [hello there]

Enter real and imaginary for #  1: 1
** Invalid input [1]

Enter real and imaginary for #  1: 1.23 4.56
Values were 1.230000 and 4.560000

Enter real and imaginary for #  2:

pax> _

使用 scanf 从用户读取输入通常会引入此类问题。直接将输入读取为字符串,然后解析字符串通常更少出错。 - bta

3

使用scanf有一种方法可以实现您想要的功能:

int readCoefficents(double complex *c) {
    int i = 0;
    double real;
    double img;
    char buf[2];
    while (scanf("%1[\n]", buf) == 0) {         // loop until a blank line or EOF
        if (scanf("%lf %lf", &real, &img) == 2) // read two floats
            c[i++] = real + img * I;
        scanf("%*[^\n]");                       // skip the rest of the line
        scanf("%*1[\n]");                       // and the newline
    }
    c[i++] = 1 + 0*I; // most significant coefficient is assumed to be 1
    return i;
}

如果用户在一行中只输入一个浮点数,则会读取下一行作为第二个值。 如果输入了任何随机垃圾,则会跳到下一行并尝试再次读取。 否则,它将继续读取成对的浮点值,直到用户输入空行或达到EOF。


0
关于 PAXDIABLO 解决方案:它不能正确处理用户输入的空行,因此应该在您的 getLine() 函数中添加这一行。
if (strlen(buff) <= 1) return NO_INPUT;

在这行代码之后:

if (fgets (buff, sz, stdin) == NULL)
    return NO_INPUT;

所以它将变成:

...
   if (strlen(buff) <= 1) return NO_INPUT;
   if (fgets (buff, sz, stdin) == NULL) return NO_INPUT;
....

0

不是

while(scanf("%f %f", &real, &img) == 2)

尝试

while(scanf("%f %f%*c", &real, &img) == 2)


scanf("%f%*c", &myfloat); // will read a float and all eventual characters after it

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接