C语言文件操作解析

作者: 海子
    


1.第一节


       在讨论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

2.第二节

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下两种方式打开文件程序的执行结果是相同的即会原样输出文件中所有的内容)。


2.第三节

在前面已经讨论了文件打开操作,下面说一下文件的读写操作。文件的读写操作主要有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个字节。

4.第四节

在文件操作中除了打开操作以及读写操作,还有几种比较常见的操作。下面介绍一下这些操作中涉及到的函数。

一.移动位置指针的函数

   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中,不直接向控制台输出结果。

5.第五节

在C语言中,有个符号大家都应该很熟悉,那就是EOF(End of File),即文件结束符。但是很多时候对这个理解并不是很清楚,导致在写代码的时候经常出错,特别是在判断文件是否到达文件末尾时,常常出错。

1.EOF是什么?

   在VC中查看EOF的定义可知:

   #define EOF     (-1)

   EOF只是代表一个整形常量-1。因此很多人认为在文件的末尾存在这个结束标志EOF,这种观点是错误的。事实上在文件的末尾是不存在这个标志的。那么有人会问那下面的程序如何解释?

    char ch;
    while((ch=fgetc(fp))!=EOF)
    {
        printf("%c\n",ch);
    }

   书上都通过这样的代码去判断是否读取到文本文件末尾,就是当读取到EOF的时候就结束操作。这种理解是错误的。

   先看一下函数fgetc的原型:

   int fgetc(FILE *fp);

  事实上在fgetc函数内部,每次都是读取一个字节的数据,而且这一个字节的数据是以unsigned即无符号型处理的,然后将这一个字节的数据赋给一个int型变量作为返回值返回,因此无论从文件中读取的是什么数据,作为无符号型赋值给一个int型变量,返回值不可能是负数。比如当读取到数据0xFA时,因为是以无符号处理的,因此在将0xFA赋值给int型变量的时,int型变量的高位是填充0的(为什么填充0,跟汇编语言里面的符号扩展类似,在后面会提到),因此返回的结果是0X00 00 00 FA,始终不会是负数.而若读取到文件末尾的时候,即没有数据可供读取的时候,那么返回EOF,即-1,这个-1是一个int型常量,二进制表示是0x FF FF FF FF。

    上面的代码具有很大的局限性,因为其只能判断是否到达文本文件的末尾,而不能对二进制文件进行准确的判断。因为正常情况下,文本文件中无论如何是无法读取到-1(0x FF)这个数据,因此可以判断。但是对于二进制文件不同,很可能读到的一个字节的数据就是0xFF,那么返回值此时就是-1,但是此时还未到达文件末尾,造成错误的判断。

    那么有没有办法解决?可以将ch定义为int型即可。

    下面来比较一下下面这段程序和上面程序在执行时的区别。

    int ch;
    while((ch=fgetc(fp))!=EOF)
    {
        printf("%c\n",ch);
    }

     假如在文件中读取到的数据是0xFA。

    那么对于上面一段程序的执行过程是:

    将0xFA先赋值给一个int型变量(假如是a),那么此时a为0x 00 00 00 FA,当将返回值a返回给变量ch时,由于ch是char型的,只有8位,那么只将a的低8位赋给ch,那么此时ch为0x FA,而ch是作为有符号处理的,那么此时ch的值肯定是负数。

    而若将ch定义为int型,执行过程为:

    将0xFA先赋值给一个int型变量(假如是a),那么此时a为0x 00 00 00 FA,当将返回值a返回给变量ch时,由于ch也是int型的,因此ch为0x 00 00 00 FA,是一个正数,两段程序执行得到的结果完全不同。

    下面看一下若读取到的数据是0x FF(此时未到文件末尾)时,是什么结果。

    若ch为char型,则当将返回值0x 00 00 00 FF返回时,取低8位赋给ch,那么此时ch为-1,此时会误判为到达文件末尾;

    而若ch为int型,则当将返回值0x 00 00 00 FF返回时,ch的值为0x 00 00 00 FF,此时ch不为-1,不会误判为文件末尾。

    (当然上面所述成立必须是在读取不出错的情况下才成立)

   所以很多情况下会用到函数feof.

二.feof

   feof函数的原型是

   int feof(FILE *fp);

   若到达文件末尾则返回一个非零值,否则返回0。

   在VC中查看feof函数的定义:

   #define _IOEOF          0x0010

   #define feof(_stream)     ((_stream)->_flag & _IOEOF)

   可知feof函数判断是否到达文件末尾时与_flag这个标志有关。

   下面看一下这段程序:

复制代码
#include<stdio.h>
#include<stdlib.h>

int main(void)
{
    FILE *fp;
    int ch;
    if((fp=fopen("test.txt","w+"))==NULL)
    {
        printf("can not open file\n");
        exit(0);
    }
    for(ch=65;ch<=70;ch++)
    {
        fputc(ch,fp);
    }
    rewind(fp);
    while(feof(fp)==0)
    {
        ch=fgetc(fp);
        printf("%0X\n",ch);
    }
    fclose(fp);
    return 0;
}
复制代码

执行结果是:

41
42
43
44
45
46
FFFFFFFF
Press any key to continue

为什么最后打印结果会多打印一个FFFFFFFF?不是只往文件中写入了数据65-70么?

先看一下C++ Reference中关于feof函数的描述(C++ Reference是一个比较好的网站,里面是关于C++所有库函数的描述,网址在博客首页的链接中有,http://www.cplusplus.com/reference/):

Checks whether the End-of-File indicator associated with stream is set, returning a value different from zero if it is.
This indicator is generally set by a previous operation on the stream that reached the End-of-File.

从描述中可知,只有当与文件关联的流到达文件末尾时,此时若再进行读取操作,文件结束的标志(上面所述的_flag)才会被重新置位。

因此在上述程序中,当读取完最后一个字节的数据后,文件结束标志并没有被置位,只有当位置指针到达末尾时,再发生读取操作时,而此时又没有数据可供读取,因此返回-1,所以打印出的结果中会多一个FFFFFFFF,在这之后才会将_flag重新置位,此时feof函数才能检测出已经到达了文件末尾。

那么可以通过下面的办法解决这个问题:

    ch=fgetc(fp);
    while(feof(fp)==0)
    {
        printf("%0X\n",ch);
        ch=fgetc(fp);
    }

这样就不会多打印一个FFFFFFFF了。

在上面提到汇编语言中符号扩展的问题,其实在C语言中属于数据类型转换的范畴。下面简要说明一下:

符号扩展只针对将字长小的数据赋给字长大的数据时存在,若是字长大的数据赋给字长小的数据,取低位即可。

下面看一段程序:

复制代码
#include<stdio.h>

int main(void)
{
    unsigned char ch1=0XFF;
    char ch2=0XFF;
    char ch3=0X73;
    int a=ch1;
    int b=ch2;
    int c=ch3;
    printf("%d\n%d\n%d\n",a,b,c);
    return 0;
}
复制代码


执行结果为:

255

-1

115

原因是由于ch1、ch2、ch3都是char型变量,只占一个字节,区别在于ch1是无符号的,在将ch1赋值给a时,ch1是看做无符号数据进行处理的,那么在填充a的高位是用0去填充;而对于ch2和ch3都是有符号的,那么在填充高位时就要注意了,若ch2的最高位为0,那么表示ch2是正数,此时填充高位用0填充,而若ch2的最高位为1,则填充高位数据用1填充。

如程序执行的结果所示,由于ch2的最高位为1,那么在填充b的高位的时候会用1去填充,那么b为0X FF FF FF FF;而ch3的最高位为0,那么填充c的高位用0填充,所以c的值为0x 00 00 00 73.



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值