关于文件你必须了解的一些基本概念
什么是文件?
文件是计算机文件,属于文件的一种,与普通文件的载体不同,计算机文件是以计算机硬盘为载体存储在计算机上的信息集合。
在程序设计中,我们一般关注的文件有两类,即程序文件和数据文件。
程序文件: 包括源程序(以.c为后缀)和可执行程序(以.exe为后缀)。
数据文件: 文件的内容不一定是程序,而是程序运行时读写的数据。比如程序运行时需要从中读取数据的文件,或者输出内容的文件。
注:这里本篇内容讨论的是数据文件。
文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件标识主要包含三部分:文件路径+文件名主干+文件后缀。
例如:
但是为了方便起见,文件标识常被称为文件名。
文件类型
根据数据的组织形式,数据文件被称为二进制文件或文本文件。
二进制文件: 数据在内存中以二进制的形式进行存储,如果不加转换直接输出到外存,就是二进制文件。
文本文件: 如果要求在外存上以ASCII码的形式存储,则需要在存储前进行转换。以ASCII码的形式进行存储的文件就是文本文件。
那么一个数据在外存中是如何存储的呢?
字符一律以ASCII码值进行存储;数值型数据既可以以ASCII码值进行存储,也可以以二进制的形式进行存储。
例如,有整数10000,如果我们以ASCII值的形式输出到磁盘,那么它将在磁盘中占用5个字节(一个字符一个字节);而如果以二进制的形式进行输出,那么它只在磁盘中占用4个字节(一个整型大小即可存储):
文件缓冲区
文件缓冲区是用以暂时存放读写期间的文件数据而在内存区预留的一定空间。使用文件缓冲区可减少读取硬盘的次数。
文件缓冲系统: 是指系统自动地在内存中为程序中每一个正在使用的文件开辟开辟一块“文件缓冲区”。从内存向磁盘输出的数据会先送到内存中的缓冲区,待缓冲区装满后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区,待缓冲区装满后,再从缓冲区逐个地将数据送到程序数据区。缓冲区的大小是根据C编译系统决定。
为什么存在缓冲区?
相信有很多人心里还不理解为什么存在缓冲区。举个比较形象的例子,当你的老师在忙于备考时,你有一点小问题就去请教老师,有一点小问题又去请教老师,这种情况下老师的备考效率会大大降低,那么你为什么不将你的小问题累计起来,当累计到一定量时再一次性去请教老师,这样老师的备考效率也会相对提高。
例子中的“老师”就好比操作系统,“小问题”就好比需要操作系统传输的信息,而“你积累问题到一定量再去请教老师”就好比缓冲区的工作机制。
在C语言中,"输入操作"通常指的是从文件读取数据到程序中,而"输出操作"则指将数据从程序写入文件。
总而言之,缓冲区的存在大大提高了操作系统的效率。
文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字、文件的状态及文件的当前位置等)。这些信息是保存在一个结构体变量中的,该结构体变量是由系统声明的,并将该结构体类型重命名为FILE。
例如,在VS2013编译器环境提供的stdio.h头文件中有以下的文件类型声明:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
1234567891011
不同的C编译器的FILE类型包含的内容不完全相同,但是都大同小异。
每当我们打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息。
我们可以创建一个FILE*的指针变量:
FILE* pf;//文件指针变量
1
定义pf是一个指向FILE类型的指针变量,可以使pf指向某个文件的文件信息区,通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量就能找到与它关联的文件。
比如:
文件操作函数(一)—— 文件的打开与关闭函数
文件打开函数 - fopen
函数介绍
FILE *fopen( const char *filename, const char *mode );
1
该函数的功能就是打开一个文件,函数的第一个参数是你要打开的文件的文件名,第二个参数是打开这个文件的形式。
我们知道打开一个文件时,系统会为该文件创建一个文件信息区,该函数调用完毕后,如果打开该文件成功,那么返回指向该文件信息区的指针(FILE*类型);如果打开文件失败,那么返回一个空指针(NULL)。
文件的打开形式
一、规则
文件打开方式 | 含义 |
---|---|
“r” | read:打开文件进行输入操作。该文件必须存在。 |
“w” | write:为输出操作创建一个空文件。如果已存在同名文件,则丢弃其内容,并将该文件视为新的空文件。 |
“a” | append:打开文件以在文件末尾输出。输出操作总是在文件末尾写入数据,并对其进行扩展。忽略重新定位操作(fseek、fsetpos、rewind)。如果文件不存在,则创建该文件。 |
“r+” | read/update:打开一个文件进行更新(输入和输出)。文件必须存在。 |
“w+” | write/update:创建一个空文件并打开它以进行更新(输入和输出)。如果同名文件已经存在,则将丢弃其内容,并且该文件将被视为新的空文件。 |
“a+” | append/uptate:打开一个文件进行更新(包括输入和输出),所有输出操作都在文件末尾写入数据。重新定位操作(fseek、fsetpos、rewind)会影响下一个输入操作,但输出操作会将位置移回文件末尾。如果文件不存在,则创建该文件。 |
使用上面的模式说明符,文件将作为文本文件打开。为了将文件作为二进制文件打开,模式字符串中必须包含“b”字符。这个附加的“b”字符可以附加在字符串的末尾(从而形成以下复合模式:“rb”、“wb”、“ab”、“r+b”、“w+b”、“a+b”),也可以插入在“+”符号之前(“rb+”、“wb+”、“ab+”)。
如果序列后面有其他字符,则行为取决于库实现:一些实现可能会忽略其他字符,例如,接受额外的“t”(有时用于显式表示文本文件)。
在某些库实现中,使用更新模式打开或创建文本文件可能会视为二进制文件。
举个几个例子:
1.若我们要以文本形式打开一个名叫data.txt的文件,将要对其进行输入操作,那么打开文件时应该这样写:
FILE* pf = fopen("data.txt", "r");
1
注:data.txt文件必须存在,不然打开文件失败,fopen函数会返回一个空指针。
2.若我们要以二进制打开一个名叫data.bin的文件,将要对其进行输出操作,那么打开文件时应该这样写:
FILE* pf = fopen("data.bin", "wb");
1
注:data.bin文件若存在,将销毁文件原有内容,再对其进行输出;data.bin文件若不存在,系统将主动创建一个名叫data.bin的文件。
c 语言 以 r 方式 操作和以r+ 方式操作文件的区别
在C语言中,r
和r+
是两种不同的文件打开模式,它们之间的区别在于对文件的读写权限:
-
“r”(只读):
- 以只读方式打开一个文件。
- 文件必须已经存在。
- 可以从文件中读取数据。
- 不允许向文件写入数据。
- 如果试图向该文件写入数据,程序会出错。
-
“r+”(读/写):
- 以读/写方式打开一个文件。
- 文件必须已经存在。
- 允许从文件中读取数据。
- 允许向文件写入数据。
- 写操作通常会覆盖文件中原有的内容,但具体行为可能取决于你的操作系统和编译器实现。
简而言之,r
模式只能用于读取文件,而r+
模式既可用于读取也可用于修改文件。如果你只想读取文件而不打算更改其内容,那么使用r
模式更为安全,因为它可以防止意外地修改或删除文件内容。
以下是一个简单的C语言代码示例,它展示了r
和r+
模式在文件操作中的区别:
#include <stdio.h>
int main() {
char buffer[100];
FILE *file_r, *file_r_plus;
// 打开一个只读文件("r"模式)
file_r = fopen("example.txt", "r");
if (file_r == NULL) {
printf("Failed to open the file in read-only mode.\n");
return 1;
}
// 尝试从文件中读取数据
fgets(buffer, sizeof(buffer), file_r);
printf("Read from 'r' mode: %s\n", buffer);
// 尝试向文件写入数据(不应成功)
if (fputs("Write attempt in 'r' mode.", file_r) == EOF) {
printf("Error writing to the file in 'r' mode.\n");
}
fclose(file_r);
// 打开同一个文件用于读/写("r+"模式)
file_r_plus = fopen("example.txt", "r+");
if (file_r_plus == NULL) {
printf("Failed to open the file in read-write mode.\n");
return 1;
}
// 尝试从文件中读取数据
rewind(file_r_plus); // 必须先回溯到文件开始处
fgets(buffer, sizeof(buffer), file_r_plus);
printf("Read from 'r+' mode: %s\n", buffer);
// 尝试向文件写入数据(应该成功)
fseek(file_r_plus, 0, SEEK_SET); // 移动文件指针到开始位置
if (fputs("Write attempt in 'r+' mode.", file_r_plus) == EOF) {
printf("Error writing to the file in 'r+' mode.\n");
} else {
printf("Successfully wrote to the file in 'r+' mode.\n");
}
fclose(file_r_plus);
return 0;
}
在这个例子中,我们首先以r
模式打开一个文件,并尝试读取和写入数据。由于我们在r
模式下打开了文件,所以写入操作会失败。
然后,我们以r+
模式重新打开同一文件,并再次尝试读取和写入数据。这次,因为我们在r+
模式下打开了文件,所以写入操作会成功。注意,在进行写入操作之前,我们需要使用rewind()
或fseek()
函数将文件指针移回到文件的开头,否则写入的数据可能会覆盖部分已有的文件内容。
请注意,这个示例假设你的系统上有一个名为example.txt
的文件已经存在。如果你试图用不存在的文件名调用fopen()
,那么在r
模式下,该函数会返回NULL
,而在w+
模式下,该函数会创建一个新的空文件。
w+ 和 r+的区别
w+
和r+
都是C语言中的文件打开模式,它们都允许读写操作,但有一些重要的区别:
-
“w+”(读/写):
- 以读/写方式打开一个文件。
- 如果文件不存在,则创建一个新的空文件。
- 如果文件已存在,则将文件内容清零,即删除原有的所有内容。
- 文件指针指向文件的开头。
-
“r+”(读/写):
- 以读/写方式打开一个文件。
- 文件必须已经存在。
- 不会清除文件原有的内容。
- 文件指针指向文件的开头。
总结起来,主要的区别在于对现有文件的处理:
- 使用
w+
模式时,如果文件已存在,那么它会被截断为空。这意味着你可以在不保留原有数据的情况下开始写入新的数据。 - 使用
r+
模式时,你可以从文件的当前位置读取和修改数据,但不能覆盖未被修改的部分。如果你想要从头开始写入新数据,你需要先移动文件指针到文件的开始处。
在实际使用中,选择哪个模式取决于你的具体需求:如果你需要保留文件的原始内容,并且只修改部分数据,那么使用r+
模式更为合适;如果你需要完全替换文件的内容,那么使用w+
模式可能更方便。
以下是一个简单的C语言代码示例,它展示了w+
和r+
模式在文件操作中的区别:
#include <stdio.h>
int main() {
char buffer[100];
FILE *file_w_plus, *file_r_plus;
// 创建一个包含一些文本的新文件("w+"模式)
file_w_plus = fopen("example.txt", "w+");
if (file_w_plus == NULL) {
printf("Failed to open the file in read-write mode.\n");
return 1;
}
fprintf(file_w_plus, "This is some initial text.\n");
fclose(file_w_plus);
// 打开同一个文件用于读/写("r+"模式)
file_r_plus = fopen("example.txt", "r+");
if (file_r_plus == NULL) {
printf("Failed to open the file in read-write mode.\n");
return 1;
}
// 尝试从文件中读取数据
fgets(buffer, sizeof(buffer), file_r_plus);
printf("Read from 'r+' mode: %s\n", buffer);
// 尝试向文件写入数据(应该成功)
fseek(file_r_plus, 0, SEEK_SET); // 移动文件指针到开始位置
if (fputs("Write attempt in 'r+' mode.", file_r_plus) == EOF) {
printf("Error writing to the file in 'r+' mode.\n");
} else {
printf("Successfully wrote to the file in 'r+' mode.\n");
}
fclose(file_r_plus);
// 再次打开同一文件以读/写("w+"模式)
file_w_plus = fopen("example.txt", "w+");
if (file_w_plus == NULL) {
printf("Failed to open the file in read-write mode.\n");
return 1;
}
// 尝试从文件中读取数据(内容已被清零)
rewind(file_w_plus); // 必须先回溯到文件开始处
fgets(buffer, sizeof(buffer), file_w_plus);
printf("Read from 'w+' mode after reopening: %s\n", buffer);
fclose(file_w_plus);
return 0;
}
在这个例子中,我们首先以w+
模式创建一个新文件,并向其中写入一些初始文本。然后,我们以r+
模式重新打开该文件,并尝试读取和写入数据。由于我们在r+
模式下打开了文件,所以写入操作会成功。
接下来,我们再次以w+
模式打开同一文件。这次,文件的内容被清零了,因为我们使用的是w+
模式。当我们尝试读取文件时,只能看到空字符串。这是因为w+
模式会清除原有的文件内容,如果你需要保留原有数据,请使用r+
模式。
a和a+ 操作文件的区别
在C语言中,a
和a+
是两种不同的文件打开模式,它们之间的区别在于对文件的读写权限:
-
“a”(追加):
- 以追加方式打开一个文件。
- 如果文件不存在,则创建一个新的空文件。
- 如果文件已存在,则文件指针被定位到文件末尾。
- 可以向文件追加数据。
- 不允许从文件中读取数据。
-
“a+”(追加/读取):
- 以追加/读取方式打开一个文件。
- 如果文件不存在,则创建一个新的空文件。
- 如果文件已存在,则文件指针被定位到文件末尾。
- 允许向文件追加数据。
- 允许从文件中读取数据。
总结起来,主要的区别在于对现有文件的处理以及是否可以进行读取操作:
- 使用
a
模式时,只能向文件追加数据,不能从文件中读取数据。这意味着你不能查看或修改文件的原有内容。 - 使用
a+
模式时,你可以向文件追加数据,并且可以从文件的当前位置开始读取数据。但是,如果你想移动文件指针并修改文件的其他部分,你需要使用fseek()
函数。
在实际使用中,选择哪个模式取决于你的具体需求:如果你只需要添加新数据到文件的末尾,并不需要读取文件的内容,那么使用a
模式更为合适;如果你需要追加数据的同时还能够读取文件的内容,那么使用a+
模式可能更方便。
以下是一个简单的C语言代码示例,它展示了a
和a+
模式在文件操作中的区别:
#include <stdio.h>
int main() {
char buffer[100];
FILE *file_a, *file_a_plus;
// 创建一个包含一些文本的新文件("w"模式)
FILE *file_w = fopen("example.txt", "w");
if (file_w == NULL) {
printf("Failed to open the file in write mode.\n");
return 1;
}
fprintf(file_w, "This is some initial text.\n");
fclose(file_w);
// 打开同一个文件用于追加数据("a"模式)
file_a = fopen("example.txt", "a");
if (file_a == NULL) {
printf("Failed to open the file in append mode.\n");
return 1;
}
// 尝试从文件中读取数据(不应成功)
fgets(buffer, sizeof(buffer), file_a);
printf("Read from 'a' mode: %s\n", buffer);
// 尝试向文件追加数据(应该成功)
if (fputs("Append attempt in 'a' mode.", file_a) == EOF) {
printf("Error appending to the file in 'a' mode.\n");
} else {
printf("Successfully appended to the file in 'a' mode.\n");
}
fclose(file_a);
// 再次打开同一文件以追加/读取("a+"模式)
file_a_plus = fopen("example.txt", "a+");
if (file_a_plus == NULL) {
printf("Failed to open the file in append-read mode.\n");
return 1;
}
// 尝试从文件中读取数据(应该成功)
rewind(file_a_plus); // 必须先回溯到文件开始处
fgets(buffer, sizeof(buffer), file_a_plus);
printf("Read from 'a+' mode after reopening: %s\n", buffer);
// 尝试向文件追加数据(应该成功)
fseek(file_a_plus, 0, SEEK_END); // 移动文件指针到结束位置
if (fputs("Append attempt in 'a+' mode.", file_a_plus) == EOF) {
printf("Error appending to the file in 'a+' mode.\n");
} else {
printf("Successfully appended to the file in 'a+' mode.\n");
}
fclose(file_a_plus);
return 0;
}
在这个例子中,我们首先创建了一个新文件,并向其中写入一些初始文本。然后,我们以a
模式重新打开该文件,并尝试读取和追加数据。由于我们在a
模式下打开了文件,所以读取操作会失败。
接下来,我们再次以a+
模式打开同一文件。这次,我们可以从文件中读取数据,并且可以向文件追加数据。请注意,在进行读取操作之前,我们需要使用rewind()
函数将文件指针移回到文件的开头,而在进行追加操作之前,我们需要使用fseek()
函数将文件指针移至文件末尾。
w 和 w+ 操作文件的区别
在C语言中,w
和w+
是两种不同的文件打开模式,它们之间的区别在于对文件的读写权限:
-
“w”(写入):
- 以写入方式打开一个文件。
- 如果文件不存在,则创建一个新的空文件。
- 如果文件已存在,则删除原有的内容,并将文件指针定位到文件开始处。
- 只允许向文件写入数据。
- 不允许从文件中读取数据。
-
“w+”(读/写):
- 以读/写方式打开一个文件。
- 如果文件不存在,则创建一个新的空文件。
- 如果文件已存在,则删除原有的内容,并将文件指针定位到文件开始处。
- 允许向文件写入数据。
- 允许从文件中读取数据。
总结起来,主要的区别在于是否可以进行读取操作:
- 使用
w
模式时,只能向文件写入数据,不能从文件中读取数据。这意味着你不能查看或修改文件的原有内容。 - 使用
w+
模式时,你可以向文件写入数据,并且可以从文件的当前位置开始读取数据。但是,如果你想移动文件指针并修改文件的其他部分,你需要使用fseek()
函数。
在实际使用中,选择哪个模式取决于你的具体需求:如果你只需要写入新数据到文件,并不需要读取文件的内容,那么使用w
模式更为合适;如果你需要写入数据的同时还能够读取文件的内容,那么使用w+
模式可能更方便。
以下是一个简单的C语言代码示例,它展示了w
和w+
模式在文件操作中的区别:
#include <stdio.h>
int main() {
char buffer[100];
FILE *file_w, *file_w_plus;
// 创建一个包含一些文本的新文件("w"模式)
FILE *file_w = fopen("example.txt", "w");
if (file_w == NULL) {
printf("Failed to open the file in write mode.\n");
return 1;
}
fprintf(file_w, "This is some initial text.\n");
fclose(file_w);
// 打开同一个文件用于写入数据("w"模式)
file_w = fopen("example.txt", "w");
if (file_w == NULL) {
printf("Failed to open the file in write mode.\n");
return 1;
}
// 尝试从文件中读取数据(不应成功)
fgets(buffer, sizeof(buffer), file_w);
printf("Read from 'w' mode: %s\n", buffer);
// 尝试向文件写入数据(应该成功)
if (fputs("Write attempt in 'w' mode.", file_w) == EOF) {
printf("Error writing to the file in 'w' mode.\n");
} else {
printf("Successfully wrote to the file in 'w' mode.\n");
}
fclose(file_w);
// 再次打开同一文件以读/写("w+"模式)
file_w_plus = fopen("example.txt", "w+");
if (file_w_plus == NULL) {
printf("Failed to open the file in read-write mode.\n");
return 1;
}
// 尝试从文件中读取数据(应该成功)
rewind(file_w_plus); // 必须先回溯到文件开始处
fgets(buffer, sizeof(buffer), file_w_plus);
printf("Read from 'w+' mode after reopening: %s\n", buffer);
// 尝试向文件写入数据(应该成功)
fseek(file_w_plus, 0, SEEK_SET); // 移动文件指针到开始位置
if (fputs("Write attempt in 'w+' mode.", file_w_plus) == EOF) {
printf("Error writing to the file in 'w+' mode.\n");
} else {
printf("Successfully wrote to the file in 'w+' mode.\n");
}
fclose(file_w_plus);
return 0;
}
在这个例子中,我们首先创建了一个新文件,并向其中写入一些初始文本。然后,我们以w
模式重新打开该文件,并尝试读取和写入数据。由于我们在w
模式下打开了文件,所以读取操作会失败。
接下来,我们再次以w+
模式打开同一文件。这次,我们可以从文件中读取数据,并且可以向文件写入数据。请注意,在进行读取操作之前,我们需要使用rewind()
函数将文件指针移回到文件的开头,而在进行写入操作之前,我们需要使用fseek()
函数将文件指针移至文件开始处。
r r+ w w+ a a+ 对文件指针的操作
在C语言中,r
、r+
、w
、w+
、a
和a+
这六种文件打开模式都可以通过相应的函数移动文件指针。以下是这些模式下文件指针的初始位置以及如何移动它们:
-
“r”(只读):
- 文件指针指向文件的开头。
- 可以使用
fseek()
或rewind()
函数来移动文件指针。
-
“r+”(读/写):
- 文件指针指向文件的开头。
- 可以使用
fseek()
或rewind()
函数来移动文件指针。
-
“w”(写入):
- 如果文件不存在,则创建一个新的空文件,并将文件指针定位到文件开始处。
- 如果文件已存在,则删除原有的内容并将文件指针定位到文件开始处。
- 可以使用
fseek()
函数来移动文件指针。
-
“w+”(读/写):
- 如果文件不存在,则创建一个新的空文件,并将文件指针定位到文件开始处。
- 如果文件已存在,则删除原有的内容并将文件指针定位到文件开始处。
- 可以使用
fseek()
或rewind()
函数来移动文件指针。
-
“a”(追加):
- 如果文件不存在,则创建一个新的空文件,并将文件指针定位到文件开始处。
- 如果文件已存在,则将文件指针定位到文件末尾。
- 可以使用
fseek()
函数来移动文件指针,但要注意:当进行写操作时,文件指针会自动跳回到文件末尾。
-
“a+”(追加/读取):
- 如果文件不存在,则创建一个新的空文件,并将文件指针定位到文件开始处。
- 如果文件已存在,则将文件指针定位到文件末尾。
- 可以使用
fseek()
或rewind()
函数来移动文件指针,但要注意:当进行写操作时,文件指针会自动跳回到文件末尾。
请注意,在使用fseek()
函数时,需要包含stdio.h
头文件,并确保编译器支持该函数。
二、检测fopen返回值的有效性
前面说到,如果文件打开成功,fopen函数会返回指向文件信息区的指针,否则fopen函数会返回一个空指针。所以当使用接收fopen函数的返回值的指针前,我们必须检测其有效性,否则可能非法访问内存。
检测有效性:
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//失败返回
}
//使用...
1234567
相对路径与绝对路径
填写fopen函数的第一个参数的时候,如果我们要打开的文件与我们正在运行的源代码在同级目录下(以打开data.txt文件为例),那么我们应该这样写:
FILE* pf = fopen("data.txt", "r");//同级
1
但是如果我们想要打开的文件与当前运行的源代码不在同一级目录下呢?
这时我们的写法有两种,相对路径和绝对路径。
一、相对路径
当待打开的文件位于正在运行的源代码的上一级时:
FILE* pf = fopen("../data.txt", "r");//上一级
1
当待打开的文件位于正在运行的源代码的上上级时:
FILE* pf = fopen("../../data.txt", "r");//上上级
1
当待打开的文件位于正在运行的源代码的下一级时:
FILE* pf = fopen("Debug/data.txt", "r");//下一级
1
注:这里data.txt文件在Debug文件内,Debug文件与正在运行的源代码在同级目录下。
总结:
- 要打开上级的文件在原来的基础上加上". ./“,再上一级再加一个”. ./",以此类推。
- 要打开下级的文件,就需从源代码这一级开始,写出目标文件的路径。
二、绝对路径
有博友可能觉得相对路径的方法比较麻烦(可能还要去数目标文件与源文件相差的级数),绝对路径就没那么麻烦了,绝对路径就直接写出目标文件的完整路径即可,例如:
FILE* pf = fopen("D:\\code\\File_function\\File_function\\data.txt", "r");
1
注:文件的路径原本为"D:\code\File_function\File_function\data.txt",但是为了防止字符串中的’\‘及其后面的字符被整体视为为转义字符,所以需要在每个’\‘后面再加一个’\’。
文件关闭函数 - fclose
与动态开辟内存空间时一样,当我们打开文件时,会在内存中开辟一块空间,如果我们打开该文件后不关闭,那么这个空间会一直存在,一直占用那块内存空间,所以当我们对一个文件的操作结束时,一定要记住将该文件关闭。这就需要用到fclose函数来关闭文件。
int fclose( FILE *stream );
1
我们如果要关闭一个文件,那么直接将该文件的文件指针传入fclose函数即可,fclose函数如果关闭文件成功会返回0。与free函数一样,当我们调用完fclose函数将文件关闭后,我们也要将指向文件信息区的指针置空,避免该指针变成野指针。
fclose(pf);//关闭pf指向的文件
pf = NULL;//及时置空
12
文件操作正确流程
知道了如何打开文件和关闭文件,也就相当于知道了如何进入和如何出去,所以当我们要对一个文件进行操作的时候,正确的流程应该是这样的:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)//检测指针有效性
{
printf("%s\n", strerror(errno));//错误提醒
return 1;//失败返回
}
//对文件进行一系列操作
//...
//关闭文件
fclose(pf);关闭pf指向的文件
pf = NULL;//及时置空
return 0;
}
123456789101112131415161718192021
文件操作函数(二)—— 文件的顺序读写
前言
在文件操作函数(一)中,我们已经学会了怎样正确地打开和关闭一个文件,即我们已经学会了一件事的开头和结尾,不要以为已经结束了,这才刚刚开始,真正重要的是打开了文件后,如何操作文件中的数据(输入数据和输出数据)。
对文件数据的读写可以分为顺序读写和随机读写。顺序读写,即挨着顺序对文件中的数据进行输入或输出。
在这片博客中,我们首先介绍文件的顺序读写需要用到的文件操作函数。
字符输入输出函数 - fgetc和fputc
一、fputc函数
int fputc( int c, FILE *stream );
1
fputc函数的第一个参数是待输出的字符,第二个参数该字符输出的位置,即fputc函数的功能是将一个字符输出到指定的位置。该函数调用完毕会返回用户传入的字符。
例如,我们要将小写字母a~z写入到data.txt文件中。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件进行输出字符操作
char i = 0;
for (i = 'a'; i <= 'z'; i++)
{
fputc(i, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
1234567891011121314151617181920212223
二、fgetc函数
int fgetc( FILE *stream );
1
fgetc函数只有一个参数,即你要读取的文件的文件指针。fgets函数的功能就是从指定位置读取一个字符。该函数调用成功会返回读取到的的字符;若读取文件时发生错误,或是已经读取到文件末尾,则返回EOF。
例如,我们要将文件data.txt文件中的内容全部读取,并打印到屏幕上。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件进行输入字符操作
int ch = 0;
while ((ch = fgetc(pf))!= EOF)
{
printf("%c", ch);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
1234567891011121314151617181920212223
文本行输入输出函数 - fgets和fputs
一、fputs函数
int fputs( const char *string, FILE *stream );
1
fputs函数的第一个参数是待输出的字符串,第二个参数该字符串输出的位置,即fputs函数的功能是将一个字符串输出到指定的位置(有没有发现fputs函数的参数设计和fputc函数参数的设计非常类似)。该函数调用成功会返回一个非负值;若输出时发生错误,则返回EOF。
例如,我们要将字符串"hello world!"写入到data.txt文件中。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件进行输出字符串操作
char arr[] = "hello world!";
fputs(arr, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
1234567891011121314151617181920
二、fgets函数
char *fgets( char *string, int n, FILE *stream );
1
fgets函数的第三个参数是你要读取的文件的文件指针,第二个参数是你要读取的字符个数(也可以说是字节个数),第一个参数是你所读取到的数据的储存位置。fgets函数的功能就是从指定位置读取指定字符个数的数据储存到指定位置。该函数调用成功会返回用于储存数据的位置的地址,如果读取过程中发生错误,或是读取到了文件末尾,则返回一个空指针(NULL)。
例如,我们要从data.txt文件中提取数据。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件进行输入字符串操作
char arr[10] = { 0 };
fgets(arr, 6, pf);
printf("%s\n", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
123456789101112131415161718192021
fgets函数读取字符的过程中会出现两种情况:
- 在fgets函数读取到指定字符数之前,若读取到换行符(’\n’),则停止读取,读取带回的字符包含换行符。
- 直到fgets函数读取到第n-1个字符时都没有遇到换行符(’\n’),则返回读取到的n-1个字符,并在末尾加上一个空字符一同返回(共n个字符)。
格式化输入输出函数 - fscanf和fprintf
一、fprintf函数
int fprintf( FILE *stream, const char *format [, argument ]...);
1
fprintf函数的第一个参数是数据输出的目的地,后面的参数博友们可能看不太懂,但是我们可以看看库函数printf函数的函数声明:
int printf( const char *format [, argument]... );
//printf函数的声明
12
我们发现fprintf函数除了第一个参数以外,其它参数都与printf函数的参数一样,虽然我们不知道fprintf函数后面的参数代表的意思,但是我们会用printf函数啊,所以我们使用fprintf函数传参时可以模仿者printf的传参形式。
例如我们要将一个结构体类型的变量信息输出到data.txt文件中去。
#include <stdio.h>
#include <string.h>
#include <errno.h>
struct S
{
char name[20];
char sex[5];
int age;
};
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件进行格式化输出操作
struct S s = { "zhangsan", "nan", 20 };
fprintf(pf, "%s %s %d\n", s.name, s.sex, s.age);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
1234567891011121314151617181920212223242526
其实,fprintf函数的功能就是将“区域三”的数据,以“区域二”的格式输出到“区域一”。
二、fscanf函数
int fscanf( FILE *stream, const char *format [, argument ]... );
1
fscanf函数的第一个参数是读取数据的位置,后面的参数我们也可以类比scanf函数的参数:
int scanf( const char *format [,argument]... );
1
我们可以看到fscanf函数的参数也是,除了第一个参数以外,其他参数就是scanf函数的参数,于是我们可以试着尝试使用。
例如,我们要将刚才用fprintf函数输出到data.txt文件中的数据读取出来。
#include <stdio.h>
#include <string.h>
#include <errno.h>
struct S
{
char name[20];
char sex[5];
int age;
};
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件进行格式化输入操作
struct S tmp = { 0 };
fscanf(pf, "%s %s %d", tmp.name, tmp.sex, &(tmp.age));
printf("%s %s %d\n", tmp.name, tmp.sex, tmp.age);
//将tmp中的内容打印出来,看是否读取成功
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
12345678910111213141516171819202122232425262728
其实,fscanf函数的功能就是将“区域一”的数据,以“区域二”的格式输入到“区域三”。
二进制输入输出函数 - fread和fwrite
一、fwrite函数
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
1
fwrite函数的第一个参数是输出数据的位置,第二个参数是要输出数据的元素个数,第三个参数是每个元素的大小,第四个参数是数据输出的目标位置。该函数调用完后,会返回实际写入目标位置的元素个数,当输出时发生错误或是待输出数据元素个数小于要求输出的元素个数时,会返回一个小于count的数。
概括一下,fwrite函数的功能就是将buffer位置的,每个元素大小为size的,count个元素,以二进制的形式输出到stream位置。
例如,我们要将数组arr中的10个元素以二进制的形式输出到data.txt文件中去。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "wb");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件以二进制形式进行输出操作
int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
fwrite(arr, sizeof(int), 10, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
1234567891011121314151617181920
注:此时打开文件的形式应为"wb",即以二进制的形式打开文件,进行输出操作。
二、fread函数
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
1
fread函数的第一个参数是接收数据的位置,第二个参数是要读取的每个元素的大小,第三个参数是要读取的元素个数,第四个参数是读取数据的位置。函数调用完会返回实际读取的元素个数,若在读取过程中发生错误或是在未读取到指定元素个数时读取到文件末尾,则返回一个小于count的数。
概括一下,fread函数的功能就是从stream位置,以二进制的形式读取count个每个元素大小为size的数据,到buffer位置。
例如,我们要将刚才用fwrite函数输出到data.txt文件的数据读取出来。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "rb");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件以二进制形式进行输入操作
int arr[10] = { 0 };
fread(arr,sizeof(int),10,pf);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}//将arr中的内容打印出来,看是否读取成功
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
12345678910111213141516171819202122232425
注:此时打开文件的形式应为"rb",即以二进制的形式打开文件,进行输入操作。
C语言中scanf和printf、fscanf和fprintf、sscanf和sprintf这三对函数的区别
前言
C语言中,许多函数的函数名过于相似,使用者要是不能很好地区分这些函数,就会造成误用,最终导致代码的结果大相径庭。对于scanf和printf函数、fscanf和fprintf函数、sscanf和sprintf函数这三对函数,它们的函数名都只是各自相差一个字母而已,但其功能及其用法却并不相同,有多少人能够真正分清楚它们各自的功能与使用方法呢?
其中,知道printf和scanf函数的人最多,其次是fprint和fscanf函数,知道sprintf和sscanf函数的人最少。本篇博文,就带大家一同认识认识这三对函数,区分它们各自的功能与使用方法。
scanf和printf函数
scanf函数和printf函数是应用于标准输入流和标准输出流的格式化输入输出语句。
一、scanf函数
scanf函数的功能是从标准输入流读入格式化的数据。
int scanf ( const char * format, ... );
1
从标准输入流(stdin)读取数据,并根据参数格式将它们存储到附加参数所指向的位置。附加参数应指向已分配的对象(即附加参数应是一个地址,或者说指针),这些对象的类型由格式字符串中相应的格式说明符指定。
例如,以下代码:
scanf("%d", &input);
//scanf("%d", input);//error
12
scanf函数以%d(整型)的格式从标准输入流读入的数据存储到&input所指向的内存空间。所以说,我们之后若是打印变量input,就会打印出已读取的这个值。
二、printf函数
printf函数的功能是将格式化的数据打印到标准输出流上去。
int printf ( const char * format, ... );
1
printf函数将format指向的C字符串写入标准输出流(stdout)。如果format字符串中包含格式说明符(以%开头的子序列),则format后面的附加参数将被格式化并插入结果字符串中,以替换它们各自的说明符。
我们平时已经习惯了这样使用printf函数:
printf("hello world!\n");//第一种写法
1
其实传入printf函数的参数本质上是一个指向字符串的指针,于是我们这样写也是没问题的:
char arr[] = "hello world!\n";
printf(arr);//第二种写法
12
第一种写法中,直接将一个字符串传入printf函数,实际上也是将该字符串的首地址传入了函数,本质上与第二种写法相同,所以那样写也是没有问题的。
fscanf和fprintf函数
fscanf函数和fprintf函数是应用于所有输入流和所有输出流的格式化输入输出语句。
fscanf函数和fprintf函数是一对文件操作函数,在博主的文件操作函数(二)中有详细介绍,这里就不再详细介绍。
既然说fscanf函数和fprintf函数是应用于所有的标准输出流和标准输入流的函数,那么这对函数的功能也就包含了printf函数和scanf函数的功能。我们完全可以用fscanf函数和fprintf函数来做scanf函数和printf函数做的事。
一、fscanf函数实现scanf函数的功能
int fscanf( FILE *stream, const char *format [, argument ]... );
1
因为fscanf函数的功能是,从stream中读取数据,并根据参数格式将其存储到附加参数所指向的位置,所以我们只需将参数stream的位置传入stdin,使其在标准输入流中读取数据即可。
所以,下面两句代码的作用是等效的:
scanf("%d", &input);
fscanf(stdin, "%d", &input);
12
二、fprintf函数实现printf函数的功能
int fprintf( FILE *stream, const char *format [, argument ]...);
1
因为fprintf函数的功能是,将格式化数据写入stream中,所以我们只需将参数stream的位置传入stdout,使其将数据写入标准输出流即可。
所以,下面两句代码的作用是等效的:
printf("%d\n", input);
fprintf(stdout, "%d\n", input);
12
三、拓展
我们之前在介绍文件操作函数的时候就说过,当你要对某一个流进行输入或是输出操作的时候,首先要打开该流,当输入输出操作结束后要及时关闭该流。
既然printf函数和scanf函数也要对标准输入流和标准输出流进行操作,那么为什么在使用printf函数和scanf函数之前,并没有类似于打开文件流的fopen函数的使用,在使用完printf函数和scanf函数后又没有类似于关闭文件流的fclose函数的使用呢?
其实,当C程序运行起来的时候,会自动打开下面这三个流:
符号 | 名称 | 对应操作(显示)位置 |
---|---|---|
stdin | 标准输入流 | 键盘 |
stdout | 标准输出流 | 屏幕 |
stderr | 标准错误流 | 屏幕 |
而当程序运行结束后,这三个流又会自动关闭。所以,当我们需要对标准输入流或是标准输出流进行输入输出操作的时候,并不需要通过某些操作来打开这两个流,也不需要我们来关闭这两个流。
sscanf和sprintf函数
sscanf函数可以从字符串中读取格式化数据,sprintf函数可以将格式化数据写入字符串。
一、sscanf函数
int sscanf ( const char * s, const char * format, ...);
1
sscanf函数从s读取数据并根据参数格式将其存储到附加参数给定的位置,就像使用scanf一样,但sscanf函数是从s读取数据而不是标准输入(stdin)。
例如,我们要将字符串arr里面的内容按照不同的格式读取出来。
#include <stdio.h>
int main()
{
char arr[] = "2021 dragon";
int year = 0;
char s[10] = { 0 };
sscanf(arr, "%d %s", &year, s);
printf("%d\n", year);//2021
printf("%s\n", s);//dragon
return 0;
}
1234567891011
也就是将从区域一中,以区域二的格式读取的数据,存储到区域三指向的空间。
二、sprintf函数
int sprintf ( char * str, const char * format, ... );
1
sprintf函数与printf函数功能相似,区别在于:printf函数是将数据格式化后直接打印在屏幕上,而sprintf函数是将数据格式化后存储在str所指向的字符串中。
例如,将数据按照不同的格式写入字符串arr中。
#include <stdio.h>
int main()
{
char arr[20] = { 0 };
int year = 2021;
char s[] = "dragon";
sprintf(arr, "%d %s", year, s);
printf("%s\n", arr);//2021 dragon
return 0;
}
12345678910
也就是将从区域三中,以区域二的格式读取的数据,存储到区域一指向的位置。
总结
函数 | 功能 |
---|---|
scanf,printf | 应用于标准输入流和标准输出流的格式化输入输出语句。 |
fscanf,fprintf | 应用于所有输入流和所有输出流的格式化输入输出语句。 |
sscanf,sprintf | 应用于字符串和格式化数据之间的相互转换 |
文件操作函数(三)—— 文件的随机读写
前言
在文件操作函数(二)中,我们介绍了文件的顺序读写函数,即挨着顺序对文件中的数据进行输入或输出操作的函数。但是若只能规规矩矩地,从前到尾对文件进行输入输出操作,那么就比较笨拙,为了能更加灵活地对文件进行输入输出操作,出现了另一类文件操作函数,使得我们可以对文件进行随机读写。
需要注意的是,随机读写并非胡乱读写文件的某一位置,而是按照操作者的意愿,随机读写文件的某一位置。
例如,data.txt文件中的内容是"abcdef",执行完以下代码后,我们如何再次读取文件开头的数据(字符’a’)。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//用字符输入函数读取文件信息
int ch = fgetc(pf);
printf("%c\n", ch);//观察第一次读取到的字符
ch = fgetc(pf);
printf("%c\n", ch);//观察第二次读取到的字符
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
12345678910111213141516171819202122
第一次读取字符时,指针位于文件开头,读取到了第一个字符以后,指针就会自动向后移动,指向后面的内容:
还未读取时,文件指针指向文件信息区的起始位置:
读取’a’字符以后,文件指针自动跳过字符’a’,指向下一个字符:
再次读取,就读取到的是字符’b’了,文件指针再次后移:
所以,如果我们继续用fgetc函数向后读取文件内容,那么读取到的就是字符’c’了。
那么,在读取了字符’a’和字符’b’后,我们如何再次读取到字符’a’,或是不读取下一个字符’c’,而读取其后的某一指定位置的数据呢?
这就是所谓的文件的随机读写了。
fseek函数
int fseek( FILE *stream, long offset, int origin );
1
fseek函数的第一个参数既是要移动位置的文件指针,第三个参数是“初始位置”(并非文件信息区的起始位置),第二个参数是文件指针经操作后相对于这个“起始位置”的偏移量,单位为字节。fseek函数如果调用成功,则返回0;若调用失败,则返回一个非0的值。
关于fseek函数的第三个参数,可以有以下三种形式:
参数形式 | 代表意义 |
---|---|
SEEK_CUR | 文件指针的当前位置 |
SEEK_SET | 文件开头 |
SEEK_END | 文件末尾 |
所以,如果我们用fseek函数,对上述例题中pf指针进行操作,使其下一次读取字符时能读取到字符’a’,则有以下三种写法:
1.让文件指针相对于其当前位置向前偏移2个字节。
fseek(pf, -2, SEEK_CUR);//调整文件指针位置
ch = fgetc(pf);//读取到字符'a'
printf("%c\n", ch);
123
2.让文件指针相对于文件开头偏移0个字节(即指向文件开头)。
fseek(pf, 0, SEEK_SET);//调整文件指针位置
ch = fgetc(pf);//读取到字符'a'
printf("%c\n", ch);
123
3.让文件指针相对于文件末尾向前偏移6个字节。
fseek(pf, -6, SEEK_END);//调整文件指针位置
ch = fgetc(pf);//读取到字符'a'
printf("%c\n", ch);
123
ftell函数
为了更好地明确文件指针位于什么位置,于是出现了ftell函数,它用于计算当前文件指针相对于起始位置的偏移量。
long ftell( FILE *stream );
1
ftell函数的参数是一个文件指针。ftell函数调用成功,则返回文件指针相对于起始位置的偏移量;若调用失败,则返回 - 1。
例如,我们要计算上述例题中,读取了两个字符后,pf相对于起始位置的偏移量,我们可以这样计算:
long Offset = ftell(pf);
1
此时,变量Offset中存放的就是pf指针相对于起始位置的偏移量。
rewind函数
void rewind( FILE *stream );
1
rewind函数的作用是让传入的文件指针返回文件的起始位置。例如,在上述例题中,我们可以直接让文件指针返回文件的起始位置,这样就能读取到字符’a’:
rewind(pf);//调整文件指针位置
ch = fgetc(pf);//读取到字符'a'
printf("%c\n", ch);
123
拓:用fseek函数与ftell函数求文件大小
求一个文件的大小其实很简单,你只需要将文件指针移到该文件末尾,然后求文件指针相对于起始位置的偏移量即可。
fseek(pf, 0, SEEK_END);//将文件指针置于文件末尾
int FileLen = ftell(pf);//求文件指针相对于文件起始位置的偏移量
fseek(pf, 0, SEEK_SET);//将文件指针放回文件开头
123
文件操作函数(四)—— 文件的结束判定
前言
在文件操作函数(二)中,我们介绍了一系列的与文件的顺序读写有关的函数,它们调用成功与失败时的返回值各不相同,为了能更好地记忆这些函数,我们将这些函数的返回值进行了总结,并列入表格:
函数 | 调用成功时的返回值 | 调用失败时的返回值 |
---|---|---|
fputc | 返回输出字符的ASCII码值 | EOF |
fgetc | 返回输入字符的ASCII码值 | EOF |
fputs | 非负值 | EOF |
fpets | 返回输入字符串的首地址 | NULL |
fprintf | 返回输出的字节个数 | 负值 |
fscanf | 成功转换和分配的字段数 | EOF |
fwrite | 要求写入的完整项的数目 | 小于要求写入的完整项的数目 |
fread | 要求读取的完整项的数目 | 小于要求读取的完整项的数目 |
这样一一列举出来后,看似已经非常清楚明了,实际却还有一些小细节:
- fgetc函数返回EOF时,可能是读取数据时发生错误,也可能是已经读取到文件末尾了。
- fgets函数返回NULL时,可能是读取数据时发生错误,也可能是已经读取到文件末尾了。
- fscanf函数返回EOF时,可能是读取数据时发生错误,也可能是在第一次转换之前到达文件流的末尾。
- fread函数返回的值小于要求读取的完整项的数目时,可能是读取数据时发生错误,也可能是在达到读取的规定数目之前遇到文件结尾。
这些不同的情况返回的却是同一个值,当fgetc函数返回EOF时,我们不能断定一定是读取数据时发生错误了,当fscanf函数返回EOF时,我们也不能断定它就是文件读取结束了。所以,出现了一类函数,它们的功能就是,判断文件操作函数是以一种什么方式结束。
注意:以下介绍的函数的功能不是判断文件操作函数是否调用失败,而是在文件操作函数已经调用失败的情况下,判断文件操作函数调用失败的原因。
ferror函数
int ferror( FILE *stream );
1
ferror函数的功能就是判断使用某一文件指针的过程中,是否发生错误,若使用时没有发生错误,则ferror函数返回0;否则,ferror函数将返回一个非零的值。调用ferror函数时,我们只需将待检查的文件指针传入即可。
if (ferror)
{
printf("文件指针使用时,发生错误\n");
}
1234
feof函数
int feof( FILE *stream );
1
feof函数的功能也是判断使用某一文件指针的过程中,是否读取到文件末尾,若使用时没有读取到文件末尾,则feof函数返回0;否则,feof函数将返回一个非零的值。调用feof函数时,也只需将待检查的文件指针传入即可。
if (feof(pf))
{
printf("文件指针使用时,读取到文件末尾\n");
}
1234
ferror函数和feof函数搭配使用
当前言中说到的文件操作函数调用失败时,我们就可以同时运用ferror函数和feof函数,来判断文件操作函数调用失败的准确原因。
例如,文件data.txt文件中的数据为"abcdef",我们要用fgetc函数读取data.txt文件中的数据,当数据读取完之后,我们就可以用ferror函数和feof函数,来判断最后一次fgetc函数调用失败的原因:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//读取文件中的数据
int ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c", ch);
}
if (ferror(pf))
{
printf("文件指针使用时,发生错误而结束\n");
}
else if (feof(pf))
{
printf("文件指针使用时,读取到文件末尾而结束\n");
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
. fgetc函数返回EOF时,可能是读取数据时发生错误,也可能是已经读取到文件末尾了。
2. fgets函数返回NULL时,可能是读取数据时发生错误,也可能是已经读取到文件末尾了。
3. fscanf函数返回EOF时,可能是读取数据时发生错误,也可能是在第一次转换之前到达文件流的末尾。
4. fread函数返回的值小于要求读取的完整项的数目时,可能是读取数据时发生错误,也可能是在达到读取的规定数目之前遇到文件结尾。
这些不同的情况返回的却是同一个值,当fgetc函数返回EOF时,我们不能断定一定是读取数据时发生错误了,当fscanf函数返回EOF时,我们也不能断定它就是文件读取结束了。所以,出现了一类函数,它们的功能就是,判断文件操作函数是以一种什么方式结束。
注意:以下介绍的函数的功能不是判断文件操作函数是否调用失败,而是在文件操作函数已经调用失败的情况下,判断文件操作函数调用失败的原因。
ferror函数
int ferror( FILE *stream );
1
ferror函数的功能就是判断使用某一文件指针的过程中,是否发生错误,若使用时没有发生错误,则ferror函数返回0;否则,ferror函数将返回一个非零的值。调用ferror函数时,我们只需将待检查的文件指针传入即可。
if (ferror)
{
printf("文件指针使用时,发生错误\n");
}
1234
feof函数
int feof( FILE *stream );
1
feof函数的功能也是判断使用某一文件指针的过程中,是否读取到文件末尾,若使用时没有读取到文件末尾,则feof函数返回0;否则,feof函数将返回一个非零的值。调用feof函数时,也只需将待检查的文件指针传入即可。
if (feof(pf))
{
printf("文件指针使用时,读取到文件末尾\n");
}
1234
ferror函数和feof函数搭配使用
当前言中说到的文件操作函数调用失败时,我们就可以同时运用ferror函数和feof函数,来判断文件操作函数调用失败的准确原因。
例如,文件data.txt文件中的数据为"abcdef",我们要用fgetc函数读取data.txt文件中的数据,当数据读取完之后,我们就可以用ferror函数和feof函数,来判断最后一次fgetc函数调用失败的原因:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//读取文件中的数据
int ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c", ch);
}
if (ferror(pf))
{
printf("文件指针使用时,发生错误而结束\n");
}
else if (feof(pf))
{
printf("文件指针使用时,读取到文件末尾而结束\n");
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}