转自:http://www.cnblogs.com/dolphin0520/archive/2011/10/05/2199396.html
C语言文件操作解析(一)
在讨论C语言文件操作之前,先了解一下与文件相关的东西。
一.文本文件和二进制文件
文本文件的定义:由若干行字符构成的计算机文件,存在于计算机系统中。文本文件只能存储文件中的有效字符信息,不能存储图像、声音等信息。狭义上的二进制文件则指除开文本文件之外的文件,如图片、DOC文档。
事实上,无论是上面所定义的文本文件还是二进制文件,在计算机中存储都是以二进制的形式存储的,因此其本质并没有区别。所以广义上的二进制文件便指所有的文件。
通常意义下,我们所说的文本文件指只包含了纯文本信息的文件(通过手动编辑完成,包含的都是可显字符),二进制文件特指文件里面存储的是二进制代码的文件。至于为什么在计算机内存储的都是二进制数据,而给我们所呈现的确是文字、图像等信息,这跟计算机硬件组成有关系,因为计算机里面的元件是晶体管,其只有两种稳定的状态,因此二进制的0和1能表示其状态。很多个晶体管的不同状态的组合便呈现给我们不同的信息了。下面以汉字在计算机中的表示为例。
二.汉字在计算机中的表示
用计算机去处理汉字信息,必须对汉字进行编码,变成能被计算机识别的二进制。汉字编码主要有输入码、机内码、字形码三种。分别有不同的作用。
输入码:为了能直接使用西方英文标准键盘输入汉字,必须制定相应的编码规则,如拼音码(拼音输入法)、数字码(数字输入法)等
机内码:指汉字在计算机内部的表示形式,即二进制形式,通常采用两字节来表示一个汉字,每个字节的最高位设置为1(其值为负),如汉字“我”在计算机内的表示为11001110 11010010.
字形码:存储在计算机内的汉字需要在屏幕上显示或者打印机上输出时,需要知道汉字的字形信息,而汉字的机内码并不能表示汉字的字形信息,因此需要专门的字形码 。最通用的字形信息显示采用点阵的形式,即将汉字的字形分解成若干个“点”形成的点阵。每个点有黑白两种信息,有笔画的用黑表示,反之用白表示。汉字的点阵信息量是很大的,比如16*16的点阵需要用256位表示其信息,则需要32字节的空间。
计算机中存放了所有汉字的字形码组合起来的字形库也称字模库,当汉字输出或者显示的时候由专门的字形检索程序根据这个汉字的机内码在字模库中找出与之对应的字形码,然后根据字形码输出到显示设备上。
所以我们平常所看到的文本文件或者图片等在计算机上都是以二进制形式存储,只是在显示的时候以人所能够识别的方式呈现给我们。
测试程序
#include<stdio.h>#include<string.h>int main(void){ char s[]="我"; unsigned char *p=(unsigned char *)s; printf("%d\n",strlen(s)); printf("%X\n",*p); printf("%X\n",*(p+1)); return 0;}
输出结果:
2
CE
D2
Press any key to continue
C语言文件操作解析(二)
C语言中对文件进行操作必须首先打开文件,打开文件主要涉及到fopen函数。fopen函数的原型为
FILE* fopen(const char *path,const char *mode)
其中path为文件路径,mode为打开方式
1)对于文件路径,只需注意若未明确给出绝对路径,则默认该文件在工程的目录下。若需给出绝对路径,则注意转义字符'\',比如有文件test.txt存放在C盘根目录下,则文件路径参数值应为C:\\test.txt。
2)对于mode,主要由r,w,a,+,b,t六个字符组合而成。
r:只读方式,文件必须存在
w:只写方式,若文件存在,则原有内容会被清除;若文件不存在,则会建立文件
a:追加方式打开只写文件,只允许进行写操作,若文件存在,则添加的内容放在文件末尾;若不存在,则建立文件
+:可读可写
b:以二进制方式打开文件
t:以文本方式打开文件(默认方式下以文本方式打开文件)
下面是常见的组合:
r: 以只读的方式打开文件,只允许读,此文件必须存在,否则返回NULL,打开成功后返回文件指针,位置指针指向文件头部
r+: 以可读可写的方式打开文件,允许读写,此文件必须存在,否则返回NULL,打开成功后返回文件指针,位置指针指向文件头部
rb+: 以可读可写、二进制方式打开文件,允许读写,此文件必须存在,否则返回NULL,打开成功后返回文件指针,位置指针指向文件头部
rt+: 以可读可写、文本方式打开文件,允许读写,此文件必须存在,否则返回NULL,打开成功后返回文件指针,位置指针指向文件头部
w: 以只写的方式打开文件,只允许写,若文件存在,文件中原有内容会被清除;若文件不存在,则创建文件,打开成功后返回文件指针,位置指针指向文件头部
w+: 以读写的方式打开文件,允许读写,若文件存在,文件中原有内容会被清除;若文件不存在,则创建文件,打开成功后返回文件指针,位置指针指向文件头部
a: 以追加、只写的方式打开文件,只允许写。若文件存在,则追加的内容添加在文件末尾,若文件不存在,则创建文件。打开成功后返回文件指针,位置指针指向文件头部(注意很多书上或资料上讲述追加方式打开成功后位置指针指向文件末尾是错误的)
a+: 以追加、可读写的方式打开文件,允许读写。若进行读操作,则从头开始读;若进行写操作,则将内容添加在末尾。若文件不存在,则创建文件。打开成功后返回文件指针,位置指针指向文件头部。
其他方式类似。
下面讨论一下以二进制方式和文本方式打开文件有什么区别。
其实这两种方式打开文件并没有太大的区别,仅仅只有一点区别就是在处理某些特殊字符的时候。
以文本方式打开文件,若将数据写入文件,如果遇到换行符'\n'(ASII 值为10,0A),则会转换为回车—换行'\r\n'(ASII值为13,10,0D0A)存入到文件中,同样读取的时候,若遇到回车—换行,即连续的ASII值13,10,则自动转换为换行符。
而以二进制方式打开文件时,不会进行这样的处理。
还有如果以文本方式打开文件时,若读取到ASCII码为26(^Z)的字符,则停止对文件的读取,会默认为文件已结束,而以二进制方式读取时不会发生这样的情况。由于正常情况下我们手动编辑完成的文件是不可能出现ASCII码为26的字符,所以可以用feof函数去检测文件是否结束。以上所述的两点区别只在windows下存在,在unix下没有区别。
注意:1)在以追加方式打开文件时,位置指针指向文件的首部。
在这里区分一下位置指针和文件指针:
文件指针:指向存储文件信息的一个结构体的指针
位置指针:对文件进行读写操作时移动的指针
在头文件<stdio.h>中存在一个结构体_iobuf,在VC6.0中选中FILE,然后F12,则可以看到_iobuf的具体定义:
struct _iobuf { char *_ptr; int _cnt; char *_base; int _flag; int _file; int _charbuf; int _bufsiz; char *_tmpfname; };typedef struct _iobuf FILE;
比如用FILE *fp定义了一个文件指针,并成功打开一个文件之后,fp只是指向该结构体,而在对文件进行读写操作时,fp的值并不会改变,改变的是结构体中_ptr的值,这个_ptr就是位置指针。
2)以追加方式打开时,若进行写操作,则rewind函数和fseek函数不会起到作用,因为以追加方式打开时进行写操作的话,系统会自动将位置指针移动到末尾。
3)当文件打开用于更新时,可以通过文件指针对文件进行读写操作,但是如果没有给出fseek或者rewind的话,读操作后面不能直接跟写操作,否则会是无效的写操作(位置指针会移动,但是需要写入文件的内容不会被写入到文件当中),但是写操作后可以直接跟读操作。
1.测试程序
假设工程目录下已存在文件test.txt,文件中含有的字符串为"ABC"
/*测试fopen函数以追加方式打开文件时初始指针的位置 2011.10.5*/#include<stdio.h>#include<stdlib.h>int main(void){ int n; FILE *fp; if((fp=fopen("test.txt","a"))==NULL) { printf("can not open file\n"); exit(0); } n=ftell(fp); //得到此时fp所处位置距文件首的偏移字节数 printf("%d\n",n); fputs("test",fp); n=ftell(fp); printf("%d\n",n); fclose(fp); return 0;}
输出结果为:
0
7
Press any key to continue
由输出结果可知,初始打开文件后,指针是位于文件首部,只是在往文件中添加内容时,才将文件指针移动到文件末尾。
2.测试程序
/*测试以二进制方式和文本方式打开文件的区别 2011.10.5*/ #include<stdio.h>#include<stdlib.h>int main(void){ char ch; int i; char s[]={'A','B','\n','C'}; FILE *fp1,*fp2; if((fp1=fopen("test1.txt","wt"))==NULL) { printf("can not open file\n"); exit(0); } if((fp2=fopen("test2.txt","wb"))==NULL) { printf("can not open file\n"); exit(0); } for(i=0;i<4;i++) { fputc(s[i],fp1); //以文本方式向文件中写入数据 fputc(s[i],fp2); //以二进制方式向文件中写入数据 } fclose(fp1); fclose(fp2); if((fp1=fopen("test1.txt","rt"))==NULL) { printf("can not open file\n"); exit(0); } if((fp2=fopen("test1.txt","rb"))==NULL) { printf("can not open file\n"); exit(0); } ch=fgetc(fp1); while(!feof(fp1)) //以文本方式从文件中读取数据 { printf("%02X",ch); ch=fgetc(fp1); } printf("\n"); ch=fgetc(fp2); while(!feof(fp2)) //以二进制方式从文件中读取数据 { printf("%02X",ch); ch=fgetc(fp2); } printf("\n"); fclose(fp1); fclose(fp2); return 0;}
在向文件中写完数据后,用UltraEdit以二进制方式打开test1.txt和test2.txt,看到的结果如下:
根据得到的结果可知,以文本方式写入时,多写入了一个字符0D,即'\r'。
程序输出结果:
41420A43
41420D0A43
请按任意键继续. . .
分别以文本方式和二进制方式读取test1.txt时,输出的内容不同。
可知在以文本方式读取时,对'\r\n'进行了转换,而二进制方式读取时却没有进行这样的转换。
3.测试程序
/*测试读操作后能否直接跟写操作 2011.10.5*/#include<stdio.h>#include<stdlib.h>int main(void){ int ch; int n; FILE *fp; if((fp=fopen("test.txt","r+"))==NULL) { printf("can not open file\n"); exit(0); } fseek(fp,1L,0); //将fp移动到距文件首1字节的位置 ch=fgetc(fp); printf("%c\n",ch); //rewind(fp); fseek(fp,1L,0); fputs("test",fp); ch=fgetc(fp); printf("%c\n",ch); fclose(fp); return 0;}
假设工程已经存在文件test.txt,文件中含有字符串"ABCDEFGH"。
则上述程序执行结果为:
B
F
请按任意键继续. . .文件中内容为"AtestFGH"。
与预想结果相同,因此读取到字符'B'后,再将位置指针置到距文件首1字节处,即字符'B'处,写入"test"后,会覆盖掉"BCDE",写完后位置指针指向字符'F',因此此时进行读操作,得到的结果是'F'。
但是如果将fseek(fp,1L,0);这句注释掉,则执行结果为:
B
G
请按任意键继续. . .
文件中的内容为"ABCDEFGH"。
注释掉fseek一句后,读取完字符'B'后,位置指针指向字符'C',再进行写操作,位置指针会向后移动4个字节的位置,指向字符'G',因此第二次读取的输出结果为'G'。但是文件中的内容没有被改写,相当于这次写操作是无效操作。
4.测试程序
#include<stdio.h>#include<stdlib.h>int main(void){ FILE *fp; int ch; if((fp=fopen("test","rb"))==NULL) { printf("can not open file\n"); exit(0); } ch=fgetc(fp); while(feof(fp)==0) { printf("%02X\n",ch); ch=fgetc(fp); } fclose(fp); return 0;}
运行此程序之前,用VC6.0建立一个二进制文件test放在工程目录下,然后输入数据"23 13 0E 1A 35"。保存完毕后再执行此程序,执行结果为:
23
13
0E
1A
35
Press any key to continue
但是若将打开方式改成"rt",则执行结果为:
23
13
0E
Press any key to continue
之所以出现这种原因,在上面已经提到,就是在以文本方式打开文件时,若读取到ASCII码为26的字符,则会停止读取,默认文件内容已经结束。但是以二进制方式打开文件则不会出现这种情况,会将文件中所有的内容原样输出(注意这种区别只在windows下存在,在unix下两种方式打开文件程序的执行结果是相同的即会原样输出文件中所有的内容)。
C语言文件操作解析(三)
在前面已经讨论了文件打开操作,下面说一下文件的读写操作。文件的读写操作主要有4种,字符读写、字符串读写、块读写以及格式化读写。
一.字符读写
字符读写主要使用两个函数fputc和fgetc,两个函数的原型是:
int fputc(int ch,FILE *fp);若写入成功则返回写入的字符,否则返回-1
int fgetc(FILE *fp); 若读取成功则返回读取的字符,否则返回-1
注意:1)对于fputc函数和fgetc函数,每次操作,fputc只能写入1个字节的数据,无论参数ch多大,只将其低8位的数据写入到文件中;fgetc 每次只能返回一个字节的数据。
2)对于fgetc函数,若读取成功则返回读取到的字符,否则返回-1.这里面返回-1(即EOF)有两种情况:一种是读到文件结束已经没有任何字符可供读取了,另一种是读取出错。由于通常情况下,在文本文件中可显字符是不可能出现ASCII码为-1的字符,因此可以通过fgetc的返回结果判断文件是否结束(读取不出错的情况下)。但是在二进制文件中则不能这么判断了,因为二进制文件中很可能就含有FF这样的数据,如果将存储fgetc读取结果的变量ch定义为char型,则不能判断二进制文件是否结束,但是如果定义为int型,则同样可以判断,因为即使读取的字符是FF,但是由于ch是int型,则事实上ch=0x000000FF,并不等于-1,因此可以判断文件是否结束。(注意以上所述只在文件读取不出错的情况下成立,若文件读取出错,是不能这么判断文件是否结束,必须通过feof()函数来判断)
测试程序:
#include<stdio.h>#include<stdlib.h>int main(void){ FILE *fp; int ch; if((fp=fopen("test.txt","wb+"))==NULL) { printf("can not open file\n"); exit(0); } fputc(-1,fp); //-1的二进制为FF fputc(385,fp); //385二进制为110000001 rewind(fp); ch=fgetc(fp); while(feof(fp)==0) { printf("%d\n",ch); ch=fgetc(fp); } fclose(fp); return 0;}
执行结果为:
255
129
Press any key to continue
由于fputc每次只写入一个字节的数据,因此虽然第二次想写入385,但是只将其低8位数据写入,所以输出结果为129.
若将上述程序中的ch定义为char型,则执行结果为:
-1
-127
Press any key to continue
原因上面已经解释了.
二.字符串读写
字符串读写主要涉及到两个函数fputs和fgets,这两个函数的原型是:
int fputs(const char *s,FILE *fp);
char *fgets(char *s,int n,FILE *fp);
对于fputs函数,将字符串写入文件,若写入成功则返回一个非负值,否则返回-1;
对于fgets函数,从文件中读取不超过n-1个字符到字符数组中(若文件中字符少于n-1个,则只读取文件中存在的字符),系统在字符数组末尾自动添加一个'\0',返回字符数组的首地址。
注意:1)对于fgets函数,在读取过程中,若读取到字符'\n',则读取过程提前结束。
测试程序:
#include<stdio.h>#include<stdlib.h>int main(void){ FILE *fp; char s[10]; if((fp=fopen("test.txt","wb+"))==NULL) { printf("can not open file\n"); exit(0); } fputc('A',fp); fputc('B',fp); fputc('\n',fp); fputc('C',fp); rewind(fp); fgets(s,5,fp); printf("%s\n",s); fclose(fp); return 0;}
执行结果为:
AB
Press any key to continue
由此可知当读取到换行符'\n'时便停止读取了。
三.块读写
块读写主要涉及到两个函数fread和fwrite,这两个函数的原型是:
unsigned int fread(void *buffer,unsigned int size,unsigned int n,FILE *fp);
从文件读取一组数据存放在首地址为buffer的内存空间中,size为一个数据块的大小,n为要读取的数据块的个数,若读取成功,则返回读取的数据的数据块的个数,否则返回0.
unsigned int fwrite(const void *buffer,unsigned int size,unsigned int n,FILE *fp);
向文件中写入数据,写入成功返回写入数据块的个数,否则返回0.
块读写一般用于结构体。
注意:1)块读写常用于结构体。
2)fread和fwrite一般成对出现,如果对文件进行写操作用的是fwrite,则用fread读取,否则可能会得到意想不到的结果。
测试程序:
#include<stdio.h>#include<stdlib.h>typedef struct node{ char name[20]; double score; int age;}Student;int main(void){ FILE *fp; int i; Student s1[3]={{"liudehua",85.5,45},{"zhangxueyou",79.3,47},{"guofucheng",83.4,43}}; Student s2[3]; if((fp=fopen("test.txt","wb+"))==NULL) { printf("can not open file\n"); exit(0); } printf("%d\n",fwrite(s1,sizeof(Student),3,fp)); //printf("%d\n",fwrite(s1,sizeof(s1),1,fp)); //注意和上一句的区别 rewind(fp); printf("%d\n",fread(s2,sizeof(Student),3,fp)); for(i=0;i<3;i++) { printf("%s %lf %d\n",s2[i].name,s2[i].score,s2[i].age); } fclose(fp); return 0;}
执行结果为:
3
3
liudehua 85.500000 45
zhangxueyou 79.300000 47
guofucheng 83.400000 43
Press any key to continue
四.格式化读写
格式化读写主要涉及到两个函数:fscanf和fprintf,两个函数的原型是
int fscanf(FILE *fp,const char *format[,argument]....);
用于从文件格式化读取数据,若读取成功,则返回读取的数据个数,否则返回-1
int fprintf(FILE *fp,const char *format[,argument]....);
用于向文件格式化写入数据,若写入成功,则返回写入的字符个数,否则返回-1
注意:1)格式化读写和其他几种读写有很大的不同。格式化读写是以我们人所能识别的格式将数据写入文件,即若以格式化方式写入一个整型数值65,则其实是写入的两个字符'6'和'5',即占2字节,而不是4字节,但是若以块写方式写入,则其占4字节。即在使用格式化读写时系统自动进行了一些转换。
2)fprintf和fscanf函数一般成对出现,若数据是用fprintf进行写入的,则最好使用fscanf进行读取。
3)在使用fprintf函数写入时,若文件是以文本方式打开,如果参数format中包含了'\n',则最后文件中会被写入换行符;而若文件以二进制方式打开,则文件中不会被写入换行符,这点区别在上一篇博客中已经提到。
测试程序:
#include<stdio.h>#include<stdlib.h>typedef struct node{ char name[20]; double score; int age;}Student;int main(void){ FILE *fp; int i; Student s1[3]={{"liudehua",85.5,45},{"zhangxueyou",79.3,47},{"guofucheng",83.4,43}}; Student s2[3]; if((fp=fopen("test.txt","wb+"))==NULL) { printf("can not open file\n"); exit(0); } for(i=0;i<3;i++) { printf("%d\n",fprintf(fp,"%s %lf %d\n",s1[i].name,s1[i].score,s1[i].age)); } rewind(fp); for(i=0;i<3;i++) { printf("%d\n",fscanf(fp,"%s %lf %d",s2[i].name,&s2[i].score,&s2[i].age)); } for(i=0;i<3;i++) { printf("%s %lf %d\n",s2[i].name,s2[i].score,s2[i].age); } fclose(fp); return 0;}
执行结果:
22
25
24
3
3
3
liudehua 85.500000 45
zhangxueyou 79.300000 47
guofucheng 83.400000 43
Press any key to continue
文件test.txt中的内容是:
若将打开方式改成"wt+",则文件中的内容为:
而若以fread和fwrite方式进行读写时,其结果如下:
测试程序:
#include<stdio.h>#include<stdlib.h>typedef struct node{ char name[20]; double score; int age;}Student;int main(void){ FILE *fp; Student s1[3]={{"liudehua",85.5,45},{"zhangxueyou",79.3,47},{"guofucheng",83.4,43}}; if((fp=fopen("test.txt","wt+"))==NULL) { printf("can not open file\n"); exit(0); } fwrite(s1,sizeof(s1),1,fp); fclose(fp); return 0;}
则文件中的内容为:
从这里就可以看出格式化读写跟其他方式的区别。
测试程序:
#include<stdio.h>#include<stdlib.h>int main(void){ FILE *fp; int n=32768; if((fp=fopen("test.txt","wt+"))==NULL) { printf("can not open file\n"); exit(0); } fwrite(&n,sizeof(int),1,fp); fclose(fp); return 0;}
执行后,用二进制方式打开文件:
而32768的二进制为00000000100000000000000即00 80 00 00,内容占4个字节。
而
#include<stdio.h>#include<stdlib.h>int main(void){ FILE *fp; int n=32768; if((fp=fopen("test.txt","wt+"))==NULL) { printf("can not open file\n"); exit(0); } fprintf(fp,"%d",n); fclose(fp); return 0;}
执行的结果:
即用fprintf写入的是51,50,55,54,56,即跟字符'3','2','7','6','8'各自对应的整数值,内容占5个字节。
C语言文件操作解析(四)
在文件操作中除了打开操作以及读写操作,还有几种比较常见的操作。下面介绍一下这些操作中涉及到的函数。
一.移动位置指针的函数
rewind函数和fseek函数,这两个函数的原型是:
void rewind(FILE *fp); 将位置指针移动到文件首
int fseek(FILE *fp,long int offset,int origin); 将位置指针移动到距离origin的offset字节数的位置
其中对于fseek函数中的参数,origin为起始点,offset为距离origin的偏移字节数
origin的值有三个:SEEK_SET(0)—>文件首,SEEK_CUR(1)—>当前位置,SEEK_END(2)—>文件尾。
注意:1)若文件是以追加方式打开,则当进行写操作时,这两个函数是不起作用的,无论将位置指针移动哪个位置,始终将添加的数据追加到文件末尾。
二.其他常用函数
1.ftell函数
long int ftell(FILE *fp);
计算当前位置指针距文件首的字节数,若出错,则返回-1L。
利用ftell函数可以计算出文件的大小。
2.feof函数
int feof(FILE *fp);
检测当前位置指针是否到达文件末尾,若到达文件末尾,则返回一个非零值,否则返回0。
3.ferror函数
int ferror(FILE *fp);
检测文件操作过程中是否出错,若出错,则返回一个非零值,否则返回0
4.remove函数
int remove(const char *filename);
删除文件,若删除成功,则返回0,否则返回非零值
5.rename函数
int rename(const char *oldname,const char *newname);
将文件重命名,重命名成功则返回0,否则返回非零值。
6.freopen函数
FILE* freopen(const char *filename,const char *mode,FILE *stream);
实现重定向输入输出。此函数在测试数据时用得比较多。
7.fclose函数
int fclose(FILE *stream);
关闭一个流,若成功,则返回0,否则返回-1.注意每次对文件操作完之后需关闭流,否则可能会造成数据丢失。
测试程序:
#include<stdio.h>#include<stdlib.h>int main(void){ freopen("input.txt","r",stdin); freopen("output.txt","w+",stdout); int i; int a[10]; for(i=0;i<10;i++) { scanf("%d",&a[i]); } for(i=0;i<10;i++) { printf("%d\n",a[i]); } return 0;}
假设工程目录下已经存在input.txt,文件中的数据为1 2 -1 3 4 5 7 8 9 10,则运行之后,不需要从控制台输入数据,程序直接从input.txt中读取数据,然后将结果输出到output.txt中,不直接向控制台输出结果。
来源:https://www.cnblogs.com/milier-otw/archive/2013/03/15/2960781.html