了解前我先来了解数据流和缓冲区的概念
-
数据流
就C程序而言,从程序移进,移出字节,这种字节流就叫做流,程序与数据的交互是以流的形式进行的。 -
缓冲区
程序执行时,所提供的额外内存,可用来暂时存放准备执行的数据。它的设置是为了提高存取效率,因为内存的存取速度比磁盘驱动器快得多。
NSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
1. 文件的打开和关闭
1.1 文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
例如,VS2013编译环境提供的stdio.h头文件中有以下的文件类型申明:
struct_iobuf {
char*_ptr; //指向buffer中第一个未读的字节
int _cnt; //记录剩余的未读字节的个数
char*_base;//文件的缓冲
int _flag;//打开文件的属性
int _file;//获取文件描述
int _charbuf;//单字节的缓冲,即缓冲大小仅为1个字节
int _bufsiz;//记录这个缓冲大小
char*_tmpfname;//临时文件名
};
typedefstruct_iobufFILE;
1.2文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
//打开文件
FILE*fopen ( constchar*filename, constchar*mode );
//关闭文件
intfclose ( FILE*stream );
- 文件的打开
fopen():打开文件 - 文件的关闭
fclose():关闭文件 - 文件的读写
fgetc():读取一个字符
fputc():写入一个字符
fgets():读取一个字符串
fputs():写入一个字符串
fprintf():写入格式化数据
fscanf():格式化读取数据
fread():读取数据
fwrite():写入数据 - 文件状态检查
feof():文件是否结束
ferror():文件读/写是否出错
clearerr():清除文件错误标志
ftell():文件指针的当前位置 - 文件指针定位
rewind():把文件指针移到开始处
fseek():重定位文件指针
文件的使用方式:
使用方式 | 作用 | 如果文件不存在 |
---|---|---|
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a”(追加) | 向文本文件添加数据 | 建立一个新的文件 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
“ab”(追加) | 向一个二进制文件尾添加数据 | 出错 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建立一个新的文本文件 | 建立一个新的文件 |
“a+”(读写) | 打开一个文本文件,在文件尾进行读写 | 建立一个文件 |
“rb+”(读写) | 为了读和写,打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,建立一个新的二进制文件 | 建立一个新的文件 |
“ab+”(读写) | 打开一个二进制文件,在文件尾进行读写 | 建立一个新的文件 |
文件的顺序读写
函数名 | 功能 | 适用性 |
---|---|---|
fgetc() | 字符输入函数 | 所有输入流 |
fputc() | 字符输出函数 | 所有输出流 |
fgets() | 文本行输入函数 | 所有输入流 |
fputs() | 文本行输出函数 | 所有输出流 |
fscanf() | 格式化输入函数 | 所有输入流 |
fprintf() | 格式化输出函数 | 所有输出流 |
fread() | 二进制输入 | 文件 |
fwrite() | 二进制输出 | 文件 |
fputc/fgetc/fputs/fgets
int fputc ( int character, FILE * stream )
int fgetc ( FILE * stream)
int fputs ( const char * str, FILE * stream )
char * fgets ( char* str, int num, FILE * stream )
// fputc
//将a-z的字符写到test.txt文档里面
FILE*pf = fopen("test.txt", "w");
if (NULL == pf)
{
perror("fopen");
return 1;
}
for (int i = 0; i < 26; i++) {
fputc('a' + i, pf);
}
//读文件 fputs
int i = 0;
for (i = 0; i < 26; i++)
{
int ch = fgetc(pf);
printf("%c ", ch);
}
//fgets
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//写文件-一行一行写
fputs("hello\n", pf);
fputs("bite\n", pf);
//读文件-一行一行读,写到将文件里面写到arr数组里
char arr[20] = "#########";
fgets(arr, 20, pf);
printf("%s", arr);
fgets(arr, 20, pf);
printf("%s", arr);
注意:这里fputs函数虽然是整行写入,但会覆盖掉原始数据.
fscanf/fprintf
//int fscanf(FILE* fp, char* format, ...);
//int fprintf(FILE* fp, char* format, ...);
//fprintf
struct S s = { "zhangsan", 20, 95.5f };
//把s中的数据写到文件中
FILE*pf = fopen("test.txt", "w");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//写文件
fprintf(pf, "%s %d %.1f", s.name, s.age, s.score);
fclose(pf);
pf = NULL;
//fscanf
struct S s = {0};
//把s中的数据写到文件中
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//读文件
fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));
printf("%s %d %.1f\n", s.name, s.age, s.score);
fclose(pf);
pf = NULL;
**如何将fscanf和fprintf实现scanf和printf呢?** VS会默认打开3个文件
stdin、stdout、stderr,使用 scanf()、gets() 等函数从键盘读取数据,使用 printf()、puts() 等函数向屏幕上输出数据,使用的标准I/O流,所以我们只要吗指针指向键盘和屏幕就可以将将fscanf和fprintf实现scanf和printf了。
//scanf(...)
//fscanf(stdin,...) 标准输入流
//
//printf
//fprintf(stdout, ...) 标准输出流
int main()
{
int ch = fgetc(stdin);
fputc(ch, stdout);
return 0;
}
fread/frwrite
size_t fwrite(voidbuffer,size_ size,size_t count,FILEstream);
size_t fread(voidbuffer,size_t size,size_t count,FILEstream);
1.buffer: 是读取的数据存放的内存的指针,
(可以是数组,也可以是新开辟的空间)
ps : 是一个指向用于保存数据的内存位置的指针(为指向缓冲区保存或读取的数据或者是用于接收数据的内存地址)
2.size : 是每次读取的字节数
3.count : 是读取的次数
4.stream : 是要读取的文件的指针
ps: 是数据读取的流(输入流)
//二进制的读文件
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s1 = { "zhangsan", 20, 95.5f };
//把s中的数据写到文件中
FILE*pf = fopen("test.txt", "wb");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//二进制的写到文件中
fwrite(&s, sizeof(s), 1, pf);
fclose(pf);
pf = NULL;
return 0;
struct S s2 = { 0 };
//把s中的数据写到文件中
FILE* pf = fopen("test.txt", "rb");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//二进制的读文件
fread(&s, sizeof(s), 1, pf);
printf("%s %d %f\n", s.name, s.age, s.score);//读到屏幕中
fclose(pf);
pf = NULL;
return 0;
}
文件的随机读写
fseek()函数:
该函数可以从定位位置的偏移量处开始读写;
int fseek( FILE stream, long offset, int origin );
返回值:
- 如果成功,fseek返回0;
- 否则,它返回一个非零值;
- 在无法查找的设备上,返回值未定义;
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return;
}
//定位指针:比如要读取从头开始向后偏移 2 个单位的一个字符
fseek(pf, 2, SEEK_SET);
int ch = fgetc(pf);
printf("%c\n", ch);
//第二次读取:要拿到当前文件指针所处位置向后偏移5个单位的字符
fseek(pf, 5, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch);
//第三次读取:要拿到文件流末尾向前偏移8个单位的一个字符
fseek(pf, -8, SEEK_END);
ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
注意: 在每使用完一次fseek函数后,文件指针会自动向后移动一位。
ftell():
该函数可以返回文件指针相对于起始位置的偏移量;
long int ftell ( FILE * stream );
rewind()
让文件指针回到文件初始位置;
void rewind ( FILE * stream );
2.文本文件和二进制文件
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节(VS2013测试)。
#include <stdio.h>
int main()
{
int a=10000;
FILE*pf=fopen("test.txt", "wb");
fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
fclose(pf);
pf=NULL;
return 0;
}
3. 文件读取结束的判定
3.1被错误使用的feof
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
例如:
文本文件:
- 如果用 fgetc() 读取,要判断 feof() 的返回值是否为EOF;
- 如果用 fgets() 读取,要判断 feof() 的返回值是否为NULL(0);
二进制文件:
都是使用 fread() 读取,要判断其返回值与指定读取个数的大小,如果小于实际要读的个数,就说明发生读取异常,
如果等于实际要读的个数,就说明是因读取成功而结束;
对于读取异常的判断,我们考虑判断 ferror() 函数的返回值:
- 若ferrror()为真——异常读取而结束;
- 若feof()为真——正常读取到尾而结束;
#include <stdio.h>
enum { SIZE = 5 };
int main(void)
{
double a[SIZE] = { 1.,2.,3.,4.,5. };
FILE* fp = fopen("test.bin", "wb"); // 必须用二进制模式
fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组
fclose(fp);
double b[SIZE];
fp = fopen("test.bin", "rb");
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 { // error handling
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp)) {
perror("Error reading test.bin");
}
}
/*
//判断是什么原因结束的
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(pf);
pf = NULL;
return 0;
}