C 语言的 scanf() 函数

概述

作用: Read formatted data from the standard input stream.

从键盘输入的和输出到屏幕的都是文本。从键盘输入的都是文本, 因为键盘只能产生文本字符. 例如, 要输入数字 10, 就要用键盘键入字符 1 和字符 0. 但是计算机中存储的数据都是数值类型, 因此需要将字符转换为数值类型.

scanf() 把输入的字符串转换成整数、浮点数、字符或字符串,而 printf() 正好相反,把整数、浮点数、字符和字符串转换成显示在屏幕上的文本。根据转换说明来判断进行哪种转换。

scanf() 和 printf() 类似,都使用格式字符串和参数列表。

scanf() 中的格式字符串表明字符输入流的目标数据类型. 两个函数的主要的区别在参数列表中. printf() 函数使用变量, 常量和表达式, 而 scanf() 使用指向变量的指针.

函数原型:

int scanf( const char *format, argument1, argument2, argument3, ... );

Parameters:
format: Format control string
argument: Optional arguments

Return Value:

return the number of fields successfully converted and assigned; the return value does not include fields that were read but not assigned. A return value of 0 indicates that no fields were assigned. The return value is EOF for an error or if the end-of-file character or the end-of-string character is encountered in the first attempt to read a character.

C 语言通过函数原型检查函数调用时的实参个数和类型是否正确。

但是这个机制对于 scanf 和 printf 都不起作用,因为这两个函数的函数原型中参数个数是可变的。

程序员要自己负责转化说明的数量、类型与后面的参数的数量、类型匹配。

转换说明

int

十进制:

%d 或 %i

八进制:

%o

十六进制:

%x, %X

%d 可以识别键入内容中的 +-0, 1, 2, 3, 4, 5, 6, 7, 8, 9. 一直读到非数字字符, 便认为读到了整数的末尾. 然后, scanf() 把非数字字符放回输入. 这意味着程序在下一次读取输入时, 首先读到的是上一次读取丢弃的非数字字符. 最后, scanf() 计算已读取数字 (可能还有符号) 相应的数值, 并将计算后的值放入指定的变量中.

%x 要求 scanf() 识别十六进制数 a~f 和 A~F.

%d 会跳过空白符. 程序示例:

#include<stdio.h>
int main(void) {
int a;
while (scanf("%d",&a)!=EOF) {
printf("%d\n", a);
}
return 0;
}

执行结果:

3
3
5
5
6
6
^Z
^Z
^Z

unsiged int

十进制:

%u

short

%hd 或 %hi

unsigned short

%hu %ho %hx

long

%ld 或 %li

unsigned long

%lu %lo %lx

long long

%lld

unsigned long long

%llu

float

%f, %e, %g, %a, %E, %F, %G, %A

浮点转换说明要求 scanf() 识别小数点, 指数记数法 (即 e 记数法) 和十六进制指数记数法 (即 p 记数法).

double

%lf %le %lg

long double

%Lf %Le %Lg

指针/地址

%p

sizeof 返回类型

%zd %zo %zx

两个指针的差值

%td %to %tx

char

%c

字符串

%s

只会读取第一个单词, 遇到空白就结束读取, 剩余内容保留在缓存中, 等待下一个读取的命令, 作为下一个读取命令的输入.

代码示例:

#include<stdio.h>
int main(void)
{
char str1[10], str2[20];
printf("Enter your name: ");
scanf("%s", str1);
scanf("%s", str2);
printf("%s\n", str1);
printf("%s\n", str2);
return 0;
}

结果:

Enter your name: mike tom
mike
tom

一次性输入两个单词, 用空格分隔, 第一个单词被 scanf("%s", str1); 语句读入, mike 储存在 str1 中, 剩下的空格和 tom 留在缓存中, 遇到 scanf("%s", str2); 语句时被读入, 其中空格被忽略, tom 被储存在 str2 中.

scanf 以 %s 读入字符串时,输入字符的前面的空白将被忽略。一直往后读取, 直到再次遇到空白.

代码示例:

#include<stdio.h>
int main(void) {
char name[10];
while (scanf("%s", name) != EOF)
puts(name);
return 0;
}

执行结果:

hello world
hello
world
nihao
nihao
^Z
^Z
^Z

当 scanf() 把字符串放进指定数组中时, 它会在字符序列的末尾加上 '\0', 当数组中的内容成为一个 C 字符串.

修饰符

可以在转换说明中 (百分号和转换字符之间) 使用修饰符.

当使用多个修饰符时, 需要按照下表所示顺序书写:

数字表示最大字段宽度, 输入达到最大字段宽度时, 或第 1 次遇到空白字符时停止.

程序示例:

牛客 HJ4 字符串分隔

描述

•输入一个字符串,请按长度为 8 拆分每个输入字符串并进行输出;

•长度不是 8 整数倍的字符串请在后面补数字 0,空字符串不处理。

输入描述:连续输入字符串(每个字符串长度小于等于 100)

输出描述:依次输出所有分割后的长度为8的新字符串

示例 1

输入:

abc

输出:

abc00000

代码:

#include <stdio.h>
#include <string.h>
int main(void) {
char str[101];
while (scanf("%8s", str) != EOF) {
printf("%s", str);
for (int i = 8 - strlen(str); i > 0 ; i--) {
putchar('0');
}
printf("\n");
}
return 0;
}

执行结果:

helloworld
hellowor
ld000000
nihaoahaodenihaowozhidaolekeyimeiwenti
nihaoaha
odenihao
wozhidao
lekeyime
iwenti00
^Z
^Z
^Z

代码中, 每次读取 8 个字符进入 str, 其他输入存放在缓存中, 暂时不读进来, 留待下一轮循环读取.

格式字符串中的空白

scanf 使用空白将键入的内容分成多个字段.

在依次将转换说明和字段进行匹配时跳过键入的内容中的这些空白.

唯一例外的是 %c 转换说明, 根据 %c 转换说明, scanf 将会读入键入的每一个字符, 包括空白.

%c 将键入的所有字符都读入, 包括空白.

除了 %c 之外的其他转换说明, 将会跳过键入的内容中的空白, 直到遇到非空白字符, 才开始尝试读取这个非空白字符, 如果符合转换说明要求的格式, 则进行读入, 否则发生错误, 这些键入的内容依然存在于缓存中, 留待下一次读入.

程序在下一次读取时, 首先碰到的是上一次读取时丢弃的内容.

对于带有多个转换说明的 scanf 函数, C 规定, 在第 1 个出错的地方就停止读取输入.

格式字符串中的普通字符

scanf() 函数允许把普通字符放在格式字符串中, 除了空格外的普通字符必须与键入的内容严格匹配.

例如下面的这个输入:

scanf("%d,%d",&a,&b);

被解释为: 用户需要键入一个数字, 一个逗号, 一个数字.

由于在这个格式字符串中, 第一个 %d 后面紧跟着一个逗号, 所以在键入的内容中, 第一个数字后面必须紧跟一个逗号.

第二个 %d 在读取键入的内容时, 会跳过第二个数字前面的所有空白.

程序示例:

#include<stdio.h>
int main(void) {
int a = 0, b = 0;
printf("输入两个整数:");
scanf("%d,%d", &a, &b);
printf("%d %d\n", a, b);
return 0;
}

结果 1:

输入两个整数:1,2
1 2

可以看出, 两个整数都读取成功.

结果 2:

输入两个整数:1 ,2
1 0

a 读取成功, b 读取失败, 还是原来的值, 即 0.

结果 3:

输入两个整数:1, 2
1 2

逗号紧跟第一个数, 第二个数和逗号之间可以有空白.

格式字符串中的空白意味着跳过下一个输入项前面的所有空白.

程序示例:

#include<stdio.h>
int main(void) {
int a = 0, b = 0;
printf("输入两个整数:");
scanf("%d ,%d", &a, &b);
printf("%d %d\n", a, b);
return 0;
}

结果 1:

输入两个整数:1 ,2
1 2

所有空白也包括没有空白的特殊情况:

结果 2:

输入两个整数:1,2
1 2

除了 %c 之外的其他转换说明, 将会跳过键入的内容中的空白, 因此下面这两句话效果相同:

scanf("%d%d", &a, &b); // 没有空白
scanf("%d %d", &a, &b); // 有空白

根据 %c 转换说明, scanf() 将会读入键入的每一个字符, 包括空白. 但是如果 %c 前面有一个空格, 则键入内容中的空白将被忽视, 从第一个非空白字符开始读取.

scanf("%c", &ch); // 从键入内容的第一个字符开始读取, 包括空白符.
scanf(" %c", &ch); //从键入内容的第一个非空白字符开始读取.

* 修饰符

把 * 放在 % 和转换字符之间会使得 scanf 跳过相应的输出项.

程序示例:

#include<stdio.h>
int main(void)
{
printf("Please enter three integers: ");
int a = 0, b = 0, c = 0;
scanf("%*d %*d %d", &c);
printf("The last integer is %d.\n", c);
return 0;
}

带了 * 则后面不需要对应的变量去接受键入的内容. 因此这里只需要一个变量 c.

程序示例:

// 输入一系列数,每三个数输出一个,保证输入的数的个数是3的倍数
#include<stdio.h>
int main(void) {
int a;
while (scanf("%*d %*d %d",&a)!=EOF) {
printf("%d\n", a);
}
return 0;
}

执行结果:

1 2 3 4 5 6 7 8 9
3
6
9
^Z
^Z
^Z

在程序中需要读取文件中特定列的内容时, 这项跳过功能很有用.

scanf() 函数的返回值

scanf() 返回成功读取的项数. 如果没有读取任何项, 且需要读取一个数字而用户却输入一个非数值字符串, scanf() 便返回 0.

当一个也没有读取成功时, 返回 0.

代码示例:

#include<stdio.h>
int main(void) {
int a = 0;
while (scanf("%d", &a) != EOF) {
printf("%d\n", a);
}
return 0;
}

如果输入正确, 读取成功, 则输出 a 的值, 如果输入错误, 导致错误的输入将一直留在缓冲区, 下次读取时还是这个错误的输入, 而 a 将一直保持原来的值, 将构成一个死循环. 虽然读取失败了, 但是这里并没有返回 EOF 而是返回了 0.

代码示例:

#include<stdio.h>
int main(void) {
int a = 0;
int b = scanf("%d", &a);
printf("%d\n", a);
printf("%d\n", b);
return 0;
}

当输入正确时, 执行结果:

3
3
1

当输入错误时, 执行结果:

e
0
0

可见, 当输入一个字符时, scanf() 函数的返回值为 0, 而 a 保持原来的值不变.

代码示例:

#include<stdio.h>
int main(void) {
int a = 0, b = 0;
int c = scanf("%d %d", &a, &b);
printf("%d\n", a);
printf("%d\n", b);
printf("%d\n", c);
return 0;
}

两个都读取成功的执行结果:

1 2
1
2
2

读取成功一个的执行结果:

1 a
1
0
1

此时第一个输入是正确的, a 读取成功, 而 b 读取失败, 保持原来的 0.

第一个输入错误而第二个输入正确的执行结果:

? 1
0
0
0

这里虽然第二个输入是正确的, 但是第一个读取失败后, ? 被放回缓冲区, b 读取的仍然是 ?, 所以 b 也读取失败.

当 scanf() 检测到文件结尾时, 会返回 EOF.

代码示例:

#include<stdio.h>
int main(void) {
char a[100];
int c = scanf("%s", &a);
printf("%s\n", a);
printf("%d\n", c);
return 0;
}

执行结果:

^Z
^Z
^Z
烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫?狪
-1

此处, 用三个 ^Z 模拟文件结尾, 得到 scanf() 的返回值为 -1.

如果给字符串以初始值, 代码示例:

#include<stdio.h>
int main(void) {
char a[100] = "hello"; // 给字符串以初始值
int c = scanf("%s", &a);
printf("%s\n", a);
printf("%d\n", c);
return 0;
}

执行结果:

^Z
^Z
^Z
hello
-1

用三个 ^Z 模拟文件结尾, 得到 scanf() 的返回值为 -1, 字符串 a 保持原来的值.

scanf() 通配符用法

[] 内部的内容表示需要读入的字符, 只读入这些东西, 遇到不是这些东西时就停止读取, 将余下的东西都放在内存中等着下次读取.

-是范围连接符,也可以直接列出你需要读入的字符。

代码:

// []内部的内容表示需要读入的字符, 只读入这些东西,
// 遇到不是这些东西时就停止读取, 将余下的东西都放在内存中等着下次读取.
#include<stdio.h>
#include<string.h>
int main() {
char nums[100] = { '\0' };
char c;
scanf("%[0-9]", nums);
scanf("%c", &c);
printf("%s\n", nums);
printf("%c\n", c);
return 0;
}

结果:

012789cs
012789
c

^XX 表示遇到 XX 就停止读取, XX 放在内存中等着下次读取.

代码:

// ^XX 表示遇到 XX 就停止读取, XX 放在内存中等着下次读取.
#include<stdio.h>
#include<string.h>
int main() {
char nums[100] = { '\0' };
char c;
scanf("%[^=]", nums); // 什么都可以读入,遇到=停下
scanf("%c", &c); // =放在内存中等着下次读取
printf("%s\n", nums);
printf("%c\n", c);
return 0;
}

结果:

526cd#$%=
526cd#$%
=

代码:

// ^XX 表示遇到 XX 就停止读取, XX 放在内存中等着下次读取.
#include<stdio.h>
#include<string.h>
int main() {
char nums[100] = { '\0' };
char c;
scanf("%[^\n]", nums); // 什么都可以读入,遇到换行符时停下,因此可以读入多个字符串,即可以读入整行输入直到换行
scanf("%c", &c); // 换行符放在内存中等着下次读取,这里读取了这个换行符,导致c是换行符
printf("%s\n", nums);
printf("%c\n", c); // 打印一个换行符
return 0;
}

结果:

123dsf@##$ 123njjk^&*(_=
123dsf@##$ 123njjk^&*(_=

代码示例:

#include <stdio.h>
#include <string.h>
int main(void) {
char str[101];
// %n[a - z] 读入最多n个字符, 如果遇到非a-z的字符, 停止
scanf("%10[a-z]", str);
puts(str);
return 0;
}

执行结果 1 (最多读入10个字符):

abcdefghijklmn
abcdefghij

执行结果 2 (遇到不是想要读取的字符时停止):

abcde?fghijk
abcde

代码示例:

#include <stdio.h>
#include <string.h>
int main(void) {
char str[101] = "hello";
// %n[^=]” 读入"="号前的至多n个字符
scanf("%10[^=]", str);
puts(str);
return 0;
}

执行结果 1:

ok=?
ok

执行结果 2:

=?
hello

读取失败, 字符串保持原来的值不变.

%[a-zA-Z]表示只读入字母, %[0-9a-zA-Z]表示只读入字母和数字字符, 可以使用多个连接符 -

%[^0-9]表示遇到 0-9 就停止.

代码示例:

#include <stdio.h>
#include <string.h>
int main(void) {
char str[101] = "hello";
// %[^0-9]表示遇到 0-9 就停止.
scanf("%[^0-9]", str);
puts(str);
return 0;
}

执行结果:

hello5world
hello

scanf("%*[^\n]%*c")表示跳过一行字符串。其中%c可以把\n吸收掉,防止影响后续输入。

posted @   有空  阅读(167)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示

目录导航