C语言(7.文件操作)
目录
一、文件和流
1. 文件
为什么使用文件?
我们使用文件将数据存储在硬盘上,实现数据的持久化
1.1 文件分类
文件可分为程序文件和数据文件
-
程序文件:包括源程序文件(.c),目标文件(.obj),可执行程序(.exe)等
-
数据文件:程序运行时读写数据的文件
1.2 文件名
一个文件要有一个唯一的文件标识,文件标识常称为文件名
文件名包含3部分:文件路径 + 文件名主干 + 文件名后缀
如:C:\Users\wh\Desktop\817ef1b1da41686aaefa15fb26850ea.jpg
2. 文件指针
文件指针:文件类型指针(FILE*)
文件信息区:每一个被使用文件都在内存中开辟了一个文件信息区,用来存放文件的相关信息。这些文件信息被保存在结构体变量里,结构体变量的类型命名为FILE,所以文件信息区就是FILE类型的变量
不同编译器的FILE类型变量的成员变量内容不完全相同,但是大同小异
VS2013编译环境下的stdio.h头文件中对FILE类型的声明如下:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
每打开一个文件,系统就会创建一个FILE类型的变量存储文件信息,我们通过FILE*指针来维护这个变量
3. 文件缓冲区
标准C采用文件缓冲系统处理数据文件,文件缓冲系统就是系统会为程序中的每一个正在使用的文件设置一个文件缓存区。从内存到磁盘传输数据时,会优先传输到文件缓冲区中,当文件缓冲区满了或用户手动刷新时,文件缓冲区的数据才会写入磁盘中。缓冲区的大小由C编译器系统决定。
注意:操作文件时,要刷新缓冲区或者在结束时关闭文件,否则缓冲区数据没有写入磁盘,导致数据丢失
4. 流
流(stream):一串比特数据
每一个C程序运行时默认打开3个流,它们定义在<stdio.h>
中:
- 标准输入流(stdin):由键盘输入数据
- 标准输出流(stdout):数据显示在屏幕上
- 标准错误流(stderror):存储错误信息,显示在屏幕上
二、文件操作
文件操作的相关函数在stdio.h
头文件中声明
- 打开与关闭
函数名 | 函数原型 | 功能 |
---|---|---|
fopen | FILE* fopen(const char* filename, const char* mode); | 打开文件 |
fclose | int fclose(FILE* stream); | 关闭文件 |
- 顺序读写函数
函数 | 函数原型 | 功能 |
---|---|---|
fputc | int fputc(int char, FILE *stream); | 写入一个字符 |
fgetc | int fgetc(FILE *stream); | 读取一个字符 |
fputs | int fputs(const char *str, FILE *stream); | 写入一行字符串 |
fgets | char *fgets(char *str, int n, FILE *stream); | 读取一行字符串 |
fprintf | int fprintf(FILE *stream, const char *format, ...); | 格式化写入字符串 |
fscanf | int fscanf(FILE *stream, const char *format, ...); | 格式化读取字符串 |
fwrite | size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); | 写入二进制数据 |
fread | size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); | 读取二进制数据 |
函数 | 函数原型 | 功能 |
---|---|---|
sprintf | int sprintf(char *str, const char *format, ...); | 数据转换成指定格式字符串 |
sscanf | int sscanf(const char *str, const char *format, ...); | 指定格式字符串转换成数据 |
- 随机读写函数
函数 | 函数原型 | 功能 |
---|---|---|
fseek | int fseek(FILE* stream, long int offset, int origin); | 设置文件指针位置 |
ftell | long int ftell(FILE* stream); | 获取文件指针位置 |
rewind | void rewind(FILE* stream); | 重置文件指针位置 |
- 文件结果判断
函数 | 函数原型 | 功能 |
---|---|---|
feof | feof(FILE* steam); | 判断文件读取是否因为结束而停止 |
ferror | int ferror(FILE *stream); | 判断文件读取是否因为错误而停止 |
errno | errno | 错误码 |
1. 打开与关闭
1.1 打开与关闭函数
(1)fopen:打开文件,并返回一个FILE*指针
FILE* fopen(const char* filename, const char* mode);
- 返回值:该文件的FILE*类型的指针,打开失败返回NULL
- 参数:
- filename - 文件名
- mode - 文件打开方式
注意:
- 文件名是字符串类型,绝对路径中的" \ "要使用转义字符转一下
- 如:
C:\Users\wh\Desktop\817ef1b1da41686aaefa15fb26850ea.jpg
,应写为C:\\Users\\wh\\Desktop\\817ef1b1da41686aaefa15fb26850ea.jpg
(2)fclose:关闭文件,每个文件使用完都要关闭
int fclose(FILE* stream);
- 返回值:正常关闭文件时返回0,否则返回EOF(-1)
- 参数:stream - 被关闭的文件
1.2 文件打开方式
(1)文本文件只读只写
文件使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新文件 |
“a”(追加) | 向文本文件尾添加数据 | 建立一个新文件 |
(2)二进制文件只读只写
文件使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
“rb”(只读) | 为了输入数据,打开一个已经存在的二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新文件 |
“ab”(追加) | 向二进制文件尾添加数据 | 出错 |
(3)文本文件读写共用
文件使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
“r+”(读写) | 为了读和写,打开一个已经存在的文本文件 | 出错 |
“w+”(读写) | 为了读和写,新建一个文本文件 | 建立一个新文件 |
“a+”(追加) | 打开一个文件,在文件尾进行读写 | 建立一个新文件 |
(4)二进制文件读写共用
文件使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
“rb+”(读写) | 为了读和写,打开一个已经存在的二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,新建一个二进制文件 | 建立一个新文件 |
“ab+”(追加) | 打开一个文件,在文件尾进行读写 | 建立一个新文件 |
1.3 书写格式
- 文件打开一定要检查是否成功打开,若失败应报错提示
- 文件使用完毕必须关闭文件,并将指针置为NULL
- 错误提示可使用一下二者之一:
printf("%s\n", strerror(errno));
(头文件<string.h>
和<error.h>
)perror("fopen");
(头文件<error.h>
)
//打开文件
FILE* pf = fopen("myfile.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno)); //错误提示
exit(1);
}
//文件操作
、、、
//关闭文件
fclose(pf);
pf = NULL;
2. 顺序读写操作
2.1 单个字符读写
(1)fputc
将字符输出到文件
int fputc(int char, FILE *stream);
- 返回值:返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符
- 参数:
- char - 这是要被写入的字符,该字符以其对应的 int 值进行传递
- stream - 指向 FILE 对象的指针
使用:
fputc(66, pf); //将66代表的字符写入pf指向的文件中
(2)fgetc
在文件中获取字符,并将文件指针移动到下一个字符
int fgetc(FILE *stream);
- 返回值:获取的字符,如果没获取到,返回EOF
- 参数:
- stream - 指向 FILE 对象的指针
使用:
int ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c", (char)ch);
}
2.2 文本按行读写
(1)fputs
把字符串写入到指定的文件中,但不包括空字符
int fputs(const char *str, FILE *stream);
- 返回值:写入字符的个数,错误返回EOF
- 参数:
- str - 字符数组,将该字符数组中的字符串写入文件中
- stream - 指向 FILE 对象的指针
使用:
char str[] = "abcdef";
fputs(str, pf); //将"abcdef"写入pf指向的文件中
(2)fgets
从指定文件中读取一行,并把它存储在str所指向的数组内
char *fgets(char *str, int n, FILE *stream);
- 返回值:str数组,没有读到数据或错误返回NULL
- 参数:
- str - 字符数组,存储读取到的字符串
- n - 要读取的最大字符数(包括最后的“\0”)
- stream - 指向 FILE 对象的指针
注意:读取到n-1个字符、换行符、文件结尾或出现错误时停止读取
使用:
char str[20] = {0};
fgets(str, 20, pf); //读取该行的20个字符,并存储在str数组中
2.3 文本格式化读写
(1)fprintf
把指定格式的数据以字符串形式写入文件中
int fprintf(FILE *stream, const char *format, ...);
- 返回值:写入字符的个数,错误返回EOF
- 参数:
- stream - 指向 FILE 对象的指针
- format - 数据格式
使用:
fprintf(pf, "%d %c %f", 2, 'a', 1.5f); //将数据以指定格式写入pf指向的文件中
(2)fscanf
从文件中读取指定格式的数据
int fscanf(FILE *stream, const char *format, ...);
- 返回值:读取字符个数,没读到返回EOF
- 参数:
- stream - 指向 FILE 对象的指针
- format - 数据格式
使用:
fscanf(pf, "%d %c %f", &n, &ch, &f); //将文件中的数据以指定格式读取并存储到变量中
2.4 二进制比特读写
(1)fwrite
将数据写入二进制文件中
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
- 返回值:写入元素个数,与nmemb不一致时报一个错误
- 参数:
- ptr - 指针,指向需要写入文件的数据
- size - 每个元素的大小
- nmemb - 元素的个数
- stream - 指向 FILE 对象的指针
使用:
struct S s[3] = {{"zhangsan", 18}, {"lisi", 38}, {"wangwu", 32}};
fwrite(s, sizeof(s[0]), 3, pf);
(2)fread
从二进制文件中读取数据
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
- 返回值:读取元素的个数
- 参数:
- ptr - 指针,读取的数据将存储在指针指向的位置
- size - 每个元素的大小
- nmemb - 元素的个数
- stream - 指向 FILE 对象的指针
使用:
char arr[20] = {0};
fread(arr, sizeof(char), 20, pf);
2.5 格式转换函数
(1)sprintf
将指定格式的数据转换成字符串
int sprintf(char *str, const char *format, ...);
- 返回值:字符总数,错误返回EOF
- 参数:
- str - 字符数组,转换的字符串存储在该数组中
- format - 数据格式
使用:
char arr[20] = {0};
sprintf(arr, "%d %c %f", 2, 'a', 1.4f);
(2)sscanf
将字符串转换成指定格式的数据
int sscanf(const char *str, const char *format, ...);
- 返回值:字符总数,错误返回EOF
- 参数:
- str - 被转换的字符串
- format - 数据格式
使用:
char str[] = "123 a";
sscanf(str, "%d %c", &n, &ch);
2.5 各种scanf、printf的对比
函数 | 功能 |
---|---|
scanf | 从标准输入流中读取指定格式的数据 |
printf | 在标准输出流中打印指定格式的数据 |
fscanf | 从任何流中读取指定格式的数据 |
fprintf | 在任何流中打印指定格式的数据 |
sscanf | 将字符串转换成指定格式的数据 |
sprintf | 将指定格式的数据转换成字符串 |
3. 随机读写操作
3.1 fseek
根据文件指针的位置和偏移量来定位文件
int fseek(FILE* stream, long int offset, int origin);
- 返回值:如果成功,则该函数返回零,否则返回非零值
- 参数:
- stream - 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
- offset - 这是相对 whence 的偏移量,以字节为单位。
- origin - 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一
文件起始位置常量:
常量 | 描述 |
---|---|
SEEK_SET | 文件的开头 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件的末尾 |
使用:
fseek(pf, 3, SEEK_SET); //将文件当前指针定位到文件开头向后偏移3个字符的位置
fseek(pf, 3, SEEK_CUR); //将文件当前指针定位到当前位置向后偏移3个字符的位置
fseek(pf, -3, SEEK_END); //将文件当前指针定位到文件结尾向前偏移3个字符的位置
3.2 ftell
获取文件当前指针位置相对于起始位置的偏移量
long int ftell(FILE* stream);
- 返回值:文件当前指针离起始位置的偏移量
- 参数:
- stream - 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
使用:
long size = ftell(pf);
3.3 rewind
让指针回到文件起始位置
void rewind(FILE* stream);
- 返回值:无
- 参数:
- stream - 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
使用:
rewind(pf);
4. 文件读取结果判断
4.1 判断文件读取结束
- 文本逐字符读取:
fgetc
返回值为EOF结束
int ch = 0;
while ((ch = fgetc(fp)) != EOF) //判断返回值是否为EOF
{
putchar(c);
}
- 文本逐行读取:
fgets
返回值为NULL结束
char arr[1024] = {0};
int len = 0;
while ((len = fgets(arr, 1024, fp)) != NULL) //判断返回值是否为NULL
{
puts(arr);
}
- 二进制读取:
fread
返回值小于实际读取个数结束
size_t size = 0;
char str[1024] = "\0";
while ((size = fread(str, sizeof(char), 1024, fp)) != 0) //判断返回值是否为0
{
fwirte(str, sizeof(char), size, stdout);
}
4.2 文件结束原因判断
(1)feof
文件读取结束后,判断文件是否是遇到文件尾结束,feof
是在头文件<stdio.h>
中声明的宏。
feof(FILE* steam);
- 返回值:遇到文件尾结束返回非零值,因为其他原因结束返回0
- 参数:
- stream - 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
(2)ferror
文件读取结束后,判断文件是否是因为出错而结束,ferror
是在头文件<stdio.h>
中声明的函数
int ferror(FILE *stream);
- 返回值:如果是读取错误结束,则返回非0值,否则返回0
- 参数:
- stream - 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
使用:
int ch = 0;
while ((ch = fgetc(fp)) != EOF) //判断返回值是否为EOF
{
putchar(c);
}
if (feof(fp) != 0) //文件正常结束判断
{
puts("End of File!\n");
}
else if (ferror(fp) != 0) //文件异常结束判断
{
puts("I/O Error\n");
}
(3)errno
errno:库函数中用来盛放错误代码的宏,声明于errno.h
一旦发生错误,编译器就会将errno修改成对应的错误码