scanf 是 scan format 的缩写,意思是格式化扫描,也就是从键盘获得用户输入,和 printf 的功能正好相反。
严格来说,scanf() 是从标准输入文件中读取数据,而这个标准输入文件通常都是“黑底白字”的控制台,或者说是键盘。
C语言 scanf() 位于
int scanf ( const char * format, argument... );
format 为格式字符串,由格式说明符和普通字符构成。其中:
格式说明符以%开头,比如 %d、%s、%c 等,表示要读取什么样的数据;
普通字符按照原样输入,比如英文、数字、逗号、空格等。
argument 为参数列表,或者变量列表,多个参数以,分隔。每个参数都是一个指针,用来指明将数据存储在哪里。参数的个数和类型,要与格式说明符一一对应。
注意:argument 指向的位置必须已被分配内存,并且允许写入。
scanf() 会根据 format 中的格式说明符来读取数据,并将读取到的数据放到 argument 指定的位置。
int 表示 scanf() 的返回值类型,也即处理结果的数据类型:
如果读取成功,scanf() 将返回成功匹配并赋值的个数;
如果读取失败,或者达到文件末尾,或者遇到输入结束的条件,则返回 EOF。
EOF 是在 stdio.h 中定义的宏,它的值在不同的平台或者不同的编译器中可能不同,但通常都是 -1。
format 中的格式说明符
format 中的格式说明符比较复杂,它的标准写法如下:
%[*][width][length]specifier
末尾的 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,并且不区分大小写。
注意:这里不强调字符的顺序,只要出现在[ ]中的字符,不管先后,都能匹配成功。
为了简化字符集合的写法,scanf() 支持使用连字符-来表示一个范围内的字符,例如 %[a-z]、%[0-9] 等。
连字符左边的字符对应一个 ASCII 码,连字符右边的字符也对应一个 ASCII 码,位于这两个 ASCII 码范围以内的字符就是要读取的字符。
注意:连字符左边的 ASCII 码要小于右边的,如果反过来,那么它的行为是未定义的。
常用的连字符举例:
%[a-z]表示读取 abc...xyz 范围内的字符,也即小写字母;
%[A-Z]表示读取 ABC...XYZ 范围内的字符,也即大写字母;
%[0-9]表示读取 012...789 范围内的字符,也即十进制数字。
你也可以将它们合并起来,例如:
%[a-zA-Z]表示读取大写字母和小写字母,也即所有英文字母;
%[a-z-A-Z0-9]表示读取所有的英文字母和十进制数字;
%[0-9a-f]表示读取十六进制数字。
char *
[^characters]
不允许读取的字符合集。出现在[ ]中的字符不会被读取。
char *
n
不读取任何字符,只计算截止到目前读取的字符的个数,并将它存储到对应参数指定的位置。
int *
%
% 后面再跟一个 %,表示读取一个 %,类似于 % 的转义形式。
char *
*(星号)
* 表示将读取到的字符丢弃,或者忽略,也即不进行存储。因为没有任何字符需要存储,所以它没有对应的参数。
width
width 表示允许读取的最大字符个数。超过 width 的字符即使符合要求,也不会被读取。
length
length 是 specifier 的子说明符,用来修改对应参数的数据类型,它只能是 hh、h、l、ll、j、z、t、L 其中之一。
length 的用法及说明
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 标准引入的说明符或者子说明符。
C语言 scanf() 用法举例
为了方便读者理解,这里给出几个有代表性的例子。
简单的综合示例
#include
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
int main() {
int n;
float f;
char url[23];
scanf("%2d", &n);
scanf("%*[^\n]"); scanf("%*c"); //清空缓冲区
scanf("%5f", &f);
scanf("%*[^\n]"); scanf("%*c"); //清空缓冲区
scanf("%21s", url);
printf("Result: n=%d, f=%g, str=%s\n", n, f, url);
return 0;
}
输入示例①:
52↙
3.1415↙
https://54benniao.com↙
Result: n=52, f=3.141, str=https://54benniao.com
输入示例②:
5201314↙
3.1415926↙
https://www.54benniao.com↙
Result: n=52, f=3.141, str=https://www.54benniao
为了避免受到缓冲区中遗留数据的影响,每次读取结束我们都使用scanf("%*[^\n]"); scanf("%*c");来清空缓冲区。
限制读取数据的长度在实际开发中非常有用,最典型的一个例子就是读取字符串:我们为字符串分配的内存是有限的,用户输入的字符串过长就存放不了了,就会冲刷掉其它的数据,从而导致程序出错甚至崩溃;如果被黑客发现了这个漏洞,就可以构造栈溢出攻击,改变程序的执行流程,甚至执行自己的恶意代码,这对服务器来说简直是灭顶之灾。
匹配特定的字符
%s 说明符会匹配除空白符以外的所有字符,它有两个缺点:
%s 不能读取指定字符,比如只想读取小写字母,或者十进制数字等,%s 就无能为力;
%s 读取到的字符串中不能包含空白符,有些情况会比较尴尬,例如,无法将多个单词存放到一个字符串中,因为单词之间就是以空格为分隔的,%s 遇到空格就读取结束了。
使用 %[xxx] 就可以解决以上问题,请看下面的例子:
#include
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
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