C Primer Plus 第13章 文件输入/输出 13.7 其他标准I/O函数

13.7.1  int ungetc(int c,FILE * fp)函数

int ungetc(int c,FILE * fp)函数将c指定的字符放回输入流中。

如果向输入流中放入了一个字符,下一次调用标准函数就会读入那个字符。

例如,假定需要一个函数读取下一个冒号前的全部字符 ,但是不包括冒号本身。可以使用getchar()或者getc()函数进行读入,直到将冒号读入为直,然后使用ungetc()函数将冒号放回到输入流中。ANSI C 标准保证每次只会放回一个字符 。

13.7.2  int fflush()函数

fflush的原型是:

int fflush(FILE * fp);

调用fflush()函数可以将缓冲区中任何未写的数据发送到一个由fp指定的输出文件中去这个过程称为刷新缓冲区(flushing a buffer)如果fp是一个空指针,将刷新掉所有的输出缓冲。对一个输入流使用fflush()的效果是没有定义的。只要最近一次使用流的操作不是输入操作,就可以对一个更新流(任何读写模式的流)使用这个函数。

13.7.3  int setvbuf()函数

原型:int setvbuf(FILE * restrict fp, char * restrict buf, int mode, size_t size);

setvbuf()函数建立了一个供标准I/O函数使用的替换缓冲区。打开文件以后,在没有对流进行任何操作以前,可以调用这个函数。由指针fp来指定流,buf指向将使用的存储区。如果buf的值不是NULL,就必须创建这个缓冲区。例如,可以声明一个1024个字符的数组,然后传递该数组的地址。但是,如果buf的值为NULL,函数会自动为自己分配一个缓冲区。size变量为setvbuf()函数指定数组的大小。mode将从下列选项中选取:_IOFBF表示完全缓冲(缓冲区满的时候刷新),_IOLBF(缓冲区满的时候或者一个新行写入的时候刷新),_IONBF表示无缓冲。如果成功执行函数将返回一个0值,否则返回非零值 。

假定有一个处理存储数据对象(每个对象的大小为3000字节)的程序,就可以使用setvbuf()函数创建一个缓冲区,其大小和该数据对象的大小相符。

13.7.4  二进制I/O:fread()和fwrite()函数

以前所使用的标准I/O函数都是面向文本的,用于处理字符和字符串。如果要把数据保存到一个文件中,该怎么办呢?的确可以使用fprintf()函数的%f格式保存一个浮点值,不过那样就将它做为一个字符串存储了。例如,下面的序列将num作为一个8字符的字符串0.333333存储:

double num = 1./3. ;

fprintf(fp,%f,num);

使用%0.2f说明符可以把它存储为4字符的字符串0.33。使用%.l2f说明符可以把它存储为14字符的字符串0.333333333333。改变说明符可以改变存储这一值所需的空间,这也会导致存储不同的值。在num的值存为0.33以后,读取文件的时候就没有办法恢复其完整的精度。总之,fprintf()函数以一种可能改变数字值的方式将其存储为字符串。

最精确和一致的存储数字的方法就是使用与程序 所使用的相同的位格式。所以,一个double值就应该存储在一个double大小的单元中。如果把数据存储在一个使用与程序具有相同表示方法的文件中,就称数据以二进制形式存储。这中间没有从数字形式到字符串形式的转换。对于标准I/O,fread()和fwrite()函数提供了这种二进制服务。

实际上,所有的数据都是以二进制的方式进行存储的。甚至连字符也都是使用字符编码的二进制表示来存储。然而,如果文件中的全部数据都 以字符编码的形式被解读,我们才称该文件包含文本数据。如果这些数据的部分或全部以二进制形式的数字被解读,就称文本包含二进制数据(另外,包含表示机器语言指令数据的文件也是二进制文件)。

术语二进制和文本的使用可能会造成混淆。ANSI C 认可两种打开文件的模式:二进制模式和文本模式。很多操作系统认可两种文件格式:二进制格式和文本格式。信息可以作为二进制数据或文本数据存储和读取。这些都是互相关联的,但又不完全相同。可以用二进制模式打开文本格式的文件,可以将文本存储在二格式的文件中,也可以使用getc()函数复制包含二进制数据 的文件。不过通常情况下,还是使用二进制模式将二进制数据存储到二进制格式的文件中。与之类似,用的最频繁的还是使用以文本模式打开的文本文件中的文本数据(字处理器产生的文件通常都是二进制文件,因为其中包含了很多描述字体和格式的非文本信息)。

13.7.5  size_t fwrite()函数

fwrite()函数的原型是:

size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);

fwrite()函数将二进制数据写入文件。size_t类型是根据标准C类型定义的。它是sizeof运算符返回的类型,通常是unsigned int 类型,不过具体的实现中可以选择其他类型。指针ptr是要写入的数据块的地址。size表示要写入的数据块的大小(以字节为单位)。nmemb表示数据块的数目。像一般函数那样,fp指定要写入的文件。例如,要保存一个256字节大小的数据对象(如一个数组),可以这样做:

char buffer[256];
fwrite(buffer,256,1,fp);

这一调用将一块256字节大小的数据块从缓冲区写入到文件。再者,要保存一个10个doube值的数组,可以这样做:

double earnings[10];
fwrite(earnings,sizeof(double),10,fp);

这一调用将earnings数组中的数据写入文件,数据分成10块,每块都是double大小。

您也会注意到,fwrite()原型中有一个奇怪的声明void * ptr。fwrite()的一个问题就是它的第一个参数不是固定的类型。比如,第一个例子中使用了字符指针 buffer,第二个例子中使用了double指针earnings。在ANSI函数原型下,这些实际参数都被转换成指向void的指针,这种指针可以作为一种普通的指针类型工作。

fwrite()返回成功写入的项目数。正常情况下,它与nmemb相等,不过如果写入错误的话,返回值就会小于nmemb。

13.7.6  size_t fread()函数

fread()函数的原型是:

size_t fread(void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);

fread()函数与fwrite()函数的参数相同。这时ptr为读入文件数据的内存存储地址,fp指定要读取的文件。使用这一函数来读取fwrite()写入的文件数据。例如,要恢复前一例子中保存的包含10个double值的数组,可以使用下面的函数调用:

double earnings[10];

fread(earnings,sizeof(double),10,fp);

将10个double值复制到earnings数组中。

fread()函数返回成功读取的项目数。正常情况下它与nmemb相等;不过如果读取错误的话,返回值就会小于nmemb。

13.7.8  一个fread()和fwrite()的例子

我们使用上述一些函数将一系列文件的内容追加到另一个文件的结尾。

一个问题是把文件信息传递给程序中,这可以用交互式或者命令行参数方式完成。这里使用第一种方法,下面几行是程序设计的计划:

**请求一个目的文件名,并打开该文件;

**使用一个循环请求源文件;

**依次以读取模式打开每个源文件,并将其内容追加到目的文件;

为了示范setvbuf()函数的使用,我们使用它来指定一个不同的缓冲区大小。下面详细分析了打开文件的具体步骤:

**以追加模式打开最后一个命令行文件。

**如果不能成功打开则退出;

**为这个文件建立一个1024字节的缓冲区;

**如果不能完成则退出;

与之类似,我们可以通过下列步骤来详细描述每个文件的复制过程:

**如果该文件与目的文件相同,则跳到下一个文件;

**如果不能以读取模式打开该文件,则跳到下一个文件;

**把该文件的内容添加到目的文件中。

作为练习,我们使用fread()和fwrite()进行复制。程序清单13.6列出了结果程序。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 1024
#define SLEN 81
void append(FILE * source,FILE * dest);
int main(void)
{
    FILE *fa, *fs;         //fa指向追加的目的文件,fs指向源文件;
    int files = 0;
    char file_app[SLEN];   //被追加文件的名称;
    char file_src[SLEN];   //源文件的名称;
    puts("Enter name of destination file:");
    gets(file_app);
    if((fa=fopen(file_app,"a"))==NULL)
    {
        fprintf(stderr,"Can't open %s\n",file_app);
        exit(2);
    }
    if(setvbuf(fa,NULL,_IOFBF,BUFSIZE)!=0)
    {
        fputs("Can't create output buffer\n",stderr);
        exit(3);
    }
    puts("Enter name of first source file (empty line of quit):");
    while (gets(file_src) && file_src[0]!='\0')
    {
        if(strcmp(file_src,file_app)==0)
            fputs("Can't append file to itself\n",stderr);
        else if ((fs=fopen(file_src,"r"))==NULL)
            fprintf(stderr,"Can't open %s\n",file_src);
        else
        {
            if(setvbuf(fs,NULL,_IOFBF,BUFSIZE)!=0)
            {
                fputs("Can't create input buffer\n",stderr);
                continue;
            }
            append(fs,fa);
            if(ferror(fs)!=0)
                fprintf(stderr,"Error in reading file %s.\n",file_src);
            if(ferror(fa)!=0)
                fprintf(stderr,"Error in writing file %s.\n",file_app);
            fclose(fs);
            files++;
            printf("File %s appended.\n",file_src);
            puts("Next file(empty line to quit): ");
        }
    }
    printf("Done %d files appended.\n",files);
    fclose(fa);
    return 0;
}

void append(FILE *source, FILE *dest)
{
    size_t bytes;
    static char temp[BUFSIZE];  //分配一次

    while((bytes=fread(temp,sizeof(char),BUFSIZE,source))>0)
        fwrite(temp,sizeof(char),bytes,dest);
}

下面的代码创建了一个供目的文件使用的1024字节大小的缓冲区:

if(setvbuf(fa,NULL,_IOFBF,BUFSIZE)!=0)
    {
        fputs("Can't create output buffer\n",stderr);
        exit(3);
    }

如果setvbuf()不能创建缓冲区,它返回一个非零值,上面的代码将终止程序。使用同样的方法可以为正被复制的文件建立一个1024字节大小的缓冲区。通过用NULL作为setvbuf()的第二个参数,我们让函数负责为缓冲区分配存储空间。

下面的代码防止程序将文件追加到它本身

        if(strcmp(file_src,file_app)==0)
            fputs("Can't append file to itself\n",stderr);

append()函数完成复制任务。这里没有一次复制一个字节,而是利用fread()和fwrite()函数每次复制1024个字节:

void append(FILE *source, FILE *dest)
{
    size_t bytes;
    static char temp[BUFSIZE];  //分配一次

    while((bytes=fread(temp,sizeof(char),BUFSIZE,source))>0)
        fwrite(temp,sizeof(char),bytes,dest);
}

注意数组temp是具有静态存储时期(表示它的分配发生在编译时,而不是每次调用append()时)和代码块作用域的,这意味着它是该函数私有的。

本例中使用文本模式打开文件,通过使用"ab"和"rb"模式,该程序也可以处理二进制文件。

13.7.9  使用二进制I/O进行随机存取

随机存取最常用于使用二进制I/O写入的二进制文件。

程序清单13.7中的程序创建了一个double类型的数值文件,然后允许您访问这些内容。

程序清单13.7  randbin.c程序

/*randbin.c  --随机存取,二进制I/O */
#include <stdio.h>
#include <stdlib.h>
#define ARSIZE 1000

int main(void)
{
    double numbers[ARSIZE];
    double value;
    const char * file = "numbers.dat";
    int i;
    long pos;
    FILE *iofile;
    //创建一组double类型的值
    for(i=0;i<ARSIZE;i++)
        numbers[i]=100.0*i+1.0/(i+1);
    //尝试打开文件
    if((iofile=fopen(file,"wb"))==NULL)
    {
        fprintf(stderr,"Cloud not open %s for output.\n",file);
        exit(1);
    }
    //把数组中的数据以二进制形式写入到文件
    fwrite(numbers,sizeof(double),ARSIZE,iofile);
    fclose(iofile);
    if((iofile=fopen(file,"rb"))==NULL)
    {
        fprintf(stderr,"Cloud not open %s for random access.\n",file);
        exit(1);
    }
    //从文件中读取所选的项目
    printf("Enter an index in the range 0-%d.\n",ARSIZE-1);
    scanf("%d",&i);
    while(i>=0 && i<ARSIZE)
    {
        pos=(long)i*sizeof(double);  //计算偏移量
        fseek(iofile,pos,SEEK_SET);  //在文件中定位到那里
        fread(&value,sizeof(double),1,iofile);
        printf("The value there is %f.\n",value);
        printf("Next index(out of range to quit): \n");
        scanf("%d",&i);
    }
    fclose(iofile);
    puts("Bye!");
    return 0;
}

程序首先创建了一个数组,然后在其中存放了一些值。接着它以二进制模式创建了一个名为numbers.dat的文件,然后使用fwrite()把数组的内容复制到文件中。每个double值的64模式从内存复制到文件中。不能通过文本编辑器来读取结果的二进制文件,因为没有把这些值翻译成字符串。但是,存储在文件中的每个值和它在内存中的存储方式完全相同,因此没有损失任何精度。而且每个值都在文件中精确占用64位存储空间,所以可以很容易的计算出每个值的位置。

程序第二部分为了读取文件,请求用户输入一个值的索引。通过将索引与double值占用的字节数相乘就可以得到一个文件中的位置。然后程序使用fseek()定位到该位置,利用fread()读取该位置的数据。注意这里并没有使用格式说明符。而是由fread()把从该位置开始的8个字节复制到内存中由&value指定的位置,然后使用printf()显示value的值。

转载于:https://my.oschina.net/idreamo/blog/850003

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值