文件的操作
我们使用文件可以将数据直接存放在电脑的硬盘上,做到数据的持久化。硬盘上的文件是文件,但在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件的功能角度分类的)。
程序文件:
包括源程序文件,后缀为.c,目标文件(Windows环境后缀是.obj),可执行程序(Windows环境后缀为.exe)。
数据文件:
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
文件的打开和关闭
文件指针
缓冲文件系统中,关键的概念是”文件类型指针”,简称”文件指针”。每个被使用的文件都会在内存中开辟一个相应的文件信息区,用来存放文件的相关信息。这些信息被保存在结构体变量中,该结构体类型是有系统声明的,取名为FILE。
创建一个FILE*的指针变量:
FILE *pf;
定义pf是指向FILE类型数据的指针变量。通过文件指针变量可以找到与它关联的文件。
文件的打开和关闭
1.打开文件
FILE * fopen ( const char * filename, const char * mode );
功能:
打开文件
打开其名称在参数文件名中指定的文件,并将其与可在将来的操作中由返回的 FILE 指针标识的流相关联。
流上允许的操作以及执行这些操作的方式由 mode 参数定义。
如果已知返回的流不引用交互式设备,则默认情况下将完全缓冲。
返回的指针可以通过调用 fclose 或 freopen 与文件解除关联。所有打开的文件在正常程序终止时自动关闭。
运行环境支持至少同时打开FOPEN_MAX个文件。
参数:
filename:
包含要打开的文件的名称的 C 字符串。
其值应遵循运行环境的文件名规范,并且可以包含路径(如果系统支持)。
mode:
包含文件访问模式的 C 字符串。它可以是:
文件使用方式 | 含义 |
---|---|
“r” | 读:打开文件进行输入操作。该文件必须存在。 |
“w” | 写:为输出操作创建一个空文件。如果同名文件已存在,则丢弃其内容,并将该文件视为新的空文件。 |
“一” | 附加:打开文件以在文件末尾输出。输出操作始终在文件末尾写入数据,并将其展开。重新定位操作(fseek、fsetpos、rewind)将被忽略。如果该文件不存在,则创建该文件。 |
“r+” | 读取/更新:打开文件进行更新(用于输入和输出)。该文件必须存在。 |
“w+” | 写入/更新:创建一个空文件并将其打开以进行更新(用于输入和输出)。如果同名文件已存在,则丢弃其内容,并将该文件视为新的空文件。 |
“a+” | 追加/更新:打开一个文件进行更新(用于输入和输出),所有输出操作都在文件末尾写入数据。重新定位操作(fseek、fsetpos、倒带)会影响下一个输入操作,但输出操作会将位置移回文件末尾。如果该文件不存在,则创建该文件。 |
如果模式说明符位于上方,则该文件将作为文本文件打开。为了将文件作为二进制文件打开,模式字符串中必须包含“b”字符。这个额外的“b”字符可以附加在字符串的末尾(从而形成以下复合模式:“rb”,“wb”,“ab”,“r +b”,“w + b”,“a + b”),也可以插入在字母和混合模式的“+”符号之间(“rb +”,“wb+”,“ab +”)。
文本文件是包含文本行序列的文件。根据应用程序运行的环境,在文本模式下的输入/输出操作中可能会发生一些特殊的字符转换,以使其适应特定于系统的文本文件格式。尽管在某些环境中不会发生转换,并且文本文件和二进制文件的处理方式相同,但使用适当的模式可提高可移植性。
对于打开以进行更新的文件(包括“+”号的文件),允许输入和输出操作,在执行写入操作之后的读取操作之前,应刷新(fflush)或重新定位(fseek,fsetpos,rewind)。在读取操作之后的写入操作之前(每当该操作未到达文件末尾时),应重新定位流(fseek,fsetpos,倒带)。
返回值:
如果文件已成功打开,该函数将返回一个指向 FILE 对象的指针,该对象可用于在将来的操作中标识流。
否则,将返回空指针。
在大多数库实现中,errno 变量在失败时也设置为特定于系统的错误代码。
2.关闭文件
int fclose ( FILE * stream );
功能:
关闭文件
关闭与流关联的文件并解除其关联。
与流关联的所有内部缓冲区都将与其解除关联并刷新:写入任何未写入的输出缓冲区的内容,并丢弃任何未读输入缓冲区的内容。
即使调用失败,作为参数传递的流也将不再与文件或其缓冲区相关联。
参数:
stream:
指向指定要关闭的流的 FILE 对象的指针。
返回值:
如果流已成功关闭,则返回零值。
失败时,将返回 EOF。
如何在test.txt中写入hello world?
int main()
{
FILE *pf;
//打开文件
pf = fopen("D:/文件夹/桌面/test.txt", "w");
if (pf != NULL)
{
//fprintf(pf, "hello world\n"); //按格式化输入函数
//fputs("hello world", pf); //字符输出函数
fclose(pf);
}
return 0;
}
文件的随机读写
1.重新定位流位置指示器
int fseek ( FILE * stream, long int offset, int origin );
功能:
重新定位流位置指示器
将与流关联的位置指示器设置为新位置。
对于以二进制模式打开的流,通过将偏移量添加到由原点指定的参考位置来定义新位置。
对于在文本模式下打开的流,偏移量应为零或先前调用 ftell 返回的值,并且源必须SEEK_SET。
如果使用这些参数的其他值调用函数,则支持取决于特定的系统和库实现(不可移植)。
成功调用此函数后,将清除流的文件末尾内部指示器,并且删除以前对此流的 ungetc 调用的所有影响。
在打开进行更新(读+写)的流上,对 fseek 的调用允许在读取和写入之间切换。
参数:
stream:指向标识流的 FILE 对象的指针。
offset:
二进制文件:要从源偏移的字节数。
文本文件:零或 ftell 返回的值。
origin:
位置用作偏移的参考。它由中定义的以下常量之一指定,专门用作此函数的参数:
选项 | 参考位置 |
---|---|
SEEK_SET | 文件开头 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件结束 * |
返回值:
如果成功,该函数返回零。
否则,它将返回非零值。
如果发生读写错误,则设置误差指示器(ferror)。
例子:
int main()
{
FILE *pf;
//打开文件
pf = fopen("D:/文件夹/桌面/test.txt", "w");
fputs("我爱你", pf);
fseek(pf, 4, SEEK_SET); //将文件指针从当前位置修改到第四个字节处
fputs("中国!", pf);
fclose(pf);
return 0;
}
//桌面下的test.txt文件中显示 我爱中国!
2.获取流中的当前位置
long int ftell ( FILE * stream );
功能:
获取流中的当前位置
返回流的位置指示器的当前值。
对于二进制流,这是从文件开头开始的字节数。
对于文本流,数值可能没有意义,但仍可用于在以后使用 fseek 将位置恢复到相同的位置(如果有使用 ungetc 放回的字符仍然挂起读取,则行为未定义)。
参数:
stream:
指向标识流的 FILE 对象的指针。
返回值:
成功时,将返回仓位指标的当前值。
失败时,返回 -1L,并将 errno 设置为特定于系统的正值。
例子:
int main()
{
FILE *pf;
//打开文件
pf = fopen("D:/文件夹/桌面/test.txt", "w");
fputs("this is a test!", pf);
printf("%ld\n", ftell(pf)); //16
fseek(pf, 4, SEEK_SET);
printf("%ld\n", ftell(pf)); //4
fclose(pf);
return 0;
}
3.将流的位置设置为开头
void rewind ( FILE * stream );
功能:
将流的位置设置为开头
将与流关联的位置指示器设置为文件的开头。
成功调用此函数后,将清除与流关联的文件结尾和错误内部指示器,并且删除以前对此流的 ungetc 调用的所有影响。
在打开以进行更新(读+写)的流上,倒带调用允许在读取和写入之间切换。
参数:
steam:
指向标识流的 FILE 对象的指针。
返回值:
没有
例子:
int main()
{
FILE *pf;
//打开文件
pf = fopen("D:/文件夹/桌面/test.txt", "w");
fputs("this is a test!", pf);
printf("%ld\n", ftell(pf)); //16
rewind(pf);
printf("%ld\n", ftell(pf)); //0
fclose(pf);
return 0;
}
二进制文件和文本文件
数据在内存中以二进制的形式存储,如果不加转换就输出到外存就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符形式存储的文件就是文本文件。
文件读取结束的判定
文本文件是否读取结束
1.从流中获取字符
int fgetc ( FILE * stream );
功能:
从流中获取字符
返回指定流的内部文件位置指示器当前所指向的字符。然后,内部文件位置指示器将前进到下一个字符。
如果流在调用时位于文件末尾,则该函数将返回 EOF 并设置流的文件末尾指示符 (feof)。
如果发生读取错误,该函数将返回 EOF 并设置流(ferror)的错误指示器。
fgetc 和 getc 是等效的,除了 getc 在某些库中可以作为宏实现。
参数:
stream:指向标识输入流的 FILE 对象的指针。
返回值:
成功时,将返回字符读取(提升为 int 值)。
返回类型为 int 以适应特殊值 EOF,这表示失败:
如果位置指示器位于文件末尾,则该函数返回 EOF 并设置流的 eof 指示符 (feof)。
如果发生其他读取错误,该函数也会返回 EOF,但会改为设置其错误指示器(ferror)。
2.从流中获取字符串
char * fgets ( char * str, int num, FILE * stream );
功能:
从流中获取字符串
从流中读取字符并将其作为 C 字符串存储到 str 中,直到读取 (num-1) 字符或达到换行符或文件末尾,以先发生者为准。
换行符会使 fget 停止读取,但该函数将其视为有效字符,并包含在复制到 str 的字符串中。
终止空字符会自动追加到复制到 str 的字符之后。
请注意,fgets 与 gets 完全不同:fgets 不仅接受流参数,还允许指定 str 的最大大小,并在字符串中包含任何结尾换行符。
参数:
str:指向在其中复制字符串读取的字符数组的指针。
num:要复制到 str 中的最大字符数(包括终止空字符)。
stream:指向标识输入流的 FILE 对象的指针。stdin 可以用作从标准输入读取的参数。
返回值:
成功时,该函数返回 str。
如果在尝试读取字符时遇到文件结尾,则设置 eof 指示符 (feof)。如果这发生在读取任何字符之前,则返回的指针为空指针(并且 str 的内容保持不变)。
如果发生读取错误,则会设置错误指示器(ferror),并且还会返回空指针(但 str 所指向的内容可能已更改)。
3.写入要流式传输的字符
int fputc ( int character, FILE * stream );
功能:
写入要流式传输的字符
向流写入一个字符并前进位置指示器。
字符写在流的内部位置指示器指示的位置,然后自动前进一个。
参数:
character:
要写的字符的int推广。
写入时,该值在内部转换为无符号字符。
stream:
指向标识输出流的 FILE 对象的指针。
返回值:
成功时,将返回所写字符。
如果发生写入错误,则返回 EOF 并设置错误指示器(ferror)。
4.写入要流式传输的字符串
int fputs ( const char * str, FILE * stream );
功能:
写入要流式传输的字符串
将 str 指向流的 C 字符串写入流。
该函数开始从指定的地址 (str) 进行复制,直到到达终止空字符 (‘\0’)。此终止空字符不会复制到流中。
请注意,fput 不仅不同于可以指定目标流的放置,而且 fput 不会写入其他字符,而 put 会自动在末尾附加换行符。
参数:
str:
包含要写入流的内容的 C 字符串。
stream:
指向标识输出流的 FILE 对象的指针。
返回值:
成功时,将返回非负值。
出错时,该函数返回 EOF 并设置误差指示器 (ferror)。
5.打印错误消息
void perror ( const char * str );
功能:
打印错误消息
将 errno 的值解释为错误消息,并将其打印到 stderr(标准错误输出流,通常是控制台),可以选择将其前面加上 str 中指定的自定义消息。
errno 是一个整数变量,其值描述由调用库函数生成的错误条件或诊断信息(C 标准库的任何函数都可以为 errno 设置一个值,即使未在此引用中显式指定,即使没有发生错误),有关详细信息,请参阅 errno。
perror 生成的错误消息取决于平台。
如果参数 str 不是空指针,则打印 str 后跟冒号 (:)和一个空格。然后,无论 str 是否是空指针,都会打印生成的错误说明,后跟换行符 (‘\n’)。
perror 应该在产生错误后立即调用,否则它可能会被调用其他函数覆盖。
参数:
str:
C 字符串,其中包含要在错误消息本身之前打印的自定义消息。
如果它是空指针,则不会打印前面的自定义消息,但仍会打印错误消息。
按照约定,应用程序本身的名称通常用作参数。
返回值:
没有
6.检查错误指示器
int ferror ( FILE * stream );
功能:
检查错误指示器
检查是否设置了与流关联的错误指示器,如果设置了,则返回一个与零不同的值。
此指示器通常由流上一个失败的操作设置,并通过调用清除器、倒带或重新打开来清除。
参数:
stream:
指向标识流的 FILE 对象的指针。
返回值:
如果设置了与流关联的错误指示器,则返回非零值。
否则,将返回零。
7.检查文件结尾指示器
int feof ( FILE * stream );
功能:
检查文件结尾指示器
检查是否设置了与流关联的文件结尾指示器,如果设置了与零不同的值,则返回一个与零不同的值。
此指示器通常由流上尝试读取文件末尾或文件末尾的先前操作设置。
请注意,流的内部位置指示器可能指向下一个操作的文件末尾,但在操作尝试读取该点之前,仍可能无法设置文件结束指示器。
此指标通过调用清除器、倒带、fseek、fsetpos 或 freopen 来清除。虽然如果仓位指标没有被这样的调用重新定位,但下一个 i/o 操作很可能会再次设置指标。
参数:
stream:
指向标识流的 FILE 对象的指针。
返回值:
如果设置了与流关联的文件结尾指示器,则返回非零值。
否则,将返回零。
例子:
int main()
{
FILE *p1 = fopen("D:/文件夹/桌面/文本1.txt", "r");
if (!p1)
{
//将 errno 的值解释为错误消息,并将其打印到 stderr(标准错误输出流,通常是控制台)
perror("file opening faild"); //如果没有找到文本1文件,控制台打印 file opening faild: No such file or directory
return EXIT_FAILURE; //故障终止代码
}
char c;
//fgetc 当读取失败或者遇到文件结束,都会返回EOF
while ((c = fgetc(p1)) != EOF)
{
putchar(c);
}
//如果位置指示器位于文件末尾,则该函数返回 EOF 并设置流的 eof 指示符 (feof)。
//如果发生其他读取错误,该函数也会返回 EOF,但会改为设置其错误指示器(ferror)。
if (ferror(p1))
{
puts("I\O error when reading");
}
else if (feof(p1))
{
puts("End of file reached successfully");
}
fclose(p1);
return 0;
}
如何将文本1的内容写入到文本2?
int main()
{
FILE *fpIn = fopen("D:/文件夹/桌面/文本1.txt", "r");
FILE *fpOut = fopen("D:/文件夹/桌面/文本2.txt", "w+");
char c[100];
while (!feof(fpIn) && !ferror(fpIn))
{
fgets(c,99,fpIn);
fputs(c, fpOut);
}
fclose(fpIn);
fclose(fpOut);
return 0;
}
二进制文件读取是否结束,判断返回值是否小于实际要读的个数
fread判断返回值是否小于实际要读取的个数。
1.写入要流式传输的数据块
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
功能:
写入要流式传输的数据块
写入一个计数元素数组,每个元素的大小为字节,从 ptr 指向流中的当前位置的内存块。
流的位置指示器按写入的总字节数提前。
在内部,该函数解释所指向的块,就好像它是类型的元素数组一样,并按顺序将它们写入,就好像为每个字节调用一样。ptr(size*count)unsigned charstreamfputc
参数:
ptr:
指向要写入的元素数组的指针,转换为 const void*。
size:
要写入的每个元素的大小(以字节为单位)。
size_t是无符号整数类型。
count:
元素数,每个元素的大小为字节大小。
size_t是无符号整数类型。
stream:
指向指定输出流的 FILE 对象的指针。
返回值:
将返回成功写入的元素总数。
如果此数字与 count 参数不同,则写入错误会阻止函数完成。在这种情况下,将为流设置误差指示器(ferror)。
如果大小或计数为零,则该函数返回零,并且错误指示符保持不变。
size_t是无符号整数类型。
2.从流中读取数据块
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
功能:
从流中读取数据块
从流中读取计数元素数组,每个元素的大小为字节,并将它们存储在 ptr 指定的内存块中。
流的位置指示器按读取的总字节数提前。
如果成功,则读取的总字节数为(大小*计数)。
参数:
ptr:
指向大小至少为(大小
计数)字节的内存块的指针,转换为 void
。
size:
要读取的每个元素的大小(以字节为单位)。
size_t是无符号整数类型。
count:
元素数,每个元素的大小为字节大小。
size_t是无符号整数类型。
stream:
指向指定输入流的 FILE 对象的指针。
返回值:
将返回成功读取的元素总数。
如果此数字与 count 参数不同,则表示读取时出现读取错误或到达文件末尾。在这两种情况下,都设置了正确的指示器,可以分别用铁和feof进行检查。
如果大小或计数为零,则该函数返回零,并且 ptr 所指向的流状态和内容保持不变。
size_t是无符号整数类型。
例子:
#define SIZE 5
int main()
{
double a[SIZE] = { 1.0, 2.0, 3.0, 4.0, 5.0 };
FILE *fp = fopen("D:/文件夹/桌面/二进制1.bin", "wb");
fwrite(a, sizeof(a[0]), SIZE, fp); //Write block of data to stream
fclose(fp);
double b[SIZE];
fp = fopen("D:/文件夹/桌面/二进制1.bin", "rb");
size_t count = fread(b, sizeof(b[0]), SIZE, fp);
if (count == SIZE) //读取成功
{
puts("Array read successfully\n");
//打印
for (int i = 0; i < SIZE; ++i)
{
printf("%f ", b[i]);
}
printf("\n");
}
else
{
if (feof(fp))
{
printf("Unexpected end of file\n");
}
else if (ferror(fp))
{
perror("Error reading test.bin");
}
}
fclose(fp);
return 0;
}
如何将图片1复制粘贴为图片2?
int main()
{
FILE *fpIn = fopen("D:/文件夹/桌面/图片1.bmp", "rb");
FILE *fpOut = fopen("D:/文件夹/桌面/图片2.bmp", "wb");
int temp[100];
while (!feof(fpIn))
{
fread(temp, sizeof(int), 100, fpIn);
fwrite(temp, sizeof(int), 100, fpOut);
}
fclose(fpIn);
fclose(fpOut);
return 0;
}
文件缓冲区
ANSIC标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”,从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的.
冲洗流
int fflush ( FILE * stream );
功能:
冲洗流
如果给定流已打开以进行写入(或者如果它已打开以进行更新,并且最后一个 i/o 操作是输出操作),则其输出缓冲区中的任何未写入数据都将写入该文件。
如果流是空指针,则刷新所有此类流。
在所有其他情况下,行为取决于特定的库实现。在某些实现中,刷新打开以进行读取的流会导致其输入缓冲区被清除(但这不是可移植的预期行为)。
此调用后,流将保持打开状态。
当文件关闭时,由于调用关闭或程序终止,将自动刷新与其关联的所有缓冲区。
参数:
stream:
指向指定缓冲流的 FILE 对象的指针。
返回值:
零值表示成功。
如果发生错误,则返回 EOF 并设置错误指示器。