为了提高数据输入/输出的处理效率,可以将程序运行时所需要的原始数据从文件中读取,并将程序运行的结果写入到文件中。
(1)文件概述
1)基本概念
文件是指存储在外部介质上数据的集合,可以是程序文件、可执行文件、也可以是原始数据文件或一组输出结果。 文件有不同的类型,可以通过扩展名来区分。操作系统是以文件为单位对数据进行管理的,并把每一个与主机相连的输入或输出设备都看作是一个文件。文件系统功能是操作系统的重要功能和组成部分。
c语言程序中的数据可以从文件中读入或输出到文件中。c语言对文件的处理主要依赖于标准输入输出函数。c语言处理的文件与windows操作系统的文件概念相同。
2)文件分类
根据要处理的文件存储的编码形式,可以把文件分为ASCII文件和二进制文件。ASCII文件又称为文本文件(.TXT),是以ASCII码值进行存储和编码的,文件的内容就是字符,每个字符在内存中占一个字节;而二进制文件是把内存中的数据按其在内存中的存储形式原样输出到磁盘。
3)文件缓冲区
对文件的输入/输出分为缓冲文件系统和非缓冲文件系统。
通过设置文件缓冲区来缓解系统对磁盘文件的存取速度与内存数据存取速度不同,而在数据量较大时数据读写的矛盾。
标准c语言采用缓冲文件系统处理文本文件和二进制文件。要求程序与文件之间有一个内存缓冲区,程序与文件的数据交换通过缓冲区来进行。缓冲文件系统是指系统在内存区为每一个正在使用的文件开辟一个缓冲区。
C语言中对文件的读写都是用库函数来实现的,标准C语言规定了若干输入输出函数,用他们来对文件进行读写。
在非缓冲文件系统中,由每个程序来为每个文件设置缓冲区。不同的文件系统对文件的处理采用不同的方式,UNIX采用缓冲文件系统来处理文本文件,用非缓冲系统处理二进制文件。标准C语言只采用缓冲文件系统。非缓冲文件系统只以二进制方式处理文件,且降低了程序的可移植性。
4)文件类型指针
在缓冲文件系统中,每个被使用的文件都在内存中开辟一个区域,用来存放文件的相关信息(如文件名,状态等),把这些信息保存在结构体类型的变量中,由系统为该结构体类型取名为FILE,包含在stdio.h文件中,例如:
typedef struct{ short level; //缓冲区的使用量 unsigned flag; //文件状态标志 char fd; //文件号 short bsize //缓冲区大小 unsigned char *buffer //文件缓冲区首地址 unsigned char *curp //指向文件缓冲区的指针 unsigned char *hold //其他信息 unsigned istemp short token }FILE;
不同的C语言编译系统的FILE类型包含的内容不完全一样,但大同小异。定义文件指针的方法如下:
FILE *p;//p是一个指向FILE类型结构体的指针变量。
使用p指向某文件的结构体变量,从而可以访问该文件的信息。换句话说,通过文件指针变量可以找到与它相关的文件。有几个文件就要设几个FILE类型的指针变量。
(2)文件的打开和关闭
1)打开文件
标准c语言规定引用I/O函数库时,用fopen()函数实现打开文件,该函数调用的一般形式为:
FILE *fp;
fp=fopen(文件名,文件使用方式);
常用的文件使用方式及其含义:
文件使用方式 含义
"r"(只读) 为输入打开一个文本文件
"w" (只写) 为输出打开一个文本文件
"a"(追加) 向文本文件尾增加数据
"rb"(只读) 为输入打开一个二进制文件
"wb"(只写) 为输出打开一个二进制文件
"ab" (追加) 向二进制文件尾增加数据
"r+"(读写) 为读/写打开一个文本文件
"w+" (读写) 为读/写建立一个文本文件
"a+" (读写) 为读/写打开一个文本文件
"rb+"(读写) 为读/写打开一个二进制文件
"wb+"(读写) 为读/写建立一个二进制文件
"ab+"(读写) 为读写打开一个二进制文件
说明:
1)"r"方式打开已存在的文件,用于向计算机读入数据。
2) "w"方式是向打开的文件写(输入)数据。若不存在该文件,则在打开时新建一个指定名字的文件。若打开的文件已经存在,则重新向文件里写数据,原来的数据被全部覆盖。
3) "a"方式是向已经存在的并打开的文件末尾增加数据。
4) "r+"方式是从已经存在的文件中向计算机中输入数据,也可以将数据写入文件中。
5) "w+"方式先建立一个文件,再向该文件写入数据,也可以将数据写入到文件中。
6) "a+"向已存在的文件中数据末尾增加数据,也可以读文件中的数据。
"rb+","wb+","ab+"相应于二进制文件的读写方式。
if((fp=fopen("file1","r"))==NULL){//NULL在标准I/O函数库中被定义为0; printf("Can't open this file! "); exits(0);//作用是关闭所有的文件,终止正在执行的程序。 }
2)关闭文件
关闭文件即是文件指针不再指向该文件。文件使用完毕之后应该立即关闭该文件。
函数调用的一般形式:
fclose(fp);
fclose函数成功关闭文件后返回0;否则返回EOF(EOF在stdio.h中被定义为-1)。
在关闭文件之后如果再次想要执行对文件的读写操作,则必须再执行"打开"操作。
(3)文件的顺序读写
c语言提供了多组向文件读写的函数,如fsanf和fprintf函数、fgetc和fputc函数、fread和fwrite函数、fgets和fputs函数等,下面开始逐一介绍:
1)格式化读写函数fsanf和fprintf函数:
scanf函数是从键盘输入数据给程序,printf是将程序运行结果输出到屏幕上。而scanf和fprintf函数是从文件中读写数据。
函数调用的一般形式:
fscanf(文件指针,格式字符串,输入表列);
fprintf(文件指针,格式字符串,输出表列);
例如:fsanf(fp,"%d%f",&i,&s);
2)字符串读写函数fgetc和fputc
fgetc函数用于从指定的文件中读出一个字符给变量,该函数调用的一般形式为:
ch=fgetc(fp);
注意:当都到文件结束符时,函数返回一个文件结束标志EOF,其值为-1。
因为标准c使用缓冲文件系统来处理二进制文件,有可能读进来的二进制恰好是-1。所以,可以使用系统提供的feof函数来判断文件是否结束。feof(fp)的值为1(真)数据读写结束,否则为0(假)未结束。
fputc函数实现向以经打开的文件中写入一个字符,该函数调用的一般形式为
fputc(ch,fp);
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 int main(){ 5 //1.定义一个打开文件的指针 6 FILE *fp; 7 //2.定义一个接收用户输入的字符和用于新建文本的名称及路径 8 char ch,filepath[20],filename[20]; 9 strcpy(filepath,"d:/test/"); 10 printf("请输入需要新建并打开的文件名称: "); 11 gets(filename); 12 //拼接用户文件的路径 13 strcat(filepath,filename); 14 //新建文件或以覆盖方式打开文件,进行写操作 15 if((fp=fopen(filepath,"w"))==NULL){ 16 printf("Can't open file! "); 17 exit(0); 18 } 19 fprintf(fp,"%s","下面是操作文件时产生的数据: "); 20 char name[10]; 21 int age; 22 printf("请输入您的姓名,以回车结束和年龄: "); 23 gets(name); 24 scanf("%d",&age); 25 fprintf(fp,"name=%s ,age=%d",name,age); 26 strcpy(name,""); 27 age=0; 28 printf("再输入一些自己的介绍,以#号结束: "); 29 while(ch!='#'){ 30 fputc(ch,fp); 31 ch=getchar(); 32 } 33 fclose(fp); 34 if((fp=fopen(filepath,"r"))==NULL){ 35 printf("Can't open file! "); 36 exit(0); 37 } 38 printf("下面打印文件中的内容: "); 39 //进行读操作,并打印在屏幕上 40 while((ch=fgetc(fp))!=' '){ 41 putchar(ch); 42 } 43 //输出最后的while条件中的回车符 44 putchar(ch); 45 fscanf(fp,"name=%s ,age=%d",&name,&age); 46 printf("name=%s,age=%d",name,age); 47 while((ch=fgetc(fp))!=-1){ 48 putchar(ch); 49 } 50 fclose(fp); 51 return 0; 52 }
在这里,第二个语句name=MenAngel明明结尾没有' '但依然在输出结果中有了' ',可能由于上面的scanf语句输入年龄时的回车造成下面ch=getchar();接收字符时出现问题。后续详细探讨!
3)数据块读写函数fread和fwrite
使用fread函数和fwrite函数可以一次性读出或写入一组数据,该函数调用的一般形式:
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
其中,buffer是一个指针,是读出或写入数据的起始地址。size是要读写数据的字节数。count是读写多个size字节数(数据项),fp是文件类型指针。例如:
fread(a,2,3,fp);//a[3];也可以写成fread(a,sizeof(int),3,fp);
其中,a是整型数组名称,一个整型数占两个字节。函数的含义是从fp所指向的文件读入3次(每次2个字节)数据,存入a数组中。又如,已知结构体类型为:
struct student
{
char name[10];
int num;
int age;
char addr[20];
}stu[30];
结构体数组stu有30个数组元素,每个数组元素都存放一个学生的数据信息(包括name、num、age、和addr)。假设学生的数据已经存在了fp指向的文件中,从文件中读出一个学生的一个数据信息给stu的数组元素。程序段如下:
for(i=0;i<30;i++){
fread(&stu[i],sizeof(struct student),1,fp);
}
同样也可以将数组元素的值写入磁盘文件。程序段如下:
for(i=0;i<30;i++){
fwrite(&stu[i],sizeof(struct student),1,fp);
}
1 //本程序运行前首先保证h盘下有个test文件夹 2 #include<stdio.h> 3 #include<string.h> 4 #include<stdlib.h> 5 #include<time.h> 6 7 struct student{ 8 char name[10]; 9 long number; 10 int age; 11 }; 12 typedef struct student Student; 13 int main(){ 14 printf("首先测试fwrite函数和fread函数: "); 15 srand(time(NULL)); 16 //测试fwrite函数和fread函数; 17 //1产生测试的数据 18 Student stu[10]; 19 int i; 20 char str[15]; 21 strcpy(str,"student"); 22 char temp[10]; 23 for(i=0;i<10;i++){ 24 itoa(i+1,temp,10); 25 strcat(str,temp); 26 strcpy(stu[i].name,str); 27 stu[i].number=1400+i+1; 28 stu[i].age=rand()%20+10; 29 strcpy(str,"student"); 30 } 31 32 /*用来验证自定义生成的数据是否正确 33 for(i=0;i<10;i++){ 34 printf("第%d个学生的信息为:name=%9s,num=%ld,age=%d ",i,stu[i].name,stu[i].number,stu[i].age); 35 } 36 */ 37 38 //2输入字符串自定义输出文件名 39 FILE *fp; 40 strcpy(str,""); 41 strcpy(str,"H:/test/"); 42 printf("请输入需要新建的文件名: "); 43 gets(temp); 44 strcat(str,temp); 45 //3写入文件中 46 if((fp=fopen(str,"w"))!=NULL){//这里fp=fopen(str,"w")必须加上括号,否则编译器按照从右向左的方式运算 47 for(i=0;i<10;i++){ 48 //每次写一个学生的数据 49 fwrite(&stu[i],sizeof(Student),1,fp); 50 } 51 } 52 fclose(fp); 53 //4打开文件,读数据到stu_test数组中 ,然后打印信息。 54 Student stu_test[10];//这里不直接用stu数组的原因是为了防止先前数据的影响 55 fp=fopen(str,"r"); 56 for(i=0;i<10;i++){ 57 fread(&stu_test[i],sizeof(Student),1,fp); 58 } 59 printf("下面打印出从文件中读入的信息: "); 60 for(i=0;i<10;i++){ 61 printf("第%d个学生的信息为:name=%9s,num=%ld,age=%d ",i,stu[i].name,stu[i].number,stu[i].age); 62 } 63 return 0; 64 }
4)字符串读写函数fgets和fputs
函数调用的一般形式:
fgets(str,n,fp);
其中,str是指向字符型的指针,n是整型变量,fp是文件指针。
这里n包括字符串最后一个字符' '。如果函数调用成功返回地址str,失败则返回NULL。如果在没有读完字符串前遇到EOF或换行符,读操作结束,函数返回str的首地址。
str可以是字符数组名、字符串常量和字符型指针。
问:c语言中有string类型吗?
答:一直以来对这个问题都不是很清楚:只是知道C语言里有<string.h>这个头文件,所以就想当然的认为C语言里有string这个类型。事实上,有。 字符串类型 用 char 声明。
char str[]="This is a string";
也可以自己定义一个:typedef char* string.
fputs函数
fputs(str,fp);
功能:将str指向的字符串输出到fp所指向的文件中,向指定的文件输出(或写入)一个字符串。
注意:字符串末尾的' '不能输出。如果输出成功,则函数值为0,否则为EOF。
1 #include<stdio.h> 2 #include<string.h> 3 4 int main(){ 5 //下面进行fputs函数和fgets函数的测试 6 printf("下面进行fputs函数和fgets函数的测试: "); 7 char *p; 8 FILE *fp; 9 char str[15]; 10 char temp[10]; 11 strcpy(str,"h:/test/"); 12 printf("请输入你想要新建的文件名称: "); 13 gets(temp); 14 strcat(str,temp); 15 if((fp=fopen(str,"w"))!=NULL){ 16 //将文件所在的根目录输出到文件中 17 fputs(str,fp); 18 } 19 fclose(fp); 20 fp=fopen(str,"r"); 21 //由于fgets函数可以接受字符指针作为参数,在这里做出测试 22 p=(char *)malloc(20*sizeof(char)); 23 fgets(p,20,fp); 24 fclose(fp); 25 printf("文件工作的目录为: %s",p); 26 return 0; 27 }
(4)文件的定位与随机读写
文件的定位是指文件中的位置指针重新确定位置。文件打开后,系统都默认有个位置指针指向文件开头(即第一个数据),位置指针采用顺序读写文件时,每读写一次操作后,该指针自动向后移动一个字符位置,但如果想要改变这种读写方式就可以用下面的函数:
1).文件指针重定位函数rewind:
函数调用的一般形式:
rewind(fp);
使文件中的位置指针重新返回到文件的开头。该函数无返回值。
2).随机读写函数fseek
对文件的内容不但可以顺序读写,还可以随机读写。随机读写就是根据用户的需要将文件中位置指针移到某个指定位置的读写方式。用函数fseek实现随机读写,改变文件的位置指针。
函数调用的一般形式:
fseek(文件类型指针,位移量,起始点);
功能:从起始点开始,位置指针移动一个位移量。例如:
fseek(fp,40L,0);//表示位置指针从文件开头向前移40个字节
fseek(fp,20L,1);//表示位置指针从当前位置向前移20个字节
fseek(fp,-3L,2);//表示位置指针从文件末尾向后退3个字节
函数的操作实例:
3)其他相关函数
1.取文件指针的位置函数ftell
函数调用的一般形式:
ftell(fp);
功能:取得位置指针指向文件中的当前位置(即当前地址值)
如果ftell返回值是-1L(长整型常量),则表示出错,返回0值时表示无错。
d=ftell(fp);
if(d==-1L){
printf("error!);
}
2.检测调用文件是否出错的函数ferror
函数调用的一般形式:
ferror(fp);
功能:若该函数的值为非0表示出错,返回0值时表示无错。
3.检测文件指针函数feof
feof(fp);
功能:检测文件中的位置指针是否移动到文件的末尾,若为末尾,则返回1,否则返回0;
1 //运行本程序前,首先在h盘下新建一个test文件夹 2 #include<stdio.h> 3 #include<string.h> 4 5 int main(){ 6 //1.首先从键盘获得3行自定义原始数据 7 char str[3][30]; 8 int i; 9 printf("请输入3句话,用来测试fseek函数,rewind函数,ftell函数,ferror函数,和feof函数: "); 10 for(i=0;i<3;i++){ 11 gets(str[i]); 12 } 13 FILE *fp; 14 //2.拼接新建文件路径 15 char string[20]; 16 strcpy(string,"H:/test/"); 17 char temp[10]; 18 printf("请输入要新建的文件名: "); 19 gets(temp); 20 strcat(string,temp); 21 printf(string); 22 //3.判断是否正确打开文件 23 if(!ferror(fp=fopen(string,"w"))){ 24 printf("正确以写入和读的方式打开文件。 "); 25 for(i=0;i<3;i++){ 26 fprintf(fp,"%s ",str[i]); 27 } 28 } 29 fclose(fp); 30 fp=fopen(string,"r"); 31 //4.测试 feof函数,用于逐一读文件中的字符,在程序中输出 32 char ch; 33 printf("写入到文件中的内容有: "); 34 35 ch=fgetc(fp); 36 while(!feof(fp)){ 37 putchar(ch); 38 ch=fgetc(fp); 39 } 40 /* 41 上面代码也可以写成如下方式: 42 while(!feof(fp)){ 43 ch=fgetc(fp); 44 putchar(ch); 45 }不过此时将会多输出一个空格,就是当fp到达末尾时产生的ch也会输出。 46 47 */ 48 //5.测试rewind函数和ftell函数 49 rewind(fp); 50 long d=ftell(fp); 51 printf("调用rewind后文件当前的位置是:%ld ",d); 52 53 //5.测试fseek函数,将指针从文件开头移到前面10个字符长度处 54 fseek(fp,10*sizeof(char),0); 55 d=ftell(fp); 56 printf("文件指针当前的位置是: %ld ",d); 57 strcpy(string,""); 58 fgets(string,10,fp); 59 printf("文件指针后面的字符串是:%s ",string); 60 return 0; 61 }
注意:
1.原意我想用fp=fopen(string,"wr");但事实证明不行,一次只能选择一次操作,或者写或者读。
2.原意我想在调用fseek函数后,将除去10个字符后的所有字符全部用一个字符串囊括然后输出fgets()函数貌似只能顺序读取到出现 的位置自动结束。因此,出现只读到"gel!"这几个字符。
至此,c语言系列结束!后续有数据结构篇和c++篇!