C语言把文件看作是一个字符(或字节)的序列,即由一个个字符或字节的数据顺序组成。一个输入输出流就是一个字符流或字节流。
数据在磁盘的存储:1.字符存储2.按字节存储(二进制文件)
文件缓冲区:ANSI C标准采用“缓冲文件系统”处理数据文件。
输出文件缓冲区
输出
程序数据区 磁盘
输入
输入文件缓冲区
当缓冲区满了,将数据从缓冲区输入或输出
C中的缓冲文件系统
1. 文件类型指针:
缓冲文件系统,关键是文件指针。每个被使用的文件都在内存中开辟一个响应的文件信息区,用来存放文件的相关信息(如,文件的名字、文件的形态以及当前位置等)。
这些信息保存在一个结构体变量中。
该结构体由系统声明(在某个头文件中,如stdio.h)
Typedef struct
{
short level; //缓冲区”满”或”空”的状态
unsignedflags; //文件状态标志
char fd; //文件描述符
unsigned char hold;//如缓冲区无内容不读取字符
short bsize; //缓冲区大小
unsigned char*buffer;//数据缓冲区的位置
unsigned char*curp;//指针当前的指向
unsigned istemp; //临时文件指示器
short token; //用于有效性检查
}FILE;
一般不用FILE定义变量,而是定义FILE类型的指针变量来指向文件的信息区。
通过该文件信息区的信息就能访问这个文件。(这些信息是在打开文件时由系统根据文件的情况自动放入的,用户不必过问)
因此,通过文件指针变量能够找到预支相关联的文件。
FILE *fp; fp指向某个文件在内存中的文件信息区。
2. 打开和关闭文件:
打开文件后才能对文件进行读写。打开文件就是:为文件建立相应的文件信息区(存放文件的信息)和文件缓冲区(暂存文件的输入输出信息)。
2.1 fopen
调用方式:
fopen(文件名,使用文件的方式);
如:FILE *fp;
fp=fopen(“file1”,”r”);
调用正确,返回文件在文件信息区的地址,错误,返回NULL
使用文件的方式:
文件使用方式 | 含义 | 如果指定文件不存在 |
“r”(只读) | 为了输入数据,打开已经存在的文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a” | 向文本尾添加数据 | 出错 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb” |
| 建立一个新文件 |
“ab”(追加) |
| 出错 |
“r+”(读写) |
| 出错 |
“w+”(读写) |
| 建立新文件 |
“a+”(读写) |
|
|
“rb+”(读写) |
|
|
“wb+” |
| 建立新文件 |
“ab+” |
|
|
如果不能实现“打开”任务fopen会返回出错信息(NULL),出错原因:
文件不存在、磁盘出故障、磁盘满等
常见的打开文件的方法:
if(fp=fopen(“file1”,”r”)==NULL)
{
printf(“can’t open thefile\n”);
exit(0); //表示正常退出
}
程序中可以使用的3个标准的流文件:标准输入流(向终端输入-键盘)、标准输出流(向终端输出-屏幕)、标准出错输出流(向终端输出错误)
系统已经对这三个文件制定了与终端的对应关系。
程序开始运行时系统自动打开这三个标准流文件,因此不需要用fopen打开这三个文件,系统定义了3个文件指针变量stdin、stdout、stderr
如果程序中指定从stdin所指的文件输入数据,就是从终端键盘输入数据
2.2用fclose函数关闭数据文件
fclose(文件指针);
成功完成关闭操作,返回0;否则返回-1
当使用完一个文件,要关闭它,防止被误用。“关闭”就是撤销文件信息区和文件缓冲区,是文件指针不再指向该文件。此后不能通过该指针引用该文件
用fclose关闭文件,将文件缓冲区的数据输入或输出,然后才撤销文件信息区(有的系统会自动在程序关闭时完成该操作)。
3. 顺序读写数据文件。
顺序读写文件需要用库函数实现。
3.1向文件读写字符
fgetc(fp); 从文件读取一个字符 读成功返回读的字符,否则返回文件结束标志(E0F即-1)
fputs(ch,fp);向文件写入字符ch ::
#include<stdio.h>
#include<stdlib.h>
intmain()
{
FILE*fp;
charch;
if((fp=fopen("1.txt","w+"))==NULL)
{
printf("Can'topenthefile\n");
exit(0);
}
ch=getchar();
while(ch!='#')
{
fputc(ch,fp);
ch=getchar();
}
fclose(fp);
return0;
}
将一个文件中的信息复制到另一个文件中
#include<stdio.h>
#include<stdlib.h>
intmain(void)
{
FILE*in,*out;
if((in=fopen("1.txt","r"))==NULL)
{
printf("Can'topenthefile\n");
exit(0);
}
if((out=fopen("2.txt","w"))==NULL)
{
printf("Can'topenthefile\n");
exit(0);
}
while(!feof(in))
fputc(fgetc(in),out);
fclose(in);
fclose(out);
return0;
}
3.2 向文件写入一个字符串。
fgets(str,n,fp);
函数原型:char *fgets(char*str,int n,FILE *fp);
n是得到的字符个数,实际上只是读了n-1个字符,然后在最后加’\0’,把它们放入字符串str中。如果在读完n-1个字符前遇到换行符’\n’或文件结束标志EOF,读入即结束。执行成功返回str数组元素首地址,如果一开始就遇到文件结尾或读数据出错返回NULL
fputs(str, fp);
函数原型:int fputs(char *str,FILE*fp);
字符串末尾的’\0’不输出,成功返回值为0,失败返回EOF
feof(fp):
feof(fp)有两个返回值:如果遇到文件结束,函数feof(fp)的值为非零值,否则为0
(C语言中如何表示路径,如在windows C:\ 在C语言中要表示为C:\\,因为C语言把\作为转义字符)
3.3格式化的方式读写文件。需要不断的进行格式转换,花费时间多。经常采用二进制方式读写
3.4用二进制方式向文件读写一组数据:
在程序中有时需要一次输入输出一组数据(数组或结构体变量的值),C语言允许fread从文件读入一个数据块,用fwrite向文件写入一个数据块。读写都是以而仅是的形式,节省时间。
调用形式:fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
buffer:是一个地址。对fread来说是存放从文件读出数据的存储区的地址
对fwrite来说是把从此地址开始的存储区的数据向文件输出
这个地址是起始地址
size:要读的字节数
count:要读多少个数据项
返回值是数据项的个数
具体用法来看看下面这个例子:
输入学生的信息,读出来:
#include<stdio.h>
#include<stdlib.h>
#defineSIZE3
structStudent_type
{
charname[20];
intnum;
intage;
};
intmain()
{
voidsave();
voiddisplay();
save();
display();
return0;
}
voidsave()
{
FILE*fp;
structStudent_typestud[SIZE];
inti;
if((fp=fopen("stu.dat","wb"))==NULL)
{
printf("Can'topenthefile\n");
exit(0);
}
printf("Pleaseinputthestudents'info:\n");
for(i=0;i<SIZE;i++)
scanf("%s%d%d",stud[i].name,&stud[i].num,&stud[i].age);
if(fwrite(&stud[0],sizeof(structStudent_type),5,fp)!=5)
{
printf("Can'twrite\n");
exit(0);
}
fclose(fp);
}
voiddisplay()
{
structStudent_typestud[SIZE];
FILE*fp;
inti;
if((fp=fopen("stu.dat","rb"))==NULL)
{
printf("Can'topenthefile\n");
exit(0);
}
for(i=0;i<SIZE;i++)
{
if(fread(&stud[i],sizeof(structStudent_type),1,fp)!=1)
{
printf("Readerror!\n");
exit(0);
}
printf("%s%d%d\n",stud[i].name,stud[i].num,stud[i].age);
}
fclose(fp);
}
如果要将sut_list.dat的数据读入到stu.dat中需要加入这个函数:
voidload()
{
FILE*fp;
structStudent_typestud[SIZE];
inti;
if((fp=fopen("stu_list.dat","rb"))==NULL)
{
printf("Can'topenthefile\n");
exit(0);
}
for(inti=0;i<SIZE;i++)
{
if(fread(&stud[i],sizeof(structStudent_type),1,fp)!=1)
{
if(feof(fp))
{
fclose(fp);
return;
}
printf("filereaderror\n");
}
}
fclose(fp);
}
多了一步,判断文件是否已经读完了。
4.随机读写文件
4.1文件位置标记
每个文件都有一个文件位置标记。顺序读写时:读一个字符,文件为位置标记自动下移一个字符,直到文件结束。
随机读写文件,关键在控制文件位置标记。这样可以在任意位置读写了
4.2文件位置标记的定位
4.2.1 用rewind函数使文件位置标记重新回到文件开头,此函数没有返回值
rewind(fp);
4.2.2 用fseek函数改变文件位置标记
调用形式:fseek(文件类型指针,位移量,起始位置);
位移量是long型整数
起始位置:
起始点 | 名字 | 用数字代表 |
文件开头 | SEEK_SET | 0 |
文件当前位置 | SEEK_CUR | 1 |
文件末尾位置 | SEEK_END | 2 |
4.2.3 用ftell函数测定文件位置标记的当前位置:返回相对于文件开头位移量,调动出错返回-1L
实现随机读写的一个例子:
#include <stdio.h>
#include <stdlib.h>
#define SIZE 3
struct Student_type
{
char name[20];
int num;
int age;
}stud[SIZE];
int main()
{
FILE *fp;
int i;
if((fp=fopen("stu.dat","rb"))==NULL)
{
printf("Can't open file\n");
exit(0);
}
for(i=0;i<3;i+=2)
{
fseek(fp,i*sizeof(struct Student_type),0);
fread(&stud[i],sizeof(struct Student_type),1,fp);
printf("%s %d %d\n",stud[i].name,stud[i].num,stud[i].age);
}
fclose(fp);
return 0;
}
4.5 文件读写的出错检查
4.5.1 ferror函数
在调用各种输出函数(fputc,fread等)如果出错,除了返回值有所反应外,还可以用ferror函数检查错误
返回0未出错,返回非0值出错。
对同一个文件每一次调用输入输出函数都会产生一个新的ferror函数值,因此在调用完一个输入输出函数时要立即检查ferror函数值,否则信息会丢失。
再执行fopen函数时,ferror初始值自动置为0
4.5.2clearerr函数。将文件错误标志和文件结束标志自动置为0,以便下一次读写。