【问题标题】:Storing strings using fgets使用 fgets 存储字符串
【发布时间】:2017-04-20 18:06:58
【问题描述】:

我有一个文件,其中包含有关此类电影的信息:

Film code
Name
Year of release
Movie length(in minutes)
The film producer

我必须从文件中读取此信息并将该信息存储到指针中。到目前为止我的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct filmiab
{
    int koodpk;
    char *nimed;
    int aasta;
    int kestus;
    char *rezi;
} filmiab;

int main()
{
    filmiab *db;

    FILE *f1;
    f1 = fopen("filmid.txt", "r");

    db->nimed = (char*)malloc(sizeof(db->nimed) * sizeof(char));
    db->rezi = (char*)malloc(sizeof(db->rezi) * sizeof(char));

    while(1)
    {
        fscanf(f1, "%d ", &db->koodpk);
        fgets(db->nimed, 100, f1);
        db->nimed = (char*)realloc(db->nimed, sizeof(char) * sizeof(db->nimed)); //gets more memory to store the strings
        fscanf(f1, "%d %d ", &db->aasta, &db->kestus);
        fgets(db->rezi, 100, f1);
        db->rezi = (char*)realloc(db->rezi, sizeof(char) * sizeof(db->rezi));

        printf("Filmi kood: %d\nFilmi nimi: %sAasta: %d\nKestus minutites: %d\nFilmi rezis66r: %s\n",
        db->koodpk, db->nimed, db->aasta, db->kestus, db->rezi);
        printf("\n");
    }

    return 0;
}

它只是进入一个无限循环并且只打印最后 5 行。我知道使用 fgets 时,它会用最后 5 行替换所有字符串。 但是我该怎么做才能存储所有信息,这样我就可以在另一个函数中将它们打印出来(或只是使用它们)。为什么会陷入无限循环?

编辑: 我必须只使用在结构中创建的指针。

编辑2: 现在这两行 fgets(db->nimed, 100, f1); fgets(db->rezi, 100, f1); 存储所需的信息和空格。这样做它只存储电影和制片人的名称。

【问题讨论】:

  • 1) filmiab *db; --> filmiab db; 2) char *nimed; --> char nimed[100];
  • 它只是给出一个错误:二进制操作数无效。但这里不是这样。问题出在while循环中。编辑:我必须使用指针 - 这是要求之一(学校作业)。
  • 请在使用指针前在指针中设置合适的内存块。
  • 退出循环示例:if(EOF==fscanf(f1, "%d ", &amp;db-&gt;koodpk)) break;
  • 我已经完成了,我只是省略了那部分。

标签: c


【解决方案1】:

它只是进入一个无限循环

那是因为它是一个无限循环。你有一个没有中断条件的while(1)。它应该在无法读取任何行后中断。

每次处理文件时,即fopenfgetsfscanf,都需要检查操作是否成功。如果失败,代码将继续执行任何垃圾结果。

这对于fscanf 来说尤其是个问题,因为如果它失败了,它会将文件指针留在原来的位置,并且可能会不断地一遍又一遍地重新扫描同一行。一般来说,avoid scanf and fscanf。相反,fgets 整行,以确保它被读取,并使用sscanf 扫描它。


另一个问题是你分配内存的方式不对。

filmiab *db;

这会将一个指针放在堆栈上,但它指向垃圾。没有为实际结构分配内存。

db->nimed = (char*)malloc(sizeof(db->nimed) * sizeof(char));

sizeof(db-&gt;nimed)不是db-&gt;nimed中字符串的长度,而是指针的大小。可能是 4 或 8 个。所以你只分配了 4 或 8 个字节。

fgets(db->nimed, 100, f1);

然后您使用fgets 读取最多 100 个字节,可能会导致缓冲区溢出。

db->nimed = (char*)realloc(db->nimed, sizeof(char) * sizeof(db->nimed));

那么你重新分配的太少,太晚了。同样,和以前一样,这只是分配 4 或 8 个字节。它可能什么都不做,因为内存已经是这个大小了。


要解决这个问题,首先将整个结构放入堆栈。

filmiab db;

然后为其字符串分配必要的空间。请注意,由于sizeof(char) 始终为 1,因此无需包含它。 There's also no need to cast the result of malloc.

db.nimed = malloc(100);
db.rezi = malloc(100);

现在无需重新分配,您已经拥有 100 字节的内存,并且可以使用 fgets 对其进行写入。


为了将来参考,这是我将如何重做。

int main() {
    filmiab db;

    char file[] = "filmid.txt";
    FILE *f1 = fopen(file, "r");
    if( f1 == NULL ) {
        fprintf( stderr, "Could not open %s for reading: %s", file, strerror(errno) );
    }

    char line[1024];
    int state = 0;

    while(fgets(line, 1024, f1) != NULL) {
        switch(state % 5) {
            case 0:
                sscanf(line, "%d", &db.koodpk);
                break;
            case 1:
                db.nimed = strdup(line);
                break;
            case 2:
                sscanf(line, "%d", &db.aasta);
                break;
            case 3:
                sscanf(line, "%d", &db.kestus);
                break;
            case 4:
                db.rezi = strdup(line);
                printf("Filmi kood: %d\nFilmi nimi: %sAasta: %d\nKestus minutites: %d\nFilmi rezis66r: %s\n",
                        db.koodpk, db.nimed, db.aasta, db.kestus, db.rezi);
                printf("\n");
                break;
            default:
                // Should never get here
                assert(0);
                break;
        }

        state++;
    }

    return 0;
}

有一个可以重复使用的大行缓冲区,它是 1K,但一次只有 1K。 strdup 复制字符串,但只分配足够的内存来保存字符串。这消除了预测行有多大的需要,并且还避免了使用大量重新分配的内存碎片。

在这种特殊情况下,由于 db 正在被重用,最好为 db.nimeddb.rezi 分别分配 1024 个,但我想演示更一般的情况,即读入的内容将坚持下去。

while(fgets(line, 1024, f1) != NULL) 确保我会读到文件末尾。然后line 使用switch 语句处理,具体取决于接下来的行类型。这将读取文件的过程与处理数据的过程分开,后者可能是不可预测的并且需要大量的错误检查,而处理数据则更容易一些。从技术上讲,我应该检查那些sscanfs 是否成功,但我很懒。 :)

【讨论】:

  • 任务是获取一定数量的内存,然后在需要时使用realloc。关键是我必须使用 realloc。
  • @SanderTs。请取 5 并重新阅读 cmets 并从顶部回答。 realloc 的用途是缩小分配的不必要的内存,而不是扩大分配不足的内存,从你的代码编写方式来看,虽然最初分配的不够。
  • @SanderTs。这对realloc 来说不是一个好任务。您可以使用strlen fgets 之后将db.nimeddb.rezi 缩小到最小大小,但是这是一种从文件中读取的低效方式,并且您需要它们再次成为 100 字节以便下一次循环,所以没有必要缩小它。从文件中读取行时,通常分配一个大缓冲区,例如char line[1024],然后将其与fgets( line, 1024, fp ) 重用。然后处理line 并将其部分复制到正确大小的内存中。
  • 我解决了无限循环问题。但是有没有办法通过以某种方式扫描整个文件然后分配所需的内存来获得所需的内存。因为当老师检查程序时,他可能会使用另一个文件,该文件可能有也可能没有超过 100 个不同的电影名称。
  • @SanderTs。是的,但这不是一个好主意。您必须将每行阅读两次。一次,一个字符一个字符地确定它有多大,但是你不能存储任何东西,因为你没有分配内存。然后您将fseek 回到行首,分配确切的内存量,然后再次读取该行。这是非常低效的,磁盘 I/O 很慢。你最好分配一个单一的、可重用的行缓冲区,读入它,然后确定你需要从该行缓冲区中获得多少内存。
猜你喜欢
  • 2019-06-01
  • 1970-01-01
  • 2015-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-17
  • 1970-01-01
  • 2013-09-14
相关资源
最近更新 更多