C语言的文件输入输出
一些概念
- 程序文件:包括源程序文件(后缀为.c)、目标文件(后缀为.obj)、可执行文件(后缀为.exe)等。这种文件的内容是程序代码。
- 数据文件:文件的内容不是程序,而是供程序运行时读写的数据。
- 数据流:输入输出是数据传送的过程码,数据如流水一样从一处流向另一处,因此常将输入输出形象地称为流。
文件名的格式
文件分类
- ASCII文件:
- 二进制文件(映像文件):数据在内存中是以二进制形式存储的,如果不加转换地输出到外存,就是二进制文件,可以认为它就是存储在内存地数据的映像,所以称之为映像文件
- 如果要求在外存上以ASCII代码形式存储,则需要在存储前进行转换。ASCII文件又称文本文件,每一个字节存放一个字符的ASCII代码。
文件缓冲区
ANSI C标准采用**"缓冲文件系统"处理数据文件,所谓缓冲文件系统是指系统自动地在内存区为程序中每个正在使用地文件开辟一个文件缓冲区**。从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(给程序变量)。这样做是为了节省存取时间,提高效率,缓冲区的大小由各个具体的C编译系统确定。
文件指针类型
缓冲文件系统中,关键的概念是**”文件类型指针“,简称”文件指针“**。每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的相关信息(如文件的名字、文件状态及文件当前位置等).。这些信息是保存在一个结构体变量中的。该结构体由系统声明的,取名为FILE。
【例】有一种C编译环境提供的stdio.h头文件中有以下的文件类型声明:
可以不用管FILE中具体的内容,只需要知道其中存放了文件的相关信息即可。
定义一个FILE类型的变量:FILE f1;
以上定义了一个结构体变量f1,用它来存放一个文件的有关信息。但是一般不定义FILE类型的变量命名,而是设置一个指向FILE类型变量的指针变量,然后通过它来引用这些FILE类型变量。如果有n个文件,应设n个指针变量,分别指向n个FILE类型变量,以实现对n个文件的访问。
打开与关闭文件
fopen
fopen(文件名,使用文件方式)
文件使用方式 | 含义 | 如果指定的文件不存在 |
---|---|---|
r(只读) | 为了输入数据,打开一个已存在的文本文件 | 出错 |
w(只写) | 为了输出数据,打开一个文本文件 | 建立新文件 |
a(追加) | 向文本文件尾添加数据 | 出错 |
rb(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
wb(只写) | 为了输出数据,打开一个二进制文件 | 建立新文件 |
ab(追加) | 向二进制摁键尾添加数据 | 出错 |
r+(读写) | 为了读和写,打开一个文本文件 | 出错 |
w+(读写) | 为了读和写,建立一个新的文本文件 | 建立新文件 |
a+(读写) | 为了读和写,打开一个文本文件 | 出错 |
rb+(读写) | 为了读和写,打开一个二进制文件 | 出错 |
wb+(读写) | 为了读和写,建立一个新的二进制文件 | 建立新文件 |
ab+(读写) | 为读写打开一个二进制文件 | 出错 |
注:
-
如果不能实现”打开“的任务,fopen函数将会带回一个出错信息。出错的原因可能是:用r方式打开一个并不存在的文件;磁盘出故障;磁盘已满无法建立新文件等。此时fopen函数将带回一个空指针值NULL
-
不带b打开一个文本文件,输入和输出都能读到’\r’和’\n’。带b打开一个文本文件,输入和输出不能读到"\r" “\n”。
-
文件的输入和输出的形式和"使用文件的方式"无关,只和读写语句有关。
//按ASCII方式进行输入输出 fscanf(); fprintf(); //按二进制进行输入输出 fread(); fwrite();
fclose
fclose(文件指针)
函数也带回一个值,当成功地执行了关闭操作,则返回值为0,否则返回EOF(-1)。
向文件写入数据
fgetc & fpuc
函数名 | 调用形式 | 功能 | 返回值 |
---|---|---|---|
fgetc | fgetc(fp) | 从fp指向的文件读入一个字符 | 读成功,带回所读的字符,失败则返回文件结束符EOF(-1) |
fputc | fputc(ch,fp) | 把字符ch写到文件指针变量fp所指向的文件中 | 输出成功,返回值就是输出的字符;输出失败,则返回EOF(即-1) |
【例10.2】将一个磁盘文件中的信息复制到另一个磁盘文件中。今要求将上例建立的file1.dat文件中的内容复制到另一个磁盘文件file2.data中。
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *in,*out;
char ch,infile[10],outfile[10];
printf("输入读入文件的名字:");
scanf("%s",infile);
printf("输入输出文件的名字:");
scanf("%s",outfile);
if((in=fopen(infile,"r"))==NULL)
{
printf("无法打开%s文件\n",infile);
exit(0);
}
if((out=fopen(outfile,"w"))==NULL)
{
printf("无法打开%s文件\n",outfile);
exit(0);
}
ch = fgetc(in);
while(!feof(in))
{
fputc(ch,out);
putchar(ch);
ch = fgetc(in);
}
putchar(10);
fclose(in);
fclose(out);
return 0;
}
在文件的所有有效数字符后有一个文件尾标志。当读完全部字符后,文件读写位置标记就指向最后一个字符的后面,即指向了文件尾标志。**如果再执行读取操作,则会读出-1(不要理解为最后有一个结束字节,再其中存放了数值-1.它只是一种处理方法)。**文件尾标志用标识符EOF表示,EOF在stdio.h头文件中被定义为-1。
feof函数可以检测文件尾标志是否已被读取过。如果文件尾标志已被读出,则表示文件已结束,此时feof函数值为真(以1表示),否则feof函数值为假(以0表示)。不要把feof函数的值与文件尾标志的假设值-1相混淆。
fgets & fputs
函数名 | 调用形式 | 功能 | 返回值 |
---|---|---|---|
fgets | fgets(str,n,fp) | 从fp指向的文件读入一个长度为(n-1)的字符串,存放到字符数组str中。 | 读成功,返回地址str,失败则返回NULL |
fputs | fputs(str,fp) | 把str所指向的字符串写道文件指针变量fp所指向的文件中 | 输出成功,则返回0;否则返回非0值 |
格式化的方式读写文本文件
fprintf(文本指针,格式字符串,输出表列)
fscanf(文件指针,格式字符串,输入表列)
例如:
fprintf(fp, "%d,%6.2f", i, f);
它的作用是将int型变量i和float变量f的值按%d和%6.2f的格式输出到fp指向的文件中。若i = 3, f = 4.5,则输出到磁盘文件上的是以下的字符:
3,4.5
这是和输出到屏幕的情况相似的,只是它没有输出到屏幕而是输出到文件而已。
fscanf(fp, "%d,%f", &i, &f);
磁盘文件上如果有字符“3,4.5”,则从磁盘文件中读取整数3送给整型变量i,读取实数4.5送给float型变量f。
由于在输入时要将文件中的ASCII码转换为二进制形式再保存在内存变量中,在输出时又要将内存中的二进制形式转换成字符,要花费较多的时间。
二进制方式向文件读写一组数据
fread(buffer,size,count,fp)
fwrite(buffer,size,count,fp);
其中:
buffer:是一个地址。对fread来说,它是用来存放从文件读入的数据的存储区的地址。
对fwrite来说,是要把此地址开始的存储区中的数据向文件输出(以上指的是起始地址)。
size:要读写的字节数
count:要读写多少个数据项(每个数据项长度为size)
fp:FILE类型的指针。
fread是一个函数。从一个文件流中读数据,最多读取count个元素,每个元素size字节,如果调用成功返回实际读取到的元素个数,如果不成功或读到文件末尾返回 0。
【例10.4】从键盘输入10个学生的有关数据,然后把它们转存到磁盘文件上去。
#include<stdio.h>
#define SIZE 10
struct Student_type
{
char name[10];
int num;
int age;
char addr[15];
}stud[SIZE];
void save()
{
FILE *fp;
int i;
if((fp=fopen("stu.dat","wb"))==NULL)
{
printf("无法打开文件\n");
return ;
}
for(i=0;i<SIZE;i++)
{
// 如果没读到数据
if(fwrite(&stud[i],sizeof(struct Student_type),1,fp)!=1)
printf("文件写入错误\n");
}
fclose(fp);
}
int main()
{
int i;
printf("请输入学生的数据\n");
for(i=0;i<SIZE;i++)
scanf("%s%d%d%s",stud[i])
return 0;
}
随机读写数据文件
文件位置标记及定位
前已介绍,为了对读写进行控制,系统为每个文件设置了一个文件读写位置标记,用来指示“接下来要读写的下一个字符的位置”。
文件位置标记的定位
rewind(文件位置指针);//使文件位置标记指向文件开头
fseek(文件类型指针,位移量,起始点);
“起始点”用0,1或2代替,0代表“文件开始位置”,1为“当前位置”,2为“文件末尾位置”。
“位移量”指以“起始点”为基点,向前移动的字节数。位移量应是long型数据。
fseek函数一般用于二进制文件。
ftell(文件位置指针)//函数测定文件位置标记的当前位置
如果调用函数时出错,ftell函数返回值为-1L。
【例10.6】在磁盘文件上存有10个学生的数据。要求将第1,3,4,7,9个学生数据输入计算机,并在屏幕上显示出来。
#include<stdio.h>
#include<stdlib.h>
struct Student_type {
char name[10];
int num;
int age;
char addr[15];
}stud[10];
int main() {
int i;
FILE *fp;
if((fp=fopen("stu.dat","rb")) == NULL) {
printf("不能打开文件\n");
exit(0);
}
for(i = 0; i < 10; i += 2) {
// 读取二进制文件(文件指针,偏移量,位置)
fseek(fp, i*sizeof(struct Student_type), 0);
// 二进制读取方式,(buffer, size, count, fp)
fread(&stud[i], sizeof(struct Student_type), 1, fp);
}
fclose(fp);
return 0;
}
文件读写的出错检测
ferror
如果ferror返回值为0(假),表示未出错;如果返回一个非零值,表示出错。
应该注意,对同一个文件每一次调用输入输出函数,都会产生一个新的ferror函数值,因此,应当在调用一个输入输出函数后立即检查ferror函数的值,否则信息会丢失。
在执行fopen函数时,ferror函数的初始值自动置为0
clearerr
clearerr的作用时使文件出错标志和文件结束标志置为0。假设在调用一个输入输出函数时出现错误,ferror函数值为一个非零值。应该立即调用clearerr,使ferror的值变成0,以便再进行下一次的检测。
只要出现文件读写出错标志,它就一直保留,直到对同一文件调用clearerr函数或rewind函数,或任何其他一个输入输出函数。
习题
1. 什么是文件型指针?通过文件指针访问文件有什么好处?
- 缓冲文件系统中,关键概念是“文件类型指针”,简称“文件指针”。每个被使用的文件都再内存中开辟一个相应的文件信息区,用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息保存在一个结构体变量中。该结构体类型是由系统声明的,取名为FILE。
- 助记:
- “缓冲文件系统”、关键概念是文件型指针、使用文件都需要开辟一个文件信息区、文件指针就指向这些文件信息区、文件信息区有关信息有:文件名字、文件状态、文件当前位置
- 通过文件指针访问文件的好处是:可以随机访问文件,有效表示数据结构,动态分配内存,方便使用字符串,有效使用数组。
- 助记:
- 随机访问文件:指针可以随意的指向任何文件,因此文件指针可以随机访问文件。
- 有效表示数据结构:文件指针指向了文件类型的结构体。这种结构体是一种数据结构。
- 动态分配内存:如果看到“动态分配内存”,就应该想到两个函数——malloc和realloc。因为这两个函数可以动态分配内存,因此文件指针可以动态分配内存。
- 方便使用字符串:文件指针可以指向字符串,只要取到字符串的首地址。
- 方便使用数组:文件指针可以指向数组,只要取到数组的首地址就行。
2. 对文件的打开与关闭的含义是什么?为什么要打开和关闭文件?
- “打开”是指为文件建立相应的信息区(用来存放有关文件的信息)和文件缓冲区(用来暂时存放输入输出的数据)
- 助记:打开一个文件,系统需要有地方存储这类文件,因此需要一个文件信息区。文件的读入需要一个缓冲区,因此要有一个文件缓冲区
- “关闭”是指撤销文件信息区和文件缓冲区,使文件指针变量不再指向该文件,显然就无法进行对文件的读写了。
- 助记:文件关闭了就代表没有用了,系统就可以关闭相应的文件信息区和文件缓冲区。
3. 从键盘输入一个字符串,将其中的小写字母全部转换成大写字母,然后输出到一个磁盘文件test中保存,输入的字符串以“!”结束。
#include<stdio.h>
#include<stdlib.h>
int main()
{
char ch;
FILE *fp = NULL;
if((fp=fopen("test","w"))==NULL){
printf("打开test文件错误\n");
exit(0);
}
printf("输入一串字符,以“!”为结尾,这串字符将存入文件test中\n");
while((ch=getchar())!=EOF&&ch!='!')
{
if(ch>='a'&&ch<='z')
{
ch = ch-'a'+'A';
}
fputc(ch,fp);
// ch = getchar(); 不要写
}
fclose(fp);
printf("文件已关闭,程序退出!\n");
return 0;
}
4. 有两个磁盘文件A和B,各存放一行字母,今要求把这两个文件中的信息合并(按字母顺序排列),输出到一个新文件C中去。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char str_a[30],str_b[30],str_c[30];
FILE *fa,*fb,*fc;
printf("有两个磁盘文件A和B,各存放一行字母今要求把这两个文件中的信息合并(按字母顺序排列),输出到一个新文件C中去。\n");
printf("--------\n");
fa = fopen("file_a.txt","r");
if(fa==NULL)
{
printf("文件file_a打不开!\n");
exit(0);
}
fb = fopen("file_b.txt","r");
if(fb==NULL)
{
printf("文件file_b打不开!\n");
exit(0);
}
fc = fopen("file_c.txt","w");
if(fc==NULL)
{
printf("文件file_c打不开!\n");
exit(0);
}
fgets(str_a,10,fa);
fgets(str_b,22,fb);
int flag = strcmp(str_a,str_b);
if(flag>0)
{
fputs(str_a,fc);
fputc('\n',fc);
fputs(str_b,fc);
}else{
fputs(str_b,fc);
fputc('\n',fc);
fputs(str_a,fc);
}
fclose(fa);
fclose(fb);
fclose(fc);
return 0;
}
5. 有5个学生,每个学生有3门课程的成绩,从键盘输入学生数据(包括学号,姓名,3门课程成绩),计算出平均成绩,将原有数据和计算出的平均分数存放在磁盘文件stud中。
#include <stdio.h>
#include<stdlib.h>
#define NAMLEN 20
#define STUCNT 5
typedef struct student_t{
int num;
char name[NAMLEN];
int score[3];
} student;
int main(){
FILE *sp;
if((sp=fopen("stud.txt","wb"))==NULL)
{
printf("无法打开文件stud.txt!\n");
exit(0);
}
student students[STUCNT];
int sum;
double aver = 0;
for (int i = 0; i < STUCNT; i++){
scanf("%d%s%d%d%d", &students[i].num, students[i].name, &students[i].score[0], &students[i].score[1], &students[i].score[2]);
sum = students[i].score[0] + students[i].score[1] + students[i].score[2];
}
aver = sum/STUCNT;
for(int i=0;i<STUCNT;i++)
{
if(fwrite(&students[i],sizeof(student),1,sp)!=1)
printf("文件写入错误\n");
}
char str_chn[] = {"平均分为 "};
fwrite(str_chn,sizeof(char)*5,1,sp);
fwrite(&aver,sizeof(int),1,sp);
fclose(sp);
return 0;
}
6. 将第5题stud文件中的学生数据,按平均分进行排序处理,将已排序的学生数据存入一个新文件stu_ sort 中。
#include<stdio.h>
#include<stdlib.h>
#define NAMLEN 20
#define STUCNT 5
typedef struct student_t{
int num;
char name[NAMLEN];
int score[3];
} student;
float score_aver[STUCNT];//平均成绩
int main()
{
FILE *sp,*sps;
if((sp=fopen("stud.txt","rb"))==NULL)
{
printf("无法打开stud.txt文件\n");
exit(0);
}
student stu[STUCNT];
for(int i=0;i<STUCNT;i++)
{
fread(&stu[i],sizeof(student),1,sp);
printf("%d %s %d %d %d\n",stu[i].num,stu[i].name,stu[i].score[0],stu[i].score[1],stu[i].score[2]);
}
for(int i=0;i<STUCNT;i++)
{
fread(&score_aver[i],sizeof(student),1,sp);
printf("%6.2f \n",score_aver[i]);
}
for(int i=0;i<STUCNT;i++)
{
int min = i;
int j;
for(j = i;j<STUCNT;j++)
{
if(score_aver[min]>score_aver[j])
{
min = j;
}
}
if(min != i)
{
int t = score_aver[min];
score_aver[min] = score_aver[i];
score_aver[i] = t;
student temp;
temp = stu[min];
stu[min] = stu[i];
stu[i] = temp;
}
}
if((sp=fopen("stu_sort.txt","wb"))==NULL)
{
printf("无法打开stu_sort.txt文件\n");
exit(0);
}
for(int i=0;i<STUCNT;i++)
{
fwrite(&stu[i],sizeof(student),1,sp);
// printf("%d %s %d %d %d\n",stu[i].num,stu[i].name,stu[i].score[0],stu[i].score[1],stu[i].score[2]);
}
for(int i=0;i<STUCNT;i++)
{
fwrite(&score_aver[i],sizeof(student),1,sp);
// printf("%6.2f \n",score_aver[i]);
}
return 0;
}
7. 将第6题已排序的学生成绩文件进行插入处理。插入一个学生的3门课程成绩,程序先计算新插入学生的平均成绩,然后将它按成绩高低顺序插入,插入后建立一个新文件。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Student
{
int num;
char name[30];
int score[3];
float avg;
};
void sort(struct Student stu[],int len)
{
int i,j;
struct Student tmp;
for(i=0;i<len;i++)
{
int min = i;
for(j=i+1;j<len;j++)
{
if(stu[j].avg > stu[min].avg)
min = j;
}
if(min != i)
{
tmp = stu[min];
stu[min] = stu[i];
stu[i] = tmp;
}
}
}
int main()
{
int i;
struct Student stu[5];
FILE *fp = NULL;
if((fp=fopen("stu_sort.txt","rb"))==NULL)
{
printf("无法打开文件read error\n");
exit(0);
}
if(fread(stu,sizeof(stu),1,fp)!=1)
{
printf("写入错误\n");
exit(0);
}
fclose(fp);
struct Student new_stu[6];
memcpy(new_stu,stu,sizeof(stu));
printf("请输入一个新的学生信息:\n");
scanf("%d%s%d%d%d",&new_stu[5].num,new_stu[5].name,&new_stu[5].score[0],&new_stu[5].score[1],&new_stu[5].score[2]);
new_stu[5].avg = (new_stu[5].score[0]+new_stu[5].score[1]+new_stu[5].score[2])/3.0;
sort(new_stu,6);
FILE *fw = fopen("tmp_sort","wb");
fwrite(new_stu,sizeof(new_stu),1,fw);
fclose(fw);
return 0;
}
8. 将第7题结果仍存入原有的stu_sort 文件而不另建立新文件。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Student
{
int num;
char name[30];
int score[3];
float avg;
};
void sort(struct Student stu[],int len)
{
int i,j;
struct Student tmp;
for(i=0;i<len;i++)
{
int min = i;
for(j=i+1;j<len;j++)
{
if(stu[j].avg > stu[min].avg)
min = j;
}
if(min != i)
{
tmp = stu[min];
stu[min] = stu[i];
stu[i] = tmp;
}
}
}
int main()
{
int i;
struct Student stu[5];
FILE *fp = NULL;
if((fp=fopen("stu_sort.txt","rb"))==NULL)
{
printf("无法打开文件read error\n");
exit(0);
}
if(fread(stu,sizeof(stu),1,fp)!=1)
{
printf("写入错误\n");
exit(0);
}
fclose(fp);
struct Student new_stu[6];
memcpy(new_stu,stu,sizeof(stu));
printf("请输入一个新的学生信息:\n");
scanf("%d%s%d%d%d",&new_stu[5].num,new_stu[5].name,&new_stu[5].score[0],&new_stu[5].score[1],&new_stu[5].score[2]);
new_stu[5].avg = (new_stu[5].score[0]+new_stu[5].score[1]+new_stu[5].score[2])/3.0;
sort(new_stu,6);
FILE *fw = fopen("stu_sort.txt","wb");
fwrite(new_stu,sizeof(new_stu),1,fw);
fclose(fw);
return 0;
}
9. 有一磁盘文件employee,内存放职工的数据。每个职工的数据包括职工姓名、职工号、性别、年龄、住址、工资、健康状况、文化程度。今要求将职工名、工资的信息单独抽出来另建一个简明的职工工资文件。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct employee
{
int num;
char name[20];
char sex[4];
int age;
char addr[60];
int salary;
char health[10];
char classes[10];
};
struct emp
{
char name[30];
int salary;
};
int main()
{
int i;
FILE *fp1,*fp2;
struct emp emp_arr[5];
struct employee employee_arr[5];
if((fp1 = fopen("stud.txt","rb"))==NULL)
{
printf("无法打开employee\n");
exit(0);
}
fread(employee_arr,sizeof(employee_arr),1,fp1);
fclose(fp1);
for(i=0;i<5;i++)
{
strcpy(emp_arr[i].name,employee_arr[i].name);
emp_arr[i].salary = employee_arr[i].salary;
}
if((fp2 = fopen("emp.txt","wb"))==NULL)
{
printf("无法打开emp\n");
exit(0);
}
fwrite(emp_arr,sizeof(emp_arr),1,fp2);
fclose(fp2);
return 0;
}
10.从第9题的“职工工资文件”中删去一个职工的数据,再存回原文件。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct emp {
char name[32];
int salary;
};
int main( void ) {
int i;
FILE *fp;
char name[32];
struct emp emp_arr[5];
fp=fopen("emp", "rb");
fread(emp_arr, sizeof(emp_arr), 1, fp);
fclose(fp);
printf("name:");
scanf("%s", &name);
fp=fopen("emp", "wb");
for (i=0; i<5; i++) {
if ( strcmp(emp_arr[i].name, name) == 0 ) {
continue;
}
fwrite(&emp_arr[i], sizeof(emp_arr[i]), 1, fp);
}
fclose(fp);
}
11. 从键盘输入若干行字符(每行长度不等),输入后把它们存储到一磁盘文件中。再从该文件中读入这些数据,将其中小写字母转换成大写字母后在显示屏上输出。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
// int i;
FILE *fp ;
if((fp= fopen("tmpxx.txt", "w"))==NULL)
{
printf("无法打开tmp文件\n");
exit(0);
}
char buf[1024] = {};
printf("请输入一些字符串,这些字符串将会存入到tmp文件中.\n");
scanf("%s",buf);
while(strcmp(buf,"#")!=0)
{
strupr(buf);
strcat(buf,"\n");
if(fputs(buf,fp)==-1)
printf("文件写入失败!\n");
memset(buf,'0',sizeof(buf));
scanf("%s",buf);
}
fclose(fp);
return 0;
}