【C语言进阶】文件操作
文章目录
本章目标:
掌握C语言的文件操作。
本章重点:
1.为什么使用文件?
2.什么是文件?
3.文件的打开和关闭
4.文件的顺序读写
5.文件的随机读写
6.文本文件和二进制文件
7.文件读取结束的判定
8.文件缓冲区
一、为什么使用文件?
一般情况下,程序运行时产生的数据时存放在内存中的,当程序退出的时候,这些数据就不存在了。
比如通讯录程序,我们期望每次运行时都会按照我们的操作保留和删除相关的数据,等到下次运行的时候,上一次产生的数据仍然保留。
这就涉及到了数据持久化的问题,方法一般有:把数据存放在磁盘文件中、存放到数据库等。
使用文件可以使我们将数据直接存放到电脑的硬盘上,做到了数据的持久化。
二、什么是文件?
磁盘上的文件就是文件。
在程序设计中,一般谈到的文件有两种:程序文件、数据文件(从文件功能的角度分类)。
1 程序文件
包括源程序文件(C语言源文件
.c
),目标文件(Windows环境下.obj
),可执行程序(Windows环境下.exe
)。(程序在编译、链接、运行时产生的文件)
2 数据文件
文件的内容不一定是程序,还有可能是程序在运行时产生的数据,比如程序运行时需要从中读取数据(读取)的文件,或者象棋中输入数据(写入)的文件。
3 文件名
一个文件要有一个唯一确定的文件标识,以方便用户的识别和引用。
文件名包含3个部分:文件路径、文件名主干、文件后缀
例如:C:\code\test.txt
为了方便起见,文件标识常被称为文件名。
4 注意
之前各章节所处理数据的输入和输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。
本章学习的是将信息存储到磁盘上,当需要的时候可以从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上的文件。
本章讨论的是数据文件。
三、文件的打开和关闭
1 文件指针
缓冲文件系统中,关键的概念是文件类型指针,简称文件指针。
内存中的文件信息区,存放文件信息:文件名、文件状态、文件的当前位置。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息。这些信息是保存在一个结构体变量中的,该结构体类型是由系统声明的,取名为 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;
说明:
1 不同的C编译器的 FILE
类型包含的内容不完全相同,大同小异。
2 每当打开一个文件的时候,系统会根据文件的情况自动创建一个 FILE
结构体类型的变量,并填充其中的信息,使用者不必关心具体细节。
3 一般都是通过一个 FILE
类型的结构体指针来维护这个 FILE
结构体变量,这样使用起来就更加方便。
2 文件指针的使用
创建一个 FILe
类型的指针变量。
示例:
FILE* pf;//文件指针变量
说明:
1 pf
是一个指向 FILE
类型数据的指针变量。可以让 pf
指向某个文件的文件信息区(是一个结构体变量)。通过改文件信息区中的信息就能够访问该文件。
2 通过文件指针变量能够找到与它关联的文件。
图示:
3 文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束后应该关闭文件。
编写程序的时候,在打开文件的同时,都会返回一个 FILE
类型的指针变量来指向该文件,也相当于是建立了指针和文件的关系。
3.1 文件函数
ANSIC
规定使用 fopen
函数来打开文件,使用 fclose
来关闭文件。
示例:
//打开文件
FILE* fopen(const char* filename, const char* mode);
//关闭文件
int fclose(FILE* stream);
filename --> 文件名
mode --> 打开方式
3.2 打开方式
3.2.1 表格
文件使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
“r” (只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w" (只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a” (追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
“rb” (只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb” (只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
“ab” (追加) | 向一个二进制文件尾添加数据 | 建立一个新的文件 |
“r+” (读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+” (读写) | 为了读和写,建立一个新的文件 | 建立一个新的文件 |
“a+” (读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
“rb+” (读写) | 为了读和写,打开一个二进制文件 | 出错 |
“wb+” (读写) | 为了读和写,建立一个新的二进制文件 | 建立一个新的文件 |
“ab+” (读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新的文件 |
3.2.2 示例
代码示例:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
FILE* pFile;
//打开文件
pFile = fopen("myfile.txt", "w");//相对路径 -- 当前程序所在路径下
//文件操作
if (pFile != NULL)
{
fputs("fopen example", pFile);//将字符串写入文件
//fputs("fopen example", stdout);//将字符串(写)输出到屏幕上
//fgetc(stdin);//从键盘读取
//stdout -- 标准输出
//stdin -- 标准输入
//关闭文件
fclose(pFile);
pFile = NULL;//指针置空,防止出现野指针
}
pFile = fopen("data.h", "r");//当前路径下没有data.h文件
if (pFile == NULL)//空指针检查
{
perror("fopen");//fopen : No such file or directory
return;
}
return 0;
}
说明:
1 读与写:
2 fopen("myfile.txt", "w")
当前路径下没有 myfile.txt
文件,以"w"
方式打开文件会建立一个新的文件。
四、文件的顺序读写
C语言程序一运行起来就默认打开三个流(stream)。
流 | 含义 | 类型 |
---|---|---|
stdin | 标准输入流 | FILE* |
stdout | 标准输出流 | FILE* |
stderr | 标准错误流 | FILE* |
1 顺序读写函数
1.1 函数简单介绍
函数 | 功能 | 使用场所 |
---|---|---|
fgetc() | 字符输入函数(从流中获取字符) | 所有输入流 |
fputc() | 字符输出函数 | 所有输出流 |
fgets() | 文本行输入含函数 | 所有输入流 |
fputs() | 文本行输出函数 | 所有输出流 |
fscanf() | 格式化输入函数 | 所有输入流 |
fprintf() | 格式化输出函数 | 所有输出流 |
fread() | 二进制输入 | 二进制文件 |
fwrite() | 二进制输出 | 二进制文件 |
1.2 fgetc()
与 fputc()
1.2.1 fgetc()
从流中获取字符
int fgetc ( FILE * stream );
含义:
- 返回指定流的内部文件位置指示器当前指向的字符。然后,内部文件位置指示器将前进到下一个字符。
参数:
stream
:指向标识输入流的FILE
对象的指针。
返回值:
- 读取成功后,将返回读取的字符(整型提升为
int
值);读取失败则返回EOF
。
代码示例:
#include<stdio.h>
int main()
{
//fgetc()示例
FILE* pFile;
pFile = fopen("myfile.txt", "r");
if (pFile == NULL)
{
perror("fopen");
return 1;
}
int ch = 0;
while ((ch = fgetc(pFile)) != EOF)
{
printf("%c\n", ch);//每次一个字符地打印文件内容
}
fclose(pFile);//关闭文件
pFile = NULL;//文件指针主动置空,防止出现野指针
return 0;
}
1.2.2 fputc()
将字符写入流
int fputc ( int character, FILE * stream );
含义:
- 将字符写入流的内部位置指示器指示的位置,然后自动前进 1。
参数:
-
character
:要写入的字符的int
提升(整型提升)。写入时,该值在内部转换为无符号字符
。 -
stream
:指向标识输出流的FILE
对象的指针。
返回值:
- 写入成功后,将返回所写字符。
- 如果发生写入错误,则返回
EOF
并设置错误指示器 (ferror
)。
示例代码:
#include<stdio.h>
int main()
{
//fputc()示例
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//将字符写入文件
//fputc('a', pf);
//fputc('b', pf);
//fputc('c', pf);
int i = 0;
for (i = 0; i < 26; i++)
{
fputc('a' + i, pf);//将a~z写入到pf所指的文件中
}
for (i = 0; i < 26; i++)
{
fputc('a' + i, stdout);//将a~z写入到stdout输出流中(打印到屏幕上)
}
/*
输出到一行上且不换行
abcdefghijklmnopqrstuvwxyz
*/
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
1.3 fgets()
与 fputs()
1.3.1 fgets()
从流中获取字符串
char * fgets ( char * str, int num, FILE * stream );
含义:
-
从流中读取字符,并将它们作为 C 字符串存储到
str
中,直到读取 (num-1
) 个字符或到达换行符或文件末尾。 -
换行符(
\n
)使fgets
停止读取,但它被函数视为有效字符,并包含在复制到str
的字符串中。 -
终止
null
字符(\0
)会自动附加到复制到str
的字符之后。 -
请注意,
fgets
与gets
有很大不同:fgets
不仅接受流参数,还允许指定str
的最大大小,并在字符串中包含任何结束换行符。
参数:
str
:指向复制读取的字符串的char
数组的指针。num
:要复制到str
中的最大字符数(包括终止null
字符)。( 最终只会读取num-1
个字符,剩余1位由\0
填充)stream
:指向标识输入流的FILE
对象的指针。stdin
可以用作从标准输入读取的参数。
返回值:
- 读取成功后,该函数返回
str
。 - 如果在尝试读取字符时遇到文件末尾,则设置
eof
指示符(feof
)。如果在读取任何字符之前发生这种情况,则返回的指针为空指针(并且str
的内容保持不变)。 - 如果发生读取错误,则设置错误指示符(
ferror
),并返回null
指针(但str
指向的内容可能已更改)。
代码示例:
#include<stdio.h>
int main()
{
//fgets()示例
FILE* pf = fopen("data.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//data.txt文件存放的内容:abcdefghijklmnopqrstuvwxyz
//读文件 - 读一行
char arr[10] = { 0 };
fgets(arr, 10, pf);//只读取9个字符,剩余1为由\0填充
printf("%s\n", arr);
//abcdefghi
fgets(arr, 10, pf);//只读取9个字符,剩余1为由\0填充
printf("%s\n", arr);
//jklmnopqr
fgets(arr, 9, pf);//只读取8个字符,剩余1为由\0填充
printf("%s\n", arr);
//stuvwxyz
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
1.3.2 fputs()
将字符串写入流
int fputs ( const char * str, FILE * stream );
含义:
- 将
str
指向的 C 字符串 写入流。 - 该函数从指定的地址 (
str
) 开始复制,直到到达终止null
字符 ('\0'
)。此终止null
字符不会复制到流中。 - 请注意,
fput
不仅与put
的不同之处在于可以指定目标流,而且fput
不会写入其他字符,而put
会自动在末尾附加一个换行符。
参数:
str
:C 字符串,其中包含要写入流的内容。stream
:指向标识输出流的FILE
对象的指针。
返回值:
- 写入成功后,将返回一个非负值。
- 出错时,该函数返回
EOF
并设置错误指示器(ferror
)。
代码示例:
#include<stdio.h>
int main()
{
//fputs()示例
FILE* pf = fopen("data.txt", "w");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//写文件 - 写一行
fputs("hello pokemon.\n", pf);
//将字符串写入stdout输出六中,相当于将字符串打印到屏幕上
fputs("hello world.\n", stdout);//hello world.
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
1.4 fread()
与 fwrite()
1.4.1 fread()
从流中读取数据块
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
含义:
- 从
stream
流中读取元素个数为count
的数组,每个元素的大小为size
字节,并将它们存储在ptr
指定的内存块中。 - 如果成功,读取的总字节数为 (
size*count
)。
参数:
ptr
:指向大小至少为 (size*count
) 字节的内存块的指针,转换为void*
。size
:要读取的每个元素的大小(以字节为单位)。count
:元素个数。stream
:指向指定输入流的FILE
对象的指针。
返回值:
- 返回成功读取的元素总数。
- 如果返回值与
count
参数不同,则表示读取时发生读取错误或到达文件末尾。 - 如果
size
或count
为零,则该函数返回零,并且ptr
指向的流状态和内容保持不变。
代码示例:
#include<stdio.h>
struct S
{
int a;
float s;
char str[10];
};
int main()
{
//fread()示例
struct S s = { 0 };
FILE* pf = fopen("my_file.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
fread(&s, sizeof(struct S), 1, pf);
printf("%d %.2f %s\n", s.a, s.s, s.str);
fclose(pf);
pf = NULL;
return 0;
}
1.4.2 fwrite()
写入要流式传输的数据块
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
含义:
- 从
ptr
指向的内存块开始,将一个元素个数为count
的数组写入到流中的当前位置,每个元素的大小为size
字节。 - 在内部,该函数将
ptr
指向的块解释为一个size
类型的元素大小为count
的数组,并按顺序将它们写入,就好像为每个字节调用了一样。
参数:
ptr
:指向要写入元素的数组的指针,转换为const void*
。size
:要写入的每个元素的大小(以字节为单位)。count
:元素个数。stream
:指向指定输出流的FILE
对象的指针。
返回值:
- 返回成功写入的元素总数。
- 如果返回值与
count
参数不同,则写入错误会阻止函数完成。 - 如果
size
或count
为零,则函数返回零。
代码示例:
#include<stdio.h>
struct S
{
int a;
float s;
char str[10];
};
int main()
{
//fwrite()示例
struct S s = { 99, 3.14f, "pokemon" };
FILE* pf = fopen("my_file.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fwrite(&s, sizeof(struct S), 1, pf);//将结构体变量s的数据以二进制形式写入文件
fclose(pf);
pf = NULL;
return 0;
}
2 一组函数简单对比
scanf() / fscanf() / sscanf()
printf() / fprintf() / sprintf()
函数 | 用途 |
---|---|
scanf() | 从标准输入流读取格式化的数据 |
printf() | 向标准输出流写入格式化的数据 |
fscanf() | 适用于所有输入流的格式化输入函数 |
fprintf() | 适用于所有输出流的格式化输出函数 |
sscanf() | 从字符串中读取格式化的数据 |
sprintf() | 将格式化的数据转换成字符串 |
2.1 fscanf()
与 fprintf()
2.1.1 简单介绍
fscanf()
从流中读取格式化数据
int fscanf ( FILE * stream, const char * format, ... );
- 从流中读取数据,并根据参数格式将它们存储到其他参数所指向的位置。
fprintf()
将格式化数据写入流式处理
int fprintf ( FILE * stream, const char * format, ... );
- 将按
format
格式指向的 C 字符串写入流。如果format
包含格式说明符,则格式后面的其他参数将被格式化并插入到生成的字符串中,以替换其各自的说明符。
2.1.2 示例1
代码示例:
#include<stdio.h>
struct S
{
int a;
float s;
};
int main()
{
//fscanf()和fprintf()示例1
FILE* pf = fopen("my_data.txt", "w");//以写的方式打开文件,如果没有该文件就建立一个新的文件
if (NULL == pf)
{
perror("fopen");
return 1;
}
//写文件
struct S s = { 100, 3.14f };
fprintf(pf, "%d %f", s.a, s.s);//将结构成员分别写入pf所指向的文件中
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
2.1.3 示例2
代码示例:
#include<stdio.h>
struct S
{
int a;
float s;
};
int main()
{
//fscanf()和fprintf()示例2
FILE* pf = fopen("mydata.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//写文件
struct S s = {0};
fscanf(pf, "%d %f", &(s.a), &(s.s));//从pf所指向的文件中以"%d %f"格式读取数据
fprintf(stdout, "%d %.2f\n", s.a, s.s);//将(s.a, s.s)以"%d %f\n"的格式写入stdout输出流中(打印到屏幕上)
//100 3.14
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
2.2 sscanf()
与 sprintf()
2.2.1 简单介绍
sscanf()
从字符串中读取格式化数据
int sscanf ( const char * s, const char * format, ...);
- 从
s
读取数据,并根据参数格式(format
)将它们存储到附加参数(...
可变参数 )给定的位置,就像使用scanf
一样,本函数是从s
读取而不是从标准(stdin
)输入 。
sprintf()
将格式化数据写入字符串
int sprintf ( char * str, const char * format, ... );
- 使用与在
printf
上使用格式时打印的文本相同的文本组成一个字符串,但内容不是被打印,而是作为 C 字符串存储在str
指向的缓冲区中。 - 缓冲区的大小应足够大,以包含整个生成的字符串。
- 终止
null
字符会自动追加在内容之后。 - 在
format
参数之后,该函数至少需要与格式所需的附加参数一样多的参数。 - 将结果写入字符串
str
。如果所写入的字符串(加上终止空字符)超出由str
所指向的数组的大小,则行为未定义。
2.2.2 示例
代码示例:
#include<stdio.h>
struct S
{
int a;
float s;
char str[10];
};
int main()
{
//sscanf()和sprintf()示例
char arr[30] = { 0 };
struct S s = { 100, 3.14f, "hehe" };
struct S tmp = { 0 };
sprintf(arr, "%d %f %s", s.a, s.s, s.str);//将后续参数格式化的数据转换成字符串
//将结构体变量的成员数据以字符串的形式写入字符数组中
printf("%s\n", arr);
//100 3.140000 hehe
sscanf(arr, "%d %f %s", &(tmp.a), &(tmp.s), tmp.str);//从字符串中读取格式化的数据到后续参数中
//将从arr字符数组中读取到的数据按照"%d %f %s"的格式分别存储到后续参数中
printf("%d %f %s\n", tmp.a, tmp.s, tmp.str);
//100 3.140000 hehe
return 0;
}
五、文件的随机读写
1 fseek()
根据文件指针的位置和偏移量来定位文件指针。(定位文件指针)
函数原型:
int fseek(FILE* stream, long int offset, int origin);
说明:
FILE* stream --> 文件指针
long int offset --> 偏移量
int origin --> 原始位置
//origin参数
SEEK_SET -- 文件开头
SEEK_CUR -- 文件指针的当前位置
SEEK_END -- 文件结束
示例1:
#include<stdio.h>
int main()
{
//fseek()示例1
FILE* pFile;
//打开文件,准备向文件中输入数据,没有文件则自动创建一个
pFile = fopen("example.txt", "wb");
fputs("This is an apple.", pFile);//向文件中输入字符串
fseek(pFile, 9, SEEK_SET);//定位文件指针
//从文件起始位置向右偏移9,即 n(n apple.)的位置
fputs(" sam", pFile);//向文件中输入字符串
fclose(pFile);//关闭文件
pFile = NULL;//指针置空,防止出现野指针
return 0;
}
示例2:
#include<stdio.h>
int main()
{
//fseek()示例2
FILE* pf = fopen("data1.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
//定位文件指针到f
int ch = fgetc(pf);
printf("%c\n", ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b
ch = fgetc(pf);
printf("%c\n", ch);//c
//int pos = ftell(pf);
//printf("%d\n", pos);//3
fseek(pf, -3, SEEK_CUR);//从文件指针的当前位置(d)向左偏移3,就回到了文件的起始位置
ch = fgetc(pf);
printf("%c\n", ch);//a
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
2 ftell()
返回文件指针当前位置相较于起始位置的偏移量。
函数原型:
long int ftell(FILE * stream);
示例:
#include<stdio.h>
int main()
{
//ftell()示例
FILE* pFile;
long size;
pFile = fopen("myfile.txt", "rb");//“rb”只读,打开失败会出错
if (pFile == NULL)//打开失败检查
{
perror("Error opening file");
}
else
{
//pFile较文件结束SEEK_END位置偏移0,相当于pFile指向文件结束
fseek(pFile, 0, SEEK_END);
size = ftell(pFile);//计算文件指针当前位置相较于起始位置的偏移量
fclose(pFile);//关闭文件
pFile = NULL;//文件指针置空,防止出现野指针
printf("size of myfile.txt:%ld bytes.\n", size);
//myfile.txt:fopen example -- 13
}
return 0;
}
3 rewind()
让文件指针的位置回到文件的起始位置。(让文件指针指向文件的起始位置)
函数原型:
void rewind(FILE * stream);
示例:
#include<stdio.h>
int main()
{
//rewind()示例
char buf[27];
FILE* pFile = fopen("file.txt", "w+");//“w+”读写数据
if (pFile == NULL)//打开失败检查
{
perror("Error opening file");
}
//文件读写
int i;
for (i = 'A'; i <= 'Z'; i++)
{
fputc(i, pFile);//每次写入一个字符,指针自动向后偏移一个单位
}//结束后,文件指针指向文件末尾
rewind(pFile);//让文件指针指向文件起始位置
fread(buf, 1, 26, pFile);//从文件中读取数据写入buf数组中
fclose(pFile);//关闭文件
pFile = NULL;//文件指针置空,防止出现野指针
buf[26] = '\0';//添加字符串结束标志
puts(buf);//打印字符串
//ABCDEFGHIJKLMNOPQRSTUVWXYZ
return 0;
}
六、文本文件和二进制文件
根据数据的组织形式,数据文件分为文本文件或者二进制文件。
-
数据在内存中以二进制的形式存储,如果不加转换就输出到外存,就是二进制文件。
-
如果要求在外存上以
ASCII
码的形式存储,则需要在存储前将二进制进行转换。以ASCII
字符的形式存储的文件就是文本文件。
1 数据在内存中的存储
一个数据在内存中是怎么存储的呢?
字符一律以 ASCII
形式存储,数值型数据既可以用 ASCII
形式存储,也可以用二进制形式存储。
例如:整数10000,如果以 ASCII
码的形式输出到磁盘,则磁盘占用5个字节(5个字符,每个字符一个字节),而以二进制形式输出,则在磁盘上只占用4个字节。
示例:
#include<stdio.h>
int main()
{
int a = 10000;
FILE* pf = fopen("test.txt", "wb");
fwrite(&a, 4, 1, pf);//将a的值以二进制的形式写到文件中
fclose(pf);//关闭文件
pf = NULL;//文件指针置空,防止出现野指针
return 0;
}
图示说明:打开二进制文件
七、文件读取结束的判定
1 被错误使用的 feof
feof
的作用:当文件读取结束的时候,判断读取结束的原因是否是因为遇到文件尾而结束。(已经结束,是否为EOF
)
注意:在文件读取的过程中,不能使用 feof
函数的返回值直接来判定文件是否结束。
2 判断文件是否读取结束
2.1 文本文件
文本文件读取是否结束,判断返回值是否是 EOF
( fgetc
),或者 NULL
( fgets
)
例如:
fgetc
判断是否为 EOF
。
fgets
判断返回值是否为 NULL
。
2.2 二进制文件
二进制文件读取结束判断,判断返回值是否小于实际要读取的个数。
例如:
fread
判断返回值是否小于实际要读取的个数。
3 示例
示例:文本文件
#include<stdio.h>
#include<stdlib.h>//EXIT_FAILURE
int main()
{
//文本文件
int c;
FILE* pf = fopen("file.txt", "r");
if (!pf)//指针为空进入
{
perror("File opening failed.");
return EXIT_FAILURE;
}
//fgetc:当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((c = fgetc(pf)) != EOF)
{
putchar(c);//输出
}
putchar('\n');
//ABCDEFGHIJKLMNOPQRSTUVWXYZ
//判断是什么原因导致结束的
if (ferror(pf))//判断是否因发生错误而导致读取结束
{
puts("I/O error when reading.");
}
else if (feof(pf))//判断是否因遇到EOF而导致读取结束
{
puts("End of file reached successfully.");//输出结果
}
return 0;
}
示例:二进制文件
#include<stdio.h>
enum
{
SIZE = 5 //枚举常量
};
int main()
{
//二进制文件
double arr[SIZE] = { 1.,2.,3.,4.,5. };
FILE* pf = fopen("test.bin", "wb");//二进制模式
fwrite(arr, sizeof * arr, SIZE, pf);//写入double数组
fclose(pf);//关闭文件
pf = NULL;//文件指针置空,防止出现野指针
double brr[SIZE];
pf = fopen("test.bin", "rb");
size_t ret_code = fread(brr, sizeof * brr, SIZE, pf);//读取duoble数组并接收返回值
if (ret_code == SIZE)
{
puts("Array read successfully,contents:");
int i;
for (i = 0; i < SIZE; i++)
{
printf("%f ", brr[i]);
}
putchar('\n');
/* 读取成功
* Array read successfully,contents:
* 1.000000 2.000000 3.000000 4.000000 5.000000
*/
}
else
{
if (feof(pf))//遇到文件结束标志返回的
{
printf("Error reading test.bin:unexpected end of file\n");
}
else if (ferror(pf))//遇到错误返回的
{
printf("Error reading test.bin");
}
}
fclose(pf);//关闭文件
pf = NULL;//文件指针置空,防止出现野指针
return 0;
}
八、文件缓冲区
1 简单介绍
ANSIC
标准采用“缓冲文件系统”来处理数据文件,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。
-
从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一并送到磁盘上。
-
如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。
-
缓冲区的大小是由C编译系统决定的。
注意:如果缓冲区没有满,可以刷新缓冲区,让缓冲区数据放入硬盘。
2 示例
代码示例:
#include<stdio.h>
#include<windows.h>//Sleep()
int main()
{
//文件缓冲区
FILE* pf = fopen("test1.txt", "w");
fputs("abcdef", pf);//会先将数据放在输出缓冲区中
printf("睡眠10秒,已经在写数据了,打开test.txt文件,发现文件没有内容\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);//刷新缓冲区,才将输出在缓冲区的数据写到文件(磁盘)内
printf("再睡眠10秒,再次打开test.txt文件,发现文件有内容了\n");
Sleep(10000);
fclose(pf);//在关闭文件的时候,也会刷新缓冲区
pf = NULL;//文件指针置空,防止出现野指针
return 0;
}
说明:
1 因为有缓冲区的存在,C语言在操作文件的时候,需要刷新缓冲区或者在文件操作结束的时候关闭文件才能对数据进行读写操作。如果不作刷新缓冲区的操作,可能导致读写文件的问题。
总结:
本节介绍了什么是程序文件与数据文件以及文件的打开与关闭方式;接着给出了文件顺序读写函数以及文件随机读写函数;最后给出了数据文件的分类、文件读取结束的判定方法和文件缓冲区的简单概念。
感谢您的阅读!如有任何错误,欢迎您的批评指正!