C语⾔——你不得不知道的scanf的⾼级⽤法
如果你想让⾃⼰的输⼊更加炫酷、更加个性化、更加安全,那么还需要学习 scanf() 的⾼级⽤法,这才是⼤神和菜鸟的分⽔岭。
好了,⾔归正传,我们分三个⽅⾯讲解 scanf() 的⾼级⽤法。
1) 指定读取长度
还记得在 printf() 中可以指定最⼩输出宽度吗?就是在格式控制符的中间加上⼀个数字,例如,%10d表⽰输出的整数⾄少占⽤ 10 个字符的位置:
如果整数的宽度不⾜ 10,那么在左边以空格补齐;
如果整数的宽度超过了 10,那么以整数本⾝的宽度来输出,10 不再起作⽤。
其实,scanf() 也有类似的⽤法,也可以在格式控制符的中间加⼀个数字,⽤来表⽰读取数据的最⼤长度,例如:
%2d表⽰最多读取两位整数;
%10s表⽰读取的字符串的最⼤长度为 10,或者说,最多读取 10 个字符。
请看下⾯的例⼦:
#include<stdio.h>
int main(){
int n;
float f;
char str[23];
scanf("%2d",&n);
scanf("%*[^\n]");scanf("%*c");//清空缓冲区
scanf("%5f",&f);
scanf("%*[^\n]");scanf("%*c");//清空缓冲区
scanf("%22s", str);
printf("n=%d, f=%g, str=%s\n", n, f, str);
return0;
}
输⼊⽰例 ①:
20↙
100.5↙
c.biancheng↙
n=20, f=100.5, str=c.biancheng
输⼊⽰例 ②:
8920↙
10.2579↙
data.biancheng↙
n=89, f=10.25, str=data.biancheng.
这段代码使⽤了多个 scanf() 函数连续读取数据,为了避免受到缓冲区中遗留数据的影响,每次读取结束我们都使⽤scanf("%*[^\n]"); scanf("%*c");来清空缓冲区。
限制读取数据的长度在实际开发中⾮常有⽤,最典型的⼀个例⼦就是读取字符串:我们为字符串分配的内存是有限的,⽤户输⼊的字符串过长就存放不了了,就会冲刷掉其它的数据,从⽽导致程序出错甚⾄崩溃;如果被⿊客发现了这个漏洞,就可以构造栈溢出攻击,改变程序的执⾏流程,甚⾄执⾏⾃⼰的恶意代码,这对服务器来说简直是灭顶之灾。
在⽤ gets() 函数读取字符串的时候,有⼀些编译器会提⽰不安全,建议替换为 gets_s() 函数,就是因为 gets() 不能控制读取到的字符串的长度,风险极⾼。
2) 匹配特定的字符
%s 控制符会匹配除空⽩符以外的所有字符,它有两个缺点:
1. %s 不能读取特定的字符,⽐如只想读取⼩写字母,或者⼗进制数字等,%s 就⽆能为⼒;
2. %s 读取到的字符串中不能包含空⽩符,有些情况会⽐较尴尬,例如,⽆法将多个单词存放到⼀个字符串中,因为单词之间就是以空格
为分隔的,%s 遇到空格就读取结束了。
要想解决以上问题,可以使⽤ scanf() 的另外⼀种字符匹配⽅式,就是%[xxx],[ ]包围起来的是需要读取的字符集合。例如,%[abcd]表⽰只读取字符abcd,遇到其它的字符就读取结束;注意,这⾥并不强调字符的顺序,只要字符在 abcd 范围内都可以匹配成功,所以你可以输⼊ abcd、dcba、ccdc、bdcca 等。
请看下⾯的代码:
#include<stdio.h>
int main(){
char str[30];
scanf("%[abcd]", str);
printf("%s\n", str);
return0;
}
输⼊⽰例 ①:
abcdefgh↙
abcd
输⼊⽰例 ②:
baccbaxyz↙
baccba
使⽤连接符
为了简化字符集合的写法,scanf() ⽀持使⽤连字符-来表⽰⼀个范围内的字符,例如 %[a-z]、%[0-9] 等。
连字符左边的字符对应⼀个 ASCII 码,连字符右边的字符也对应⼀个 ASCII 码,位于这两个 ASCII 码范围以内的字符就是要读取的字符。注意,连字符左边的 ASCII 码要⼩于右边的,如果反过来,那么它的⾏为是未定义的。
常⽤的连字符举例:
%[a-z]表⽰读取 范围内的字符,也即⼩写字母;
%[A-Z]表⽰读取 XYZ 范围内的字符,也即⼤写字母;
%[0-9]表⽰读取 789 范围内的字符,也即⼗进制数字。
你也可以将它们合并起来,例如:
%[a-zA-Z]表⽰读取⼤写字母和⼩写字母,也即所有英⽂字母;
%[a-z-A-Z0-9]表⽰读取所有的英⽂字母和⼗进制数字;
%[0-9a-f]表⽰读取⼗六进制数字。
请看下⾯的演⽰:
#include<stdio.h>
int main(){
char str[30];
scanf("%[a-zA-Z]", str);//只读取字母
printf("%s\n", str);
return0;
}
输⼊⽰例:
abcXYZ123↙
abcXYZ
不匹配某些字符
假如现在有⼀种需求,就是读取换⾏符以外的所有字符,或者读取 0~9 以外的所有字符,该怎么实现呢?总不能把剩下的字符都罗列出来吧,⼀是⿇烦,⼆是不现实。
C语⾔的开发者们早就考虑到这个问题了,scanf() 允许我们在%[ ]中直接指定某些不能匹配的字符,具体⽅法就是在不匹配的字符前⾯加上^,例如:
%[^\n]表⽰匹配除换⾏符以外的所有字符,遇到换⾏符就停⽌读取;
%[^0-9]表⽰匹配除⼗进制数字以外的所有字符,遇到⼗进制数字就停⽌读取。
请看下⾯的例⼦:
#include<stdio.h>
int main(){
char str1[30], str2[30];
scanf("%[^0-9]", str1);
scanf("%*[^\n]");scanf("%*c");//清空缓冲区
scanf("%[^\n]", str2);
printf("str1=%s \nstr2=%s\n", str1, str2);
return0;
}
输⼊⽰例:
abcXYZ@#87edf↙
c c++ java python go javascript↙
str1=abcXYZ@#
str2=c c++ java python go javascript
请注意第 6 ⾏代码,它的作⽤是读取⼀⾏字符串,和 gets() 的功能⼀模⼀样。你看,scanf() 也能读取带空格的字符串呀,谁说 scanf()不能完全取代 gets(),这明显是错误的说法。
另外,scanf() 还可以指定字符串的最⼤长度,指定字符串中不能包含哪些字符,这是 gets() 不具备的功能。
例如,读取⼀⾏不能包含⼗进制数字的字符串,并且长度不能超过 30:
#include<stdio.h>
int main(){
char str[31];
scanf("%30[^0-9\n]", str);
printf("str=%s\n", str);
return0;
}
输⼊⽰例 ①:
c.biancheng biancheng↙
str=c.biancheng
输⼊⽰例 ②:
I have been programming for 8 years.↙
str=I have been programming for
3) 丢弃读取到的字符
在前⾯的代码中,每个格式控制符都要对应⼀个变量,把读取到的数据放⼊对应的变量中。其实你也可以不这样做,scanf() 允许把读取到的数据直接丢弃,不往变量中存放,具体⽅法就是在 % 后⾯加⼀个*,例如:
%*d表⽰读取⼀个整数并丢弃;
%*[a-z]表⽰读取⼩写字母并丢弃;
%*[^\n]表⽰将换⾏符以外的字符全部丢弃。
请看下⾯的代码演⽰:
#include<stdio.h>
int main(){
int n;
char str[30];
scanf("%*d %d",&n);
scanf("%*[a-z]");
scanf("%[^\n]", str);
printf("n=%d, str=%s\n", n, str);
return0;
}
输⼊⽰例:
100 999abcxyzABCXYZ↙
n=999, str=ABCXYZ
对结果的分析:整数 100 被第⼀个 scanf() 中的%*d读取后丢弃了,整数 999 被第%d读取到,并赋值给 n。此时缓冲区中剩下abcxyzABCXYZ,第⼆个 scanf() 将 abcxyz 读取并丢弃,剩下的 ABCXYZ 被最后⼀个 scanf() 读取到并赋值给 str。
⼤家有没有意识到,将读取到的字符直接丢弃,这就是在清空输⼊缓冲区呀,虽然有点蹩脚,但是⾏之有效。在 中我们已经给出了使⽤scanf() 清空缓冲区的⽅案,就是:
scanf("%*[^\n]"); scanf("%*c");
下⾯我们就来解释⼀下。
⾸先需要明⽩的是,等到需要清空缓冲区的时候,缓冲区中的最后⼀个字符⼀定是换⾏符\n,因为输⼊缓冲区是⾏缓冲模式,⽤户按下回车键会产⽣换⾏符,结束本次输⼊,然后输⼊函数开始读取。
scanf("%*[^\n]");将换⾏符前⾯的所有字符清空,scanf("%*c");将最后剩下的换⾏符清空。
有些⽹友将这两条语句合并起来,写作:
scanf("%*[^\n]%*c");
这是错误的。合并以后的语句不能清空单个换⾏符,因为该语句要求换⾏符前边⾄少要有⼀个其它的字符,单个换⾏符会导致匹配失败。
总结
scanf() 控制字符串的完整写法为:
%{*} {width} type
其中,{ } 表⽰可有可⽆。各个部分的具体含义是:
type表⽰读取什么类型的数据,例如 %d、%s、%[a-z]、%[^\n] 等;type 必须有。printf怎么输出字符
width表⽰最⼤读取宽度,可有可⽆。
*表⽰丢弃读取到的数据,可有可⽆。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论