[C语言] C语言能对文件进行哪些操作?
文件操作
一. 文件的分类
-
程序文件
比如:
C语言的源程序文件(
.c
为后缀的文件 )目标文件( 在Windows环境中 以
.obj
为后缀 )可执行程序文件( 在Windows环境中 以
.exe
为后缀 ) -
数据文件
数据文件的内容,不一定是程序。可以是程序运行时所需要读取、改变的数据。
比如:
存在两个源文件
test1.c
test2.c
如果
test1.c
文件可以对test2.c
文件中的数据进行读取等操作,那么test1.c
就是程序文件,test2.c
就是数据文件。
PS:以下讨论的均为数据文件
二. 文件的操作
2.1 文件指针
每次打开一个文件,计算机都会在内存中开辟一块区域来存放该文件的各种信息(比如文件名、文件的状态、文件的地址、文件的大小等)。
这些信息都存放在一个结构体变量中,此结构体变量的类型默认被系统声明为
FILE
。所以,被使用文件的文件信息区,本质上就是一个FILE
类型的结构体变量。每当一个文件打开后,计算机会自动根据文件的状态、情况自动生成一个
FILE
类型的结构体变量,并存入该文件的各种信息。
FILE
类型的具体成员,内容。在不同的编译器中是不完全相同的,但是差别不大
FILE
类型定义的结构体指针变量,就是一个文件指针变量FILE* pf //pf 文件指针变量
pf
是一个指向 FILE
类型数据的指针变量,可以指向某个文件的文件信息区,通过文件信息区中存放的信息可以进一步访问该文件。所以,通过文件指针变量能够找到与其相关联的文件。
2.2 文件的打开与关闭
fopen(文件打开函数)
及 fclose(文件关闭函数)
fopen 文件打开
FILE* fopen( const char *filename, const char *mode );
第一个参数
filename
,应该传入 需打开文件的文件名尽量详细需要打开的文件名,如:
C:\\Program Files\\TEST.c
若只传入
TEST.c
,只会默认打开(创建).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 文件关闭
int fclose( FILE* stream );
参数的类型是
FILE*
的指针变量,此指针变量 需指向 已打开文件的文件信息区的地址例如
#include
int main () { //打开文件 FILE * pf = fopen ("test.txt","w"); if (pf != NULL) { //文件操作 //………… //关闭文件 fclose (pf); pf = NULL; } return 0; }
2.3 文件的顺序读写
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 单个字符读写
fputc
输出字符函数
#include
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
函数,向屏幕输出字符的时候,只需要把目标文件地址改为 标准输出流 就可以了:#include
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
输入字符函数
.c
源文件的路径下创建 test2.txt
文件,并输入相应的内容#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
并输出,是因为fgetc
读取成功的返回值就是读取的内容,屏幕上也输出了 a
、b
、c
、d
。fgetc
函数的返回值就是 读取到的字符的 ASCII
值。ch
为什么不用 char
类型呢?读取的内容不是字符吗?用 char
类型的变量来接收也可以吗?fgetc
将读取到的字符以 int
类型返回 或者 返回 EOF
,表示读取错误 或 文件结尾。fgetc
的返回值,并不一定全都是 字符,也有可能是 EOF
。所以我们要用 int
类型的变量接收。
fgetc
函数,传参传入的是 需要被存放到内存中的 文件的数据 的地址。并且,在程序执行时 屏幕上输出的内容是不同的,意味着fgetc
函数读到的数据是不同的,但传入的参数均为变量pf
, 这就说明,fgetc
函数会将传入的地址向后移动一位(移动到下一次需要读取的数据的地址)然后我们回过头来发现,上边我们使用
fputc
字符输出函数的时候,每次传入的参数也是同一个变量,但是输出的字符位置却不一样,所以fputc
函数的使用,也会将传入的地址向后移动一位,以便下一次输出不覆盖之前的输出。
2.3.3 整行字符读写
fputs 文本行输出函数
fputs
函数 与fputc
函数的使用方法类似,只不过本函数是输出一行,而另一个是输出单个字符以下示例:
#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 文本行输入函数
fgets
函数的使用方法,就与fgetc
函数有很大的不同了。char* fgets(char* string, int n, FILE* stream);
三个参数分别代表:
string
:需要输入的字符串地址n
:需要输入到第几位stream
:读取的文件的文件指针使用方法如下:
#include
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 格式化数据读写
fscanf 格式化输入函数
和 fprintf 格式化输出函数
scanf
和 printf
长得很像。其实不仅长得像,用法也很类似:fprintf 格式化输出函数
用结构体来举例:
#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 格式化输入函数
还是用结构体来举例,不过这次是将文件中的数据存入内存中:
#include
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 二进制读写
fwrite 二进制输出
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
:文件流
#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 二进制输入
size_t fread(void* buffer, size_t size, size_t count, FILE* stream );
fread
函数的参数表示的是:
void* buffer
:需要输入内存的地址size_t size
:读取的文件中的数据的类型的大小size_t count
:读取的数据的个数FILE* stream
:需要读取的文件流
#include
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 文件的随机读写
2.4.1 定位(指定)文件指针
fseek
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
函数时,#include
#include #include 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
函数,就可以将文件指针定位到文件内容的其他地方,时文件指针指向的文件内容改变:#include
#include #include 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 返回偏移量
ftell
函数。ftell
ftell
函数,可以返回 文件指针相对于文件内容初始位置 的偏移量long ftell( FILE *stream );
ftell
函数没有什么需要特别注意的地方,了解一下如何使用就足够了:#include
#include #include 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 返回初始位置
rewind
void rewind( FILE *stream );
rewind
函数可以将 文件指针 重新指向 文件内容的初始位置。#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 等网站自行学习作者: 哈米d1ch 发表日期:2022 年 3 月 2 日