C语言进阶(十四) - 文件管理

目录

  • 前言
  • 1. 文件概念
    • 1.1 程序文件
    • 1.2 数据文件
  • 1.3 文件名
  • 2. 文件相关操作
    • 2.1 文件的打开和关闭
      • 2.1.1 文件指针
      • 2.1.2 文件的打开和关闭
        • fopen()函数 - 打开文件
          • 文件打开模式一览表
        • fclose()函数 - 关闭文件
          • 流是什么?
        • 例子
    • 2.2 文件的顺序读写函数
      • 2.2.1 文件相关输入输出函数介绍
        • 一览表
        • fgetc()函数
        • fputc()函数
        • fgets()函数
        • fputs()函数
        • fscanf()函数
        • fprintf()函数
        • fread()函数
        • fwrite()函数
        • 标准输入与标准输出
      • 2.2.2 输入函数对比
        • sscanf()函数
        • sprintf()函数
    • 2.3 文件的随机读写
      • fseek()函数
      • ftell()函数
      • rewind()函数
  • 3. 文本文件与二进制文件是啥?
  • 4. 文件读取结束的判定及文件读取结束的原因
    • feof()函数 - 判断导致文件读取结束的原因
    • ferror()函数 - 判断导致文件结束的原因
  • 5. 文件缓冲区
    • 什么是文件缓冲区
    • 为什么有文件缓冲区
    • fflush()函数
  • 结语

前言

我们知道写的C程序是运行在内存上的,当程序运行结束后,程序相关的数据就消失了,这些数据并没有保存起来。如何保存程序运行产生的数据呢?我们可以把数据保存到磁盘文件中。通过C语言的文件相关的知识,我们将会有办法把程序运行产生的数据写入我们指定的磁盘文件中。
磁盘(外存)中的文件存放的信息是持久化的,不会像运行在内存中的程序那样,在程序运行结束或突然计算机断电等情况导致数据消失。


1. 文件概念

我们习惯的数据处理方式是在键盘上(终端)输入数据最终到运行的程序,从运行的程序输出数据最终到屏幕或控制台上(终端)。文件是在电脑磁盘或硬盘上的,也相当于输入终端与输出终端,并在文件与运行的程序之间进行数据的输入与输出操作。

文件是以计算机硬盘为载体存储在计算机上的信息集合。比如磁盘上的文件就是文件。
程序设计中按文件功能分类一般可以把文件分为程序文件和数据文件两种。

1.1 程序文件

文件的内容是程序。

源程序文件:后缀为.c
目标文件:后缀为.obj(windows环境)、.o(linux环境)
可执行程序文件:后缀为.exe(windows环境)

1.2 数据文件

文件的内容是程序运行时读写(输入输出)的数据,包括程序运行需要对文件读取数据的文件或输出数据的文件。

1.3 文件名

一个文件会有一个唯一的文件标识,这个文件标识常常被称为文件名,以便于用户和系统识别和引用。
这个唯一的文件标识包含三个部分:文件路径+文件名主干+文件后缀
如一个桌面上的文本文件:C:\Users\未禾\Desktop\text.txt

注意:只有文件名主干和文件后缀并不是完整的文件名,同一目录下自然不会有相同的文件名主干与文件后缀的组合,但在不同的路径目录下可能存在相同的文件名主干与文件后缀的组合。不加上文件路径就无法区分这种情况。


2. 文件相关操作

2.1 文件的打开和关闭

2.1.1 文件指针

缓冲文件系统中,关键的概念是"文件类型指针",简称"文件指针"。

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(包括但不限于文件名、文件状态、文件当前位置等)。文件的这些信息统统保存在一个结构体变量(其实就是文件信息区)中,这个结构体类型由系统帮我们声明,并重命名为FILE
不同的C编译器声明的FILE包含的具体成员是大同小异的。

//visual stdio2013编译器其提供的文件类型参考
struct _iobuf {
    char *_ptr;
    int _cnt;
    char *_base;
    int _flag;
    int _file;
    int _charbuf;
    int _bufsiz;
    char *_tmpfname;
};
typedef struct _iobuf FILE;

每当打开一个文件时,系统会根据文件的情况在自动创建一个FILE类型的变量 (一个文件信息区),并填充其中的相关信息,我们不用关心具体填了什么,关心如何通过FILE类型指针访问文件即可。

一般是通过一个FILE类型的指针来维护FILE结构体的变量,使用很是方便。
创建一个FILE*的指针变量pfFILE* pf = NULL;

pf是一个指向FILE类型结构体的指针变量,我们使pf指向某一个文件对应的文件信息区(就是一个结构体变量),那么通过对应文件信息区中的信息就能够访问该文件。于是通过文件信息区这个桥梁,指针pf关联到其指向的文件信息区所对应的文件。

C语言进阶(十四) - 文件管理_第1张图片


2.1.2 文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件

ANSIC标准规定使用fopen()函数来打开文件,使用fclose函数关闭文件。

fopen()函数 - 打开文件

FILE* fopen(const char* filename, const char* mode);
头文件

功能:以某一种模式mode打开一个名为filename的文件。
filename指向了一个字符串,mode也指向了一个字符串。
函数返回类型是文件指针类型,打开成功就返回一个文件指针,这个文件指针指向了打开的那个文件;打开失败则返回NULL(空指针)。

mode是文件打开的模式。

文件打开模式一览表
** 文件打开方式** **含义 ** 所打开的文件不存在
“r”(只读) 为了输入数据,以文本文件的形式打开一个文件,该文件必须存在 出错
“w”(只写) 为了输出文件,新建一个空内容文件。如果已经存在同名的文件,就把此文件视为新的空文件 新建一个空文件
“a”(追加) 向文本文件尾输入数据,随机读取函数(fseek/fsetpos/rewind)将失效。 新建一个空文件
“rb"或"r+b”(只读) 为了输入数据,以二进制文件(binary)的形式打开一个文件,该文件必须存在 出错
“wb"或"w+b”(只写) 为了输入数据,以二进制的形式打开一个文件 新建一个空文件
“ab"或"a+b”(追加) 以二进制的形式打开一个文件,向二进制文件尾输入数据 出错
“r+”(读写) 为了读和写,以文本文件形式打开一个文件进行,该文件必须存在 出错
“w+”(读写) 为了读和写,以文本文件的形式新建一个空内容文件。如果已经存在同名的文件,就把此文件视为新的空文件 新建一个空文件
“a+”(读写) 以二进制文件的形式打开一个文件,在文件未尾进行读和写 新建一个空文件
“rb+”(读写) 为了读和写,以二进制的形式打开一个文件,该文件必须存在 出错
“wb+”(读写) 为了读和写,以二进制文件的形式新建一个空内容文件。如果已经存在同名的文件,就把此文件视为新的空文件 新建一个空文件
“ab+”(读写) 以二进制文件的形式打开一个文件,在文件末尾进行读和写 建立一个新文件
fclose()函数 - 关闭文件

int fclose(FILE* stream);
头文件

功能:关闭与流关联的文件并解除流与文件的关联。就是强制刷新文件缓存区并关闭文件指针stream指向的文件
stream是指向FILE对象的指针。
如果关闭成功,则返回0,;否则返回EOF。
文件关闭之后文件指针一般也要手动置为NULL
所有与流相关的内部缓冲区都将与流解除关联并刷新:写入任何未写入输出缓冲区的内容,丢弃任何未读输入缓冲区的内容。
即使调用失败,作为参数传递的流也将不再与文件及其缓冲区关联。

流是什么?

首先我们要知道外部的输入输出设备是多种多样的,包括但不限于:键盘、鼠标、屏幕、磁盘、U盘、硬盘、光盘、软盘等等。这些设备输入输出的格式很可能各自都有差异,我们在写程序时并不会关注这些差异,在这些输入输出设备之前,数据先要要经过中的数据系统经过一系列操作就可以实现把数据采用正确的格式输入到内存或输出到设备。是对输入输出设备的一种抽象。

在计算机编程中,流是一个类的对象,很多文件的输入输出操作都以类的成员函数的方式来提供。
计算机中的流其实是一种信息的转换。它是一种有序流,因此相对于某一对象,通常我们把对象接收外界的信息输入(Input)称为输入流,相应地从对象向外输出(Output)信息为输出流,合称为输入/输出流(I/O Streams)。对象间进行信息或者数据的交换时总是先将对象或数据转换为某种形式的流,再通过流的传输,到达目的对象后再将流转换为对象数据。所以,可以把流看作是一种数据的载体,通过它可以实现数据交换和传输。

例子

以只写的模式打开文件test.txt,写入0到9共10个整数。

#include 

int main() {
    //以只写的模式打开文件test.txt
	FILE* pf = fopen("test.txt", "w");
	//判断文件是否打开成功
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	//写(输出)操作
	int i = 0;
	for (i = 0; i < 10; i++) {
		fprintf(pf, "%d ", i);
	}
	//关闭文件指针pf指向的文件
	fclose(pf);
	pf = NULL;
	return 0;
}

结果:
C语言进阶(十四) - 文件管理_第2张图片
以只读的模式打开文件test.txt,从文件中读取信息之后再输出数据到屏幕上

#include 

int main() {
	//以只读的模式打开文件test.txt
	FILE* pf = fopen("test.txt", "r");
	//判断文件是否打开成功
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	//读(输入)操作
	int arr[10] = { 0 };
	int i = 0;
	for (i = 0; i < 10; i++) {
		fscanf(pf, "%d", &arr[i]);
	}
	//输出数据到屏幕上
	for (i = 0; i < 10; i++) {
		printf("%d ", arr[i]);
	}
	//关闭文件指针pf指向的文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:
C语言进阶(十四) - 文件管理_第3张图片


2.2 文件的顺序读写函数

文件顺序读写指的是在读写函数调用之后,文件位置指示器默认指向文件起始位置,随着读或写操作而依次向后移动,直到函数调用结束或到达文件末尾为止,所以我们并不能决定从文件的什么位置开始读或写操作。
以后我们会介绍如何通过文件指针找到文件的位置指示器并修改它的指向。

2.2.1 文件相关输入输出函数介绍

一览表
函数名 功能 适用情况
fgetc 字符输入函数 所有输入流(包括文件)
fputc 字符输出函数 所有输入流(包括文件)
fgets 文本行输入函数 所有输入流(包括文件)
fputs 文本行输出函数 所有输入流(包括文件)
fscanf 格式化输入函数 所有输入流(包括文件)
fprintf 格式化输出函数 所有输入流(包括文件)
fread 二进制输入函数 文件
fwrite 二进制输出函数 文件
fgetc()函数

int fgetc(FILE* stream);
头文件

功能:从文本文件中读取一个文件指针stream指向的文件缓冲区内部的文件位置指示器当前指向的字符,之后文件位置指示器指向下一个字符。
stream是指向FILE**对象的指针,**该对象标识输入流。
返回类型是int
如果读取成功,则返回读取成功的字符的ANSIC值;
如果读取失败或到文件末尾,则返回EOF。可能的原因是读取到文件末尾或读取错误。

例子

#include 

int main() {
	//打开文件 - 文本文件 - 只读
	FILE* pf = fopen("test.txt", "r");
	//判断文件是否打开成功
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	//读(输入)文件操作
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF) {
		printf("%c", ch);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

提前设置的文件中数据
C语言进阶(十四) - 文件管理_第4张图片
运行结果:
C语言进阶(十四) - 文件管理_第5张图片


fputc()函数

int fputc(int character, FILE* stream);
头文件

功能:把一个字符character写入stream指向的文件中,之后位置指示器向前推进1
character是待写入字符的ANSIC值,stream是指向FILE对象的指针,该对象标识输出流。
返回类型是int
如果写入成功,则返回写入字符的ANSIC值;否则返回EOF

例子

#include 

int main() {
	//打开文件 - 文本文件 - 只读
	FILE* pf = fopen("test.txt", "w");
	//判断文件是否打开成功
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	//读(输入)文件操作
	int ch = 'a';
	for (; ch <= 'z'; ch++) {
		fputc(ch, pf);
	}

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

写入文件之前
C语言进阶(十四) - 文件管理_第6张图片
结果
C语言进阶(十四) - 文件管理_第7张图片


fgets()函数

char* fgets(char* str, int num, FILE* stream);
头文件

功能从文件中读取至多num-1个字符并将其作为字符串(自动在字符末尾补加**'\0'**)储存到str中。读取结束条件包括已经成功读取num-1个字符、遇到换行符'\n'、遇到文件结束符EOF
str指向字符数组的指针,用来存放读取的字符串。
num是读取并存入str的最大字符数,包括'\0'在内。
stream是指向标识输入流的FILE对象的指针。
返回类型是char*
如果读取成功,则返回str否则返回NULL读取失败情况包含:读取到文件末尾、读取错误。

例子:

#include 

int main() {
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	//判断文件是否打开成功
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	//读操作
	char str[20] = { 0 };
	//读取最多四个字符组成一个字符串
	fgets(str, 8, pf);
	if (str != NULL) {
		printf("%s\n", str);

	}

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

提前设置好的文件数据
C语言进阶(十四) - 文件管理_第8张图片
运行结果:
C语言进阶(十四) - 文件管理_第9张图片


fputs()函数

int fputs(const char* str, FILE* stream);
头文件

功能str指向的字符串写入文件中。字符串末尾的'\0'不会写入文件中。
puts()函数不同的一点是:不会在文件末尾
自动
追加换行符'\n'
str指向了要写入文件的内存中的一个字符串
stream是指向FILE对象的指针,该对象标识输出流。
返回类型是int
如果写入成功,则返回一个非负值;否则返回EOF

例子:

#include 

int main() {
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	//判断文件是否打开成功
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	//写操作
	char str1[20] = "Hello world!";
	char str2[20] = "Hello world!\n";
	char str3[20] = "Hello world!\n";
	fputs(str1, pf);
	fputs(str2, pf);
	fputs(str3, pf);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

C语言进阶(十四) - 文件管理_第10张图片
结果:
C语言进阶(十四) - 文件管理_第11张图片


fscanf()函数

int fscanf(FILE* stream, const char* format, ...);
头文件

功能:从文件中读取格式化数据,即根据形参格式将数据储存到附加实参所指向的位置。
stream是指向FILE对象的指针,该对象标识输出流。
scanf()函数相比仅仅多了一个参数FILE* stream,输出终端可以选择是文件还是屏幕等终端了。
返回类型是int
返回成功读取参数列表中参数的个数。

例子:

#include 

struct book {
	char book_name[50];
	int day;
	float price;
};

int main() {
	struct book a = { 0 };
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	//判断文件是否打开成功
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	//读文件
	fscanf(pf, "%s %d %f", a.book_name, &a.day, &a.price);
	printf("%s %d %f", a.book_name, a.day, a.price);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

提前设置好的文件数据:
C语言进阶(十四) - 文件管理_第12张图片
运行结果:
C语言进阶(十四) - 文件管理_第13张图片


fprintf()函数

int fprintf(FILE* stream, const char* format, ...);
头文件

功能:将格式化的数据写入文件,将按格式指向的字符串写入文件,同时附加参数替换各自对应的格式转换符。
**printf()**函数的不同在于指针stream指向了FILE,可以选择不同的输出流,以此来输出到不同的外部设备上。
返回类型是int
写入成功,则返回成功写入的字符总数;写入错误则返回负数。

例子:

#include 

struct book {
	char book_name[50];
	int day;
	float price;
};

int main() {
	struct book a = { "Journeytothewest", 10, 60.5};
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	//判断文件是否打开成功
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	//写文件
	fprintf(pf, "%s %d %f", a.book_name, a.day, a.price);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

C语言进阶(十四) - 文件管理_第14张图片

运行结果:
C语言进阶(十四) - 文件管理_第15张图片


fread()函数

size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
头文件

功能:从文件中读取一个**count**个元素的数组,每个元素大小为**size**,把这个数组储存到**ptr**指向的内存块。
**ptr****:指向一个大小至少为(sizecount)字节的内存块的指针,转换为void
**size**
:**要读取的每个元素的大小(以字节为单位)。
count:元素的数量,每个元素的大小为size字节。
stream:指向指定输入流的FILE对象的指针。
返回值类型是**size_t**
返回成功读取的元素总数,小于**count**时需要判断是读取到文件末尾还是读取错误。

例子:

#include 

struct book {
	char book_name[50];
	int day;
	float price;
};

int main() {
	struct book a = { 0 };
	//打开文件 - 二进制 -读
	FILE* pf = fopen("test.txt", "rb");
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	//读文件
	fread(&a, sizeof(struct book), 1, pf);
	printf("%s %d %f", a.book_name, a.day, a.price);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

C语言进阶(十四) - 文件管理_第16张图片
运行结果:
C语言进阶(十四) - 文件管理_第17张图片


fwrite()函数

size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
头文件

功能:从**ptr**所指向的内存块写入一个**count**个元素的数组,每个元素的大小为**size**字节
**ptr****:**指向要写入的元素数组的指针,转换为const void*类型。
size:要写入的每个元素大小,单位是字节。
count:元素的数量。
stream:指向指定输出流的FILE对象的指针。
返回类型为size_t
正常情况返回成功写入的元素的个数。如果size或count有一个为零,函数返回0。

例子:

#include 

struct book {
	char book_name[50];
	int day;
	float price;
};

int main() {
	struct book a = { "Journeytothewest", 10, 60.5 };
	//打开文件 - 二进制 -写
	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	//写文件
	fwrite(&a, sizeof(struct book), 1, pf);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

结果:
C语言进阶(十四) - 文件管理_第18张图片


标准输入与标准输出

我们直到想要从内存中向文件中写数据或者从文件向内存中读数据都要先打开文件,该文件返回一个FILE*的指针,称为流,读写操作完成之后需要关闭文件,关闭流。
但我们平常在写C程序时,在使用sacnf()``printf()时并没有打开流的操作,使用结束后也没有关闭流的操作。这是因为我们所写的C程序,只要运行起来就会默认打开3个流,程序结束时再关闭这3个流。

FILE* stdin 标准输入流,默认是键盘
FILE* stdout标准输出流,默认是屏幕
FILE* stderr标准错误流,默认是屏幕

这三个流是不需要我们手动打开和关闭的。
例子:在fprintf()函数中使用标准输出流stdout

#include 

int main() {
	char str[20] = { 0 };
	int a = 0;
	fscanf(stdin, "%s %d", str, &a);

	fprintf(stdout, "%s %d", str, a);
	return 0;
}

运行结果:
C语言进阶(十四) - 文件管理_第19张图片

2.2.2 输入函数对比

我们很早之前学习了printf()函数scanf()函数,上一小节由介绍了fprintf()函数、fscanf()函数。它们各自十分相似,下面在介绍两个也很相似的函数sscanf()sprintf()
函数原型对比:
输出函数
int printf(const char* format, ...);
int fprintf(FILE* stream, const char* format, ...);

printf()相比,多了个参数stream,指向了一个FILE对象。把数据输出到指定的流。

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

printf()相比,多了个参数str,指向了一个字符数组。把格式化数据转换为一个字符串。

输入函数
int scanf(const char* format, ...);
int fscanf(FILE* stream, const char* format, ...);

printf()相比,多了参数stream,指向了一个FILE对象。把指定流里的数据读取到内存。

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

scanf()相比,多了参数str,指向了一个字符串。把一个字符串转换为格式化数据。


sscanf()函数

int sscanf(const char* str, const char* format, ...);
头文件

功能:把指针str**指向的字符串的内容转换为格式化的数据。**从字符串读取数据,并根据参数格式将其存储到附加参数指定的位置。
返回类型为int
如果读取成功,则返回参数列表中成功填充的项的个数。
如果在成功解释任何数据之前出现输入失败,则返回EOF。比如说传给str是空指针NULL时。

例子:

#include 

int main() {
	char str[100] = "helloworld! 1024 1024.2";
	char s[20] = { 0 };
	int a = 0;
	float b = 0.0;
	//读数据,参数别忘了取地址
	sscanf(str, "%s %d %f", s, &a, &b);
	printf("%s %d %f", s, a, b);

	return 0;
}

运行结果:
C语言进阶(十四) - 文件管理_第20张图片


sprintf()函数

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

功能:把格式化的数据转换为一个字符串并放到str指向的内存空间。将自动补加结尾的'\0'.
返回类型是int
如果成功,则返回写入的字符总数,不包括自动追加的末尾的'\0'
如果失败,返回一个负数。

例子:

#include 

int main() {
	char str[100] = { 0 };
	char s[] = "hello world!";
	int a = 1024;
	float b = 1024.2f;
	//
	sprintf(str, "%s %d %f", s, a, b);

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

运行结果:
C语言进阶(十四) - 文件管理_第21张图片


2.3 文件的随机读写

你想从不是文件起始位置处开始读写文件内容吗?让我们来认识认识通过文件指针定位文件指示器的几个函数吧,它们能满足我们的需要。

fseek()函数

int feek(FILE* stream, long int offset, int origin);
头文件

功能:通过文件指针sream将文件位置指示器指向一个新的位置。offset是相对于origin的偏移量,可以是负整数。
offset是从相对位置偏移的字节数。
origin是位置指示器偏移时参考的位置。有三种
SEEK_SET :文件的默认的起始位置
SEEK_CUR:文件(位置指示器)的当前位置
SEEK_END:文件的末尾
返回类型为int
函数调用成功,则返回0;否则返回非0

例子:

#include 
#include 

int main() {
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	//
	int a = 10000;
	fprintf(pf, "%d", a);
	//文件指针指向文件位置指示器设置为起始位置
	fseek(pf, 0, SEEK_SET);

	//关闭文件
	fclose(pf);
	pf = NULL;
	//睡眠10秒
	Sleep(10000);

	pf = fopen("test.txt", "r");
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	//从文件读一个字符
	int c = fgetc(pf);
	printf("%c\n", c);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

结果:
C语言进阶(十四) - 文件管理_第22张图片
C语言进阶(十四) - 文件管理_第23张图片


ftell()函数

long int ftell(FILE* stream);
头文件

功能:返回stream**指向的文件的位置指示器的当前值。即文件指针相对于起始位置的偏移量 **

返回类型为long int
如果成功,则返回文件位置指示器当前值;否则返回-1L


rewind()函数

void rewind(FIEL* stream);
头文件

功能:将文件位置指示器初始化为默认的文件开头位置。
没有返回值。


3. 文本文件与二进制文件是啥?

首先二者都是文件,只是数据文件(外存)对于数据的储存方式有着不同。
在内存中数据本质都是以二进制的形式储存的,这样原汁原味不加转换直接输出到文件中的,就是二进制文件。
而将内存的数据都以ANSIC码值的形式输出到文件,文件存的是数据的ANSIC值,那么就是文本文件。
对于字符型数据,文本文件和二进制文件都是以ANSIC码值的形式存放的;因为字符本质就是以ANSIC码值形式存放在内存中的。
对于其他数值型数据,文本文件是以ANSIC值的形式存放的,二进制文件是以二进制的形式存放的。
内存中相同的数据采用二进制形式储存到二进制文件的大小往往比采用ANSIC码值形式储存到文本文件的大小要小上不少。不过也有例外情况就是了(比如存放整数1)。
图解:
C语言进阶(十四) - 文件管理_第24张图片


4. 文件读取结束的判定及文件读取结束的原因

在我们读进行读取文件的操作时,总会遇到读取文件结束的时候。其中可以分为情况:

  1. 读取文件并正常结束
  2. 应该继续读取文件,但是遇到文件末尾而正常结束
  3. 文件读取错误而异常结束

由上述知道,文件读取结束的原因有几种,情况1可以通过函数返回值清楚的判断,但情况2与3就不能简单判断原因,而是要通过具体的函数专门判断。
文本文件读取是否结束可以判断返回值:
fgetc()函数当返回值是EOF时文件读取结束。
fgets()函数当返回值是NULL时文件读取结束。
二进制文件是否读取结束可以判断返回值:
fread()函数的返回值小于实际要读的个数时文件读取结束。

feof()函数 - 判断导致文件读取结束的原因

int feof(FILE* stream);
头文件

判断文件读取结束的原因是否是遇到到文件末尾而结束。
如果是,则返回非0;否则返回0

ferror()函数 - 判断导致文件结束的原因

int ferror(FILE* stream);
头文件

判断文件读取结束的原因是否是读取错误。
如果是,则返回非0;否则返回0

文本文件例子

#include 

int main() {
    //打开文件
    FILE* pf = fopen("test.txt", "r");
    if (!pf) {
        perror("fopen");
        return 1;
    }
    //读取字符
    int c = 0;
    while ((c = fgetc(pf)) != EOF) {
        printf("%c ", c);
    }
    //文件读取结束,判断原因
    if (feof(pf)) {
        printf("遇到文件末尾而结束!\n");
    }
    else if (ferror(pf)) {
        printf("读取错误而结束!\n");
    }

    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

运行结果:
C语言进阶(十四) - 文件管理_第25张图片
二进制文件例子

#include 

int main() {
	//写
	FILE* pf = fopen("test.txt", "wb");
	if (!pf) {
		perror("fopen");
		return 1;
	}
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,0 };
	fwrite(arr1, sizeof(arr1[0]), 10, pf);
	fclose(pf);
	pf = NULL;
	//读
	pf = fopen("test.txt", "rb");
	if (!pf) {
		perror("fopen");
		return 1;
	}
	int arr2[10] = { 0 };
	int ret = fread(arr2, sizeof(arr2[0]), 10, pf);

	if (ret == 10) {
		printf("读取成功\n");
		int i = 0;
		for (i = 0; i < 10; i++) {
			printf("%d ", arr2[i]);
		}	
	}
	else {
		if (feof(pf)) {
			printf("读取到文件末尾而结束\n");
		}
		else if (ferror(pf)) {
			printf("读取错误而结束\n");
		}
	}
	return 0;
}

运行结果:
C语言进阶(十四) - 文件管理_第26张图片


5. 文件缓冲区

什么是文件缓冲区

ANSIC标准是采用文件缓冲系统来处理数据文件的。那你可能会问:缓冲文件系统是什么?
缓冲文件系统是指系统自动的在内存为程序中每一个正在使用的文件开辟一块文件缓冲区
**从内存向磁盘输出数据会先送到内存的文件缓冲区,装满缓冲区后才一起送到磁盘上。**如果从磁盘向计算机读入数据,则从磁盘文件读取数据输入到内存缓冲区,充满缓冲区或强制刷新如(fflush()函数)缓冲区时,会从缓冲区逐个将数据送到程序数据区(如程序变量等)。而缓冲区的大小是根据C编译系统决定的。
简化图示:
C语言进阶(十四) - 文件管理_第27张图片

为什么有文件缓冲区

从内存向文件读写数据都要借助于操作系统,每次都会让操作系统产生开销。文件缓冲区的存在可以把一定大小输入输出的数据储存起来,直到把文件缓冲区放满或者遇到把缓冲区强制刷新的操作时,操作系统再把数据一次性存入文件后读入内存。当然游戏相关的需要即使反馈的不是这样。

fflush()函数

int fflush(FILE* stream);
头文件

功能:强制刷新指向的缓冲区。

例子:

#include 
#include 

int main() {
	
	FILE* pf = fopen("test.txt", "wb");
	if (!pf) {
		perror("fopen");
		return 1;
	}
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,0 };

	fwrite(arr1, sizeof(arr1[0]), 10, pf);
	printf("先把数据送到缓冲区,休眠10s\n");
	Sleep(10000);
	printf("强制刷新缓冲区,休眠10s,因为关闭文件也会强制刷新缓冲区\n");
	fflush(pf);
	Sleep(10000);

	fclose(pf);
	pf = NULL;

	return 0;
}

结语

本节主要介绍了文件的概念与文件相关的操作。文件操作如今并不经常使用,毕竟与文件进行读写数据的效率是比较低的,数据库是主流的方式。


END

你可能感兴趣的:(C语言学习之路,c语言)