[C/C++]scanf与sscanf
一、scanf基本概念
scanf函数是格式输入函数,即按用户指定的格式从键盘上把数据输入到指定的变量之中,其关键字最末一个字母f即为“格式”(format)之意。
scanf函数的一般形式
scanf(格式控制,地址表列)
int scanf(char *format[,argument,...]);
“格式控制”的含义同printf函数;“地址表列”是由若干个地址组成的表列,可以是变量的地址,或字符串的首地址。
scanf()函数返回成功赋值的数据项数,读到文件末尾出错时则返回EOF。
注意上面的scanf("%d,%d,%d",&a,&b,&c);中%d,%d,%d之间有逗号,在输入数据时也要加逗号,如果去掉逗号,输入时就不用逗号,而用空格,tab键或回车键将各个数据隔开
格式字符说明
%a,%A 读入一个浮点值(仅C99有效)
%c 读入一个字符
%i 读入十进制,八进制,十六进制整数
%o 读入八进制整数
%x,%X 读入十六进制整数
%s 读入一个字符串,遇空格、制表符或换行符结束。
%f,%F,%e,%E,%g,%G 用来输入实数,可以用小数形式或指数形式输入。
%p 读入一个指针
%u 读入一个无符号十进制整数
%n 至此已读入值的等价字符数
%[] 扫描字符集合
%% 读%符号
附加格式说明字符表修饰符说明
L/l 长度修饰符 输入"长"数据
h 长度修饰符 输入"短"数据
W 整型常数 指定输入数据所占宽度
* 表示本输入项在读入后不赋值给相应的变量
scanf的返回值
scanf的返回值由后面的参数决定
scanf("%d%d", &a, &b);
如果a和b都被成功读入,那么scanf的返回值就是2
如果只有a被成功读入,返回值为1
如果a和b都未被成功读入,返回值为0
如果遇到错误或遇到end of file,返回值为EOF。
且返回值为int型.
使用scanf函数时应该注意的问题
1、scanf()中的变量必须使用地址。
2、scanf()的格式控制串可以使用其它非空白字符,但在输入时必须输入这些字符。
3、在用"%c"输入时,空格和“转义字符”均作为有效字符。
问题一
scanf()函数不能正确接受有空格的字符串?如: I love you!
- int main()
- {
- char str[80];
- scanf("%s",str);
- printf("%s",str);
- return 0;
- }
输入:I love you!
输出:scanf()函数接收输入数据时,遇以下情况结束一个数据的输入:(不是结束该scanf函数,scanf函数仅在每一个数据域均有数据,并按回车后结束)。
① 遇空格、“回车”、“跳格”键。
② 遇宽度结束。
③ 遇非法输入。
所以,上述程序并不能达到预期目的,scanf()扫描到"I"后面的空格就认为对str的赋值结束,并忽略后面的"love you!".这里要注意是"love you!"还在键盘缓冲区(关于这个问题,网上我所见的说法都是如此,但是,我经过调试发现,其实这时缓冲区字符串首尾指针已经相等了,也就是说缓冲区清空了,scanf()函数应该只是扫描stdin流,这个残存信息是在stdin中)。我们改动一下上面的程序来验证一下:
- #include<windows.h>
- int main()
- {
- char str[80];
- char str1[80];
- char str2[80];
- scanf("%s",str);
- printf("%s",str);
- Sleep(5000);
- scanf("%s",str2);
- printf("\n%s",str1);
- printf("\n%s",str2);
- return 0;
- }
输入:I love you!
1
输出:
I
love
you!
好了,原因知道了,那么scanf()函数能不能完成这个任务?回答是:能!别忘了scanf()函数还有一个 %[] 格式控制符,请看下面的程序:
- int main()
- {
- char string[50];
- scanf("%[^\n]",string);
- printf("%s\n",string);
- return 0;
- }
问题二
键盘缓冲区残余信息问题
- int main()
- {
- int a;
- char c; do
- {
- scanf("%d",&a);
- scanf("%c",&c);
- printf("a=%d c=%c\n",a,c);
- }while(c!='N');
- }
scanf("%c",&c);这句不能正常接收字符,什么原因呢?我们用printf("c=%d\n",c);将C用int表示出来,启用printf("c=%d\n",c);这一句,看看scanf()函数赋给C到底是什么,结果是c=10 ,ASCII值为10是什么?换行即\n.对了,我们每击打一下"Enter"键,向键盘缓冲区发去一个“回车”(\r),一个“换行"(\n),在这里 \r被scanf()函数处理掉了(姑且这么认为吧^_^),而\n被scanf()函数“错误”地赋给了c.解决办法:可以在两个scanf()函数之后加个fflush(stdin);,还有加getch() , getchar()也可以,但是要视具体scanf()语句加那个,这里就不分析了,读者自己去摸索吧。但是加fflush(stdin);不管什么情况都可行。
函数名: fflush
功 能: 清除一个流
用 法: int fflush(FILE *stream);
- #include <stdio.h>
- int main()
- {
- int a;
- char c; do
- {
- scanf("%d",&a);
- fflush(stdin);
- scanf("%c",&c);
- fflush(stdin);
- printf("a=%d c=%c\n",a,c); }while(c!='N');
- }
这里再给一个用“空格符”来处理缓冲区残余信息的示例:运行出错的程序:
- int main()
- {
- int i;
- char j;
- for(i = 0;i < 10;i++)
- scanf("%c",&j);
- }
使用了空格控制符后:
- int main()
- {
- int i;
- char j;
- for(i = 0;i < 10;i++)
- scanf(" %c",&j);
- }
可以运行看看两个程序有什么不同。
问题三
如何处理scanf()函数误输入造成程序死锁或出错?
- #include <stdio.h>
- int main()
- {
- int a,b;
- scanf("%d,%d",&a,&b);
- printf("%d+%d=%d",a,b,a+b);
- }
如上程序,如果正确输入a,b的值,那么没什么问题,但是,你不能保证使用者每一次都能正确输入,一旦输入了错误的类型,你的程序不是死锁,就是得到一个错误的结果,呵呵,这可能所有人都遇到过的问题吧?解决方法:scanf()函数执行成功时的返回值是成功读取的变量数,也就是说,你这个 scanf()函数有几个变量,如果scanf()函数全部正常读取,它就返回几。但这里还要注意另一个问题,如果输入了非法数据,键盘缓冲区就可能还个有残余信息问题。正确的例程:
- int main()
- {
- int a,b,c;
- while(scanf("%d,%d",&a,&b)!=2)fflush(stdin);
- printf("%d+%d=%d",a,b,a+b);
- }
补充
fflush(stdin)这个方法在GCC下不可用。(在VC6.0下可以)
以下是 C99 对 fflush 函数的定义:
int fflush(FILE *stream);
如果stream指向输出流或者更新流(update stream),并且这个更新流最近执行的操作不是输入,那么fflush函数将把任何未被写入的数据写入stream指向的文件(如标准输出文件stdout)。否则,fflush函数的行为是不确定的。
fflush(NULL)清空所有输出流和上面提到的更新流。如果发生写错误,fflush函数会给那些流打上错误标记,并且返回EOF,否则返回0。
由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用fflush(stdin) 是不正确的,至少是移植性不好的。
可采用如下方法:
- void flush()
- {
- char c;
- while ((c=getchar()) != '\n'&&c!=EOF) ;
- }
- #include <stdio.h>
- int main()
- {
- int a,b,c;
- while(scanf("%d,%d",&a,&b)!=2) flush();
- c=a+b;
- printf("%d+%d=%d",a,b,c);
- }
二,sscanf相关
看了几篇介绍sscanf函数,真是发现自己好多东西没理解透。
第一篇:
此文所有的实验都是基于下面的程序:
- char str[10];
- for (int i = 0; i < 10; i++)
- str[i] = '!';
执行完后str的值为
- str = "!!!!!!!!!!"
下面我们做几个小实验,看看使用sscanf和正则表达式格式化输入后,str有什么变化。
实验1:
- sscanf( "123456" , "%s" , str) ; ---------str的值为 "123456\0!!!"
注意:原字符串只有前七个被覆盖!同时将原字符的的第7个字符‘\0’也同时复制了过来!
实验2:
- sscanf( "123456" , "%3s" , str) ; ---------str的值为 "123\0!!!!!!"
此时sscanf只拷贝3个字符给str,然后把第4个字符设为null字符。
实验3:
sscanf( "aaaAAA" , "%[a-z]" , str) ; ---------str的值为 "aaa\0!!!!!!"
开始使用正则表达式,括号里面的a-z就是一个正则表达式,它可以表示从a到z的任意字符,
在继续讨论之前,先来看看百分号表示什么意思,%表示选择 ,%后面的是条件,比如实验1的"%s",s是一个条件,表示任意字符,"%s"的意思是:只要输入的东西是一个字符,就把它拷贝给str。实验2的"%3s"又多了一个条件:只拷贝3个字符。实验3的“%[a-z]”的条件稍微严格一些,输入的东西不但是字符,还得是一个小写字母的字符,所以实验3只拷贝了小写字母"aaa"给str,别忘了加上null字符。
实验4:
sscanf( "AAAaaaBBB" , "%[^a-z]" , str) ; ---------str的值为 "AAA\0!!!!!!"
对于所有字符,只要不是小写字母,都满足"^a-z"正则表达式,符号^表示逻辑非。前3个字符都不是小写字符,所以将其拷贝给str,但最后3个字符也不是小写字母,为什么不拷贝给str呢?这是因为当碰到不满足条件的字符后,整个sscanf就会停止执行,不再扫描之后的字符。
实验5:
sscanf( "AAAaaaBBB" , "%[A-Z]%[a-z]" , str) ; ---------段错误
这个实验的本意是:先把大写字母拷贝给str,然后把小写字母拷贝给str,但很不幸,程序运行的时候会发生段错误,因为当sscanf扫描到字符a时,违反了条件"%[A-Z]",sscanf就停止执行,不再扫描之后的字符,所以第二个条件也就没有任何意义。
实验6:
- sscanf( "AAAaaaBBB" , "%*[A-Z]%[a-z]" , str) ; ---------str的值为 "aaa\0!!!!!!"
这个实验出现了一个新的符号:%*,与%相反,%*表示过滤满足条件的字符,在这个实验中,%*[A-Z]过滤了所有大写字母,然后再使用%[a-z]把之后的小写字母拷贝给str。
实验7:
sscanf( "AAAaaaBBB" , "%[a-z]" , str) ; ---------str的值为 "!!!!!!!!!!"
如果sscanf函数未找到符合条件的字符传入,则str将保持原值!做完前面几个实验后,我们都知道sscanf拷贝完成后,还会在str的后面加上一个null字符,但如果没有一个字符满足条件,str将保持原值! 。这个实验也说明了,如果不使用%*过滤掉前面不需要的字符,你永远别想取得中间的字符。
实验8:
sscanf( "AAAaaaBC=" , "%*[A-Z]%*[a-z]%[^a-z=]" , str) ; ---------str的值为 "BC\0!!!!!!!"
这是一个综合实验,但这个实验的目的不是帮我们复习前面所学的知识,而是展示两个值得注意的地方:
注意1:%只能使用一次,但%*可以使用多次,比如在这个实验里面,先用%*[A-Z]过滤大写字母,然后用%*[a-z]过滤小写字母。
注意2:^后面可以带多个条件,且这些条件都受^的作用,比如^a-z=表示^a-z且^=(既不是小写字母,也不是等于号)。
实验9:
首先,%*[^0-9]过滤前面非数字的字符,然后用%i把数字字符转换成int型的整数,拷贝到变量k,注意参数必须使用k的地址。
第二篇:
sscanf是可以直接用正则表达式的。
第一个参数是源字符串。第三个及以后的参数是可变参数列表,用于接收解析出来之后的值。最有玄机的是第二个参数,也就是所谓的format。我们知道prinft,sprintf,scanf,sscanf这四个函数(以及相应的双字节版本)都接收一个名为format的字符串作为参数,以便对输入输出做格式化,比如
- sscanf(“12”, "%s", str)
可以把字符串"12"(念做一二)格式化成10进制数12(念做十二),并赋值给in,而
- sscanf(“12”, "%d", &in)
可以把字符串"12"格式化成字符串12,并拷贝到str指向的内存区域。但是你可真不一定了解,format里是可以直接用正则表达式的!
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- static void sscanf_test(void)
- {
- int ret;
- char *string;
- int digit;
- char buf1[255];
- char buf2[255];
- char buf3[255];
- char buf4[255];
- /*1.最简单的用法*/
- string = "china beijing 123";
- ret = sscanf(string, "%s %s %d", buf1, buf2, &digit);
- printf("1.string=%s\n", string);
- printf("1.ret=%d, buf1=%s, buf2=%s, digit=%d\n\n", ret, buf1, buf2, digit);
- /*
- 执行结果:
- 1.ret=3, buf1=china, buf2=beijing, digit=123
- 可以看出,sscanf的返回值是读取的参数个数
- */
- /*2.取指定长度的字符串*/
- string = "123456789";
- sscanf(string, "%5s", buf1);
- printf("2.buf1=%s\n\n", buf1);
- /*
- **执行结果:
- **2.buf1=12345
- */
- /*3.取到指定字符为止的字符串*/
- string = "123/456";
- sscanf(string, "%[^/]", buf1);
- printf("3.string=%s\n", string);
- printf("3.buf1=%s\n\n", buf1);
- /*
- **执行结果:
- **3.buf1=123
- */
- /*4.取到指定字符集为止的字符串*/
- string = "123abcABC";
- sscanf(string, "%[^A-Z]", buf1);
- printf("4.string=%s\n", string);
- printf("4.buf1=%s\n\n", buf1);
- /*
- **执行结果:
- **4.buf1=123abc
- */
- /*5.取仅包含指定字符集的字符串*/
- string = "0123abcABC";
- sscanf(string, "%[0-9]%[a-z]%[A-Z]", buf1, buf2, buf3);
- printf("5.buf1=%s, buf2=%s, buf3=%s\n\n", buf1, buf2, buf3);
- /*
- **执行结果:
- **5.buf1=123, buf2=abc, buf3=ABC
- */
- /*6.获取指定字符中间的字符串*/
- string = "ios<android>wp7";
- sscanf(string, "%*[^<]<%[^>]", buf1);
- printf("6.buf1=%s\n\n", buf1);
- /*
- **执行结果:
- **6.buf1=android
- */
- /*7.指定要跳过的字符串*/
- string = "iosVSandroid";
- sscanf(string, "%[a-z]VS%[a-z]", buf1, buf2);
- printf("7.string=%s\n", string);
- printf("7.buf1=%s, buf2=%s\n\n", buf1, buf2);
- /*
- **执行结果:
- **7.buf1=ios, buf2=android
- */
- /*8.分割以某字符隔开的字符串*/
- string = "android-iphone-wp7";
- /*
- **字符串取道'-'为止,后面还需要跟着分隔符'-',
- **起到过滤作用,有点类似于第7点
- */
- sscanf(string, "%[^-]-%[^-]-%[^-]", buf1, buf2, buf3);
- printf("8.string=%s\n", string);
- printf("8.buf1=%s, buf2=%s, buf3=%s\n\n", buf1, buf2, buf3);
- /*
- **执行结果:
- **8.buf1=android, buf2=iphone, buf3=wp7
- */
- /*9.提取邮箱地址*/
- string = "Email:beijing@sina.com.cn";
- sscanf(string, "%[^:]:%[^@]@%[^.].%s", buf1, buf2, buf3, buf4);
- printf("9.buf1=%s, buf2=%s, buf3=%s, buf4=%s\n\n", buf1, buf2, buf3, buf4);
- /*
- **执行结果:
- **9.buf1=Email, buf2=beijing, buf3=sina, buf4=com.cn
- */
- /*10.过滤掉不想截取或不需要的字符串--补充,
- **在%号后面加一*号,代表过滤这个字符串,不读取
- */
- string = "android iphone wp7";
- sscanf(string, "%s %*s %s", buf1, buf2);
- printf("10.string=%s\n", string);
- printf("10.buf1=%s, buf2=%s\n\n", buf1, buf2);
- /*
- **执行结果:
- **10.android wp7
- */
- }