在学习文件之前,我们得先了解什么叫做文件名。
所谓的文件名包括三个部分:文件路径+文件名主干+文件后缀
例如:c:\code\test.txt这个就是一个文件名
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE
文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。
下面是能在文件中使用的方式:
文件使用方式 | 含义 | 如果指定文件不存在 |
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a”(追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
“ab”(追加) | 向一个二进制文件尾添加数据 | 建立一个新的文件 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建议一个新的文件 | 建立一个新的文件 |
“a+”(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
“rb+”(读写) | 为了读和写打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
“ab+”(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新的文件 |
不知道大家发现了什么规律没有:凡是带 r 的都是关于二进制的,凡是带 a 的如果指定文件不存在则出错,其他的都是建立一个新的文件
下面简单写个代码介绍:
#include <stdio.h>
int main ()
{
FILE * pFile;
//打开文件
pFile = fopen ("myfile.txt","w");//如果没有这个文件则新建一个
//文件操作
if (pFile!=NULL)//如果这个文件不为空进入,并写入下面这段代码,然后关闭
{
fputs ("fopen example",pFile);
//关闭文件
fclose (pFile);
}
return 0;
}
文件的顺序读写
功能 | 函数名 | 适用于 |
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输出 | fwrite | 文件 |
对比一组函数:
fgetc和fputc
fgetc():
- 功能:从指定的文件流(FILE类型指针)中读取一个字符。
- 返回值:成功时返回读取到的字符,并将其转换为整型(int类型),通常可以赋值给char变量。如果到达文件末尾或者发生错误,则返回EOF(通常定义为-1)。
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("pf");
return 1;
}
int ch;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c", ch);
}
fclose(pf);
pf = NULL;
}
首先我们得保证test.txt文件存在才能使用 r ,如果没有可以现在当前路径下创建一个,然后在指定的test.txt文件里面存放需要的字符,然后才能将数据打印在屏幕上,不然屏幕上啥也没有。
fputc():
- 功能:向指定的文件流(FILE类型指针)中写入一个字符。
- 返回值:成功时返回写入的字符(作为整型),如果发生错误则返回EOF。
int main()
{
FILE* pf = fopen("test2.txt","w");
if (pf == NULL)
{
perror("pf");
return 1;
}
int ch = 'A';
int ret = fputc(ch, pf);//将字符'A'写入到文件里面
fclose(pf);
pf = NULL;
return 0;
}
这里使用的是“w”可以不新建文件,然后我们将字符“A”输入到文件“test2.txt”里面去,运行一下,然后打开指定文件就可以看到里面多了一个字符“A”了。
总结来说,fgetc()
用于从文件读取单个字符,而 fputc()
用于向文件写入单个字符。
fgets和fputs
fgets():
功能:从指定的文件流(FILE类型指针)读取一个字符串,并将其存储到缓冲区中。读取时会考虑换行符('\n'),如果读取到了换行符或到达了指定的最大字符数(包括结束的空字符 '\0'),则停止读取。
函数原型:
char *fgets(char *str, int n, FILE *stream);
其中:
str
是指向接收读取内容的字符数组的指针。n
指定要读取的最大字符数,不包括终止的空字符 '\0'。stream
是指向打开的文件流的指针。-
int main() { FILE* pf = fopen("test3.txt", "r"); if (pf == NULL) { perror("pf"); return 1; } char arr[20]; if(fgets(arr, sizeof(arr), pf)); { printf("%s", arr);// 输出读取到的字符串 } fclose(pf); pf = NULL; return 0; }
对于这段代码我是这样理解的:首先得有一个test3.txt文件,然后用将pf里面的内容输出最大sizeof(arr)个字符到arr里面去,最后打印arr,也就是test3.txt里面的内容
fputs():
功能:将一个字符串写入到指定的文件流(FILE类型指针)。它不会自动添加换行符,若需要在字符串后添加换行符,应手动包含在字符串内。
函数原型:
int fputs(const char *str, FILE *stream);
其中:
str
是指向要写入的字符串的指针。stream
是指向打开的文件流的指针。
int main()
{
FILE* pf = fopen("test4.txt","w");
if (pf == NULL)
{
perror("pf");
return 1;
}
char* ch = "hello world";
fputs(ch, pf);
fclose(pf);
pf = NULL;
return 0;
}
这段代码是将ch的内容通过fputs存放到pf里面,打开test4.txt即可看见这段“hello world”
fscanf和fprintf
fscanf():
功能:从指定的文件流(FILE类型指针)读取格式化的数据。
函数原型:
int fscanf(FILE *stream, const char *format, ...);
其中:
stream
指向要读取的已打开文件流。format
是一个格式字符串,它告诉函数如何解析和解释从文件读取的数据。- 可变参数列表根据
format
中指定的格式接收并存储读取到的数据。
int main()
{
char c[20];
int a;
double d;
FILE* pf = fopen("test5.txt", "r");
if (pf == NULL)
{
perror("pf");
return 1;
}
fscanf(pf, "%s %d %lf", c, &a, &d);
printf("%s %d %lf", c, a, d);
fclose(pf);
pf = NULL;
return 0;
}
可以这样理解:第一个参数为存放数据的文件,第二个参数为打印参数的格式,第三个参数为参数的地址(因为数组名就是首元素的地址,所以c不需要使用&取地址操作符)
fprintf():
功能:将格式化的数据写入到指定的文件流(FILE类型指针)。
函数原型:
int fprintf(FILE *stream, const char *format, ...);
其中:
stream
指向要写入的已打开文件流。format
是一个格式字符串,它告诉函数如何格式化输出的数据。- 可变参数列表提供了需要按照
format
格式化并写入文件的数据。
int main()
{
FILE* pf = fopen("test6.txt", "w");
if (pf == NULL)
{
perror("pf");
return 1;
}
int a = 10;
char* str = "abcdef";
double d = 3.14;
fprintf(pf, "%d %s %lf", a, str, d);
fclose(pf);
pf = NULL;
return 0;
}
第一个参数可以理解为要将数据存放到哪个文件里面去,第二个参数可以理解打印参数的格式,第三个则就是数据
fread和fwrite
fread():
功能:从指定的文件流(FILE类型指针)读取指定长度的数据块到内存缓冲区。
函数原型:
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
其中:
ptr
指向用于接收数据的缓冲区地址。size
表示每个元素的大小(以字节为单位)。count
表示要读取的元素数量。stream
是指向已打开文件的FILE指针。
int main()
{
FILE* pf = fopen("test7.bin", "rb");
if (pf == NULL)
{
perror("pf");
return 1;
}
int buffer[10];
size_t k = fread(buffer, sizeof(int), 10, pf);
if (k != 10)
{
fprintf(stderr, "Error reading from file.\n");
}
fclose(pf);
pf = NULL;
return 0;
}
fwrite():
功能:将内存缓冲区中的指定长度的数据块写入到指定的文件流(FILE类型指针)。
函数原型:
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
其中参数含义与 fread()
相似,只是方向相反:
ptr
指向包含要写入数据的缓冲区地址。size
表示每个元素的大小(以字节为单位)。count
表示要写入的元素数量。stream
是指向已打开文件的FILE指针。
int main()
{
FILE* fp = fopen("test8.bin", "wb");
if (fp == NULL) {
perror("Error opening file");
return 1;
}
int buffer[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
size_t items_written = fwrite(buffer, sizeof(int), 10, fp);
if (items_written != 10)
{
fprintf(stderr, "Error writing to file.\n");
}
fclose(fp)
return 0;
}
总结起来,fread()
用于从二进制文件中读取数据块,而 fwrite()
则用于向二进制文件中写入数据块。这两个函数都适合处理结构化数据或不关心字符编码的原始数据,它们根据指定的元素大小和数量高效地移动数据。
sscanf和sprintf
sscanf
和 sprintf
是C语言中用于处理字符串输入输出的标准库函数,它们与文件输入输出的 fscanf
和 fprintf
类似,但操作的对象是内存中的字符串而不是文件。
sscanf():
功能:从给定的字符串中读取格式化的数据。
函数原型:
int sscanf(const char *str, const char *format, ...);
其中:
str
指向包含要解析数据的字符串。format
是一个格式字符串,它告诉函数如何解析和解释字符串中的数据。- 可变参数列表根据
format
中指定的格式接收并存储读取到的数据。
int main()
{
char input[] = "123 abc XYZ";
int number;
char str[50];
if (sscanf(input, "%d %s", &number, str) == 2) {
printf("Number: %d\nString: %s\n", number, str);
}
else {
printf("Error parsing string.\n");
}
return 0;
}
sprintf():
功能:将格式化的数据写入到一个字符数组中,生成格式化的字符串。
函数原型:
int sprintf(char *str, const char *format, ...);
其中:
str
指向用于存放格式化后字符串的缓冲区地址。format
是一个格式字符串,它指导函数如何格式化输出的数据。- 可变参数列表提供了需要按照
format
格式化并放入字符串的数据。
int main()
{
int a = 10;
double b = 3.14;
char c[20] = "Hello, World!";
char buffer[100];
sprintf(buffer, "%d %f %s", a, b, c);
printf("Formatted string: %s\n", buffer); // 输出:10 3.140000 Hello, World!
return 0;
}
总结起来,sscanf()
用于从字符串中提取格式化数据,而 sprintf()
则用于创建一个格式化的字符串。这两个函数都需要一个格式字符串来指导如何解析或格式化数据,并且在处理字符串时要注意防止缓冲区溢出,确保目标字符串足够大以容纳所有格式化后的数据。
文件的随机读写
fseek
作用:根据文件指针的位置和偏移量来定位文件指针
int fseek ( FILE * stream, long int offset, int origin );
stream
:指向已打开文件的FILE结构体指针,即你想移动文件读写位置的那个文件。offset
:从whence参数所指定的位置开始偏移的字节数。正数表示向前移动(文件末尾方向),负数表示向后移动(文件开头方向)。whence
:指定起始点,有以下三种可能的值:SEEK_SET
(或0):从文件开始处偏移offset个字节。SEEK_CUR
(或1):从当前文件位置处偏移offset个字节。SEEK_END
(或2):从文件结尾处偏移offset个字节。
返回值:
- 如果成功,
fseek()
返回0,表示文件位置指示器成功地移动到了新的位置。 - 如果失败,例如如果文件不是随机访问类型或者发生其他错误,
fseek()
将返回一个非零值,并且errno会被设置为相应的错误代码。
如果不明白请看下面这段代码:
int main()
{
FILE* pFile;
pFile = fopen("example.txt", "wb");
fputs("This is an apple.", pFile);
fseek(pFile, 9, SEEK_SET);
fputs(" sam", pFile);
fclose(pFile);
return 0;
}
最开始的指针是指向T这个位置的,然后向后偏移了9字节,指向了an的n位置,然后将n ap(共4个字节)替换成了 sam(注意:空格也得算进入)
这是最开始的: 这是替换后的:
ftell
作用:返回文件指针相对于起始位置的偏移量
函数原型:
long int ftell(FILE *stream);
stream
:指向已打开文件的FILE结构体指针,你想获取其当前位置的那个文件。
返回值:
- 如果成功,
ftell()
返回当前文件位置指示器相对于文件开始处的字节数。 - 如果失败,例如如果文件不是随机访问类型或者发生其他错误,
ftell()
将返回 -1L,并且errno会被设置为相应的错误代码。
int main()
{
FILE* pf = fopen("myfile.txt", "rb");
if (pf == NULL)
{
perror("Error opening file");
}
fseek(pf, 0, SEEK_END);
long size = ftell(pf);
printf("%ld", size);
return 0;
}
ftell可以用来当不知道指针指向哪里的时候,用它来计算相对于起始位置的字节数,这样就可以清楚位置了。
rewind
作用:让文件指针的位置回到文件的起始位置
函数原型:
void rewind(FILE *stream);
stream
:指向已打开文件的FILE结构体指针,你想将其内部指针重置到文件起始位置的那个文件。
该函数没有返回值,直接将文件读写位置移动到文件开头。这相当于调用 fseek(stream, 0, SEEK_SET)
。
int main()
{
FILE* pf = fopen("data.txt", "w+");
int i;
char buffer[27];
for (i = 'A'; i <= 'Z'; i++)
{
fputc(i, pf);
}
rewind(pf);
fread(buffer, 1, 26, pf);
fclose(pf);
buffer[26] = '\0';
puts(buffer);
return 0;
}
feof
在文件读取过程中,不能用feof函数的返回值直接来判断文件是否结束。
feof 的作用是:当文件读取结束的时候,判断读取结束的原因是否是:遇到文件尾结束。
1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:
fgetc 判断是否为 EOF .
fgets 判断返回值是否为 NULL .
2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
fread判断返回值是否小于实际要读的个数。