张春玲
摘 要:scanf函数是C语言中最基本的输入函数,本文通过分析scanf函数调用过程中的实现机制,以帮助正确使用scanf函数。
关键词:C;scanf函数调用;实现机制
1 scanf函数
scanf的功能是从标准输入设备读取输入的任何固有类型的数据自动转换成机内格式并把数据输入到指定的变量之中,返回正确读入数值的个数。函数原型主要代码如下:
int_cdecl scanf(const char *format,...)
{va_list arg;
va_start(arg,format);
return vscanf(_input_l,format,NULL,arg);}
2 scanf函数调用
2.1 调用格式
scanf(格式控制,地址表列);“格式控制”为格式字符串,将用户输入的数据转换为指定格式;“地址表列”由若干个地址组成的参数表列,可以是变量的地址或字符串首地址。原型中_cdecl是c中的默认函数调用方式,调用函数参数自右向左入栈,因此scanf函数左边的第一个参数format被放于栈顶。
2.2 参数入栈
C调用协议下,为遵循对齐原则,要求每个变量地址都是sizeof(int)的倍数,因此参数入栈都是整数字节。同时调用不带原型声明的函数时,调用者会对每个参数执行“默认实际参数提升”。从scanf函数调用格式中,可变参数是若干个地址列表(指针),C中为每个指针变量统一分配4个字节,即可变参数入栈时都占用4个字节,满足了对齐原则,与指针指向的变量类型没有关系。这与printf函数调用时参数入栈不一样。
2.3 格式字符串与标准输入流的匹配
参数入栈后,编译器先获取格式字符串,对照字符串中的各项,从内存缓冲区中取数据,若没有数据,则等待用户输入。用户通过键盘输入数据,数据回显于显示器上,同时数据被存入内存缓冲区(不是键盘缓冲区)中。为什么?scanf源码中,函数功能的实现依靠vscanf函数调用_input_l,而_input_l的函数功能是把键盘输入数据写入stdin(标准输入流)来创建一个临时交换文件的缓冲区,只有当用户输入回车后,scanf函数开始从内存缓冲区取数据。在接收数据时,对照字符串的各项,并按匹配规则,逐一取数:内存缓冲区中,读取时顺序读取写入的数据,即先读先写入的数据再读后写入的数据,这与对字符串中从左到右匹配顺序一致,匹配规则如下:C99中,格式字符串有如下三种类型字符:
⑴格式说明符:遇到格式说明符去读取缓冲区时,匹配分两步,首先是格式字符与缓冲区数据类型匹配,然后格式字符与对应参数指向的变量类型匹配。匹配时先将缓冲区中一个或多个连续的空白字符(格式字符为字符型除外)移出并去掉,再將格式符与缓冲数据进行匹配(见(四)),若类型匹配,则读取缓冲区中直到遇到非法字符(与指定类型不匹配的字符)或者达到输出宽度要求前的数据,再将数据送到与格式字符类型匹配的变量中去(借助三个宏va_start;va_arg;va_end访问后面每个参数)。若格式字符类型与缓冲区数据不匹配,stdin流被阻塞,scanf函数不在读取后面的部分。若格式字符与后面的参数类型不一致,则丢弃数据。
因此匹配格式字符时,输入流中开始读取到的空白字符(尤其对字符串中多个数值型格式字符紧挨着时,需要输入的一个或多个空白字符)对匹配没有任何影响,自动略去。也就是匹配格式字符时,输入流中可以输入任意多个空白字符。如scanf(“%d”,&a);输入时可以直接输入整数,也可以输入一个或者多个空白字符再输入整数,结果一样。
⑵空白符:可以是空格、制表符和新行符。字符串中的空白字符的作用是使scanf函数在读操作中略去输入流中的一个或多个空白字符。因此,空白符是使scanf在输入流中读,但不保存结果,直到发现非空白字符为止,同时将栈中字符串位置指向下一个非空白字符。
因此匹配空白字符时,入栈的空白字符与输入流中不一定匹配,因为这时候字符串中的空白字符对字符串来说就是一个分隔符,输入数据时用户即可以输入相同个或更多个空白字符也可以不输入(当多个数值型格式字符由空白字符隔开时,在输入时必须输入空白字符),对读取数据没有任何影响,因为字符串中空白字符可以略去输入流中的多个连续空白字符。
⑶非空白符:使scanf()根据栈中字符在流中读匹配的字符并放弃。如“a=%d”,使scanf读取流中a=并放弃,如未发现匹配,scanf()返回。若字符串中非空白字符前面没有空白字符,在输入数据时,一定不要输入空白字符。例:scanf(“a=%d”,&a);输入时第一字符只能是a,若是空格则结果不对。栈中的a与输入流中的空格不匹配,因为空白字符不会自动略去。
2.4 格式字符匹配输入流中的数据
如f,则告诉scanf接收数据的变量是float类型,需从输入流中读取一个单精度数,再放入float类型变量中;如果将 L/l放在前面,则告诉scanf接收数据的变量为double。可以看出输入时区别%f,%lf的,这与printf函数不同。使用printf时,f遵循提升规则表示double类型。而scanf匹配时首先是根据格式字符类型读取数据,再将数据输入对应变量。假如scanf中f代表double,表示从输入流读取双精度数,若%f对应的变量是单精度,那不能保证将一个double型的数放到一个float类型中,原因是定义变量时float,double分别分配4个字节,8个字节,向变量输入数据时是受字节限制的。因此输入数据时要区分float,double,%f代表两种类型不行。
3 总结
scanf函数在C中应用比较频繁,本文详细分析了scanf函数调用过程,重点分析了格式字符串中三种类型的字符与内存缓冲区匹配的过程,希望对使用scanf函数有正确指导意义。
[参考文献]
[1]谭浩强.C语言程序设计[M].北京.清华大学出版社.2011.
[2]徐娟.可变参函数scanf的执行过程分析[J].信息与电脑.2013.5.