C语言scanf()处理用户输入时,不能事先获知用户的输入长度,怎么办?

不少初学者刚开始学习C语言时,都非常乐意练习写可以交互的程序。所谓“可交互”,其实就是程序允许我们输入一些信息,然后根据这些信息输出对应的结果。在这一过程中,最常使用的两个库函数就是 printf() 函数和 scanf() 函数了。

不能事先得到字符串长度,怎么办?

现在来考虑一个最简单的C语言交互程序:读取用户输入的姓名,并将其打印出来。这样的问题,相信即使是初学者也能轻易的解决:

#include <stdio.h>

int main()
{
    char buf[128];
    scanf("%s", buf);
    printf("you input: %s\n", buf);

    return 0;
}

file
这样的C语言代码的确能够在一定程度解决问题,但是还不够完美,因为只有当用户输入 128 字节长度以内的姓名时,它才能正常工作。

也许没有人的姓名长度超过 128 字节,但是我们不能假设没有人恶意的,或者调皮的输入超出 128 字节的字符串,这种情况下,上述C语言程序就不能工作了,程序会表现出不能预知的行为。(常常直接就崩溃了。)

C语言要求我们在使用变量之前声明它,因此 scanf() 函数使用变量 buf 之前要先定义。可是,没有人能够事先预知程序的使用者究竟会输入多少字符,所以上面那个“最简单的交互”问题并不简单,而且似乎这是一个不可能解决的问题。

诚然,没有人能够未卜先知,但是这并不表示不能完美解决“最简单的交互”问题。事实上,“无所不能”的C语言并非浪得虚名,有 N 种方法解决该问题。当然,本文不可能将这些方法一一列举,只将一些常用的方法举出。

非标准扩展

上述问题的确是个难题,因此有些设计者增加了一些C语言扩展,应对不能事先获知输入长度的问题。一个非常好用的扩展就是 scanf() 的“%ms”占位符,下面是一段相关的C语言代码示例:

char *m = NULL;
printf("please input a string\n");
scanf("%ms",&m);
if (m == NULL)
    fprintf(stderr, "That string was too long!\n");
else
{
    printf("this is the string %s\n",m);
    /* ... any other use of m */
    free(m);
}

file
介于 % 和 s 之间的 m 有 measure(测量)的含义,它可以测量输入字符串的长度,然后 scanf() 根据字符串的长度分配内存,并将字符串拷贝到这段内存,之后将首地址返回给 m。显然,在使用完毕后,需要调用 free() 函数释放这段内存。

不过值得说明的是,并不是所有的平台都支持这一的用法,正如小标题所述,这是C语言的“非标准扩展”。

指定 scanf() 的读取长度

一般来说,没有(中国)人的名字长度超过 128 字节,所以要解决文章开头的问题,定义长度为 128 字节的内存足够使用了。这样看来,要是仅仅为了避免用户恶意输入过长的字符串,可以为 scanf() 指定读取长度。下面是一段C语言代码示例:

char name[128];
scanf("%127s",&name);

介于 % 和 s 之间的数字应小于 name 的长度,这个数字表示 scanf() 一次最多读取 127 字节的数据放入 name。如果用户输入的字符串超出了 127 字节,剩下的字符将留在缓冲区内,等待下一次读取。

可见,在C语言程序开发中需要读取用户输入时,即使不能事先获知输入长度,也是有办法写出高稳定性的程序的。不过在实践中,一般不推荐使用 scanf() 函数处理用户输入,即使我们解决了输入长度的问题。

我们不能保证用户一定会遵守程序的设计,例如 scanf() 使用了 %d 占位符,意味着需要用户输入一个整数,但是总有人会输入别的东西(浮点数,字符串等)。

自己实现“扩展”

相比于几十年前,现代计算机的性能已经得到大大的提高,所以要实现“健壮”的交互程序,似乎只要定义一段“足够长”的内存供输入使用就可以了,但是姑且不谈“足够长”实在是一个模糊的概念,万一恶意用户有足够的耐心,他总是能够输入比“足够长”还要长的字符串。

事实上,考虑到C语言程序主要用于嵌入式开发,不应该浪费一点资源,实践中常用的方法是一次最多只读取固定长度的字节,重复这一过程,直到将所有字节全部读取,再做一个简单的组合就可以了。

下面是一段C语言代码示例,该例子每次只读取 200 个字节,而且考虑到 scanf() 的不安全,下面的代码使用了 fgets() 函数处理输入。

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

char* readinput()
{
#define CHUNK 200
   char* input = NULL;
   char tempbuf[CHUNK];
   size_t inputlen = 0, templen = 0;
   do {
       fgets(tempbuf, CHUNK, stdin);
       templen = strlen(tempbuf);
       input = realloc(input, inputlen+templen+1);
       strcpy(input+inputlen, tempbuf);
       inputlen += templen;
    } while (templen==CHUNK-1 && tempbuf[CHUNK-2]!='\n');
    return input;
}

file
敏锐的读者应该已经发现了,这其实就是前文提到的“非标准扩展”的一种实现。以后要读取用户的输入,调用我们自定义的 readinput() 就可以了:

int main()
{
    char* result = readinput();
    printf("And the result is [%s]\n", result);
    free(result);
    return 0;
}

readinput() 函数没有浪费一个字节,它总是能够根据输入字符串的长度调节要使用的内存长度。不过,因为 readinput() 使用了 realloc() 函数,不要忘记收尾时调用 free() 释放掉内存。

读者应注意,为了便于讨论,上述C语言代码没有做错误检查和处理。

小结

看似简单的C语言交互程序,其实也暗含着较难解决的问题。没有人能够未卜先知,事先知道用户究竟会输入多长的字符,但是使用C语言总是有办法写出“健壮”的程序的。值得注意的是,可能初学者对 scanf() 函数更加熟悉,但是在实践中并不推荐使用它处理输入,fgets() 函数是更好的选择。

阅读更多:   C语言
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif