一、背景知识
C语言支持文件读写,提供二进制和文本两种方式来读写文件。
二、具体使用
(一)引入输入输出库
#include<stdio.h>
(二)明白文件类型
1、主要定义
typedef struct _iobuf { char *_ptr; // 文件输入的下一个位置 int _cnt; // 当前缓冲区的相对位置 char *_base; // 文件初始位置 int _flag; // 文件标志 int _file; // 文件有效性 int _charbuf; // 缓冲区是否可读取 int _bufsiz; // 缓冲区字节数 char *_tmpfname; // 临时文件名 } FILE;
定义指针:
FILE* 变量名;
案例:
FILE* fp;
2、作用
封装文件的一些参数,用来代表一个文件。
(三)打开文件
使用fopen()打开文件。
定义如下:
FILE* fp = fopen(char* path, char* mode) 1)参数含义: path: 字符串指针类型,代表文件的路径,例如"D:/a.txt" mode: 字符串指针类型,指的用字母来代表当前是读、写、追加里面的哪种模式。 常用取值: r 代表使用文本只读;如果文件不存在会打开失败。 w 代表使用文本只写;如果文件不存在会创建新文件,如果文件存在会清空以前的内容; a 代表追加的方式写入;如果不存在文件会创建新文件,并且文件指针指向末尾;不会清空文件内容; r+ 代表以支持读取、写入的方式打开文件;如果文件不存在,会打开失败; w+ 代表以文本读取,写入的方式打开;不存在会创建新文件;存在文件会清空文件内容; a+ 代表支持添加的写入、读取;如果文件不存在,会创建新文件; rb 代表只读取二进制文件 wb 代表只写入二进制文件 ab 代表追加二进制文件 r+b 代表以读取、写入的方式操作二进制文件 w+b 代表以写入、读取的方式操作二进制文件 a+b 代表追加的方式写入二进制文件,同时支持读取; 2)返回值: 如果打开成功,返回不为空的FILE*文件指针; 如果打开失败,返回NULL,把错误代码设置在errno中。
案例:
// 用只读模式打开文本文件 D:/m.txt FILE* fp = fopen("D:/m.txt", "r");
(四)读写文本文件
1、读取单个字符
定义如下:
int fgetc(FILE* fp) 1)参数: fp: 是FILE*类型,代表读取的那个文件指针; 2)返回值(int类型): 如果不为EOF,代表是字符的ASCII码; 如果为EOF,代表文件读到末尾或者出现错误;
案例:
// 从fp文件指针代表的文件中读取一个字符 int c = fgetc(fp); // 打印该字符 printf("读取的字符是:%c \n", c);
2、写入单个字符
定义如下:
int fputc(char c, FILE* fp) 1)参数: c : char类型,被写入的字符变量; fp : FILE*类型,代表目标写入的文件指针; 2)返回值(int类型): 如果是EOF,代表写入失败; 如果等于传入的char参数,代表写入成功。
案例:
// 定义一个字符 char temp = 'a'; // 写入文件指针fp代表的文件中; int result = fputc(temp, fp);
3、写入一行字符
写入字符串后会自动换行。
函数定义如下:
int fputs(const char* buff, FILE* fp) 1)参数: buff: char*类型,指向被写入的字符串; fp: FILE*类型,代表目标写入的文件指针; 2)返回值(int类型): 非负数:代表写入成功; EOF: 代表写入出现错误;
案例:
char data[10] = {"hello\0"}; // 写入一行字符串 int result = fputs(data, fp);
4、读取一行字符
定义如下:
char* fgets(char* buff, int size, FILE* fp) 1)参数: buff: 代表存放读取结果的字符数组; size: 代表本次读取的最大字符个数; fp : FILE*类型,代表想要读取的文件指针; 2)返回值: 如果读取成功,返回指向字符串的指针,和传入的buff指针变量值相同; 如果读取失败,返回NULL;
如果读到最大个数字符,并且还没有读到换行符,就直接返回本次读取的数据。
如果读到换行符,直接返回本次读取的数据。
案例:
char data[10]; // 读取一行字符串 // 本次最多读取10个字符 char* p = fgets(data, 10, fp); printf("读取结果:%s", data);
5、格式化读取
定义如下:
int fscanf(FILE* fp, const char* format, ...) 1)参数: fp : FILE*类型,代表读取的文件指针; format : char类型,约定的格式字符串;例如"%d %c"; ... : 可根据实际传入变量的指针列表,例如"&a, &b, &c"; 2)返回值(int类型): 代表本次读取成功填充的参数个数。例如本次传入3个参数,如果全部读取成功,会返回3个。
案例:
假设此时文件的内容是"a=22,b=55"。
int a; int b; // 按照约定的格式读取 int result = fscanf(fp, "a=%d,b=%d", &a, &b); printf("a=%d, b=%d\n", a, b);
6、格式化输出
定义如下:
格式化输出变量值 int fprintf(FILE* fp, char* format, ...) 1)参数: fp : FILE*类型,代表目标写入的文件指针; format : char*类型,指向存放格式化字符串的指针; ... : 代表被输出的变量序列,例如"a, b, c"; 2)返回值(int类型): 代表本次输出成功的字符个数。 注意:fprintf()不使用缓冲;
案例如下:
int a = 100; float b = 3.14 char name[6] = {"hello\0"}; int result = fprintf(fp, "a=%d,b=%f,name=%s\n", a, b, name); // 如果写入成功,文件中增加内容为:"a=100,b=3.14,name=hello"。
(五)读写二进制文件
1、读取二进制数据块
作用是一次性读取多个字节;
定义如下:
int fread(void* buff, int unit, int size, FILE* fp) 从文件中读取数据块到缓冲区。 1)参数: buff : 代表读取后的数据存放位置的指针;例如字符数组名、结构体变量指针。 unit : 代表本次的读取单元有几个字节;一般用sizeof()来获得变量的占用字节数; size : 代表本次连续读取几个数据单元; fp : FILE*类型,代表读取的文件指针; 2)返回值(int类型): 代表本次成功读取的数据单元个数;
案例:
char buff[100]; // 本次读取,每个单元有1个字节,目标读取10个单元,文件指针是fp int size = fread(buff, 1, 10, fp); printf("读取单元个数:%d \n", size); printf("读取结果:%s \n", buff); // 假设有结构体变量struct stu stu1,读取一个结构体数据: int size = fread(&stu1, sizeof(struct stu), 1, fp);
2、写入二进制数据块
作用是能一次性写入多个字节;
定义如下:
int fwrite(void* buff, int unit, int size, FILE* fp) 写入数据块到文件中。 1)参数 buff : 代表被输出的数据存放的指针;例如字符数组名、结构体变量指针。 unit : 代表本次的写入数据单元有几个字节;一般用sizeof()来获得变量的占用字节数; size : 代表本次连续写入几个数据单元; fp : FILE*类型,代表用来写入的文件指针; 2)返回值(int类型): 代表本次成功写入的数据单元个数;
案例:
char buff[100] = {"hello"}; // 本次写入,每个单元有1个字节 // 写入单元数需要获取字符长度 // 写入的文件指针是fp int size = fwrite(buff, 1, strlen(buff), fp); printf("读取写入个数:%d \n", size);
(六)操作文件指针的函数
1、返回当前读取的文件指针位置。
定义如下:
int ftell(FILE* fp) 1)参数: fp : FILE*类型,代表目标文件指针; 2)返回值: 返回代表从第一个字节开始计数,向后移动就加一,直到当前字节的位置整数; 如果是0,代表指向文件的第一个字节;
2、把读取指针重新定位到第一个字节。
如果文件读取过很多次后,还想重新读取,就能调用"rewind(FILE* fp)"来恢复到初始位置;
定义如下:
void rewind(FILE* fp) 1)参数: fp : FILE*类型,代表目标文件指针; 2)返回值: 没有返回值。
3、定位到文件的任意一个字节位置。
定义如下:
int fseek(FILE* fp, long int offset, int start) 1)参数: fp: FILE*类型,代表操作的文件指针; offset : long int类型,表示从某个相对位置开始的偏移量;如果是正值向后偏移,如果是负值,向前偏移; start: 这是偏移量的相对位置,设置为三个常量之一: SEEK_SET 从文件开头开始; SEEK_CUR 从当前位置开始计数; SEEK_END 从文件的末尾开始计数; 2)返回值(int类型): 如果成功,返回0; 如果失败,返回一个非零整数;
案例:
// 1、重定向在距离开头5个字节的位置(文件索引从0开始) fseek(fp, 5 - 1, SEEK_SET); // 2、重定向在开头第一个字节的位置 fseek(fp, 0, SEEK_SET); // 3、重定向在末尾开始的第一个字节的位置 fseek(fp, 0, SEEK_END); // 4、重定向当前的上一个位置 fseek(fp, -1, SEEK_CUR);
4、判断文件末尾
int feof(FILE* fp) 1)参数: fp : FILE*类型,代表被判断的文件指针; 2)返回值(int类型): 如果到文件末尾,返回非零值; 如果没有到末尾,返回0;
5、关闭文件
int fclose(FILE* fp) 1)参数: fp: FILE*类型,代表关闭的文件指针; 2)返回值(int类型): 如果关闭成功,返回0; 如果关闭失败,返回常量EOF值;
(七)其他函数
1、重新打开文件
主要用来重定向流;
定义如下:
int freopen(const char* filepath, const char* mode, FILE* fp) 1)参数: filepath: 代表需要被打开的那个文件路径; mode: char*类型,指定文件的打开模式;具体定义和fopen()一样; fp: FILE*类型,是传入一个已经定义的文件指针; 2)返回值: 如果打开成功,返回指向文件流的指针。 如果打开失败,返回NULL。
案例:
// 把stdout重定向到文件里 FILE* fp = freopen("D:/m.txt", "w+", stdout); // 输出字符 printf("hello\n"); // 此时printf()函数的内容被输出到D:/m.txt文件中。
2、刷新缓冲区
定义如下:
int fflush(FILE* fp) 1)参数: fp: FILE*类型,代表被刷新的文件指针; 2)返回值: 如果成功,返回0; 如果失败,返回EOF常量值。
3、设置缓冲模式
int setvbuf(FILE* fp, char* buff, int mode, int size) 1)参数: fp: FILE*类型,代表被设置的文件指针; buff: 被设置的缓冲区,一般是char字符数组;如果为NULLL,自动创建; mode: 指定缓冲模式,是下面的任意一个常量: _IOFBF: 全缓冲 _IOLBF: 行缓冲 _ILNBF: 没有缓冲 size: 设置缓冲区字节的大小,是一个整数; 2)返回值: 如果成功,返回0; 如果失败,返回非零值;
4、手动设置缓冲区
void setbuf(FILE* fp, char* buff) 1)参数 fp : 目标文件指针; buff: 是用来缓冲数据的字符数组指针; 2)返回值 没有返回值。
案例:
char buff[100]; // 把buff设置为缓冲区 setbuf(fp, buff);
5、判断文件是否出现错误。
int ferror(FILE* fp) 1)参数 fp: 被判断的文件指针; 2)返回值 如果有错误,返回一个非零的整数; 如果没有错误,返回0。
6、生成一个临时文件名字。
定义:
char* tmpnam(char* buff) 1)参数: buff: 是存放生成的文件名存放的位置;一般是传入char类型的数组名; 2)返回值: 如果生成成功,返回和buff一样的指针; 如果失败,返回NULL。
7、返回一个临时文件
FILE* tmpfile() 1)参数: 不需要参数。 2)返回值: 如果执行成功,返回指向临时文件的FILE*文件指针; 如果执行失败,返回NULL。
8、重命名
int rename(const char* old_name, const char* new_name) 1)参数: old_name: 指向旧文件名字的字符串指针; new_name: 指向文件新名字的字符串指针; 2)返回值(int类型): 如果成功,返回0; 如果失败,返回-1。
案例:
int result = rename("D:/oo.txt", "D:/mu.txt"); printf("重命名是否成功:%d \n", result);
四、使用经验
(一)fread和fwrite可以指定每个数据单位占用几个字节(方便读写结构体变量)。
(二)文件读写的缓冲区使用char类型的数组来充当。
(三)stdio.h库有默认的缓冲流变量:
stdin 代表标准输入流(控制台输入)。
stdout 代表标准输出流。
stderr 代表标准错误流。
(四)void*类型的指针可以强制转化。 按照指定的格式来读写内存。
例如:
把void*转成int*类型,那么把连续的4个字节看做是一个int整型变量值。
如果把void*转成char*类型,那么每个字节都看做是一个字符。
(五)实际可以用二进制模式来读写文本文件。
例如: FILE*fp = fopen("D:/m.txt", "w+b");
(六)常量EOF的整型值是-1。
#define EOF -1