龙空技术网

C语言标准I/O系列的7个函数

龙骑士洞察 508

前言:

此刻我们对“c语言各函数”可能比较重视,各位老铁们都需要分析一些“c语言各函数”的相关文章。那么小编同时在网上网罗了一些有关“c语言各函数””的相关资讯,希望同学们能喜欢,你们快快来学习一下吧!

ANSI标准库的标准I/O系列有几十个函数。虽然在这里无法一一列举,但是我们会简要地介绍一些,让读者对它们有一个大概的了解。这里列出函数的原型,表明函数的参数和返回类型。我们要讨论的这些函数,除了setvbuf(),其他函数均可在ANSI之前的实现中使用。参考资料V的“新增C99和C11的标准ANSI-C库”中列出了全部的ANSI C标准I/O包。

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

int ungetc()函数把c指定的字符放回输入流中。如果把一个字符放回输入流,下次调用标准输入函数时将读取该字符(见图13.2)。例如,假设要读取下一个冒号之前的所有字符,但是不包括冒号本身,可以使用getchar()或getc()函数读取字符到冒号,然后使用ungetc()函数把冒号放回输入流中。ANSI C标准保证每次只会放回一个字符。如果实现允许把一行中的多个字符放回输入流,那么下一次输入函数读入的字符顺序与放回时的顺序相反。

The ungetc() function.

2 int fflush()函数

fflush()函数的原型如下:

int fflush(FILE *fp);

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

3 int setvbuf()

函数setvbuf()函数的原型是:

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

setvbuf()函数创建了一个供标准I/O函数替换使用的缓冲区。在打开文件后且未对流进行其他操作之前,调用该函数。指针fp识别待处理的流,buf指向待使用的存储区。如果buf的值不是NULL,则必须创建一个缓冲区。例如,声明一个内含1024个字符的数组,并传递该数组的地址。然而,如果把NULL作为buf的值,该函数会为自己分配一个缓冲区。变量size告诉setvbuf()数组的大小(sizet是一种派生的整数类型,第5章介绍过)。mode的选择如下:IOFBF表示完全缓冲(在缓冲区满时刷新);IOLBF表示行缓冲(在缓冲区满时或写入一个换行符时);IONBF表示无缓冲。如果操作成功,函数返回0,否则返回一个非零值。 假设一个程序要存储一种数据对象,每个数据对象的大小是3000字节。可以使用setvbuf()函数创建一个缓冲区,其大小是该数据对象大小的倍数。

4 二进制I/O:fread()和fwrite()

介绍fread()和fwrite()函数之前,先要了解一些背景知识。之前用到的标准I/O函数都是面向文本的,用于处理字符和字符串。如何在文件中保存数值数据?用fprintf()函数和%f转换说明只是把数值保存为字符串。例如,下面的代码:

double num = 1./3.; fprintf(fp,"%f", num);

把num存储为8个字符:0.333333。使用%.2f转换说明将其存储为4个字符:0.33,用%.12f转换说明则将其存储为14个字符:0.333333333333。改变转换说明将改变存储该值所需的空间数量,也会导致存储不同的值。把num存储为0.33后,读取文件时就无法将其恢复为更高的精度。一般而言,fprintf()把数值转换为字符数据,这种转换可能会改变值。 为保证数值在存储前后一致,最精确的做法是使用与计算机相同的位组合来存储。因此,double类型的值应该存储在一个double大小的单元中。如果以程序所用的表示法把数据存储在文件中,则称以二进制形式存储数据。不存在从数值形式到字符串的转换过程。对于标准I/O,fread()和fwrite函数用于以二进制形式处理数据。

Binary and text output.

实际上,所有的数据都是以二进制形式存储的,甚至连字符都以字符码的二进制表示来存储。如果文件中的所有数据都被解释成字符码,则称该文件包含文本数据。如果部分或所有的数据都被解释成二进制形式的数值数据,则称该文件包含二进制数据(另外,用数据表示机器语言指令的文件都是二进制文件)。 二进制和文本的用法很容易混淆。ANSI-C和许多操作系统都识别两种文件格式:二进制和文本。能以二进制数据或文本数据形式存储或读取信息。可以用二进制模式打开文本格式的文件,可以把文本存储在二进制形式的文件中。可以调用getc()拷贝包含二进制数据的文件。然而,一般而言,用二进制模式在二进制格式文件中存储二进制数据。类似地,最常用的还是以文本格式打开文本文件中的文本数据(通常文字处理器生成的文件都是二进制文件,因为这些文件中包含了大量非文本信息,如字体和格式等)。

5 sizet fwrite()

函数fwrite()函数的原型如下:

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

fwrite()函数把二进制数据写入文件。sizet是根据标准C类型定义的类型,它是sizeof运算符返回的类型,通常是unsignedint,但是实现可以选择使用其他类型。指针ptr是待写入数据块的地址。size表示待写入数据块的大小(以字节为单位),nmemb表示待写入数据块的数量。和其他函数一样,fp指定待写入的文件。例如,要保存一个大小为256字节的数据对象(如数组),可以这样做:

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

以上调用把一块256字节的数据从buffer写入文件。另举一例,要保存一个内含10个double类型值的数组,可以这样做:

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

以上调用把earnings数组中的数据写入文件,数据被分成10块,每块都是double的大小。 注意fwrite()原型中的const void * restrict ptr声明。fwrite()的一个问题是,它的第1个参数不是固定的类型。例如,第1个例子中使用buffer,其类型是指向char的指针;而第2个例子中使用earnings,其类型是指向double的指针。在ANSI C函数原型中,这些实际参数都被转换成指向void的指针类型,这种指针可作为一种通用类型指针(在ANSI C之前,这些参数使用char *类型,需要把实参强制转换成char *类型)。 fwrite()函数返回成功写入项的数量。正常情况下,该返回值就是nmemb,但如果出现写入错误,返回值会比nmemb小。

6 sizet fread()函数

sizet fread()函数的原型如下:

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

fread()函数接受的参数和fwrite()函数相同。在fread()函数中,ptr是待读取文件数据在内存中的地址,fp指定待读取的文件。该函数用于读取被fwrite()写入文件的数据。例如,要恢复上例中保存的内含10个double类型值的数组,可以这样做:

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

该调用把10个double大小的值拷贝进earnings数组中。fread()函数返回成功读取项的数量。正常情况下,该返回值就是nmemb,但如果出现读取错误或读到文件结尾,该返回值就会比nmemb小。

7 int feof(FILE *fp)和int ferror(FILE*fp)函数

如果标准输入函数返回EOF,则通常表明函数已到达文件结尾。然而,出现读取错误时,函数也会返回EOF。feof()和ferror()函数用于区分这两种情况。当上一次输入调用检测到文件结尾时,feof()函数返回一个非零值,否则返回0。当读或写出现错误,ferror()函数返回一个非零值,否则返回0。

8 一个程序示例

接下来,我们用一个程序示例说明这些函数的用法。该程序把一系列文件中的内容附加在另一个文件的末尾。该程序存在一个问题:如何给文件传递信息。可以通过交互或使用命令行参数来完成,我们先采用交互式的方法。下面列出了程序的设计方案。

询问目标文件的名称并打开它。使用一个循环询问源文件。以读模式依次打开每个源文件,并将其添加到目标文件的末尾。

为演示setvbuf()函数的用法,该程序将使用它指定一个不同的缓冲区大小。下一步是细化程序打开目标文件的步骤:

1.以附加模式打开目标文件;

2.如果打开失败,则退出程序;

3.为该文件创建一个4096字节的缓冲区;

4.如果创建失败,则退出程序。

与此类似,通过以下具体步骤细化拷贝部分:

1.如果该文件与目标文件相同,则跳至下一个文件;

2.如果以读模式无法打开文件,则跳至下一个文件;

3.把文件内容添加至目标文件末尾。

最后,程序回到目标文件的开始处,显示当前整个文件的内容。 作

为练习,我们使用fread()和fwrite()函数进行拷贝。程序append.c给出了这个程序

 /* append.c -- appends files to a file */#include <stdio.h>#include <stdlib.h>#include <string.h>#define BUFSIZE 4096#define SLEN 81void append(FILE *source, FILE *dest);char * s_gets(char * st, int n);int main(void){    FILE *fa, *fs;    // fa for append file, fs for source file    int files = 0;  // number of files appended    char file_app[SLEN];  // name of append file    char file_src[SLEN];  // name of source file    int ch;    puts("Enter name of destination file:");    s_gets(file_app, SLEN);    if ((fa = fopen(file_app, "a+")) == NULL)    {        fprintf(stderr, "Can't open %sn", file_app);        exit(EXIT_FAILURE);    }    if (setvbuf(fa, NULL, _IOFBF, BUFSIZE) != 0)    {        fputs("Can't create output buffern", stderr);        exit(EXIT_FAILURE);    }    puts("Enter name of first source file (empty line to quit):");    while (s_gets(file_src, SLEN) && file_src[0] != '0')    {        if (strcmp(file_src, file_app) == 0)            fputs("Can't append file to itselfn",stderr);        else if ((fs = fopen(file_src, "r")) == NULL)            fprintf(stderr, "Can't open %sn", file_src);        else        {            if (setvbuf(fs, NULL, _IOFBF, BUFSIZE) != 0)            {                fputs("Can't create input buffern",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 appending. %d files appended.n", files);    rewind(fa);    printf("%s contents:n", file_app);    while ((ch = getc(fa)) != EOF)        putchar(ch);    puts("Done displaying.");    fclose(fa);​    return 0;}​void append(FILE *source, FILE *dest){    size_t bytes;    static char temp[BUFSIZE]; // allocate once​    while ((bytes = fread(temp,sizeof(char),BUFSIZE,source)) > 0)        fwrite(temp, sizeof (char), bytes, dest);}​char * s_gets(char * st, int n){    char * ret_val;    char * find;​    ret_val = fgets(st, n, stdin);    if (ret_val)    {        find = strchr(st, 'n');   // look for newline        if (find)                  // if the address is not NULL,            *find = '0';          // place a null character there        else            while (getchar() != 'n')                continue;    }    return ret_val;}​

如果setvbuf()无法创建缓冲区,则返回一个非零值,然后终止程序。可以用类似的代码为正在拷贝的文件创建一块4096字节的缓冲区。把NULL作为setvbuf()的第2个参数,便可让函数分配缓冲区的存储空间。 该程序获取文件名所用的函数是sgets(),而不是scanf(),因为scanf()会跳过空白,因此无法检测到空行。该程序还用sgets()代替fgets(),因为后者在字符串中保留换行符。 以下代码防止程序把文件附加在自身末尾:

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

参数fileapp表示目标文件名,filesrc表示正在处理的文件名。append()函数完成拷贝任务。该函数使用fread()和fwrite()一次拷贝4096字节,而不是一次拷贝1字节:

void append(FILE *source, FILE *dest){    size_t bytes;    static char temp[BUFSIZE]; // allocate once​    while ((bytes = fread(temp,sizeof(char),BUFSIZE,source)) > 0)        fwrite(temp, sizeof (char), bytes, dest);}

因为是以附加模式打开由dest指定的文件,所以所有的源文件都被依次添加至目标文件的末尾。注意,temp数组具有静态存储期(意思是在编译时分配该数组,不是在每次调用append()函数时分配)和块作用域(意思是该数组属于它所在的函数私有)。 该程序示例使用文本模式的文件。使用"ab+"和"rb"模式可以处理二进制文件。

9 用二进制I/O进行随机访问

随机访问是用二进制I/O写入二进制文件最常用的方式,我们来看一个简短的例子。程序randbin.c中的程序创建了一个存储double类型数字的文件,然后让用户访问这些内容。

Listing 13.6 The randbin.c Program

/* randbin.c -- random access, binary i/o */#include <stdio.h>#include <stdlib.h>#define ARSIZE 1000​int main(){    double numbers[ARSIZE];    double value;    const char * file = "numbers.dat";    int i;    long pos;    FILE *iofile;​    // create a set of double values    for(i = 0; i < ARSIZE; i++)        numbers[i] = 100.0 * i + 1.0 / (i + 1);    // attempt to open file    if ((iofile = fopen(file, "wb")) == NULL)    {        fprintf(stderr, "Could not open %s for output.n", file);        exit(EXIT_FAILURE);    }    // write array in binary format to file    fwrite(numbers, sizeof (double), ARSIZE, iofile);    fclose(iofile);    if ((iofile = fopen(file, "rb")) == NULL)    {        fprintf(stderr,                "Could not open %s for random access.n", file);        exit(EXIT_FAILURE);    }    // read selected items from file    printf("Enter an index in the range 0-%d.n", ARSIZE - 1);    while (scanf("%d", &i) == 1 && i >= 0 && i < ARSIZE)    {        pos = (long) i * sizeof(double); // calculate offset        fseek(iofile, pos, SEEK_SET);    // go there        fread(&value, sizeof (double), 1, iofile);        printf("The value there is %f.n", value);        printf("Next index (out of range to quit):n");    }    // finish up    fclose(iofile);    puts("Bye!");​    return 0;}

首先,该程序创建了一个数组,并在该数组中存放了一些值。然后,程序以二进制模式创建了一个名为numbers.dat的文件,并使用fwrite()把数组中的内容拷贝到文件中。内存中数组的所有double类型值的位组合(每个位组合都是64位)都被拷贝至文件中。不能用文本编辑器读取最后的二进制文件,因为无法把文件中的值转换成字符串。然而,存储在文件中的每个值都与存储在内存中的值完全相同,没有损失任何精确度。此外,每个值在文件中也同样占用64位存储空间,所以可以很容易地计算出每个值的位置。 程序的第2部分用于打开待读取的文件,提示用户输入一个值在数组中的索引。程序通过把索引值和double类型值占用的字节相乘,即可得出文件中的一个位置。然后,程序调用fseek()定位到该位置,用fread()读取该位置上的数据值。注意,这里并未使用转换说明。fread()从已定位的位置开始,拷贝8字节到内存中地址为&value的位置。然后,使用printf()显示value。下面是该程序的一个运行示例:

Enter an index in the range 0-999.

500

The value there is 50000.001996.

Next index (out of range to quit):

900

The value there is 90000.001110.

Next index (out of range to quit):

The value there is 1.000000.

Next index (out of range to quit):

-1

Bye!

标签: #c语言各函数