C 文件处理
本文介绍了在 C 语言中读写文本文件和二进制文件的方法。
在 C 语言标准库中,提供了 fprintf()
、fscanf()
、fread()
、fwrite()
、fseek()
等函数进行文件操作。
为什么需要文件?
- 文件是计算机存储设备中用于存储数据的容器。
- 当程序终止时,位于内存中的数据将丢失。而文件则会一直保存在存储设备上。
- 可以直接将文件中的内容通过读取操作导入到程序中,从而节省手工录入数据的时间。
- 文件很方便复制和迁移。
文件类型
我们一般在程序中处理两种类型的文件:
- 文本文件
- 二进制文件
文本文件
文本文件是指那些能看到文件内容的文件,里面就是普通的字符文本。比如我们常见的 .txt
, .xml
, .html
, .c
, .yaml
等都是文本文件。
我们可以使用任何简单的文本编辑器(例如记事本)轻松的打开和创建文本文件。
当您打开这些文件时,您将看到文件中的所有内容都是纯文本。您可以轻松编辑或删除内容。
文本文件易于阅读,比二进制文件占用更多的存储空间。
二进制文件
除了文件文件之外的其他文件,我们一般称之为二进制文件。比如 .exe
, .bin
, .dll
, .so
等。
如果我们用文件编辑器打开二进制文件,可能会看到一些乱码符号或一些不认识的字符。
二进制文件不是以纯文本形式存储数据,而是以二进制形式(0
和 1
)存储数据。
二进制文件可以容纳更多的数据,不易读取,并且比文本文件提供更好的安全性。
文件操作
在 C 中,您可以对文件(文本或二进制)执行 5 种主要操作:
- 创建一个新文件
- 打开现有文件
- 关闭文件
- 读取文件内容
- 将数据写入文件
处理文件
处理文件时,需要声明一个文件类型的指针。文件和程序之间的通信需要此声明。
FILE *fptr;
打开文件 - 用于创建和编辑
我们使用头文件 stdio.h
中定义的 fopen()
函数打开文件。
在标准 I/O 中打开文件的语法是:
ptr = fopen("fileopen", "mode");
例如,
fopen("D:\\newfile.txt","w");
fopen("D:\\oldfile.bin","rb");
- 假设目录
d:\
中不存在文件newfile.txt
。第一个函数创建一个名为newfile.txt
的新文件,并根据写入模式w
将其打开以进行写入。 写入模式允许您创建和编辑(覆盖)文件的内容。 - 假设目录
d:\
中存在文件oldfile.bin
。第二个函数打开现有文件并使用读取二进制模式rb
读取。 读取模式只允许读取文件,不能写入文件。
在标准 I/O 中打开模式
模式 | 模式的含义 | 在文件不存在期间 |
---|---|---|
r |
读取 | 如果文件不存在,则 fopen() 返回 NULL。 |
rb |
读取二进制 | 如果文件不存在,则 fopen() 返回 NULL。 |
w |
写入 | 如果文件存在,则覆盖其内容。 如果文件不存在,它将被创建。 |
wb |
写入二进制 | 如果文件存在,则覆盖其内容。如果文件不存在,它将被创建。 |
a |
文件末尾追加数据。 | 如果文件不存在,它将被创建。 |
ab |
以二进制模式在文件追加数据。 | 如果文件不存在,它将被创建。 |
r+ |
打开读取和写入。 | 如果文件不存在,则 fopen() 返回 NULL。 |
rb+ |
以二进制模式打开读取和写入。 | 如果文件不存在,则 fopen() 返回 NULL。 |
w+ |
打开读取和写入。 | 如果文件存在,则覆盖其内容。 如果文件不存在,它将被创建。 |
wb+ |
以二进制模式打开读取和写入。 | 如果文件存在,则覆盖其内容。 如果文件不存在,它将被创建。 |
a+ |
打开读取和追加。 | 如果文件不存在,它将被创建。 |
ab+ |
以二进制模式打开读取和追加。 | 如果文件不存在,它将被创建。 |
关闭文件
对文件读/写完成后,我们应该使用 fclose()
函数关闭文件。
fclose(fptr);
这里, fptr
是与要关闭的文件相关联的文件指针。
读取和写入文本文件
我们使用函数 fprintf()
和 fscanf()
写入和读取文本文件。
他们只是 printf()
和 scanf()
的文件版本。唯一的区别是, fprint()
与 fscanf()
预期的指针是 FILE
结构。
示例 1:写入文本文件
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num;
FILE *fptr;
fptr = fopen("C:\\program.txt","w");
if(fptr == NULL)
{
printf("Error!");
exit(1);
}
printf("Enter num: ");
scanf("%d",&num);
fprintf(fptr,"%d",num);
fclose(fptr);
return 0;
}
该程序从用户那里获取一个数字并存储在文件中 program.txt
。
编译运行该程序后,可以看到在计算机的 C 盘中创建了一个文本文件 program.txt
,文件中的内容就是您输入的整数。
示例 2:从文本文件中读取
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num;
FILE *fptr;
if ((fptr = fopen("C:\\program.txt","r")) == NULL){
printf("Error! opening file");
exit(1);
}
fscanf(fptr,"%d", &num);
printf("Value of n=%d", num);
fclose(fptr);
return 0;
}
该程序读取 program.txt
文件中的整数并将其打印到屏幕上。
如果您成功运行了示例 1 并创建了文件,则运行此程序后将打印出文件中的整数。
其他函数,如 fgetchar()
, fputc()
等,可以以类似的方式使用。
读取和写入二进制文件
函数 fread()
和 fwrite()
用于读取和写入二进制文件。
写入二进制文件
fwrite()
函数用于写入二进制文件。这些函数有四个参数:
- 要写入的数据地址
- 要写入的数据大小
- 要写入数据的数量
- 指向要写入的文件的指针。
fwrite(addressData, sizeData, numbersData, pointerToFile);
示例 3:使用 fwrite() 写入二进制文件
#include <stdio.h>
#include <stdlib.h>
struct threeNum
{
int n1, n2, n3;
};
int main()
{
int n;
struct threeNum num;
FILE *fptr;
if ((fptr = fopen("C:\\program.bin","wb")) == NULL){
printf("Error! opening file");
// Program exits if the file pointer returns NULL.
exit(1);
}
for(n = 1; n < 5; ++n)
{
num.n1 = n;
num.n2 = 5*n;
num.n3 = 5*n + 1;
fwrite(&num, sizeof(struct threeNum), 1, fptr);
}
fclose(fptr);
return 0;
}
在这个程序中,我们在 C 盘中创建一个新文件 program.bin
。
我们声明了一个结构体 threeNum
,它包含三个数字类型的属性 n1
、n2
和 n3
。在主函数中定义了结构体变量 num
。
现在,在 for 循环中,我们使用 fwrite()
将值存储到文件中。
第一个参数是 num
的地址,第二个参数是结构体 threeNum
的大小。
由于我们只插入了一个 num
实例,第三个参数是 1
。最后一个参数 *fptr
指向我们存储数据的文件。
最后,我们关闭了文件。
从二进制文件中读取
函数 fread()
也有 4 个参数,类似于上面的 fwrite()
函数。
fread(addressData, sizeData, numbersData, pointerToFile);
示例 4:使用 fread() 从二进制文件中读取
#include <stdio.h>
#include <stdlib.h>
struct threeNum
{
int n1, n2, n3;
};
int main()
{
int n;
struct threeNum num;
FILE *fptr;
if ((fptr = fopen("C:\\program.bin","rb")) == NULL){
printf("Error! opening file");
exit(1);
}
for(n = 1; n < 5; ++n)
{
fread(&num, sizeof(struct threeNum), 1, fptr);
printf("n1: %d\tn2: %d\tn3: %d", num.n1, num.n2, num.n3);
}
fclose(fptr);
return 0;
}
在这个程序中,我们通过循环逐一的读取 program.bin
文件中的数据。
简单来说,我们每次从 *fptr
指向的文件中读取一个threeNum
大小的数据给结构体 threeNum
变量 num
。
最终输出了在示例 3 中插入的相同记录。
使用 fseek() 获取数据
如果你在一个文件中有很多记录,并且需要访问某个特定位置的记录,按照上面的方法,你需要遍历它之前的所有记录来获取该记录。
这会浪费大量的内存和操作时间。使用 fseek()
可以更快的获取所需的数据。 fseek()
能够将光标定位到文件中的给定记录。
fseek() 的语法
fseek(FILE * stream, long int offset, int whence);
第一个参数 stream
是指向文件的指针。第二个参数 offset
是要查找的记录的位置,第三个参数指定偏移量开始的位置。
fseek()
中定义的不同位置:
位置 | 含义 |
---|---|
SEEK_SET |
从文件开头开始偏移。 |
SEEK_END |
从文件末尾开始偏移。 |
SEEK_CUR |
从文件中光标的当前位置开始偏移。 |
示例 5:fseek()
#include <stdio.h>
#include <stdlib.h>
struct threeNum
{
int n1, n2, n3;
};
int main()
{
int n;
struct threeNum num;
FILE *fptr;
if ((fptr = fopen("C:\\program.bin","rb")) == NULL){
printf("Error! opening file");
exit(1);
}
fseek(fptr, -sizeof(struct threeNum), SEEK_END);
for(n = 1; n < 5; ++n)
{
fread(&num, sizeof(struct threeNum), 1, fptr);
printf("n1: %d\tn2: %d\tn3: %d\n", num.n1, num.n2, num.n3);
fseek(fptr, -2*sizeof(struct threeNum), SEEK_CUR);
}
fclose(fptr);
return 0;
}
该程序将以相反的顺序(从后到前)从 program.bin
文件中读取记录并打印出来。