文件基本操作 (C语言)
一切皆文件
---Linux
- 头文件 <stdio.h> 中定义了文件的相关操作
#include <stdio.h>
文件操作基本流程: 打开:fopen 相关操作 关闭:fclose fopen函数原型:FILE *fopen(const char *filename, const char *mode) fopen函数打开filename指定的文件, 并返回一个与之相关联的流。 如果打开操作失败,则返回 NULL 访问模式mode可以为下列合法值之一: "r" 打开文本文件用于读 "w" 创建文本文件用于写, 并删除已经存在的内容(如果有的话) "a" 追加, 打开或创建文本文件,并向文件末尾追加内容 "r+" 打开文本文件用于更新(即读和写) "w+" 创建文本文件用于更新, 并删除已经存在的内容(如果有的话) "a+" 追加, 打开或创建文本文件用于更新, 写文件时追加到文件末尾 ps:如果在上述访问模式之后加上 b , 如 "rb" 或 "w+b" 等,则表示对二进制文件进行操作 fclose函数原型:int fclose(FILE *fp) fclose函数的功能是关闭 fp 指向的文件 正常关闭返回0, 出错时返回非0 ps:文件操作结束不关闭文件可能丢失数据 |
- 什么是文本文件?
在文本文件中数据是以字符形式呈现的,每个字符占用一个字节,而字节在计算机中又是以ASCII码来识别的
在存储文本文件时需要先将ASCII码转换为二进制的形式,然后进行存储
比如存储12的时候会按照字符 '1' 和 '2' 来存储
'1' 的ASCII码为49 转化为二进制为 00110001 '2' 的ASCII码为50 转化为二进制为 00110010
存储形式为 [00110001][00110010]
- 什么是二进制文件?
二进制文件在存储数据时是直接以二进制的方式进行的, 存储方式与数据在内存中的存储方式相同
不需要进行转换
优点:不仅可以提高执行效率(比如从磁盘直接读取到内存不需要数据转换),还可以节约存储空间
比如存储char类型的数字12则会将其二进制数 00001100直接进行存储
存储i形式为 [00001100]
- 文本文件和二进制文件的区别(Windows)
文本文件:写入的时候会将换行符 '\n'(ASCII: 10) 解析为回车符 '\r'(ASCII:13)'\n'(ASCII:10)
读取的时候又会将回车符 '\r'(ASCII:13)'\n'(ASCII:10)解析成换行符 '\n'(ASCII: 10)
二进制文件:原样写入读出
- Linux系统下没有区别
- 三个特殊的文件
stdin :标准输入文件指针,系统分配为键盘
stdout :标准输出文件指针,系统分配为显示器
stderr :标准错误输出文件指针,系统分配为显示器
在文件操作时,系统自动与3个标准设备文件联系,这3个文件无需打开和关闭
从文件中输入和向文件输出有两个对应函数:
fprintf
函数原型为
int fprintf (FILE *, const char *, ...);
fscanf
函数原型为
int fscanf (FILE *, const char *, ...);
例如 printf("hello world!\n"); 等价于 fprintf(stdout, "hello world\n");
scanf("%d", &num); 等价于 fscanf(stdin, "%d", &num);
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main() 5 { 6 int num; 7 fscanf(stdin, "%d", &num); 8 fprintf(stdout, "%d\n", num); 9 return 0; 10 }
- 文件型结构体
C语言提供“文件型”结构来标识记录待操作文件的信息,该结构体定义在头文件 stdio.h 中
形式为:
1 typedef struct _iobuf 2 { 3 char *_ptr; /* 当前缓冲区内容指针 */ 4 int _cnt; /* 缓冲区还有多少个字符 */ 5 char *_base; /* 缓冲区的起始地址 */ 6 int _flag; /* 文件流的状态,是否错误或者结束 */ 7 int _file; /* 文件描述符 */ 8 int _charbuf; /* 双字节缓冲,缓冲2个字节 */ 9 int _bufsiz; /* 缓冲区大小 */ 10 char *_tmpfname; /* 临时文件名 */ 11 } FILE;
- 文件基本操作
打开与关闭文件 fopen fclose
1 #include <stdio.h> 2 #include <stdlib.h> 3 #define FILE_NAME "./1.txt" /* 存在的文件 */ 4 #define FILE_NAME_NO "./2.txt" /* 不存在的文件 */ 5 int main() 6 { 7 FILE *fp = fopen(FILE_NAME, "r"); 8 if(!fp){ 9 perror("|open file failed"); 10 return 1; 11 } 12 printf("|open the file %s successfully\n", FILE_NAME); 13 int i = fclose(fp); /*关闭成功返回 0*/ 14 printf("|%d\n", i); 15 16 FILE *fp_no = fopen(FILE_NAME_NO, "r"); 17 if(!fp_no){ 18 perror("|open file failed"); 19 return 1; 20 } 21 printf("|open the file %s successfully\n", FILE_NAME_NO); 22 int j = fclose(fp_no); 23 printf("|%d\n", j); 24 return 0; 25 }
输出
|open the file ./1.txt successfully |0 |open file failed: No such file or directory
字符读写函数 fgetc fputc
函数原型
1 int fgetc (FILE *); 2 3 int fputc (int, FILE *);
1 #include <stdio.h> 2 #include <stdlib.h> 3 #define FILE_NAME "./1.txt" /* 存在的文件 */ 4 int main() 5 { 6 FILE *fp = fopen(FILE_NAME, "r"); 7 if(!fp){ 8 perror("|open file failed"); 9 return 1; 10 } 11 printf("|open the file %s successfully\n", FILE_NAME); 12 char c = fgetc(fp); 13 printf("|%c\n", c); 14 fclose(fp); 15 return 0; 16 }
输出
|open the file ./1.txt successfully
|h
1 #include <stdio.h> 2 #include <stdlib.h> 3 #define FILE_NAME "./2.txt" /* 不存在的文件 */ 4 int main() 5 { 6 FILE *fp = fopen(FILE_NAME, "w"); 7 if(!fp){ 8 perror("|open file failed"); 9 return 1; 10 } 11 printf("|open the file %s successfully\n", FILE_NAME); 12 fputc('h', fp); 13 fclose(fp); 14 return 0; 15 }
新建了 2.txt文件 并写入一个字符 h
后续的函数相关操作类似,列出了函数原型,不举代码示例啦(偷懒ing)
字符串读写函数 fgets fputs
函数原型
1 int fputs (const char *, FILE *); 2 3 char * fgets (char *, int, FILE *);
格式化读写函数 fprintf fscanf
函数原型
1 int fprintf (FILE *, const char *, ...); 2 3 int fscanf (FILE *, const char *, ...);
数据块读写函数 fread fwrite
函数原型
1 size_t fread (void *, size_t, size_t, FILE *); 2 3 size_t fwrite (const void *, size_t, size_t, FILE *);
这两个函数用于读写数据块,成功返回 块数, 出错或到文件尾返回 0
第一个参数: 指向要输入/输出数据块的首地址的指针
第二个参数: 每个要读/写的数据块的大小(字节数)
第三个参数: 要读/写的数据块的个数
第四个参数: 要读/写的文件指针
fread 和 fwrite 函数一般用于 数组 结构体等块的读写(多为二进制形式)
比如
1 #include <stdio.h> 2 #include <stdlib.h> 3 #define FILE_NAME "./3.txt" /* 不存在的文件 */ 4 5 int main(){ 6 7 int num[50] = {0}; 8 for(int i = 0; i < 50; ++i){ 9 num[i] = i; 10 } 11 FILE *fp = fopen(FILE_NAME, "w+b"); 12 fwrite(num, sizeof(int), 50, fp); 13 printf("指针到达:%d\n", (int)ftell(fp)); 14 rewind(fp); 15 printf("指针到达:%d\n", (int)ftell(fp)); 16 int newnum[50] = {0}; 17 fread(newnum, sizeof(int), 50, fp); 18 for(int i = 0; i < 50; ++i){ 19 printf(" %d", newnum[i]); 20 } 21 printf("\n"); 22 fclose(fp); 23 return 0; 24 }
得到 3.txt文件 内容:
可以看到一个int类型占有4个字节,并且存储方式是低位在前高位在后
输出结果:
判断文件是否结束 feof
函数原型
int feof (FILE *);
feof函数用来判断文件是否结束, 文件结束返回一个 非0值, 未结束则返回 0
文件出错检验 ferror
函数原型
int ferror (FILE *);
ferror函数用来测试文件是否出错, 未出错返回 0, 出错返回一个 非0值
文件定位 rewind fseek ftell
函数原型
1 int fseek (FILE *, long, int); /*根据参考位置灵活移动指针到目的地*/ 2 3 long ftell (FILE *); /*用来告诉我指针距离文件头的字符数*/ 4 5 void rewind (FILE *); /*将指针重新移动到文件头*/
其中 fseek函数第一个参数操作的文件指针(fp), 第二个参数是偏移量(offset) 第三个参数是参考点:0---文件开始 2---文件末尾 1---当前位置
刷新缓冲区 fflush
函数原型
int fflush (FILE *);
对于输出流来说, fflush函数将已经写到缓冲区但尚未写入文件的所有数据写到文件中
对输入流来说, 其结果是未定义的
如果在写的过程中发生错误,则返回EOF, 否则返回 0, fflush(NULL)将清洗所有的输出流
文件删除 remove
函数原型
int remove (const char *);
remove函数删除指定的文件, 操作成功返回 0, 操作失败返回一个 非0值
文件重命名 rename
函数原型
int rename (const char *, const char *);
修改文件名成功返回 0 , 操作失败返回 非0值
创建临时文件 tmpfile
函数原型
FILE * tmpfile (void);
tmpfile函数以 "wb+" 模式创建一个临时文件(二进制的形式-->临时文件当然读写更快),该文件在被关闭或程序正常结束时将被自动删除
创建操作成功 函数返回一个流(文件的指针), 创建文件失败返回NULL
创建一个临时文件名 tmpnam
函数原型
char * tmpnam (char *);
tmpnam函数创建一个与现有文件名不同的字符串,并返回一个指向一内部静态数组的指针,tmpnam(s)函数把创建的字符串保存到数组s中,并将它作为函数返回值返回
要注意的是数组s要具有足够的空间来存储这个文件名字符串
示例
1 #include <stdio.h> 2 3 int main() 4 { 5 char s[64] = {0}; 6 tmpnam(s); 7 printf("%s\n", s); 8 return 0; 9 }
输出
\s804.
这是一个随机的文件名(只要不与现有的冲突就OK)
后记:
-- 在进行文件操作的时候根据情况选择合适的读写方式以及读写格式
-- 要灵活使用 feof fflush (!!!!切记缓冲问题) ftell fseek rewind 等函数
-- 文件使用结束切记 fclose 关闭文件, 避免出错