VS中你容易忽略的scanf细节

我相信大部分人在C语言中使用scanf都只想到输入的用法,但是它的细节往往被我们忽视。如果你是一个C语言初识者或者从未使用过VS编译器的人,那么我相信该文章会让你收获颇丰。

scanf

1.VS编译器

首先如果你用的不是VS这款编译器,那么你会觉得下面的这段代码一点问题都没有,就是最简单的输入和输出功能,但是你用的是VS,那么这段代码其实是不对的。

这段代码犯了VS编译器里面的C4996错误

C4996 是 Microsoft Visual Studio 编译器发出的警告(或错误),表示代码中使用了被标记为不安全的函数(如 scanfstrcpysprintf 等),可能导致缓冲区溢出等安全风险。‌ 编译器建议改用更安全的替代函数(如scanf_sstrcpy_s)或通过特定方式禁用警告。‌

我个人的建议是不要用scanf_s,因为scanf是C语言标准库当中的函数,scanf_s这个函数只有VS认识,其他编译器不认识,假设你的代码中使用了scanf_s函数,该代码在其他编译器上就不认识,这样你的代码的可移植性就不好!所以要想让上面的代码起作用只剩下通过特定方式禁用警告这种办法,具体操作如下:

就是在第一行添加#define _CRT_SECURE_NO_WARNINGS 1这段代码即可避免C4996错误,那么问题又来了:如果我不想每次创建.c文件都输入这段代码有什么偷懒方法吗?

答案是肯定的:有(在VS上,所有的.c文件和.cpp文件创建其实都拷贝自:newc++.cpp文件,所以只要在newc++.cpp的文件中加上这句话:#define _CRT_SECURE_NO_WARNINGS 1,以后创建的.c文件中都会有这句话)

操作如下:如果你在安装VS中没有修改路径那么你的newc++.cpp文件位置与我相同,若你不是C盘杀手,也就是修改了安装路径的话,其实也差不多(如果你用的是VS2022的话至少从Microsoft Visual Studio 向后是一样的)。

随后找到newc++.cpp文件,用记事本编辑,将#define _CRT_SECURE_NO_WARNINGS 1这段代码放入,保存即可。这样每次创建.c文件都会有这段话。

注意:再强调一遍以上所有遇到的情况和操作方法仅是VS所需要的,其它编译器不需要!!!

介绍完VS编译器,我们回到我们这篇文章的重点:容易忽略的scanf细节

2.容易忽略的scanf细节

2.1键盘输入多个变量
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	int i = 0;
	int j = 0;
	float x = 0.0f;
	float y = 0.0f;

	scanf("%d %d %f %f", &i, &j, &x, &y);

	printf("i=%d\n", i);
	printf("j=%d\n", j);
	printf("x=%f\n", x);
	printf("y=%fn", y);

	return 0;
}

上面示例中,格式字符串%d%d%f%f,表示用户输入的前两个是整数,后两个是浮点数,比如1 -20 3.4 -4.0e3。这四个值依次放入i、j、x、y四个变量。
scanf() 处理数值占位符时,会自动过滤空白字符,包括空格、制表符、换行符等。
所以,用户输入的数据之间,有一个或多个空格不影响scanf()解读数据。另外,用户使用回车键将输入分成几行,也不影响解读。

如果你觉得这有什么难的,那么我出一个题目,大家试试在不看答案的情况下能否做对

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	int x;
	float y;
	//输入"  -13.45e12# 0"
	scanf("%d%f", &x, &y);
	printf("%d\n", x);
	printf("%f\n", y);

	return 0;
}

分析:上面示例中,scanf()读取用户输入时,%d 占位符会忽略起首的空格,从'-'处开始获取数据,读取到-13 停下来,因为后面的'.'不属于整数的有效字符。也就是说,占位符%d会读到-13。第二次调用scanf() 时,就会从上一次停止解读的地方,继续往下读取。这一次读取的首字符是·,由于对应的占位符是%f,会读取到.45e12,这是采用科学计数法的浮点数格式。后面的#不属于浮点数的有效字符,所以会停在这里。所以答案是:


 

2.2scanf的占位符

scanf() 常用的占位符如下:
%c : 字符。                                                                                                                                    %d :整数。
%f: float 类型浮点数。
%lf: double 类型浮点数。
%Lf: long double类型浮点数。                                                                                                      %s : 字符串。
%[]:在方括号中指定一组匹配的字符(比如%[0-9]),遇到不在集合之中的字符,匹配将会停止。


上面所有占位符之中,除了%c 以外,都会自动忽略起首的空白字符。%c 不忽略空白字符,总是返回当前第一个字符,无论该字符是否为空格。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	int x = 10;
	char y = '0';
	scanf("%d", &x);
	printf("%d\n", x);
	
	return 0;
}

运行结果:

无论前面有多少空格都没有影响

但是如果是字符型

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	int x = 10;
	char y = '0';
	scanf("%c", &y);
	printf("%chahahaha\n", y);
	
	return 0;
}

运行结果:

%c 不忽略空白字符,总是返回当前第一个字符,无论该字符是否为空格


如果要强制跳过字符前的空白字符,可以写成scanf(" %c", &ch) ,即%c 前加上一个空格,表示跳过零个或多个空白字符。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	int x = 10;
	char y = '0';
	scanf(" %c", &y);
	printf("%chahahaha\n", y);
	
	return 0;
}

运行结果:


下面要特别说一下占位符%s,它其实不能简单地等同于字符串。它的规则是,从当前第一个非空白字符开始读起,直到遇到空白字符(即空格、换行符、制表符等)为止。
因为%s 不会包含空白字符,所以无法用来读取多个单词,除非多个%s 一起使用。这也意味着, scanf() 不适合读取可能包含空格的字符串,比如书名或歌曲名。另外,scanf()遇到%s占位符,会在字符串变量末尾存储一个空字符\0。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	char arr[10] = { 0 };
	scanf("%s", arr);
	printf("%s\n", arr);

	return 0;
}

运行结果:


scanf()将字符串读入字符数组时,不会检测字符串是否超过了数组长度。所以,储存字符串时,很可能会超过数组的边界,导致预想不到的结果。为了防止这种情况,使用%s占位符时,应该指定读入字符串的最长长度,即写成%[m]s,其中的[m]是一个整数,表示读取字符串的最大长度,后面的字符将被丢弃。

2.3scanf的赋值忽略符

有时,用户的输入可能不符合预定的格式

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	int year = 0;
	int month = 0;
	int day = 0;
	scanf("%d-%d-%d",&year, & month, &day);
	printf("%d %d %d\n", year, month, day);

	return 0;
}

上面示例中,如果用户输入2020-01-01,就会正确解读出年、月、日。问题是用户可能输入其他格式,比如2020/01/01,这种情况下,scanf()解析数据就会失败。

解决办法:

scanf()提供了一个赋值忽略符(assignment suppression character)只要把*加在任何占位符的百分号后面,该占位符就不会返回值,解析后将被丢弃。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	int year = 0;
	int month = 0;
	int day = 0;
	scanf("%d%*c%d%*c%d", &year, &month, &day);
	printf("%d %d %d\n", year, month, day);


	return 0;
}

上面示例中,%*c就是在占位符的替势号启面,加入了赋值忽略符*,表示这个占位符没有对应的变量,解读后不必返回。
 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值