【C语言】scanf函数(未完待续)

小看了这个scanf函数,不愧是C语言最复杂的函数之一。先写到这儿,后面再补充。


一、简介

隶属标准库:

<stdio.h>

函数原型:

int scanf(const char *format, ...)

函数功能:

scanf 是 scan format 的缩写,意思是格式化扫描,也就是从键盘获得用户输入。

但scanf并不是直接从键盘读入数据,中间还有输入缓冲区作为过渡。

因此scanf的功能实际是从输入缓冲区中读取数据进行格式转化后存入指定内存区域。

关于流与缓冲区的区别可以看这篇文章:流与缓冲区

参数:
format 说明符形式为::

[=%[*][width][modifiers]type=]
格式字符说明
%d读入十进制整数
%c读入一个字符
%s读入一个字符串
%f读入一个浮点数
%p读入一个指针

返回值:
如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。

二、功能演示

1、连续读入多个数据

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

请输入三个数字:1 2 3
1,2,3
3

t==3表示成功读入3个数据项。

"%d%d%d"是按十进值格式输入三个数值。输入时,在两个数据之间可以用一个或多个空格、tab键、enter键分隔。

"%d,%d,%d"则必须严格按照它的格式输入,否则就会出错。

"a=%d,b=%d,c=%d"也必须严格按照它的格式输入,否则就会出错

2、指定输入的数据不保存

百分号(%)与格式符之间的星号(*)表示读指定类型的数据但不保存。因此,

scanf("%d%*c%d",&x,&y);

当输入 10/20 时,10 放入变量 x,20 放入 y,/不保存。

3、最大域宽

在百分号(%)与格式码之间的整数用于限制读入的最大字符数。例如,希望向字符数组 address 读入不多于 5 个字符时,可以书写成如下形式:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main()
{
	int n;
	char address[6];
	scanf("%5s", address);
	printf("%s\n", address);
	return 0;
}

kjihu
kjihu

输入5个字符后,程序会自动在末尾添加\0,所以数组adress至少要有6个元素,否则就会溢出。

三、特殊情况

1、scanf遇到%c时

scanf在用“%c”输入时,不同于其他类型,scanf会读取每个字符,包括空白符(即enter,tab,空格)。

看下面的例子

#include<stdio.h>
int main(void)
{
	char a, b, c;
	int t;
	printf("请输入三个字符:");
	t = scanf("%c%c%c", &a, &b, &c);
	printf("%c,%c,%c\n", a, b, c);
	printf("%d", t);
	return 0;
}

请输入三个字符:avb
a,v,b
3

请输入三个字符:a v b
a, ,v
3

如果字符间以空格分开,scanf也会将空格读入。tab与enter也是如此。

2、scanf遇到空格会结束数据输入

#include <stdio.h> 
int main()
{
	char str[80];

	scanf("%s", str);
	printf("%s", str);
	return 0;
}

i love you
i

这里只读入了i,love you还留在输入缓冲区中。

3、scanf输入缓冲区残留问题

从本质上讲,我们从键盘输入的数据并没有直接交给 scanf(),而是放入了缓冲区中,直到我们按下回车键,scanf() 才到缓冲区中读取数据。如果缓冲区中的数据符合 scanf() 的要求,那么就读取结束;如果不符合要求,那么就继续等待用户输入,或者干脆读取失败。

看下面这个例子

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int a = 0, b = 0, c = 0, d = 0;
	scanf("%d", &a); //输入整数并赋值给变量a
	scanf("%d", &b); //输入整数并赋值给变量b
	printf("a+b=%d\n", a + b); //计算a+b的值并输出
	scanf("%d %d", &c, &d); //输入两个整数并分别赋值给c、d
	printf("c*d=%d\n", c * d); //计算c*d的值并输出
	return 0;
}
//第一种输入方式
12
60
a+b=72
12
23
c*d=276

//第二种输入方式
12 60 10 23
a+b=72
c*d=230

//第三种输入方式
12 60 10 23 99
a+b=72
c*d=230

//第四种输入方式
12 60 10
a+b=72
23
c*d=230

正常情况下是按第一种方式输入,输入一个值,按一下enter。

但像第二种一样连续输入也可以。因为第一个 scanf() 读取完毕后没有抛弃多余的值,多余的值还在缓冲区,下次调用scanf接着使用。

第三种多输入一个,scanf() 仍然能够正确读取,只是 99 没用罢了。

第四种少输入一个整数,输入三个整数后,前两个 scanf() 把前两个整数给读取了,剩下一个整数 10,而第三个 scanf() 要求输入两个整数,一个单独的 10 并不能满足要求,所以我们还得继续输入,凑够两个整数以后,第三个 scanf() 才能读取完毕。

总而言之,正是由于缓冲区的存在,才使得我们能够多输入一些数据,或者一次性输入所有数据,这可以认为是缓冲区的一点优势。然而,缓冲区也带来了一定的负面影响,甚至会导致很奇怪的行为,请看下面的代码:

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

1
a=1,c=

1w
a=1,c=w

可以看到输入1之后按下enter,还没来得及输入c的值就直接打印了。

但如果我连续输入1w,再按下enter,则正常打印。

这是因为输入1之后按下enter,实际输入的是“1\r\n”;

\r被win系统文本编辑器自动删除,此时输入缓冲区中留下“1\n”;

然后scanf从缓冲区中取数据,取走1,留下“\n”;

第二个scanf由于是“%c”格式,不会略过空白符,就将“\n”读入了。

所以输出实际上为a=1,c=\n(转义字符不可见)。

再看一个例子:

#include <stdio.h>
int main()
{
int a = 1, b = 2;
scanf("a=%d", &a);
scanf("b=%d", &b);
printf("a=%d, b=%d\n", a, b);
return 0;
}
//输入方式1:输入99,按下enter,还没来得及输入b,就直接打印了
a=99
a=99, b=2

//输入方式2:连续输入ab,就变正常了
a=99b=56
a=99, b=56

//输入方式3,跟方式2相比,多加了一个空格,就又不行了
a=99 b=36
a=99, b=2

解决办法:
以这段代码为例:

#include<stdio.h>

int main ()
{

    int a;
    char c;
    scanf("%d",&a);
    scanf("%c",&c);
    printf("%d\n",a);
    printf("%c\n",c);
    return 0;
}

以上代码中如果第一个scanf输入完后,用回车来结束对变量a的输入,按下回车后程序会直接结束赋值,开始输出结果,此时的变量c中存放的就是回车符。

解决办法有4种

(1)第一个scanf函数后加 fflush(stdin);可以用来清除回车键

(2)第一个scanf后加 getchar();来吸收回车键

(3)第一个scanf改为scanf(“%d\n”,&a);

(4)第二个scanf改为scanf(“\n%c”,&c);

4、scanf与gets一起用产生的问题

该问题与上个问题类似。
看这个例子:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int n;
	char str[80];
	printf("enter a number: ");
	scanf("%d", &n);
	printf("enter a string: ");
	gets(str);
	printf("you typed %d and \"%s\"\n", n, str);
	return 0;
}

enter a number: 123
enter a string: you typed 123 and ""

我本来想输入

123
a string

但程序并没有给我输入字符串 的机会,在接收到 123 后,程序就直接打印,结束运行了。

这是因为scanf() 函数会读取 123,但是不会读取后面的换行符\n,该换行符将保留在输入缓冲区里,接下来的 gets() 函数遇到缓冲里的换行符时,会立即得到满足(就像按下回车一样),第二行的“a string”根本不会被读取。

因此scanf() 和 gets() 最好别在一起使用。

5、scanf要输入两次才能结束

#include <stdio.h> 
main()
{
	int a;
	printf("input the data\n");
	scanf("%d ", &a);//这里多了空格
	printf("a=%d", a);
	return 0;
}

input the data
2
2
a=2

可以看到要两次输入2才能打印2。Why?

用空白符结尾时,scanf会跳过空白符去读下一个字符,所以你必须再输入一个数。这里的空白符包括空格,\t,\n,\r和换页符\f。

五、注意事项

1、高版本VS要用scanf_s代替 scanf

在高版本的 Visual Studio 编译器中,scanf 被认为是不安全的,被弃用,应当使用scanf_s代替 scanf。

例如:

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

编译会报错
在这里插入图片描述
解决方法1:
在程序顶端加入

#define _CRT_SECURE_NO_WARNINGS

解决方法2:
改用scanf_s

scanf_s("%d", &n);

六、答疑

1、scanf与scanf_s有什么区别?

一句话,scanf()不会检查输入边界,可能造成数据溢出。scanf_s()会进行边界检查。

scanf_s()用于读取字符串时,必须提供一个数字以表明最多读取多少位字符,以防止溢出。
 
 格式为scanf_s(“%s”,k,n),k为数组,n则为输入的最大长度,注意,这里的长度是计算上了\0的,即当n为4时,输入的字符串长度最大为3。

scanf_s("%s" , buffer,128);//此处必须加上128,表示最多可以读取128位,不然运行会报错。

scanf_s在安全方面确实胜过scanf,但凡事皆有代价,安全性提高的代价就是适用性降低。

scanf是C语言标准规定的函数,而scanf_s则是VS编译器替换的函数,所以在适用性方面,scanf_s就不能和scanf比了。

看这个例子

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main()
{
	int n;
	char address[5];
	scanf("%s", address);
	printf("%s\n", address);
	return 0;
}
kjih
kjih

由于数组宽度为5,所以最多存入4个字符,存5个时就会溢出。

用scanf_s就可以避免不知不觉就溢出的问题。

参考资料

1、https://baike.baidu.com/item/scanf/10773316?fr=aladdin
2、https://www.runoob.com/cprogramming/c-function-scanf.html
3、https://www.cnblogs.com/yyxayz/p/4032709.html
4、scanf与scanf_s区别https://blog.csdn.net/m0_61721774/article/details/121873511
5、https://blog.popkx.com/为什么有经验的c语言程序员都不推荐使用-scanf-函数/
6、使用scanf从键盘输入数据 https://www.cnblogs.com/zjuhaohaoxuexi/p/16255567.html

待研究

1、https://www.cnblogs.com/zjuhaohaoxuexi/p/16260922.html
2、https://blog.csdn.net/weixin_43965480/article/details/113262354
3、【C语言】scanf语句吃掉回车或者空格问题详解https://blog.csdn.net/as480133937/article/details/102625223
4、https://zhuanlan.zhihu.com/p/436420553
5、https://blog.csdn.net/wowocpp/article/details/119888876
6、强烈推荐https://www.cnblogs.com/zjuhaohaoxuexi/p/16260565.html及其系列文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值