• C Primer Plus(6) 中文版 第14章 结构和其他数据形式 14.8 把结构内容保存到文件中


    14.8 把结构内容保存到文件中
    由于结构可以存储不同类型的信息,所以它是构建数据库的重要工具。数据库文件可以任意数量的此类结构的数据对象。存储在一个结构中的整套信息被称为记录(record),单独的项被称为字段(field)。本节来谈论这个主题。
    或许存储记录最没效率的方法使用fprintf()。例如,回忆程序清单14.1中的book结构:
    #define MAXTITL 40
    #define MAXAUTL 40
    struct book {
        char title[MAXTITL];
        char author[MAXAULT];
        float value; 
    }; 
    如果pbook标识一个文件流,那么通过下面这条语句可以把信息存储于struct book类型的结构变量primer中:
    fprintf( pbook, "%s %s %.2f\n", primer.title, primer.author, primer.value );
    对于一些结构(如,有30个成员的结构)。这个方法用起来很不方便。另外,在检索时还存在问题,因为程序要知道一个字段结束和另一个字段开始的位置。虽然用固定字段宽度的格式可以解决这个问题(例如,"%39s%39s%8.2f"),但是这个方法仍然很笨拙。更好的方案是使用fread()和fwrite()函数读写结构大小的单元。这两个函数使用与程序相同的二进制表示法。例如:
    fwrite( &primer, sizeof(struct book), 1, pbooks );
    fread()函数从文件中拷贝一块结构大小的数据到&primer指向的位置。简而言之,这两个函数一次读写整个记录,而不是一个字段。
    以二进制表示法存储数据的缺点是,不同的系统可能使用不同的二进制表示法,所以数据文件可能不具可移植性。甚至同一个系统,不同编译器设置也可能导致不同的二进制布局。
    14.8.1 保存结构的程序示例
    程序清单14.14把程序清单14.2修改为一个新的版本,把书名保存在book.dat文件中。如果该文件存在,程序将显示它当前的内容,然后允许在文件中添加内容(如果你使用的是早期的Borland编译器,请参阅程序14.2后面的“Borland C和浮点数”)。 
    程序清单14.14 booksave.c程序
    /* booksave.c -- saves structure contents in a file */
    #include
    #include
    #include
    #define MAXTITL  40
    #define MAXAUTL  40
    #define MAXBKS   10             /* maximum number of books */
    char * s_gets(char * st, int n);
    struct book {                   /* set up book template    */
        char title[MAXTITL];
        char author[MAXAUTL];
        float value;
    };

    int main(void)
    {
        struct book library[MAXBKS]; /* array of structures     */
        int count = 0;
        int index, filecount;
        FILE * pbooks;
        int size = sizeof (struct book);
        
        if ((pbooks = fopen("book.dat", "a+b")) == NULL)
        {
            fputs("Can't open book.dat file\n",stderr);
            exit(1);
        }
        
        rewind(pbooks);            /* go to start of file     */
        while (count < MAXBKS &&  fread(&library[count], size,
                                        1, pbooks) == 1)
        {
            if (count == 0)
                puts("Current contents of book.dat:");
            printf("%s by %s: $%.2f\n",library[count].title,
                   library[count].author, library[count].value);
            count++;
        }
        filecount = count;
        if (count == MAXBKS)
        {
            fputs("The book.dat file is full.", stderr);
            exit(2);
        }
        
        puts("Please add new book titles.");
        puts("Press [enter] at the start of a line to stop.");
        while (count < MAXBKS && s_gets(library[count].title, MAXTITL) != NULL
               && library[count].title[0] != '\0')
        {
            puts("Now enter the author.");
            s_gets(library[count].author, MAXAUTL);
            puts("Now enter the value.");
            scanf("%f", &library[count++].value);
            while (getchar() != '\n')
                continue;                /* clear input line  */
            if (count < MAXBKS)
                puts("Enter the next title.");
        }
        
        if (count > 0)
        {
            puts("Here is the list of your books:");
            for (index = 0; index < count; index++)
                printf("%s by %s: $%.2f\n",library[index].title,
                       library[index].author, library[index].value);
            fwrite(&library[filecount], size, count - filecount,
                   pbooks);
        }
        else
            puts("No books? Too bad.\n");
        
        puts("Bye.\n");
        fclose(pbooks);
        
        return 0;
    }

    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;          // dispose of rest of line
        }
        return ret_val;
    }

    /* 首次输出:

     再次运行输出:

    */

    14.8.2 程序要点
    a+部分允许程序读取整个文件并在文件末尾的末尾添加内容。b是ANSI的一种标识方法,表明程序使用二进制文件格式。对于不接受b模式的UNIX系统,可以省略b,因为UNIX只有一种文件形式。对于早期的ANSI实现,要找出和b等价的表示法。
    我们选择二进制模式是因为fread()和fwrite()函数要使用二进制文件。虽然结构中有些内容是文本,但是value成员不是文本。如果使用文本编辑器查看book.dat,该结构本文部分的内容显示正常,但是数值部分不可读,甚至会导致文本编辑器出现乱码。
    rewind()函数确保文件指针位于文件开始处,为读文件做好准备。
    由于表达式&library[filecount]是数组中第1个新结构的地址,所以拷贝就从这里开始。
    也许该例是把结构写入文件和检索它们的最简单的方法,但是这种方法浪费存储空间,因为这还保存了结构中未使用的部分。也就是说,它保存了字符数组中未使用的部分,实际上不是每个输入项都需要这么多空间。但是,让每个输入块的大小相同在检索数据时很方便。
    另一个方法是使用可变大小的记录。为了方便读取文件中的这种记录,每个记录以数值字段规定记录的大小,这比上一种方法复杂。通常,这种方法涉及接下来要介绍的“链式结构”和第12章的动态内存分配。 

  • 相关阅读:
    Linux.14_多线程(重点总结)
    Docker 与 K8S学习笔记(二十五)—— Pod的各种调度策略(中)
    elasticsearch分组求平均值
    行业分析| 智慧消防对讲
    【HEC-RAS】模型不稳定故障排除技巧(一)
    【音视频|ALSA】SS528开发板编译Linux内核ALSA驱动、移植alsa-lib、采集与播放usb耳机声音
    MySQL分布式事务xa的介绍与使用
    Maven简单介绍及安装与配置与创建Maven项目
    深入理解WPF的ResourceDictionary
    网络安全竞赛C模块批量拿值脚本
  • 原文地址:https://blog.csdn.net/weixin_40186813/article/details/126512313