百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

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

liebian365 2024-11-21 17:35 3 浏览 0 评论

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标准保证每次只会放回一个字符。如果实现允许把一行中的多个字符放回输入流,那么下一次输入函数读入的字符顺序与放回时的顺序相反。

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函数用于以二进制形式处理数据。

实际上,所有的数据都是以二进制形式存储的,甚至连字符都以字符码的二进制表示来存储。如果文件中的所有数据都被解释成字符码,则称该文件包含文本数据。如果部分或所有的数据都被解释成二进制形式的数值数据,则称该文件包含二进制数据(另外,用数据表示机器语言指令的文件都是二进制文件)。 二进制和文本的用法很容易混淆。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 81
void 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):

0

The value there is 1.000000.

Next index (out of range to quit):

-1

Bye!

相关推荐

快递查询教程,批量查询物流,一键管理快递

作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...

一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递

对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...

快递查询单号查询,怎么查物流到哪了

输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...

3分钟查询物流,教你一键批量查询全部物流信息

很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...

快递单号查询,一次性查询全部物流信息

现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...

快递查询工具,批量查询多个快递快递单号的物流状态、签收时间

最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...

快递查询软件,自动识别查询快递单号查询方法

当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...

教你怎样查询快递查询单号并保存物流信息

商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...

简单几步骤查询所有快递物流信息

在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...

物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号

最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...

连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息

快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...

快递查询教程,快递单号查询,筛选更新量为1的单号

最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: