【C语言进阶(NEW)】六、文件操作(二)

137 阅读8分钟

六、文本文件和二进制文件

        根据数据的组织形式,数据文件被称为文本文件或者二进制文件,数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件

一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节(VS2013测试)

七、文件缓冲区

        ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”

        从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)

        缓冲区的大小根据 C编译系统决定的

文件缓冲区存在的原因:

        当我们使用 fwrite 等函数向文件中写入或者读取数据的时候,其实这些函数首先会调用系统调用,而系统调用是由操作系统提供的接口,所以写文件的操作其实最终是由操作系统来完成的;而如果我们不设置文件缓冲区,频繁的打断操作系统,让它来为我们写入、读取数据,则势必会降低操作系统的工作效率;所以设立文件缓冲区是为了提高操作系统的工作效率;

        既然只有将文件缓冲区填满后才会进行写入、读取数据,那么当我们数据非常小,不足以填满文件缓冲区的时候是不是就会发生错误呢?其实不是的,我们每次文件操作完毕后都会使用 fclose 函数来关闭文件,而fclose 函数的内部会自动执行 fflush (刷新缓冲区) 操作,所以不必担心数据过小而操作失败,这也侧面反映了如果我们在使用文件之后不对文件进行关闭的话可能会导致文件的读写问题

文件缓冲区的例子

#include <stdio.h>
#include <windows.h>
//VS2019 WIN11环境测试
int main()
{
	FILE* pf = fopen("test.txt", "w");
	fputs("abcdef", pf);//先将代码放在输出缓冲区
	printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
	Sleep(10000);

	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
	//注:fflush 在高版本的VS上不能使用了
	printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
	Sleep(10000);

	fclose(pf);
	//注:fclose在关闭文件的时候,也会刷新缓冲区
	pf = NULL;
	return 0;
}

 运行结果

这里可以得出一个结论:

        因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件的问题

八、C语言中 scanf 和 printf、fscanf 和 fprintf、sscanf 和 sprintf

        C语言中 scanf 和 printf、fscanf 和 fprintf、sscanf 和 sprintf 这三对函数,它们的函数名都只是各自相差一个字母而已,但其功能及其用法却并不相同

8.1 scanf 和 printf 函数

scanf函数和printf函数是应用于标准输入流和标准输出流的格式化输入输出语句

(1)scanf 函数

scanf函数的功能是从标准输入流读入格式化的数据

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

        从标准输入流(stdin)读取数据,并根据参数格式将它们存储到附加参数所指向的位置。附加参数应指向已分配的对象(即附加参数应是一个地址,或者说指针),这些对象的类型由格式字符串中相应的格式说明符指定

例如:

scanf("%d", &input);
//scanf("%d", input);//error

        scanf函数以%d(整型)的格式从标准输入流读入的数据存储到&input所指向的内存空间。所以说,我们之后若是打印变量input,就会打印出已读取的这个值

(2)printf 函数

printf函数的功能是将格式化的数据打印到标准输出流上去

int printf ( const char * format, ... );

        printf函数将format指向的C字符串写入标准输出流(stdout)。如果format字符串中包含格式说明符(以%开头的子序列),则format后面的附加参数将被格式化并插入结果字符串中,以替换它们各自的说明符

平时已经习惯了这样使用 printf 函数

printf("hello world!\n");//第一种写法

        第一种写法中,直接将一个字符串传入printf函数,实际上也是将该字符串的首地址传入了函数,本质上与第二种写法相同,所以那样写也是没有问题的 

	char arr[] = "hello world!\n";
	printf(arr);//第二种写法

8.2 fscanf 和 fprintf函数

        fscanf 函数和 fprintf 函数是应用于所有输入流和所有输出流的格式化输入输出语句

        fscanf函数和fprintf函数是应用于所有的标准输出流和标准输入流的函数,那么这对函数的功能也就包含了printf函数和scanf函数的功能

(1)fscanf函数

int fscanf( FILE *stream, const char *format [, argument ]... );

        fscanf函数实现scanf函数的功能,因为fscanf函数的功能是,从stream中读取数据,并根据参数格式将其存储到附加参数所指向的位置,所以我们只需将参数stream的位置传入stdin,使其在标准输入流中读取数据即可

所以,下面两句代码的作用是等效的

scanf("%d", &input);
fscanf(stdin, "%d", &input);

(2)fprintf函数

int fprintf( FILE *stream, const char *format [, argument ]...);

        因为fprintf函数的功能是,将格式化数据写入stream中,所以我们只需将参数stream的位置传入stdout,使其将数据写入标准输出流即可。

所以,下面两句代码的作用是等效的

printf("%d\n", input);
fprintf(stdout, "%d\n", input);

注:当C程序运行起来的时候,会自动打开下面这三个流

        当程序运行结束后,这三个流又会自动关闭。所以,当我们需要对标准输入流或是标准输出流进行输入输出操作的时候,并不需要通过某些操作来打开这两个流,也不需要我们来关闭这两个流

8.3 sscanf和sprintf函数

        sscanf函数可以从字符串中读取格式化数据,sprintf函数可以将格式化数据写入字符串

(1)sscanf函数

int sscanf ( const char * s, const char * format, ...);

        sscanf函数从s读取数据并根据参数格式将其存储到附加参数给定的位置,就像使用scanf一样,但sscanf函数是从s读取数据而不是标准输入(stdin)

#include <stdio.h>
int main()
{
	char arr[] = "hello 2023";
	int year = 0;
	char s[10] = { 0 };
	sscanf(arr, "%s %d", s, &year);
	printf("%d\n", year);//2023
	printf("%s\n", s);//hello
	return 0;
}

运行结果 

(2)sprintf函数

int sprintf ( char * str, const char * format, ... );

        sprintf函数与printf函数功能相似,区别在于:printf函数是将数据格式化后直接打印在屏幕上,而sprintf函数是将数据格式化后存储在str所指向的字符串中

#include <stdio.h>
int main()
{
	char arr[20] = { 0 };
	int year = 2023;
	char s[] = "hello";
	sprintf(arr, "%d %s", year, s);
	printf("%s\n", arr);//2023 hello
	return 0;
}

运行结果

总结

scanf / fscanf / sscanf 三者的区别与联系

scanf:scanf 函数是格式化输入函数,只适用于标准输入流(键盘、屏幕);

fscanf:scanf 函数也是格式化输入函数,不过它适用于所有输入流;

sscanf:sscanf 是专门针对字符串操作函数,用于将字符串数据转换为格式化的数据;

prinft / fprintf / sprintf 三者的区别与联系

prinft :prinft 函数是格式化输出函数,只适用于标准输入流(键盘、屏幕);

fprintf: fprintf 函数也是格式化输出函数,不过它适用于所有输入流;

sprintf :sprintf 是专门针对字符串操作函数,用于格式化的数据转化为字符串;

----------------我是分割线---------------

文章到这里就结束了,C语言文件操作结束