C语言scanf的用法(非常详细和全面)
scanf() 的用法
scanf 是 scan format 的缩写,意思是格式化扫描,也就是从键盘获得用户输入,和 printf 的功能正好相反。严格来说,scanf() 是从标准输入文件中读取数据,而这个标准输入文件通常都是“黑底白字”的控制台,或者说是键盘。
scanf() 的标准用法(原型)为:int scanf ( const char * format, argument... );
format 为格式字符串,由格式说明符和普通字符构成。其中:-
格式说明符以
%
开头,比如 %d、%s、%c 等,表示要读取什么样的数据; - 普通字符按照原样输入,比如英文、数字、逗号、空格等。
argument 为参数列表,或者变量列表,多个参数以
,
分隔。每个参数都是一个指针,用来指明将数据存储在哪里。参数的个数和类型,要与格式说明符一一对应。
注意:argument 指向的位置必须已被分配内存,并且允许写入。
C语言 scanf() 会根据 format 中的格式说明符来读取数据,并将读取到的数据放到 argument 指定的位置。int 表示 scanf() 的返回值类型,也即处理结果的数据类型:
- 如果读取成功,scanf() 将返回成功匹配并赋值的个数;
- 如果读取失败,或者达到文件末尾,或者遇到输入结束的条件,则返回 EOF。
EOF 是在 stdio.h 中定义的宏,它的值在不同的平台或者不同的编译器中可能不同,但通常都是 -1。
format 中的格式说明符
format 中的格式说明符比较复杂,它的标准写法如下:%[*][width][length]specifier
末尾的 specifier 不能省略,其它由[ ]
包围的部分可以省略。
specifier
specifier 是格式字符,它最重要,指明了要读取的数据的类型和形式。specifier | 匹配的字符 | 参数类型 |
---|---|---|
i |
整数,前面可以带正号+ 和负号- 。默认为十进制,带上前缀 0 表示八进制,带上前缀0x 表示十六进制。 |
int * |
d u |
十进制整数,d 表示有符号整数,u 表示无符号整数。 |
int * unsigned int * |
o | 八进制整数(无符号)。 | unsigned int * |
x |
十六进制整数(无符号),可以带有0x 或者0X 前缀。 |
unsigned int * |
f, e, g |
浮点数/小数,前面可以带正号+ 和负号- ,接受普通形式(比如 3.1415)以及科学计数法(比如 5.23e4)。 |
float * |
a | ||
c | 单个字符。如果指定的宽度 width 不是 1,那么 scanf() 会读取 width 个字符,并将它们连续存储到参数所指定的位置(末尾不追加任何字符)。 | char * |
s |
字符串,不包含空白符(空格、换行、制表符等)。读取连续的字符,直到遇见第一个空白符就结束读取。读取结束后,scanf() 会自动在末尾追加空字符\0 ,用以表示字符串的结束。 |
char * |
p |
指针/地址。在不同的平台和不同的编译器中,指针的格式可能有所区别,但它始终和在 printf() 中使用%p 输出的格式相同。 |
void ** |
[characters] |
允许读取的字符集合。只有出现在[ ] 中的字符会被读取,遇到第一个不符合的字符就结束读取,比如[abcABC] 表示读取字母 abc,并且不区分大小写。
注意:这里不强调字符的顺序,只要出现在 - 来表示一个范围内的字符,例如 %[a-z]、%[0-9] 等。连字符左边的字符对应一个 ASCII 码,连字符右边的字符也对应一个 ASCII 码,位于这两个 ASCII 码范围以内的字符就是要读取的字符。 注意:连字符左边的 ASCII 码要小于右边的,如果反过来,那么它的行为是未定义的。 常用的连字符举例:
你也可以将它们合并起来,例如:
|
char * |
[^characters] |
不允许读取的字符合集。出现在[ ] 中的字符不会被读取。 |
char * |
n | 不读取任何字符,只计算截止到目前读取的字符的个数,并将它存储到对应参数指定的位置。 | int * |
% | % 后面再跟一个 %,表示读取一个 %,类似于 % 的转义形式。 | char * |
*(星号)
* 表示将读取到的字符丢弃,或者忽略,也即不进行存储。因为没有任何字符需要存储,所以它没有对应的参数。width
width 表示允许读取的最大字符个数。超过 width 的字符即使符合要求,也不会被读取。length
length 是 specifier 的子说明符,用来修改对应参数的数据类型,它只能是 hh、h、l、ll、j、z、t、L 其中之一。specifier | |||||||
---|---|---|---|---|---|---|---|
length | d i | u o x | f e g a | c s [] [^] | p | n | |
默认(不指明length) | int * | unsigned int * | float * | char * | void ** | int * | |
hh | signed char * | unsigned char * | signed char * | ||||
h | short int * | unsigned short int * | short int * | ||||
l | long int * | unsigned long int * | double * | wchar_t * | long int * | ||
ll | long long int * | unsigned long long int * | long long int * | ||||
j | intmax_t * | uintmax_t * | intmax_t * | ||||
z | size_t * | size_t * | size_t * | ||||
t | ptrdiff_t * | ptrdiff_t * | ptrdiff_t * | ||||
L | long double * |
上面淡黄色背景的行,为 C99 标准引入的说明符或者子说明符。
scanf() 用法举例
为了方便读者理解,这里给出几个有代表性的例子。简单的综合示例
#include <stdio.h> int main() { char str[31]; int i; printf("Enter your name: "); scanf("%30s", str); printf("Enter your age: "); scanf("%d", &i); printf("Hello %s, you are %d years old.\n", str, i); printf("Enter a hexadecimal number: "); scanf("%x", &i); printf("You have entered %#x(%d).\n", i, i); return 0; }输入示例:
Enter your name: Tom↙
Enter your age: 18↙
Hello Tom, you are 18 years old.
Enter a hexadecimal number: 5e↙
You have entered 0x5e(94).
使用 width 指定读取长度
#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); return 0; }输入示例 ①:
20↙
100.5↙
http://c.biancheng.net↙
n=20, f=100.5, str=http://c.biancheng.net
8920↙
10.2579↙
http://data.biancheng.net↙
n=89, f=10.25, str=http://data.biancheng.
为了避免受到缓冲区中遗留数据的影响,每次读取结束我们都使用
scanf("%*[^\n]"); scanf("%*c");
来清空缓冲区。关于 scanf() 和缓冲区的话题,我们将在后面几节中详细讲解:
限制读取数据的长度在实际开发中非常有用,最典型的一个例子就是读取字符串:我们为字符串分配的内存是有限的,用户输入的字符串过长就存放不了了,就会冲刷掉其它的数据,从而导致程序出错甚至崩溃;如果被黑客发现了这个漏洞,就可以构造栈溢出攻击,改变程序的执行流程,甚至执行自己的恶意代码,这对服务器来说简直是灭顶之灾。
匹配特定的字符
%s 说明符会匹配除空白符以外的所有字符,它有两个缺点:- %s 不能读取指定字符,比如只想读取小写字母,或者十进制数字等,%s 就无能为力;
- %s 读取到的字符串中不能包含空白符,有些情况会比较尴尬,例如,无法将多个单词存放到一个字符串中,因为单词之间就是以空格为分隔的,%s 遇到空格就读取结束了。
使用 %[xxx] 就可以解决以上问题,请看下面的例子:
#include <stdio.h> int main() { char str1[30]; char str2[30]; scanf("%[abcd]", str1); //只读取abcd字母 scanf("%*[^\n]"); scanf("%*c"); //清空缓冲区 scanf("%[a-zA-Z]", str2); //只读取小写和大写的英文字母 printf("str1: %s\nstr2: %s", str1, str2); return 0; }输入示例:
baccdaxyz↙
abcXYZ123↙
str1: baccda
str2: abcXYZ
再比如,读取一行不能包含十进制数字的字符串,并且长度不能超过 30:
#include <stdio.h> int main() { char str[31]; scanf("%30[^0-9\n]", str); printf("str: %s", str); return 0; }输入示例:
I have been programming for 8 years now↙
str: I have been programming for