从文件读取到C中的结构

时间:2017-12-31 13:16:43

标签: c

我正在为大学做一个小项目(第一学期做书店实施)我有一个问题,从文本文件读取到结构列表,其中有一个存储作者的二维字符数组。但是,它无法正常工作(每次启动程序时,它显示列表为空)。写入文件有效(我认为,因为它用空数据覆盖了我的文本文件)。

示例数据:

Adam Mickiewicz///Pan Tadeusz/Publisher 1/1833/24.99
Jules Verne///Around The World in 80 days/Publisher 1/1904/19.99
Jean-Jacques Sempe/Rene Goscinny//Little Nicholas/Publisher 2/1963/22.99

我的结构:

#define AK 3 // Constant denoting max number of authors

typedef struct
{
    char authors[AK][100];
    char title[255];
    char publisher[100];
    unsigned int year;
    double price;

    struct Book *next;
} Book;

Book *first; // Address of the first element in a list

从文件中读取:

void read_from_file(const char *path)
{
    int no_of_authors;
    int i; 
    printf("Loading...\n"); 
    FILE *fp = fopen(path, "r"); // Opening a file

    // Checking for errors
    if (!fp) {
        printf("Error reading from file!");
        return;
    }

    // The loading mechanism 
    no_of_authors = 0;
    while (!feof(fp)) {
        Book *new = (Book*) malloc(sizeof(Book));
        for (i = 0; i < AK; i++) {
            fscanf(fp, "%s/", new->authors[i]);
        }
        fscanf(fp, "%s/%s/%u/%lf", new->title, new->publisher,
                &new->year, &new->price);
        fscanf(fp, "\n");
        new = new->next;
    }

    fclose(fp);
    printf("Loading successful.");
}

写入文件(以防万一):

void write_to_file(const char *path, Book *first)
{
    int i;
    printf("Saving...\n");
    FILE *fp = fopen(path, "w");
    Book* current = first;

    if (!fp) {
        printf("Error opening the file!");
        dump_list(first); // Dumping the list to prevent memory leaks, this works okay
    }
    // Saving mechanism
    while (first != NULL) {
        for (i = 0; i < AK; i++) {
            fprintf(fp, "%s/", current->authors[i]);
        }
        fprintf(fp, "%s/%s/%u/%lf", current->title, current->publisher,
                &current->year, &current->price);
        fprintf(fp, "\n");
    }

    fclose(fp);
    printf("Saved successfully");
}

1 个答案:

答案 0 :(得分:3)

OP最大的失败是没有检查fscanf()的返回值。如果这样做了代码,就会更快地发现问题。

在阅读数据行时,首先要考虑的是:

  

输入有问题吗?

对于学习者应用程序,这通常被认为是否定的。输入是“好”或文件结束。我们不要假设数据格式太合理了。

事实证明,虽然数据文件可能没有错误,但读取它的代码可能是错误的。代码检查*scanf()返回值的第二个微妙原因 - 自我验证。

对于定向数据,最好是使用fgets()而不是feof(), fscanf()...读取数据的。另请参阅{{3 }}

 char buf[sizeof(Book) * 2]; // Use an ample sized buffer
 while (fgets(buf, sizeof buf, fp)) {

使用"%s"读取不包含空格的文字。这也将在'/'中读取。由于'/'用于分隔,"%s"不是可接受的输入说明符。诸如“Adam Mickiewicz”之类的其他名称包括空格。不使用"%s"的第二个原因。

考虑fscanf(fp, "%s/", new->authors[i]);正在做什么。 "%s"会扫描new->authors[i]个非空白字符。非空白字符后面的字符是空白字符,绝不是'/'

使用"%[^/]"读取不包含'/'的文字。

使用"%n"跟踪当前的扫描偏移。

现在解析这条线。

    char *p = buf;
    Book *nu = malloc(sizeof *nu);  // no cast needed
    for (size_t i = 0; i < AK; i++) {
      int n = 0;
      sscanf(p, "%99[^/]/%n", nu->authors[i], &n);
      if (n == 0) {
        Handle_Parse_Error();
      }
      p += n; 
    }
    if (sscanf(p, "%254[^/]/%99[^/]/%u/%lf", 
        nu->title, nu->publisher, &nu->year, &nu->price) != 4) {
      Handle_Parse_Error();
    }

强大的代码会在每个成员上添加检查。适合编码目标。

    if (nu->year < -6000 || nu->year > 2999) Fail_Year_Range();

需要进一步的工作来将数据链接在一起。 OP的代码在这个问题上还不清楚,因此留给了OP。一种可能的方法:

Book *read_from_file(const char *path) {
  Book first;  // Only the .next member is used.
  first.next = NULL;
  Book *current = &first;

  // setup while () loop
  ...
  while (fgets(buf, sizeof bu, fp)) {
    ...
    Book *nu = malloc(sizeof *nu);
    nu->next = NULL;
    ...
    // parse data, fill in rest of nu->
    ....
    current->next = nu;  // update current pointer
    current = nu;
  }
  // close up file ...

  return first.next;
}