![[C语言] C语言能对文件进行哪些操作?](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/202306251812049.webp)
Table of Contents
文件操作 Link to 文件操作
一. 文件的分类 Link to 一. 文件的分类
文件按照功能, 区分为两类: 程序文件、数据文件
两种文件类型的区分是相对的, 并不是绝对的
程序文件
比如:
C语言的源程序文件(
.c
为后缀的文件 )目标文件( 在Windows环境中 以
.obj
为后缀 )可执行程序文件( 在Windows环境中 以
.exe
为后缀 )数据文件
数据文件的内容, 不一定是程序
可以是程序运行时所需要读取、改变的数据
为什么说两种文件类型的区分是相对的?
比如:
存在两个源文件
test1.c
test2.c
如果
test1.c
文件可以对test2.c
文件中的数据进行读取等操作, 那么test1.c
就是程序文件,test2.c
就是数据文件
PS: 以下讨论的均为数据文件
二. 文件的操作 Link to 二. 文件的操作
2.1 文件指针 Link to 2.1 文件指针
在学习文件指针之前, 先介绍一个概念: 文件信息区
文件信息区:
在C语言中, 每次打开一个文件, 操作系统都会在内存中开辟一块区域来存放该文件的各种信息(比如文件名、文件的状态、文件的地址、文件的大小等)
这些信息都存放在一个结构体变量中, 此结构体变量的类型默认被系统声明为
FILE
所以, 被使用文件的文件信息区, 本质上就是一个
FILE
类型的结构体变量每当一个文件打开后, 计算机会自动根据文件的状态、情况自动生成一个
FILE
类型的结构体变量, 并存入该文件的各种信息
FILE
类型的具体成员, 内容。在不同的编译器中是不完全相同的, 但是差别不大
我们使用FILE
类型定义的结构体指针变量, 就是一个文件指针变量
1
FILE* pf; //pf 文件指针变量
定义pf
是一个指向FILE
类型数据的指针变量, 可以指向某个文件的文件信息区, 通过文件信息区中存放的信息可以进一步访问该文件
所以, 通过文件指针变量能够找到与其相关联的文件
2.2 文件的打开与关闭 Link to 2.2 文件的打开与关闭
文件的打开操作及关闭操作, 需要使用两个函数 fopen(文件打开函数)
及 fclose(文件关闭函数)
fopen()
Link to fopen()
1
FILE* fopen(const char *filename, const char *mode);
第一个参数
filename
应该传入 需打开文件的文件名尽量详细需要打开的文件名, 如:
C:\\Program Files\\TEST.c
若只传入
TEST.c
, 只会默认打开(创建), 运行可执行程序时, 用户所在路径的TEST.c
文件第二个参数
mode
, 应该传入 表示文件打开模式(方式)的字符串具体的模式有:
表示读写权限的
字符串 权限 说明 "r"
只读
只允许读取, 不允许写入. 文件必须存在, 否则打开失败 "w"
写入
若文件不存在, 则创建一个新文件; 若文件存在, 则清空文件内容 "a"
追加
若文件不存在, 则创建一个新文件; 若文件存在, 则将写入的数据追加到文件的末尾 "r+"
读写
既可以读取也可以写入. 文件必须存在, 否则打开失败 "w+"
写入
既可以读取也可以写入. 若文件不存在, 则创建一个新文件;若文件存在, 则清空文件内容 "a+"
追加
既可以读取也可以写入. 若文件不存在, 则创建一个新文件; 若文件存在, 则将写入的数据追加到文件的末尾 表示读写方式的:
字符串 说明 "t"
以文本文件方式读写 "b"
以二进制文件方式读写
其实, 文件打开方式由 r、w、a、t、b、+ 六个字符拼成, 各字符的含义是:
r(read)
: 读取w(write)
: 写入a(append)
: 追加t(text)
: 文本文件b(binary)
: 二进制文件+
: 读取和写入
且
mode
, 传参时, 读写权限和读写方式 是可以结合使用的, 但必须将 读写方式 放在 读写权限 的 中间或者尾部, 不过 读写方式可以忽略不写, 忽略的情况下, 默认为"t"
, 即默认以文本文件的方式进行读写读写权限 及 读写方式 的结合使用, 例:
读写方式放在读写权限的尾部
"rt"
、"rb"
、"r+t"
、"r+b"
、"wt"
、"w+b"
、"at"
等等读写方式放在读写权限的中间
"rt+"
、"rb+"
、"wt+"
、"wb+"
等等
fopen
函数的返回值 是FILE*
类型的, 返回的是所打开的文件的文件信息区的首地址, 所以需要用 FILE*
类型的指针变量接收, 然后可以通过此指针变量操作此文件信息。
fclose()
Link to fclose()
1
int fclose(FILE* stream);
参数的类型是
FILE*
的指针变量, 此指针变量 需指向 已打开文件的文件信息区的地址例如
PLAINTEXT
123456789101112131415#include <stdio.h> int main () { //打开文件 FILE * pf = fopen ("test.txt","w"); if (pf != NULL) { //文件操作 //………… //关闭文件 fclose (pf); pf = NULL; } return 0; }
2.3 文件的顺序读写 Link to 2.3 文件的顺序读写
2.3.1 文件读写函数 Link to 2.3.1 文件读写函数
功能 | 函数名 | 函数 | 适用于 |
---|---|---|---|
字符输入函数 | fgetc | int fgetc(FILE* stream); | 所有输入流 |
字符输出函数 | fputc | int fputc(int c, FILE* stream); | 所有输出流 |
文本行输入函数 | fgets | char *fgets(char* string, int n, FILE* stream); | 所有输入流 |
文本行输出函数 | fputs | int fputs(const char* string, FILE* stream); | 所有输出流 |
格式化输入函数 | fscanf | int fscanf(FILE* stream, const char* format [, argument ]...); | 所有输入流 |
格式化输出函数 | fprintf | int fprintf(FILE* stream, const char* format [, argument ]...); | 所有输出流 |
二进制输入 | fread | size_t fread(void* buffer, size_t size, size_t count, FILE* stream ); | 文件 |
二进制输出 | fwrite | size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream ); | 文件 |
上面是对 打开的文件进行顺序读写时, 可用到的函数。
2.3.2 单个字符读写 Link to 2.3.2 单个字符读写
使用上边的函数, 尝试向文件中写入字符
fputc()
Link to fputc()
123456789101112131415161718192021
#include <stdio.h>
int main() {
FILE* pf = fopen("test.txt", "w");
if (pf == NULL) {
printf("打开文件失败\n");
return 0;
}
fputc('c', pf);
fputc('s', pf);
fputc('b', pf);
fputc('i', pf);
fputc('t', pf);
fclose(pf);
pf = NULL;
return 0;
}
我们使用fputc
函数成功在文件中写入了内容
不过这时候肯定会有疑惑, 比如: fputc
不是字符输出函数吗?为什么能往文件中输入字符?
需要知道为什么, 就需要学习在文件操作中的以下两个概念:
输入
在一般的认知中, 用键盘打字, 就算是输入了
但在文件操作中, 输入, 指 从键盘获取的内容 存入 内存中; 也可以指 文件中的内容 存入 内存中
输入的终点, 是内存, 而不是文件
输出
与输入相反, 在文件操作中, 把 内存中的数据 输出显示到 屏幕上, 或是 输出到 文件中, 这才是输出操作
所以, 用
fputc
字符输出函数, 往文件中输入字符
用fputc
函数, 成功向文件中写入了字符, 那么如何向屏幕上输出字符呢?需不需要先类似打开文件的操作呢?
很显然不需要先打开屏幕什么的, 为什么呢?
在C语言程序运行时, 会默认打开三个流:
stdin
: 标准输入流, 默认对应键盘
stdout
: 标准输出流, 默认对应屏幕
stderr
: 标准错误流, 默认对应屏幕
三个标准流, 都是FILE*
类型的
当我们需要用fputc
函数, 向屏幕输出字符的时候, 只需要把目标文件地址改为 标准输出流 就可以了:
1234567891011121314151617181920
#include <stdio.h>
int main() {
FILE* pf = fopen("test.txt", "w");
if (pf == NULL) {
printf("打开文件失败\n");
return 0;
}
fputc('a', stdout);
fputc('b', stdout);
fputc('c', stdout);
fputc('d', stdout);
fclose(pf);
pf = NULL;
return 0;
}
上面测试了fputc
输出字符函数, 那么怎么样使用输入字符函数将文件内的数据, 输入至内存中呢?
fgetc()
Link to fgetc()
首先在.c
源文件的路径下创建test2.txt
文件, 并输入相应的内容:
12345678910111213141516171819202122232425262728
#include <stdio.h>
int main() {
//以只读方式打开文件, 需要先创建文件
FILE* pf = fopen("test2.txt", "r");
if (pf == NULL) {
printf("打开文件失败\n");
return 0;
}
int ch;
//将 fgetc 的返回值存入 ch, 再将 ch 内容输出
ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
以上代码的运行结果如下(test2.txt
文件 在程序中被打开前 内容就为: abcdefg
):
将fgetc
的返回值存入 变量ch
并输出, 屏幕上也输出了 a
、b
、c
、d
!!! 这也说明了, 如果读取成功, fgetc
函数的返回值就是 读取到的字符的 ASCII
值
但是ch
为什么不用char
类型呢?读取的内容不是字符吗?用char
类型的变量来接收也可以吗?
答案是不可以, 为什么?
在这句话中我们可以看出, fgetc
将读取到的字符以int
类型返回 或者 返回EOF
, 表示读取错误 或 文件结尾
说明fgetc
的返回值, 并不一定全都是 字符, 也有可能是 EOF
, 所以我们要用int
类型的变量接收
fgetc
函数, 传参传入的是 需要被存放到内存中的 文件的数据 的地址并且, 在程序执行时 屏幕上输出的内容是不同的, 这意味着
fgetc
函数读到的数据是不同的, 但传入的参数均为 变量pf
, 这说明,fgetc
函数会将传入的地址向后移动一位(移动到下一次需要读取的数据的地址)然后, 上边使用
fputc
字符输出函数的时候, 每次传入的参数也是同一个变量, 但是输出的字符却不一样, 所以fputc
函数每次传入相同的函数进行调用, 也会将传入的地址向后移动一位, 以便下一次输出不覆盖之前的输出
2.3.3 整行字符读写 Link to 2.3.3 整行字符读写
对文件的内容一行一行的读写, 就需要用到这两个函数
fputs()
Link to fputs()
fputs
函数 与 fputc
函数的使用方法类似, 只不过本函数是输出一行, 而另一个是输出单个字符
以下示例:
1234567891011121314151617
#include <stdio.h>
int main() {
FILE* pf = fopen("test.txt", "w");
if (pf == NULL) {
printf("打开文件失败\n");
return 0;
}
fputs("Hello Bit\n", pf);
fputs("Great\n", pf);
fclose(pf);
pf = NULL;
return 0;
}
同样的, 会改变传入的地址, 会将传入的地址 向后移动输出的字符串位数 位
fgets()
Link to fgets()
fgets
函数的使用方法, 就与fgetc
函数有很大的不同了。
1
char* fgets(char* string, int n, FILE* stream);
三个参数分别代表:
string
: 需要输入的字符串地址n
: 需要输入到第几位stream
: 读取的文件的文件指针
使用方法如下:
1234567891011121314151617181920212223
#include <stdio.h>
int main() {
// 程序运行前需要创建好 test2.txt, 并输入相应的内容
FILE* pf = fopen("test2.txt", "r");
if (pf == NULL) {
printf("打开文件失败\n");
return 0;
}
char ch[100] = { 0 };
fgets(ch, 3, pf);
printf("%s", ch);
fgets(ch, 3, pf);
printf("%s", ch);
fclose(pf);
pf = NULL;
return 0;
}
fgets
函数可以 自定义每次输入的字符长度 , 即 第二个参数 减 1
并且, 每次输入到内存中, 如果传参不变, 会将已经输入到内存中的数据覆盖
若, 传参大于文件中数据的长度, 则输入完整
fgets(ch, 3, pf);
>>>>>> fgets(ch, 100, pf);
2.3.4 格式化数据读写 Link to 2.3.4 格式化数据读写
这里的这个格式化, 不是格式化清空的意思, 而是 有一定格式的数据, 就是格式化数据. 比如, 结构体等自定义类型
格式化的读写, 需要用到这两个函数fscanf 格式化输入函数
和 fprintf 格式化输出函数
这两个函数, 与scanf和
printf`长得很像
其实不仅长得像, 用法也很类似:
fprintf()
Link to fprintf()
用结构体来举例:
123456789101112131415161718192021222324
#include <stdio.h>
struct student {
char name[20];
int age;
char sex[10];
};
int main() {
struct student xxs = {"July", 20, "male"};
FILE* pf = fopen("test.txt", "w");
if(pf == NULL) {
printf("文件打开失败\n");
return 0;
}
fprintf(pf, "%s %d %s", xxs.name, xxs.age, xxs.sex);
fclose(pf);
pf = NULL;
return 0;
}
程序运行结果如下:
同样的, 可以将文件指针改为 标准输出流 将内存中的数据输出到 屏幕上, 这里就不演示了
fscanf()
Link to fscanf()
还是用结构体来举例, 不过这次是将文件中的数据存入内存中:
PLAINTEXT
12345678910111213141516171819202122232425#include <stdio.h> struct student { char name[20]; int age; char sex[10]; }; int main() { struct student xxs = { 0 }; FILE* pf = fopen("test2.txt", "r"); if(pf == NULL) { printf("文件打开失败\n"); return 0; } fscanf(pf, "%s %d %s", xxs.name, &(xxs.age), xxs.sex); printf("%s %d %s", xxs.name, xxs.age, xxs.sex); fclose(pf); pf = NULL; return 0; }
程序运行结果如下:
通过两个例子可以看出, fprintf
和 fscanf
两个函数, 可以对内存或者文件中的 格式化的数据 进行读写的操作
并且, 两个函数的的使用方法与printf
scanf
两个函数的使用方法 十分的相似
2.3.5 二进制读写 Link to 2.3.5 二进制读写
二进制的读取和写入, 顾名思义, 就是将数据从内存以二进制的形式输出到文件(写入文件), 或者将二进制的数据从文件中写入到内存(读取文件)
fwrite()
Link to fwrite()
1
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream );
fwrite
函数, 数据从内存以二进制的形式输出到文件(写入文件)
此函数的参数表示的是:
const void* buffer
: 需要输出到文件的数据size_t size
: 需要写入的数据的类型(大小)size_t count
: 需要写入的数据的个数FILE* stream
: 文件流
二进制输出示例:
12345678910111213141516171819202122232425
#include <stdio.h>
struct Stu {
char name[20];
int age;
char sex[10];
};
int main() {
struct Stu stu[3] = { {"CSDN", 15, "Not"}, {"July", 19, "Male"}, {"Three", 20, "Male"} };
FILE* pf = fopen("data.txt", "wb"); //以二进制输出形式打开文件(写入文件的形式)
if (pf == NULL) {
printf("打开文件失败\n");
return 0;
}
fwrite(&stu, sizeof(struct Stu), 3, pf);
fclose(pf);
pf = NULL;
return 0;
}
文件以记事本打开, 发现数据存在乱码, 那么究竟是不是二进制数据呢?
以二进制编辑器打开, 可以发现确实是二进制数据
fread 二进制输入
Link to fread 二进制输入
1
size_t fread(void* buffer, size_t size, size_t count, FILE* stream);
二进制输入与二进制输出相反, 可以将文件中的二进制数据, 输入到内存中(读取文件中的二进制数据)
fread
函数的参数表示的是:
void* buffer
: 需要输入内存的地址size_t size
: 读取的文件中的数据的类型的大小size_t count
: 读取的数据的个数FILE* stream
: 需要读取的文件流
二进制输入(读取二进制数据)的示例:
12345678910111213141516171819202122232425262728293031
#include <stdio.h>
struct Stu {
char name[20];
int age;
char sex[10];
};
int main() {
struct Stu stu[3] = {0};
FILE* pf = fopen("data.txt", "rb"); //以二进制输入形式打开文件(读取文件的形式)
// 打开的文件就是 上边的示例文件
if (pf == NULL)
{
printf("打开文件失败\n");
return 0;
}
fread(&stu, sizeof(struct Stu), 3, pf);
printf("%s %d %s\n", stu[0].name, stu[0].age, stu[0].sex);
printf("%s %d %s\n", stu[1].name, stu[1].age, stu[1].sex);
printf("%s %d %s\n", stu[2].name, stu[2].age, stu[2].sex);
fclose(pf);
pf = NULL;
return 0;
}
介绍过这些对文件顺序读写操作函数后, 可以发现 这些函数每执行一次, 文件指针 就会自然而然地按照排列顺序移动至下一个读写对象
这也就是为什么被称为顺序读写的原因
2.4 文件的随机读写 Link to 2.4 文件的随机读写
上面介绍的都是对文件进行顺序读写操作时, 能使用到的函数
被称为顺序读写操作, 是因为以上函数的每一次执行, 文件指针就会 按顺序移动至下一个需要读写对象
那么, 随机读写又是因为什么呢?
这里的随机并不是不可定的意思, 而是 不用按照顺序的的意思
2.4.1 定位(指定)文件指针 Link to 2.4.1 定位(指定)文件指针
fseek()
Link to fseek()
1
int fseek(FILE *stream, long offset, int origin);
fseek
函数的功能是, 根据文件指针 的位置和偏移量 来定位文件指针(或 通过给定文件指针 的位置和偏移量 来指定文件指针的位置)
本函数的参数含义是:
FILE *stream
: 文件流long offset
: 偏移量就是需要指定 文件指针 从初始位置偏移的位数
int origin
: 文件指针 开始偏移的初始位置此参数 C语言 给定了三个宏:
SEEK_CUR
文件指针当前在文件流内容中的位置
即 不改变文件指针的位置, 使文件指针 从当前位置 开始偏移
SEEK_END
此文件流内容的末尾
即 将文件指针指向文件流内容的末字符之后, 使文件指针 从文件流内容的末位 开始偏移
SEEK_SET
此文件流内容的开始
即 将文件指针指向文件流内容的首位, 使文件指针 从文件流内容的首位 开始偏移
fseek
函数到底如何使用呢?具体作用究竟是什么呢?:
首先, 我们先创建一个文件(我这里路径是
D:\TEST.txt
), 并输入内容
当我们不使用
fseek
函数时, PLAINTEXT
1234567891011121314151617181920212223#include <stdio.h> #include <string.h> #include <errno.h> int main() { FILE* pf = fopen("D:\\TEST.txt", "r"); if (pf == NULL) { printf("fopen::%s", strerror(errno)); return 0; } int ch = 0; for (int i = 0; i < 10; i++) { // 进行 10 次循环 ch = fgetc(pf); printf("%c\n", ch); } fclose(pf); pf = NULL; return 0; }
这段代码的运行结果是:
此时, 文件指针应该在 文件内容的
k
字符上如果再使用
ch = fgetc(pf)
, 并输出ch
存入的字符, 将输出k
但是, 如果这时候我们使用
fseek
函数, 就可以将文件指针定位到文件内容的其他地方, 使文件指针指向的文件内容改变: PLAINTEXT
123456789101112131415161718192021222324252627282930#include <stdio.h> #include <string.h> #include <errno.h> int main() { FILE* pf = fopen("D:\\TEST.txt", "r"); if (pf == NULL) { printf("fopen::%s", strerror(errno)); return 0; } int ch = 0; for (int i = 0; i < 10; i++) { // 进行 10 次循环 ch = fgetc(pf); printf("%c\n", ch); } fseek(pf, 10, SEEK_CUR); //使文件指针, 从当前位置向后偏移 10 个字符 //fseek(pf, 15, SEEK_SET); //使文件指针, 从文件内容的首位, 向后偏移 15 个字符 //fseek(pf, -5, SEEK_END); //使文件指针, 从文件内容的末字符之后, 向后偏移 -5 个字符(向前偏移 5 个字符) ch = fgetc(pf); printf("ch = %c\n", ch); fclose(pf); pf = NULL; return 0; }
fseek
函数, 三次使用的运行结果 分别为:
fseek(pf, 10, SEEK_CUR);
文件指针从当前位置向后偏移 10 个字符, 到
u
fseek(pf, 15, SEEK_SET);
文件指针从文件内容的首位, 想后偏移 15 个字符, 到
p
fseek(pf, -5, SEEK_END);
文件字符从文件内容的末字符之后, 向 前 偏移 5 个字符, 到
v
2.4.2 获取偏移量 Link to 2.4.2 获取偏移量
因为对文件进行随机读写操作 可能造成操作者不知道 文件指针此时的位置
所以为了能够确定 文件指针此时指向的位置, 就可以使用 ftell
函数
ftell()
Link to ftell()
ftell
函数, 可以返回 文件指针相对于文件内容初始位置 的偏移量
1
long ftell(FILE *stream);
ftell
函数没有什么需要特别注意的地方, 了解一下如何使用就足够了:
PLAINTEXT
1234567891011121314151617181920212223242526#include <stdio.h> #include <string.h> #include <errno.h> int main() { FILE* pf = fopen("D:\\TEST.txt", "r"); if (pf == NULL) { printf("fopen::%s", strerror(errno)); return 0; } int ch = 0; ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); long ret = ftell(pf); printf("%ld\n", ret); fclose(pf); pf = NULL; return 0; }
上述代码的运行结果:
两次
fget(pf)
之后, 计算偏移量 为2
;
2.4.3 返回初始位置 Link to 2.4.3 返回初始位置
rewind()
Link to rewind()
1
void rewind(FILE *stream);
rewind
函数可以将 文件指针 重新指向 文件内容的初始位置
C
1234567891011121314151617181920212223242526272829303132#include <stdio.h> #include <string.h> #include <errno.h> int main() { FILE* pf = fopen("D:\\TEST.txt", "r"); if (pf == NULL) { printf("fopen::%s", strerror(errno)); return 0; } int ch = 0; ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); long ret = ftell(pf); printf("%ld\n", ret); rewind(pf); ret = ftell(pf); printf("%ld\n", ret); fclose(pf); pf = NULL; return 0; }
代码运行结果:
以上就是部分的文件操作函数, 并不是全部的文件操作函数, 但是文件操作函数就只介绍到这里
传统功夫, 以点到为止
如果想要了解 学习更多的 文件操作函数, 可以参考 Win32 API
或者 Cplusplus等网站自行学习