字符串-输入
在C语言中,字符串输入的方式有很多,而输入的本质是输入设备发出数据,以数据流的方式传递给计算机系统。前面提到,输入是又缓冲区的,输入设备进行动作或数据采集(如敲击键盘打字)传递给缓冲区,然后缓冲区在吧这些数据传递进计算机。
分配空间
前面提到过,对于不需要修改的字符串,可以把字符串长度交给编译器去计算;而对需要修改的字符串,就需要我们先指定好字符串的长度大小了,而输入的字符串就是典型的需要被修改的字符串,所以在创建接收输入内容的字符串时,要意识到给字符串分配空间这一个环节,提前制定好可预见范围内的大小,方法也很简单,直接用数组形式声明即可:
char str[81];
输入函数
C语言包含了众多输入函数,比如前面提到过的scanf()
和getchar()
,这两个是属于比较常用的,而最开始,C语言最常用的输入函数是gets()
。
gets( )函数
gets()
的输入思路很简单,它读取一整行输入信息,直到读取到换行符,然后存储换行符\n
之前的信息,并把换行符丢弃,最后在末尾添加一个\0
。乍看之下,gets()
是非常智能的,没有了前面学习中scanf()
的换行符焦虑,至少对于我这个初学者来说,真的很舒服。gets()
经常和puts()
搭配使用。
然而,gets()
之所以很早之前就不被建议使用,是因为它自身的不安全性。gets()
只有唯一参数,也就是括号内只有数组名这么个指针常量一个参数,似的gets()
只知道数组开始处,无法知晓输入内容在哪里结束,这是很危险的,如果用户输入的内容远超接收信息的数组的指定长度,就会使得有多余数据留在缓冲区,导致缓冲区溢出,这些溢出的数据会被系统自动分配到任意内存地址,如果分配到尚未被定义和分配的内存空间,倒还好说,但如果被分配到程序已经在使用的内存空间,这些溢出的数据会强制擦除已经被定义和分配的内存空间,就很危险了。
这也是为什么,现在最新的C11标准,把gets()
剔除了。
fgets( )函数
fgets()
通常被认为是gets()
的安全替代品,因为它包含三个参数:
char * fgets(char * str, int n, stdin);
- 指向要存储输入信息的指针常量
- 读入字符的最大长度
- 要读入的形式(如键盘输入为stdin,文件则为其他形式)
fgets()
很好地解决了上文提到的gets()
的缺点,参数里指定了输入内容的最大长度n,如果超过了n-1,那么fgets()
只会接收n-1个字符。fgets()
经常和fputs()
搭配使用,而fputs()
只有两个参数,第二个参数是指明输出的文件(显示屏输出则为stdout)。
fgets()
的返回值是成功读取并存储了的输入信息的首元素的地址,单纯的来说,返回值类型是指向char的指针。注意,如果fgets()
读取到了文件末尾,返回值就会变成空指针(NULL)。
fgets()
是会存储换行符的,这样有好处也有坏处,不过要消除这个换行符,有两种方法:
while (words[i]!='\n') ++i;
words[i]='\0';
这种方法是对已经存储的输入内容进行遍历,查找到换行符后,把他换成空字符。
while(getchar()!='\n) continue;
第二种方法就是经典的去除换行符方法了,不过它也会舍弃掉换行符后面所有内容。
gets_s( )函数
如果只需要接收键盘输入,gets_s()
是个更高的选择,他是基于fgets()
的改进,剔除了fgets()
的第三个参数,只需要指针和最大长度参数。
如果gets_s()
读取到最大长度依然没有读取到换行符,则会直接把已经读取到的数组首字符设置会空字符,丢弃后面的输入,返回一个空指针,所以gets_s()
很安全。
丢弃过长输入的余下内容
丢弃过长输入的余下内容这一点很重要,因为多出来的字符都会被留在缓冲区,这样对程序是很危险的。
scanf( )函数
对比前面的gets()
、fgets()
、gets_s()
等函数,scanf()
最大的特点是它更像是获取单词的函数。scanf()
有两种结束读取的方式,一种是空白符(空行、空格、制表符、换行符),一种是EOF。
scanf()
有比其他输入函数的独特点,他不只是为了输入数据为字符串,它能让我们输入的数据转化成其他数据,如int。
输入函数的区别以及缓冲区概念
最后来总结一下这一节众多输入函数的区别把。
- 最简单粗暴的输入函数是
gets()
,读取一整行内容,最后丢弃了换行符以及后面内容,纯粹的输入函数,但是无法应对输入内容过长的情况,充满危险。 - 最安全的输入函数
fgets()
,要指定最大长度,还有输入文件。 gets_s()
是简化版fgets()
,只接受键盘输入,也指定了最大长度,适合新手使用。scanf()
更加针对与单词输入和数据类型转化,对于目前的学习,如果我们要靠输入来进行数值的计算,只能依靠scanf()
来将我们输入的字符转化为int、float等。
缓冲区概念:
现在回想一下之前学习中,scanf()
和换行符焦虑问题,如果我们要进行多次输入,那么对于scanf()
这个输入函数来说,他只接受规定好的结束标志字符前的内容,但不会处理结束标志字符本身,依然把它们留在缓冲区,使得while循环多次输入时,程序在输入完有效数据后第二次迭代时先接受了留在缓冲区的这些字符(如换行符),然后来了自动的一次空转,下一次迭代才继续让我们输入内容。
而前面的fgets()
会存储换行符,不会把换行符留在缓冲区,使得我们的程序不会因为缓冲区的换行符进入空转。
总而言之,学到这里,必须对缓冲区有强烈意识了,不要当它不存在。