title: 你真的会用C语言的fscanf_s吗
date: 2021-06-21 00:28:05


1.前言

最近学校让做一个C语言工程实践,我写的是一个基于链表的图书管理系统,本来这个系统的难度不是很大,但是在写的过程中总是会出一些莫名奇妙的Bug,接下来就让我给大家分享我在这个项目中踩过哪些坑,希望能够帮到大家少走一些弯路。

2.一切还得从编辑器说起

2.1.DEV++

这是我入门C语言用的编辑器,这个编辑器还是很适合新手用的,相对简单的操作步骤,以及自带编译环境,不用像CLion,VSC那样自行配置。我还是比较推荐新手用它的,除了它对新手更加友好以外,其相对笨拙的编辑提示(没有代码补全功能)有利于巩固基础知识。

不过DEV相对来说太笨拙,没有代码智能检测,也就是说有可能你会因为一些很低级的语法错误找bug找半天,而其他编辑器在你出错时会直接给你爆红

#include<stdio.h>
int mian(void)
{
    printf("hello world");
    return 0;
}

曾经初学我就因为上面的错误而浪费了我好多时间,不知道聪明的你有没有发现哪里错了呢。

2.2.Clion

后来因为用惯了IDEA,就考虑迁移到Clion,不得不说,Clion强大的功能值得大家为它付费,不过官网有社区免费版,对于绝大多数人社区版足够用了。在校学生还可以申请学校的教育邮箱免费使用JB全家桶

但是Clion也有它的缺点,对于没有接触过JB的小伙伴,编辑器本身上手需要一点的时间。并且还要自行下载配置编译环境,对于新手来说直接用Clion可能会被直接劝退。(今天又是一个劝退小技巧.jpg)

2.3.VS2019

终于说到了VS,怎么说呢,网上很多人都在说VS功能强大,但是这几天我用下来的感觉并不好。

第一:VS2019的占用实在是太大了,一键配置好C++的功能插件有9个G之大,对于很多硬盘空间不够的小伙伴来说这点还是难以接受

第二:它的代码补全真的是让人有点恼火,用惯了Clion之后习惯在代码补全提示出来按回车就可以直接把代码补全,但是VS2019还要按一下上下箭头选中你要补全什么代码,直接按回车不行,就很烦。

第三:也是我主要想说的,它会强制我们使用安全函数,比如gets要用gets_s,fopen要用fopen_s,fscanf要用fscanf_s等,然后我们学校教的都是较为老的C标准,这些以前我都还没接触过,倒是花了我很多时间去了解这些函数,尤其是我接下来说的fscanf_s;

3.fscanf_s与fscanf

在写图书管理系统时遇到结构体的读写文件,但是又不想用二进制的代码块读写,因为这样写进文件的数据在文件中打开会乱码。就想着用fscanf来读写,然后VS让我用fscanf_s,以前都还没接触过这个函数,在网上搜了很多也没看到对他讲解较为详细的教程(可能是我太菜了。。。。。)

3.1.区别

其实二者的区别就是fscanf_s比fscanf多了个域宽检查,会多一个参数

fscanf

FILE* fp;
fopen_s(&fp, "stu.txt", "r");
char str[20];
if(fp)
{	
    fscanf(fp,"%s",str);
    fclose(fp);
}

fscanf_s

FILE* fp;
fopen_s(&fp, "stu.txt", "r");
char str[20];
if(fp)
{	
    fscanf_s(fp,"%s",str,20);
    fclose(fp);
}

看出区别了吗,我猜聪明的你肯定看出两者有什么区别,fsanf_s会比fscanf多一个说明要读出数据内存大小的数字。现在读单个没有问题,那我们要读多个数据怎么办呢。

3.2.fscanf_s读取多个数据

FILE* fp;
fopen_s(&fp, "stu.txt", "r");
char s1[20];
char s2[20];
char s3[20];
if(fp)
{	
    fscanf_s(fp,"%s %s %s",s1,s2,s3,20,20,20);
    fclose(fp);
}

我猜很多小伙伴都认为因该这样读多个数据,我们现在先不说对错,实践出真知,我们先放在VS里面跑一跑再说。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main()
{
	FILE* fp;
	FILE* fe;

	fopen_s(&fe, "stu.txt", "w");
	if (fe)
	{
		fprintf(fe, "%s %s %s", "hello", "world", "C");
		fclose(fe);
	}
	char s1[20];
	char s2[20];
	char s3[20];
	fopen_s(&fp, "stu.txt", "r");
	if (fp)
	{
		fscanf_s(fp, "%s %s %s", s1, s2, s3, 20, 20, 20);
		fclose(fp);
	}
	puts(s1);
	puts(s2);
	puts(s3);
	return 0;

结果是什么呢?

可见,VS在编译时并没有给你报错,但是在运行时会发生异常。如果对异常信息不熟的小伙伴这时候肯定要摸不着头脑了(没错,说的就是我自己,哈哈哈哈),是不是感觉很坑。

那么,正确的写法是怎样的呢?我试着在网上找了很多技术博客,可是并没有找到有对此说明的教程。向身边的小伙伴求助,他让我试试在后面直接写所有的总内存大小:

fscanf_s(fp, "%s %s %s", s1, s2, s3, 60);

可是这样的写法还是是错误的,报错和上面的如出一辙。最后,我终于把正确用法试出来了

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main()
{
	FILE* fp;
	FILE* fe;

	fopen_s(&fe, "stu.txt", "w");
	if (fe)
	{
		fprintf(fe, "%s %s %s", "hello", "world", "C");
		fclose(fe);
	}
	char s1[20];
	char s2[20];
	char s3[20];
	fopen_s(&fp, "stu.txt", "r");
	if (fp)
	{
		fscanf_s(fp, "%s %s %s", s1,20, s2,20, s3,20);
		fclose(fp);
	}
	puts(s1);
	puts(s2);
	puts(s3);
	return 0;

运行结果:

可见,这样改了之后就对了,并没报错了。

3.3.踩坑+1

是不是觉得上面那样就完了呢?那我们遇到同时读多个数字和多个字符串的时候怎么办呢?是不是好多小伙伴都是这样想的?

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main()
{
	FILE* fp;
	FILE* fe;

	fopen_s(&fe, "stu.txt", "w");
	if (fe)
	{
		fprintf(fe, "%s %s %s %d %d", "hello", "world", "C",5,6);
		fclose(fe);
	}
	char s1[20];
	char s2[20];
	char s3[20];
	int a = 0, b = 0;
	fopen_s(&fp, "stu.txt", "r");
	if (fp)
	{
		fscanf_s(fp, "%s %s %s %d %d", s1, 20, s2, 20, s3, 20,&a,4,&b,4);
		fclose(fp);
	}
	puts(s1);
	puts(s2);
	puts(s3);
	printf("%d %d", a, b);
	return 0;

运行结果:

啊?怎么报错了呢?小朋友你是否有很多问号?那么这是怎么回事呢?其实我也不是很清楚,哈哈哈哈。(菜狗+1)

但是我知道怎么改正。

其实方法很简单,只需要这样改就没错了:

fscanf_s(fp, "%s %s %s %d %d", s1, 20, s2, 20, s3, 20,&a,&b);

现在我们运行试试:

成功读出数据并输出!

3.4.小结

我对上面最后出错的理解就是读字符串的时候我们需要给他指定长度,防止内存溢出。但是对于整型我们就不用给他指定了。具体原因我也不是很清楚,如果有大佬知道可以在评论区解释一下。

走到这一步真是不容易,我在这些坑上不知道浪费了多少时间。害,对于我这种菜鸡来说一遇到异常信息就抓瞎,只知道肯定是内存访问冲突了,就是不知道具体哪里错了,只能一点一点试,百度。百度上还搜不出。

好的,今天的分享到这里就结束了,希望能够帮助到大家。如果有哪里写得不对的地方请大佬们指正。敬礼.jpg

更多推荐

你真的会用C语言的fscanf_s吗