该系列为本人的学习笔记,主要由本人整理书写而成。部分内容来自教材、视频课程等,不能保证完全原创性。
萌新的学习笔记,写错了恳请斧正。
# 文件的随机读写
文件的随机读写是指我们可以控制文件位置指示器(光标)的位置,以完成复杂的读写操作
# fseek
#include <stdio.h> | |
int fseek( FILE* stream, long offset, int origin ); |
fseek 函数用于移动文件位置指示器(光标)的位置,移动成功则返回 0,发生错误则光标位置不变、返回非 0 整数并设置流结构体上的错误指示器。
其中,origin 是基准位置,可以取值有:SEEK_SET(文件头)、SEEK_CUR(当前光标位置)、SEEK_END(文件尾),一般这三个值分别是 0、1、2(最好不要这样写)。而 offset 为偏移的字节数,代表以 origin 为基准偏移特定字符,比方说 origin 为 SEEK_CUR 且 offset 为 - 1 时,就将光标左移一个字节。
# 使用实例
文件被写入 “This is a sample.”
#include <stdio.h> | |
#include <stdlib.h> | |
int main() | |
{ | |
FILE* pf = fopen("file.txt", "w"); | |
if (pf == NULL) | |
{ | |
perror("fopen"); | |
return EXIT_FAILURE; | |
} | |
fputs("This is an apple.", pf); | |
fseek(pf, 9, SEEK_SET); | |
fputs(" sam", pf); | |
fclose(pf); | |
pf = NULL; | |
return EXIT_SUCCESS; | |
} |
# ftell
#include <stdio.h> | |
long ftell( FILE* stream ); |
ftell 用于返回此时文件位置指示器相对文件头的偏移量
# 使用实例
#include <stdio.h> | |
#include <stdlib.h> | |
int main() | |
{ | |
FILE* pf = fopen("file.txt", "w"); | |
if (!pf) | |
{ | |
perror("fopen"); | |
return EXIT_FAILURE; | |
} | |
fputs("1145141919810", pf); | |
printf("%d\n", ftell(pf)); | |
fseek(pf, 0, SEEK_SET); | |
printf("%d\n", ftell(pf)); | |
fseek(pf, 7, SEEK_CUR); | |
printf("%d\n", ftell(pf)); | |
return EXIT_SUCCESS; | |
} |
# rewind
#include <stdio.h> | |
void rewind( FILE* stream ); |
rewind 函数用于将文件位置指示器返回到文件起始位置
效果等于 offset 为 0 且 origin 为 SEEK_SET 的 fseek 函数
# 文件读取结束或失败的判定
# feof
#include <stdio.h> | |
int feof( FILE *stream ); |
feof 检查流结构体上的文件尾指示器,如果指示器被设置则返回非 0 值,否则返回 0
feof 函数的作用是:当文件读取结束,判断结束的原因是否是 “遇到文件尾”
请不要在读取过程中使用 feof 的返回值来判断文件是否读到结束
# ferror
#include <stdio.h> | |
int ferror( FILE* stream ); |
ferror 检查流结构体上的错误指示器,如果指示器被设置则返回非 0 值,否则返回 0
# 典型使用方式
先判断读取是否结束,如果读取结束则通过 feof 和 ferror 判断是遇到文件尾结束还是出错
- 判断文本文件是否结束:判断返回值是否为 EOF(fgetc)或者 NULL(fgets)
- 判断二进制文件读取结束:判断 fread 返回值是否小于实际要读的个数
比方说我们处理文本文件可以:
#include <stdio.h> | |
#include <stdlib.h> | |
int main() | |
{ | |
FILE* fp = fopen("test.txt", "r"); | |
if (!fp) | |
{ | |
perror("File opening failed"); | |
return EXIT_FAILURE; | |
} | |
int c; // 注意:int,非 char,要求处理 EOF | |
while ((c = fgetc(fp)) != EOF) // 标准 C I/O 读取文件循环 | |
putchar(c); | |
if (ferror(fp)) | |
puts("I/O error when reading."); | |
else if (feof(fp)) | |
puts("End of file reached successfully."); | |
fclose(fp); | |
fp = NULL; | |
return EXIT_SUCCESS; | |
} |
处理二进制文件可以:
#include <stdio.h> | |
#include <stdlib.h> | |
enum { SIZE = 5 }; | |
double a[SIZE] = { 1.,2.,3.,4.,5. }; | |
double b[SIZE]; | |
int main() | |
{ | |
FILE* fp = fopen("test.bin", "wb"); // 必须用二进制模式 | |
if (!fp) | |
{ | |
perror("fopen-wb"); | |
return EXIT_FAILURE; | |
} | |
fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组 | |
fclose(fp); | |
fp = fopen("test.bin", "rb"); | |
if (!fp) | |
{ | |
perror("fopen-rb"); | |
return EXIT_FAILURE; | |
} | |
size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组 | |
if (ret_code == SIZE) | |
{ | |
puts("Array read successfully, contents: "); | |
for (int n = 0; n < SIZE; ++n) printf("%f ", b[n]); | |
putchar('\n'); | |
} | |
else | |
{ | |
if (feof(fp)) | |
printf("Error reading test.bin: unexpected end of file\n"); | |
else if (ferror(fp)) | |
perror("Error reading test.bin"); | |
} // error handling | |
fclose(fp); | |
fp = NULL; | |
return EXIT_SUCCESS; | |
} |
# 文件缓冲区
ANSIC 标准采用 “文件缓冲系统” 来处理数据文件。
也就是说,系统会在内存中为每个打开的文件流创建一个 “文件缓冲区”。内存中运行的程序如果想对外存中的文件进行 I/O(输入输出)操作,传递的信息就需要通过输入缓冲区和输出缓冲区。
在不刷新缓冲区的情况下,信息只有在填满整个输入 / 输出缓冲区后,整个缓冲区的信息才会整个打包发送给程序 / 文件,否则信息将滞留在缓冲区直到被刷新。
刷新缓冲区是指将已经写到输入缓冲区的信息冲入、已经写到输出缓冲区的信息冲出;将尚未写入缓冲区的信息舍弃
由于缓冲区的存在,我们在更新模式(存在标签 "+")下打开文件时:
- 若中间没有 fflush 函数或文件定位函数,则输出后不应有输入
- 若中间没有文件定位函数且输入操作没有遇到文件尾,则输入后不应有输出
# fflush
#include <stdio.h> | |
int fflush( FILE* stream ); |
fflush 函数用于刷新缓冲区,成功返回 0,否则返回 EOF 并设置流结构体的错误指示器
如果输入空指针,则刷新所有缓冲区