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

发布于:2023-01-04 ⋅ 阅读:(150) ⋅ 点赞:(0)

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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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章的动态内存分配。 


网站公告

今日签到

点亮在社区的每一天
去签到