【问题标题】:How to ask for user input without knowing length in advance? In C如何在事先不知道长度的情况下要求用户输入?在 C 中
【发布时间】:2019-10-01 16:02:30
【问题描述】:

我希望用户输入形式为 {{a,b},{c,d}} 的单行表示一个矩阵。矩阵的维度是已知的。使用 getchar(),我无法处理整数大于 9 的情况,而不会绕道而行。使用 scanf(),它会在每次使用这个函数后换行。如何正确请求这种格式的输入?在我看来,一定有比 getchar() 更简单的方法。

如前所述,我曾尝试使用适用于单个数字整数的 getchar() 循环,但考虑到多个数字时,循环的长度会变得不正确。然后,我尝试了下面的代码,但这似乎也不起作用。

int i=0;
int j=0;
for (int k=0;k<loopSize;k++){
     scanf("%d%c",&element,&c);
         matrix[i][j]=element;
         j++;
         if (j%columns==0){
            i++;
            j=0;
         }
}

如何正确请求输入?提前致谢。

【问题讨论】:

  • 这不是一个很长的绕道:num= num*10 +(c-'0');,其中 c 是一个 ASCII 数字,num 在循环之前设置为零。
  • 冗长的部分是我需要放置 if 和 else 语句以确保它只对整数输入执行此操作,而不是大括号和逗号。不是这样吗?我还需要以一种看起来太麻烦的方式使循环的长度取决于右括号的数量。我认为会有更简单的方法。
  • 您是要坚持让用户输入嵌套的大括号和逗号,还是让他们输入正确数量的数字(中间有空格)更简单?如果你想全部在一行,你必须使用读行函数(fgets()或POSIX getline())来读行,然后考虑使用sscanf()解析输入行。
  • 关键是scanf()不关心换行; %d 转换将愉快地跳过包括多个换行符在内的空格以查找下一个输入。对于带有大括号和逗号的数据,您可能会发现一种比使用sscanf() 更灵活的临时解析技术——尤其是当您必须计算{} 之间的数字数量等时。
  • 是的,我希望用户输入大括号和逗号。我尝试查找一些其他的行阅读功能来使用,但我不熟悉它们。如果我觉得只使用 scanf 或 getchar 更舒服,有没有简单的方法可以实现这一点?

标签: c input


【解决方案1】:

您可以使用getchar(),如下所示

int val = 0;
int number = 0;
int dimLevel = 2;
int state = 0; // 

while ((val = getchar()) != '\n){

    if (val == '{' && state <= dimLevel){
       state++;
    }
    else if (val == '}' && state > 0){
       state--;
    }
    else if (val == ',' && state == dimLevel){
       matrix[i][j] = number;
       number = 0;

       if (j%columns==0){
          i++;
          j=0;
       }
    }
    else if (isdigit(val) && state == dimLevel){
       number = number*10 + (val-'0');
    }
    else if (val == '-' && state == dimLevel){
       number *= -1;
    }
}

if (number && state == 0)
   matrix[i][j] = number; //last number

【讨论】:

  • 谢谢,我会试试的。
  • 人们会输入空格;他们可能会输入相邻的逗号;他们可能会输入字母;他们可能会使用方括号或圆括号。这还不是弹性代码;事实上,它是非常脆弱的代码。
  • @JonathanLeffler 我同意!但是我不能给他处理这个平台上所有类型的猴子测试的生产级代码。我将其保留为具体问题。现在由 OP 将脆弱的代码转换为生产代码。
  • 我想我可以找到解决这些问题的方法。我主要想知道是否有更简单的方法来请求输入。显然,事实并非如此。
  • 那好多了,虽然{ { 23 45 64 }, { -12, +34 } }仍然会引起各种形式的悲伤。
【解决方案2】:

一般来说,解析文本是相当困难的。您指定的语法相当简单,但更复杂的语法确实受益于lexing 和解析。在示例中我使用了re2c,而不是手动编写它。 re2c 将带有 /*!re2c cmets 的 C 文件作为输入,并将它们转换为快速词法分析器。

x-macro 允许打印符号,对调试非常有用,但在其他情况下不需要。

C.c.re:

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <limits.h>

/* X-Macro. */

#define PARAM(A) A
#define STRINGISE(A) #A

#define SYMBOL(X) X(END), X(LBRACK), X(RBRACK), X(NUMBER), X(COMMA)

enum Symbol { SYMBOL(PARAM) };
static const char *const symbols[] = { SYMBOL(STRINGISE) };

/* Once the input is read, re2c will lex it; this stores the cursors. */
static struct { const char *from, *to; } scanner;

/*!re2c
re2c:yyfill:enable   = 0;
re2c:define:YYCTYPE  = char;
re2c:define:YYCURSOR = scanner.to;
eof = "\x00"; // If you git re2c 2, there's a simpler new way.
whitespace = [ \t\v\f\n\r];
digit = [0-9];
number = "-"? digit+; */

static enum Symbol next(void) {
reset:
    scanner.from = scanner.to;
/*!re2c
    * { return errno = EILSEQ, END; }
    eof { return END; }
    whitespace+ { goto reset; }
    "{" { return LBRACK; }
    "}" { return RBRACK; }
    number { return NUMBER; }
    "," { return COMMA; }
*/
}

/* Define `CharArray`, a vector of characters -- dynamic string in which we
 will store the entire input. */
#define ARRAY_NAME Char
#define ARRAY_TYPE char
#include "BasicArray.h"

struct Token {
    enum Symbol symbol;
    const char *from;
    int length;
};

#define ARRAY_NAME Token
#define ARRAY_TYPE struct Token
#include "BasicArray.h"

/** Fills the `token` with `symbol` and the most recent scanner values. Returns
 whether it worked. */
static int init_token(struct Token *const token, const enum Symbol symbol) {
    assert(token && symbol && scanner.from && scanner.from < scanner.to);
    if(scanner.from + INT_MAX < scanner.to) return errno = EILSEQ, 0;
    token->symbol = symbol;
    token->from = scanner.from;
    token->length = (int)(scanner.to - scanner.from);
    return 1;
}

#define ARRAY_NAME Int
#define ARRAY_TYPE int
#include "BasicArray.h"

struct Matrix {
    struct IntArray ints;
    size_t cols;
};

/** Returns true if the `tokens` are a valid matrix, and puts it in `matrix`
 which must be empty. It doesn't allow empty matrices. */
static int parse(const struct TokenArray *const tokens,
    struct Matrix *const matrix) {
    size_t set_col = 0, col = 0;
    int is_set_col = 0;
    long input;
    int *output;
    const struct Token *t = 0;
    assert(tokens && matrix && !IntArraySize(&matrix->ints));
    errno = 0;

    /* Left outside. */
    t = TokenArrayNext(tokens, t);
    if(!t || t->symbol != LBRACK) return errno = EILSEQ, 0;
    goto left_middle;

left_middle:
    t = TokenArrayNext(tokens, t);
    if(!t || t->symbol != LBRACK) return errno = EILSEQ, 0;
    col = 0;
    goto inside_number;

inside_number:
    t = TokenArrayNext(tokens, t);
    if(!t || t->symbol != NUMBER) return errno = EILSEQ, 0;
    input = strtol(t->from, 0, 0);
    if(errno) return 0;
    if(input < INT_MIN || input > INT_MAX) return errno = ERANGE, 0;
    if(!(output = IntArrayNew(&matrix->ints))) return 0;
    *output = input;
    col++;
    goto end_number;

end_number:
    if(!(t = TokenArrayNext(tokens, t))) return errno = EILSEQ, 0;
    if(t->symbol == RBRACK) {
        if(is_set_col) {
            if(set_col != col) return errno = EILSEQ, 0;
        } else {
            set_col = col;
            is_set_col = 1;
        }
        goto finished_middle;
    } else if(t->symbol == COMMA) {
        goto inside_number;
    } else {
        return errno = EILSEQ, 0;
    }

finished_middle:
    if(!(t = TokenArrayNext(tokens, t))) return errno = EILSEQ, 0;
    if(t->symbol == RBRACK) {
        goto end_matrix;
    } else if(t->symbol == COMMA) {
        goto left_middle;
    } else {
        return errno = EILSEQ, 0;
    }

end_matrix:
    if((t = TokenArrayNext(tokens, t))) return 0;
    assert(is_set_col);
    matrix->cols = set_col;
    return 1;
}    


int main(void) {
    struct TokenArray tokens = ARRAY_ZERO;
    struct CharArray buffer = ARRAY_ZERO;
    struct Matrix matrix = { ARRAY_ZERO, 0 };
    enum Symbol symbol;
    const size_t granularity = 1024;
    size_t nread, i, n;
    const int *ints;
    int success = EXIT_SUCCESS;

    /* Read all contents from `stdin` at once. */
    do {
        char *read_here;
        if(!(read_here = CharArrayBuffer(&buffer, granularity))
            || (nread = fread(read_here, 1, granularity, stdin), ferror(stdin))
            || !CharArrayExpand(&buffer, nread)) goto catch;
    } while(nread == granularity);

    /* Embed '\0' on the end for simple lexing. */
    {
        char *zero = CharArrayNew(&buffer);
        if(!zero) goto catch;
        *zero = '\0';
    }

    /* We use simplified sentinel method of detecting EOF, no embedded '\0'. */
    {
        const char *const b = CharArrayGet(&buffer);
        const size_t len_to_zero = (size_t)(strchr(b, '\0') - b);
        if(len_to_zero != CharArraySize(&buffer) - 1) {
            errno = EILSEQ;
            fprintf(stderr, "Embedded '\\0' at byte %lu/%lu.\n",
                (unsigned long)len_to_zero,
                (unsigned long)CharArraySize(&buffer) - 1);
            goto catch;
        }
    }

    /* Point the `scanner` to the `buffer`. */
    scanner.to = CharArrayGet(&buffer);

    /* Scan all input. */
    while((symbol = next())) {
        struct Token *const token = TokenArrayNew(&tokens);
        if(!token || !init_token(token, symbol)) goto catch;
        printf("%.*s -> %s\n", token->length, token->from,
            symbols[token->symbol]);
    }
    /* `errno` will be set if a syntax error occurs. */
    if(errno) goto catch;

    /* Parse input if it is a valid matrix. */
    if(!parse(&tokens, &matrix)) goto catch;

    /* Print the matrix. */
    n = IntArraySize(&matrix.ints);
    ints = IntArrayGet(&matrix.ints);
    printf("matrix columns %lu.\n", (unsigned long)matrix.cols);
    for(i = 0; i < n; i++)
        printf("%4d%s", ints[i],
        (i % matrix.cols) == (matrix.cols - 1) ? "\n" : ", ");
    printf("\n");

    goto finally;

catch:
    perror("Something went wrong");
    success = EXIT_FAILURE;

finally:
    IntArray_(&matrix.ints);
    TokenArray_(&tokens);
    CharArray_(&buffer);
    return success;
}

与您的问题不太相关,但如果想尝试我编写的代码,则需要BasicArray.h。可以放置最大的固定大小的数组,但我正在处理的项目有可变大小的动态数组,所以我使用了它。

#include <stdlib.h> /* realloc free */
#include <assert.h> /* assert */
#include <errno.h>  /* errno */

/* Check defines. */
#ifndef ARRAY_NAME /* <-- error */
#error Generic ARRAY_NAME undefined.
#endif /* error --> */
#ifndef ARRAY_TYPE /* <-- error */
#error Generic ARRAY_TYPE undefined.
#endif /* --> */

/* Generics using the preprocessor;
 \url{ http://stackoverflow.com/questions/16522341/pseudo-generics-in-c }. */
#ifdef CAT
#undef CAT
#endif
#ifdef CAT_
#undef CAT_
#endif
#ifdef PCAT
#undef PCAT
#endif
#ifdef PCAT_
#undef PCAT_
#endif
#ifdef T
#undef T
#endif
#ifdef T_
#undef T_
#endif
#ifdef PT_
#undef PT_
#endif
#define CAT_(x, y) x ## y
#define CAT(x, y) CAT_(x, y)
#define PCAT_(x, y) x ## _ ## y
#define PCAT(x, y) PCAT_(x, y)
#define T_(thing) CAT(ARRAY_NAME, thing)
#define PT_(thing) PCAT(array, PCAT(ARRAY_NAME, thing))

typedef ARRAY_TYPE PT_(Type);
#define T PT_(Type)

struct T_(Array);
struct T_(Array) {
    T *data;
    /* Fibonacci; data -> (c0 < c1 || c0 == c1 == max_size). */
    size_t capacity, next_capacity;
    /* !data -> !size, data -> size <= capacity */
    size_t size;
};

/* `{0}` is `C99`. */
#ifndef ARRAY_ZERO /* <-- !zero */
#define ARRAY_ZERO { 0, 0, 0, 0 }
#endif /* !zero --> */

static int PT_(reserve)(struct T_(Array) *const a,
    const size_t min_capacity, T **const update_ptr) {
    size_t c0, c1;
    T *data;
    const size_t max_size = (size_t)-1 / sizeof(T *);
    assert(a);
    if(!a->data) {
        if(!min_capacity) return 1;
        c0 = 8;
        c1 = 13;
    } else {
        if(min_capacity <= a->capacity) return 1;
        c0 = a->capacity;
        c1 = a->next_capacity;
    }
    if(min_capacity > max_size) return errno = ERANGE, 0;
    assert(c0 < c1);
    /* Fibonacci: c0 ^= c1, c1 ^= c0, c0 ^= c1, c1 += c0; */
    while(c0 < min_capacity) {
        size_t temp = c0 + c1; c0 = c1; c1 = temp;
        if(c1 > max_size || c1 < c0) c1 = max_size;
    }
    if(!(data = realloc(a->data, c0 * sizeof *a->data))) return 0;
    if(update_ptr && a->data != data)
        *update_ptr = data + (*update_ptr - a->data);
    a->data = data;
    a->capacity = c0;
    a->next_capacity = c1;
    return 1;
}

static void PT_(array)(struct T_(Array) *const a) {
    assert(a);
    a->data          = 0;
    a->capacity      = 0;
    a->next_capacity = 0;
    a->size          = 0;
}

static void T_(Array_)(struct T_(Array) *const a) {
    if(!a) return;
    free(a->data);
    PT_(array)(a);
}

static void T_(Array)(struct T_(Array) *const a) {
    if(!a) return;
    PT_(array)(a);
}

static size_t T_(ArraySize)(const struct T_(Array) *const a) {
    if(!a) return 0;
    return a->size;
}

static T *T_(ArrayGet)(const struct T_(Array) *const a) {
    return a ? a->data : 0;
}

static T *T_(ArrayNext)(const struct T_(Array) *const a, const T *const here) {
    const T *data;
    size_t idx;
    if(!a) return 0;
    if(!here) {
        data = a->data;
        idx = 0;
    } else {
        data = here + 1;
        idx = (size_t)(data - a->data);
    }
    return idx < a->size ? (T *)data : 0;
}

static T *PT_(new)(struct T_(Array) *const a, T **const update_ptr) {
    assert(a);
    if(!PT_(reserve)(a, a->size + 1, update_ptr)) return 0;
    return a->data + a->size++;
}

static T *T_(ArrayNew)(struct T_(Array) *const a) {
    if(!a) return 0;
    return PT_(new)(a, 0);
}

static T *T_(ArrayBuffer)(struct T_(Array) *const a, const size_t buffer) {
    if(!a || !buffer || !PT_(reserve)(a, a->size + buffer, 0)) return 0;
    return a->data + a->size;
}

static int T_(ArrayExpand)(struct T_(Array) *const a, const size_t add) {
    if(!a) return 0;
    if(add > a->capacity || a->size > a->capacity - add)
        return errno = ERANGE, 0;
    a->size += add;
    return 1;
}

/* Prototype. */
static void PT_(unused_coda)(void);
/** This silences unused function warnings from the pre-processor, but allows
 optimisation, (hopefully.)
 \url{ http://stackoverflow.com/questions/43841780/silencing-unused-static-function-warnings-for-a-section-of-code } */
static void PT_(unused_set)(void) {
    T_(Array_)(0);
    T_(Array)(0);
    T_(ArraySize)(0);
    T_(ArrayGet)(0);
    T_(ArrayNext)(0, 0);
    T_(ArrayNew)(0);
    T_(ArrayBuffer)(0, 0);
    T_(ArrayExpand)(0, 0);
    PT_(unused_coda)();
}
/** {clang}'s pre-processor is not fooled if you have one function. */
static void PT_(unused_coda)(void) { PT_(unused_set)(); }

/* Un-define all macros. */
#undef ARRAY_NAME
#undef ARRAY_TYPE
#undef CAT
#undef CAT_
#undef PCAT
#undef PCAT_
#undef T
#undef T_
#undef PT_

一个编译这个:

re2c -W -o C.c C.c.re
gcc -Wall -Wextra -O3 -pedantic -ansi C.c

输入这个:

{{77,88,}}
^D

获取:

{ -> LBRACK
{ -> LBRACK
77 -> NUMBER
, -> COMMA
88 -> NUMBER
, -> COMMA
} -> RBRACK
} -> RBRACK
Something went wrong: Illegal byte sequence

输入这个:

     {{9, -8},  {-7,  4},  {2, 0}  }
^D

获取:

{ -> LBRACK
{ -> LBRACK
9 -> NUMBER
, -> COMMA
-8 -> NUMBER
} -> RBRACK
, -> COMMA
{ -> LBRACK
-7 -> NUMBER
, -> COMMA
4 -> NUMBER
} -> RBRACK
, -> COMMA
{ -> LBRACK
2 -> NUMBER
, -> COMMA
0 -> NUMBER
} -> RBRACK
} -> RBRACK
matrix columns 2.
   9,   -8
  -7,    4
   2,    0

事后看来,这对于这个问题来说可能是矫枉过正,但它可以很好地扩展。此外,在进行全面测试时,将词法分析器与解析器分开测试通常更容易,因为解析器通常使用更少的符号。

【讨论】:

    【解决方案3】:

    this answer 中,我认为有人说“矩阵的维度未知”,而不是“矩阵的维度已知”。由于标记是单个字符和数字,并且标记始终遵循相同的模式,因此可以使用来自stdio.hscanf 更紧凑地编写。

    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    
    int main(void) {
        int matrix[4];
        char rbrace;
        int success = EXIT_FAILURE;
    
        if(scanf(" { { %i , %i } , { %i , %i } %c", &matrix[0], &matrix[1],
            &matrix[2], &matrix[3], &rbrace) != 5 || rbrace != '}')
            { errno = EILSEQ; goto catch; }
    
        printf("( %4d %4d )\n( %4d %4d )\n",
            matrix[0], matrix[1], matrix[2], matrix[3]);
    
        success = EXIT_SUCCESS;
        goto finally;
    
    catch:
        perror("Something went wrong");
    
    finally:
        return success;
    }
    

    格式字符串中有空格的原因是我们希望它跳过空格,

    由一个或多个空白字符组成的指令通过读取输入执行,直到无法读取更多有效输入,或者直到第一个不是空白字符的字节仍未读取。

    rbrace 用于告诉我们有一个尾随 }}。当它匹配 4 个整数时,我认为没有办法检测尾随常量是否匹配。

    文字匹配和抑制分配的成功只能通过 %n 转换规范直接确定。

    警告,不过,文档说 %i 期望与 strtol 相同的序列,以 0 为基数,但如果数字超出范围会发生什么并不完全清楚。在我的电脑上,

    {{326345645675372456345576,6435},{6,0}}
    (   -1 6435 )
    (    6    0 )
    

    【讨论】:

      猜你喜欢
      • 2016-02-16
      • 2020-03-20
      • 1970-01-01
      • 1970-01-01
      • 2022-12-29
      • 2017-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多