【问题标题】:How to read undefined number of integers from stdin line by line?如何从标准输入中逐行读取未定义数量的整数?
【发布时间】:2019-05-17 02:28:02
【问题描述】:

我需要使用以以下格式接收的 C 从标准输入读取和存储整数:“35:27,5,10” 每行有未定义的整数数和未定义的行数。理想情况下,我希望有一个循环,在其中我可以使用最近扫描行的值来执行其他任务。我将如何扫描整数并将其存储在数组中,然后在扫描下一行时覆盖数组,依此类推,直到遇到 EOF?

我认为我必须使用 scanf() 和 strtok() 的组合,但我似乎无法弄清楚如何使用。我也尝试过使用 getchar() 但这只会让事情变得更复杂。

【问题讨论】:

  • 只用 scanf 但注意每个整数后面的内容,如果是 '\n' 则表示行尾,
  • 所以,你真的不关心未定义的行数,因为你会分别处理每一行。您只需要知道每行有多少整数可用以及它们是什么。据推测,32 位(有符号)int 值足以容纳单行上的整数数量 - 或者您是否需要满足单行上数 GB 的数据?值是否也在int(32 位有符号整数)范围内?有负值吗?除了冒号、逗号和换行符之外,您还有其他分隔符吗?你还没有展示你尝试过的东西。

标签: c int scanf stdin


【解决方案1】:

如果发现意外情况,此程序会读取该格式的文件(在标准输入上)并发出错误。

//sumline.c  sum integers on each line.
#include <stdio.h>
int main(){
  int sum=0;
  int rowcount=0;
  char buf[30]="";
  while(! feof(stdin) )
  { 
    int in;
    char sep[2];
    if((scanf("%1[^0-9-+]",sep)) && !feof(stdin))
    {
        fgets(buf,30,stdin);
        printf("unexpected %d char (%c,%30s)\n",sep[0],sep[0],buf);
        return 1;
    }
    if( ! scanf("%d",&in))
    {
        printf("malformed int\n");
        return 1;
    }
    if( feof(stdin) && rowcount == 0 )
    {
        return 0;
    }
    sum += in;
    if( ! scanf("%1[,:.\n]",sep) && !feof(stdin))
    {
        fgets(buf,30,stdin);
        printf("inexpected char %30s\n",buf);
        return 1;
    }
    else    
    {
      ++rowcount;
    }

    if( sep[0]=='\n' && rowcount )
    {
        printf("sum=%d\n",sum);
        sum=0;
        rowcount=0;
    }

    if( feof(stdin) && rowcount == 0 )
    {
        return 0;
    }
  }
return 0;
}

【讨论】:

  • 嗯 - 领先的非'\n' 空白导致printf("unexpected ... 。看起来死板但符合 OP 的目标。
  • 有人抱怨 scanf("%d" 不检查 '\n' 所以我改变了循环的顺序。如果文件声称具有这样那样的格式,最好确保确实如此
【解决方案2】:

我想我必须使用 scanf() 和 strtok() 的组合

使用fgets() 读取行,使用sscanf() 扫描读取的行以查找整数:

#include <stddef.h>  // size_t
#include <stdlib.h>  // realloc(), free()
#include <stdio.h>   // fgets(), sscanf(), printf(), puts(), fputs()

int main(void)
{
    char line_buffer[100];          // a buffer for the current line
    int *values_buffer = NULL;      // pointer to memory to store the values in
    size_t values_buffer_size = 0;  // actual size of the value buffer

    // while a line can be read from stdin ...
    while (fgets(line_buffer, sizeof line_buffer / sizeof *line_buffer, stdin)) {
        size_t num_values = 0;    // to count the values we're able to extract
        char *pos = line_buffer;  // the current read-position inside line_buffer

        // try to extract integers from the line_buffer at position pos:
        for (int consumed = 0, value;
             sscanf(pos, "%d%*[^0123456789]%n", &value, &consumed) >= 1;
             pos += consumed)
        {
            // %*[^0123456789] discards a string not containing any digit
            // %n yields the number of characters consumed

            // if the value_buffer isn't big enough ...
            if (num_values >= values_buffer_size) {
                // resize it
                int *tmp = realloc(values_buffer, (num_values + 1) * sizeof *tmp);
                if (!tmp) {
                    fputs("Not enough memory. :(", stderr);
                    free(values_buffer);
                    return EXIT_FAILURE;
                }
                // ... and update it's size
                values_buffer_size = num_values + 1;
                values_buffer = tmp;
            }

            // save the current value in value_buffer
            values_buffer[num_values++] = value;
        }

        // have fun with the values of the current line:
        if (num_values) {
            printf("Values: ");
            for (size_t i = 0; i < num_values; ++i)
                printf("%d ", values_buffer[i]);
            putchar('\n');
        } else puts("No values. :(\n");
    }

    // clean-up:
    free(values_buffer);

    if (ferror(stdin)) {
        fputs("An input error occured. :(\n", stderr);
        return EXIT_FAILURE;
    }
    else if (feof(stdin))
        puts("EOF reached.\n");
}

【讨论】:

  • 现在,如果我输入`0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001' 作为第一整数会发生什么?跨度>
  • scanf 不受这么长的整数的困扰,您正在使用 fget 将行分成几个块。
【解决方案3】:

我想我必须使用 scanf() 和 strtok() 的组合

否则我会假设:

  • 用户可以在任何数字之前输入“几乎无限”数量的零,或者大量空白,或者在小数点后输入大量零,或者...。这意味着您不能期望 (例如)同时在 RAM 中拥有一个数字的所有数字;这意味着 C 提供的所有功能都不可用。

  • 发生错误。在解析文本时,无论文本来自用户(他们需要知道为什么不喜欢他们的文本)还是来自文件或另一台计算机(开发人员需要能够发现/解决问题)。

避免这两个问题的最好办法是使用循环中的状态机;可能:

int state = NEWLINE;
unsigned int lineNumber = 0;
unsigned int dataNumber;

while( ((c = getChar()) != EOF)) && (state != ERROR) ) {
    switch(state) {
        case NEWLINE:
            lineNumber++;
            if(isDigit(c) {
                number = c - '0';
                state = FIRST_NUMBER_STARTED;
                dataNumber = 1;
            } else {
                printf("ERROR: Character at start of line is not a valid decimal digit\n");
                state = ERROR;
            }
            break;
        case FIRST_NUMBER_STARTED:
            if(isDigit(c) {
                digit = c - '0';
                if(number > UINT_MAX/10) {
                    printf("ERROR: First number on line %u is too large\n", lineNumber);
                    state = ERROR;
                } else {
                    number *= 10;
                    if(number > UINT_MAX - digit) {
                        printf("ERROR: First number on line %u is too large\n", lineNumber);
                        state = ERROR;
                    } else {
                        number += digit;
                    }
                }
            } else if(c == ';') {
                state = COLON_FOUND;
            } else {
                printf("ERROR: Invalid character after first number on line\n");
                state = ERROR;
            }
            break;
        case COLON_FOUND:
            if(isDigit(c) {
                number = c - '0';
                state = DATA_NUMBER_STARTED;
            } else {
                printf("ERROR: Character at start of data not a valid decimal digit\n");
                state = ERROR;
            }
            break;
        case DATA_NUMBER_STARTED:
            if(isDigit(c) {
                digit = c - '0';
                if(number > UINT_MAX/10) {
                    printf("ERROR: Data number %u on line %u is too large\n", dataNumber, lineNumber);
                    state = ERROR;
                } else {
                    number *= 10;
                    if(number > UINT_MAX - digit) {
                        printf("ERROR: Data number %u on line %u is too large\n", dataNumber, lineNumber);
                        state = ERROR;
                    } else {
                        number += digit;
                    }
                }
            } else if(c == ',') {
                state = COMMA_FOUND;
            } else if(c == '\n') {
                state = NEW_LINE;
            } else {
                printf("ERROR: Invalid character after data number %u on line %u\n", dataNumber, lineNumber);
                state = ERROR;
            }
            break;
        case COMMA_FOUND:
            dataNumber++;

            if(isDigit(c) {
                number = c - '0';
                state = FIRST_NUMBER_STARTED;
            } else if(c == '\n') {
                printf("ERROR: Missing number after comma at end of line %u\n", lineNumber);
                state = ERROR;
            } else {
                printf("ERROR: Invalid character after comma (after data number %u) on line %u\n", dataNumber-1, lineNumber);
            }
            break;
    }
}

注意:示例代码不存储任何数据,并且根本不处理空格(或小数点,或...)(您可以为此添加更多代码和新状态)等等。它是“非常未经测试”的代码,仅作为示例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-02
    • 1970-01-01
    • 1970-01-01
    • 2019-07-26
    • 2018-04-28
    • 2014-04-09
    • 2014-08-22
    相关资源
    最近更新 更多