C中的字符串输入和输出

roo*_*kie 7 c string

我有这段代码:

char* receiveInput(){
    char *s;
    scanf("%s",s);

    return s;
}

int main()
{
    char *str = receiveInput();
    int length = strlen(str);

    printf("Your string is %s, length is %d\n", str, length);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我收到这个输出:

Your string is hellàÿ", length is 11
Run Code Online (Sandbox Code Playgroud)

我的意见是:

helloworld!
Run Code Online (Sandbox Code Playgroud)

有人可以解释为什么,以及为什么这种编码风格很糟糕,提前谢谢

Chr*_*utz 20

几个问题已经解决了你做错了什么以及如何解决它,但你也说过(强调我的):

有人可以解释为什么,以及为什么这种编码风格很糟糕

我认为scanf阅读输入是一种可怕的方式.它与之不一致printf,很容易忘记检查错误,难以从错误中恢复,并且与普通(并且更容易正确)读取操作(如fgets公司)无关.

首先,请注意"%s"格式只有在看到空格时才会读取.为什么是空白?为什么"%s"打印出整个字符串,但是读取字符串的容量有限?

如果你想在一整行读取,你可能经常会习惯这样做,scanf提供...用"%[^\n]".什么?那是什么?什么时候成为Perl?

但真正的问题是这些都不安全.它们都自由溢出,没有边界检查.想要检查边界?好吧,你明白了"%10s"(并且"%10[^\n]"开始变得更糟).这将只读取9个字符,并自动添加终止空字符.所以这很好......因为我们的数组大小永远不需要改变.

如果我们想将数组的大小作为参数传递给scanfprintf可以做到这一点:

char string[] = "Hello, world!";
printf("%.*s\n", sizeof string, string); // prints whole message;
printf("%.*s\n", 6, string); // prints just "Hello,"
Run Code Online (Sandbox Code Playgroud)

想做同样的事情scanf吗?这是如何做:

static char tmp[/*bit twiddling to get the log10 of SIZE_MAX plus a few*/];
// if we did the math right we shouldn't need to use snprintf
snprintf(tmp, sizeof tmp, "%%%us", bufsize);
scanf(tmp, buffer);
Run Code Online (Sandbox Code Playgroud)

这是正确的 - scanf不支持"%.*s"变量精度printf,所以要做动态边界检查scanf我们必须在临时缓冲区中构造我们自己的格式字符串.这是各种各样的坏事,即使它在这里实际上是安全的,对任何人来说都是一个非常糟糕的主意.

与此同时,让我们看看另一个世界.让我们来看看世界fgets.以下是我们如何阅读以下数据fgets:

fgets(buffer, bufsize, stdin);
Run Code Online (Sandbox Code Playgroud)

无限的头痛,没有浪费的处理器时间将整数精度转换为一个字符串,只能将库重新分解为一个整数,并且所有相关元素都坐在一行上,以便我们看看它们如何协同工作.

当然,这可能不会读取整行.如果行短于bufsize - 1字符,它将只读取整行.以下是我们如何阅读整行:

char *readline(FILE *file)
{
    size_t size  = 80; // start off small
    size_t curr  = 0;
    char *buffer = malloc(size);
    while(fgets(buffer + curr, size - curr, file))
      {
        if(strchr(buffer + curr, '\n')) return buffer; // success
        curr = size - 1;
        size *= 2;
        char *tmp = realloc(buffer, size);
        if(tmp == NULL) /* handle error */;
        buffer = tmp;
      }
    /* handle error */;
}
Run Code Online (Sandbox Code Playgroud)

curr变量是阻止我们重新检查我们已经读出的数据进行优化,并且是不必要的(虽然有用,因为我们读更多的数据).如果您愿意,我们甚至可以使用返回值strchr去除结束"\n"字符.

另请注意,size_t size = 80;起始位置完全是任意的.我们可以使用81,或79或100,或将其作为用户提供的参数添加到函数中.我们甚至可以添加一个int (*inc)(int)参数,然后更改size *= 2;size = inc(size);允许用户控制数组增长的速度.当重新分配成本高昂且需要读取和处理大量数据时,这些对于提高效率非常有用.

我们可以写相同的scanf,但想想我们必须重写格式字符串的次数.我们可以将它限制为一个恒定的增量,而不是上面实现的加倍(容易),并且永远不必调整格式字符串; 我们可以放弃并只存储数字,按上述方法进行数学处理,并snprintf每次重新分配时将其转换为格式字符串,以便scanf将其转换回相同的数字; 我们可以限制我们的增长和起始位置,以便我们可以手动调整格式字符串(例如,只是增加数字),但这可能会在一段时间后变得毛茸茸并且可能需要递归(!)才能干净地工作.

此外,很难将阅读scanf与阅读与其他功能混合在一起.为什么?假设您要从一行读取整数,然后从下一行读取一个字符串.你试试这个:

int i;
char buf[BUSIZE];
scanf("%i", &i);
fgets(buf, BUFSIZE, stdin);
Run Code Online (Sandbox Code Playgroud)

这将读取"2",但然后fgets将读取一个空行,因为scanf没有读取换行符!好的,拿两个:

...
scanf("%i\n", &i);
...
Run Code Online (Sandbox Code Playgroud)

你认为这会占用换行符,而且它确实会 - 但它也会占用下一行的空白空格,因为scanf无法区分换行符和其他形式的空格.(另外,事实证明你正在编写一个Python解析器,并且在行中引入空格很重要.)为了使这个工作,你必须getchar在换行符中调用或读取一些内容并将其丢弃:

...
scanf("%i", &i);
getchar();
...
Run Code Online (Sandbox Code Playgroud)

这不是很傻吗?如果你scanf在一个函数中使用会发生什么,但是不要调用,getchar因为你不知道下一个读取scanf是否会变得更健全(或者下一个字符是否会成为换行符)?突然间,处理这种情况的最佳方法似乎是选择其中一种方式:我们是否scanf专门使用并且永远不能访问fgets样式的完全控制输入,或者我们是否fgets专门使用并且难以执行复杂的解析?

实际上,答案是我们没有.我们专门使用fgets(或非scanf函数),当我们需要scanf类似功能时,我们只需要调用sscanf字符串!我们不需要scanf不必要地破坏我们的文件流!我们可以对我们想要的输入进行所有精确控制,并且仍然可以获得scanf格式化的所有功能.即使我们不能,许多scanf格式选项在标准库附近直接对应的功能,如无限更灵活strtolstrtod功能(和朋友).另外,i = strtoumax(str, NULL)对于C99大小的整数类型看起来比看起来更清晰scanf("%" SCNuMAX, &i);,并且更安全(我们可以strtoumax对较小的类型使用该行不变,让隐式转换处理额外的位,但是scanf我们必须暂时uintmax_t读取) .

这个故事的寓意:避免scanf.如果你需要它提供的格式,并且不想(或不能)自己(更有效地)自己做,请使用fgets/ sscanf.

  • 我重新阅读了我可怕的博客帖子等同的答案,并意识到我做了一个遗漏,虽然我当时不知道.`scanf("%u",&i)`_actually产生未定义的行为_在整数溢出的情况下,不像`strtoul`处理错误,就像任何理智的人所期望的那样.在可能的情况下避免"scanf"的另一个原因. (3认同)
  • +1 Bravo !!! 这是一个非常好的解释!谢谢!我希望OP也读它:-) (2认同)

peo*_*oro 11

scanf 没有为你分配内存.

您需要为传递给的变量分配内存scanf.

你可以这样做:

char* receiveInput(){
    char *s = (char*) malloc( 100 );
    scanf("%s",s);
    return s;
}
Run Code Online (Sandbox Code Playgroud)

但警告:

  1. 调用的函数receiveInput将获取返回的内存的所有权:free(str)打印后你必须这样做main.(以这种方式取消所有权通常不被视为一种好的做法).

    一个简单的解决方法是将分配的内存作为参数.

  2. 如果输入字符串长于99(在我的情况下)你的程序将遭受缓冲区溢出(这是它已经发生的事情).

    一个简单的解决方法是传递scanf缓冲区的长度:

    scanf("%99s",s);
    
    Run Code Online (Sandbox Code Playgroud)

固定代码可能是这样的:

// s must be of at least 100 chars!!!
char* receiveInput( char *s ){
    scanf("%99s",s);
    return s;
}
int main()
{
    char str[100];
    receiveInput( str );
    int length = strlen(str);

    printf("Your string is %s, length is %d\n", str, length);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • @fehergeri:是的,stdio使用`scanf`.我猜标题已被删除了? (2认同)