代码之家  ›  专栏  ›  技术社区  ›  lemonslayer

将结构成员保存到时出现意外结果。C中的txt文件

  •  4
  • lemonslayer  · 技术社区  · 7 年前

    我在尝试 将程序中的信息保存到。txt文件 (或任何文件)。我已经看过我的代码好几次了,但似乎找不到 问题 . 起初,我认为可能存在某种形式的内存泄漏(虽然我不知道内存泄漏的后果,所以我不能确定)。

    我想澄清一下,这是一项学校作业,毕竟我是来学习的,所以不要轻易给我答案!

    这次任务是我们最后一次也是最大的一次。我们正在创建具有结构的杂货清单。当我试图使用结构成员将信息保存到中时,问题开始了。txt文件(如果需要,稍后将其加载到程序中)。我知道代码可能很可怕,看起来很伤眼睛,但请容忍我。

    这是我的“保存”功能。这是非常基本和非常可怕的。

    void saveList(struct GList *grocery)
    {
        char file[20];
        FILE *fp;
    
        printf("What do you want to save the list as? (Don't include file extension): ");
        scanf("%s", file);
    
        fp = fopen(strcat(file, ".txt"), "w");
    
        for (int i=0; i<grocery->Items; i++)
        {
            printf("%s %f %s\n", grocery->list[i].name, grocery->list[i].amount, grocery->list[i].unit);
            fprintf(fp, "%s %f %s\n", grocery->list[i].name, grocery->list[i].amount, grocery->list[i].unit);
        }
        fclose(fp);
    }
    

    这是我在程序中输入的内容(添加项目时):

    Name of the item: Avocado
    Unit: kg
    Amount: 10
    

    这就是我要保存的内容。txt文件(没有显示,但第一行总是包含一些奇怪的符号)。

      10.000000 kg
    milk 10.000000 litres
    

    同样的问题总是发生;第一个项目名称(例如鳄梨)显示为一些奇怪的符号。

    这是我的完整代码,问题可能就在这里的某个地方。

    #include <stdio.h>
    #include <stdlib.h>
    #include <ctype.h>
    #include <errno.h>
    #include <string.h>
    
    struct Grocery {
        char name[20];
        char unit[20];
        float amount;
    };
    
    struct GList {
        size_t Items;
        struct Grocery *list;
    };
    
    int addGrocery();
    void printList();
    void hQuit();
    void inputError();
    void removeItem();
    void changeItem();
    void saveList();
    
    int main()
    {
        struct GList glist;
        glist.Items = 1;
    
        size_t menuChoice = 0;
        char cont = 'y';
    
        if((glist.list = malloc(sizeof(glist.list))) == NULL)
            return ENOMEM;
    
        puts("Welcome to your Grocery List Manager");
    
        do
        {
            printf("\n- - - - - - - - - - -\n[1] Add an item\n[2] Print grocery list\n[3] Remove a grocery\n[4] Edit a grocery\n[5] Save your list\n[6] Load a list\n[7] Quit\n\nPlease choose action: ");
            if(!scanf("%u", &menuChoice))
                return EIO;
    
            putchar('\n');
    
            switch(menuChoice)
            {
                case 1:
                    addGrocery(&glist);
                    break;
                case 2:
                    printList(&glist);
                    break;
                case 3:
                    removeItem(&glist);
                    break;
                case 4:
                    changeItem(&glist);
                    break;
                case 5:
                    saveList(&glist);
                    break;
                case 6:
                    //Load shopping list
                    break;
                case 7:
                    hQuit(&glist);
                    break;
                default:
                    inputError();
                    break;
            }
        } while (cont == 'y');
    
        //free(grocery);
    
        return 0;
    }
    
    int addGrocery(struct GList *grocery)
    {
        printf("Name of the grocery: ");
        if(!scanf("%s", grocery->list[grocery->Items].name))
            return EIO;
    
        printf("Unit: ");
        if(!scanf("%s", grocery->list[grocery->Items].unit))
            return EIO;
    
        printf("Amount: ");
        if(!scanf("%f", &grocery->list[grocery->Items].amount))
            return EIO;
    
        printf("You have added %f %s of %s into your list!\n\n", grocery->list[grocery->Items].amount, grocery->list[grocery->Items].unit, grocery->list[grocery->Items].name);
    
        (grocery->Items)++;
        grocery->list = realloc(grocery->list, grocery->Items * sizeof(grocery->list));
    
        if(grocery->list == NULL)
            return ENOMEM;
    
        return 1;
    }
    
    void printList(struct GList *grocery)
    {
        if ((grocery->Items - 1) > 0)
            printf("You have added %d item(s) into your list!\n", grocery->Items - 1);
        else
            printf("You have no items in your list!\n");
    
        for (int i=1; i<grocery->Items; i++)
        {
            printf("[%d] %-10s %.1f %s\n", i, grocery->list[i].name, grocery->list[i].amount, grocery->list[i].unit);
        }
        putchar('\n');
    }
    
    void removeItem(struct GList *grocery)
    {
        size_t index = 0;
        printf("Which item would you wish to remove from the list? ");
        scanf("%u", &index);
        printf("\nYou have removed %s from your grocery list!", grocery->list[index].name);
        for (int i=(int)index; i < grocery->Items; i++)
            grocery->list[i] = grocery->list[i+1];
    
        (grocery->Items)--;
    }
    
    void changeItem(struct GList *grocery)
    {
        size_t index = 0;
        printf("Which item would you like to edit the amount of? ");
        scanf("%d", &index);
        printf("\nCurrent amount: %.1f %s\nEnter new amount: ", grocery->list[index].amount, grocery->list[index].unit);
        scanf("%f", &grocery->list[index].amount);
        printf("\nYou changed the amount to %.1f!\n", grocery->list[index].amount);
    }
    
    void hQuit(struct GList *grocery)
    {
        puts("*-*-* Thank you for using the Grocery List! *-*-*");
        free(grocery->list);
        exit(0);
    }
    
    void inputError(struct GList *grocery)
    {
        puts("No such option. Please try again!\n");
    }
    
    void saveList(struct GList *grocery)
    {
        char file[20];
        FILE *fp;
        printf("What do you want to save the list as? (Don't include file extension): ");
        scanf("%s", file);
        fp = fopen(strcat(file, ".txt"), "w");
        for (int i=0; i<grocery->Items; i++)
        {
            printf("%s %f %s\n", grocery->list[i].name, grocery->list[i].amount, grocery->list[i].unit);
            fprintf(fp, "%s %f %s\n", grocery->list[i].name, grocery->list[i].amount, grocery->list[i].unit);
        }
    
        fclose(fp);
    }
    

    如果我的代码在某些地方看起来很奇怪(为什么有一个void inputError()?)这是因为我们的老师为我们的作业制定了一些非常奇怪的规则。

    请随意抨击我的代码。

    2 回复  |  直到 7 年前
        1
  •  4
  •   ChuckCottrill    7 年前

    问问自己,“C使用基于0还是基于1的数组索引?”?

    当你打电话给AddScreery时,你会通过glist的地址,

    addGrocery(&glist);
    

    当您第一次调用AddScreery时,glist的第一个/初始值是多少?在添加第一项之前,此列表包含多少项?这是“列表”还是“数组”?

    以下是您的主要功能的前几行(回答了这个问题),

    int main()
    {
        struct GList glist;
        glist.Items = 1;
    
        if((glist.list = malloc(sizeof(glist.list))) == NULL)
            return ENOMEM;
    

    考虑定义一个函数(构造函数)来创建初始(空)列表。以及向列表中添加元素的函数。

    AddScreery函数将输入数据和将数据添加到列表中合并。考虑一个只收集输入的函数,然后调用该函数将数据添加到列表中。

    int addGrocery(struct GList *grocery)
    {
        printf("Name of the grocery: ");
        //what is the value of grocery-Items the first time this is called?
        if(!scanf("%s", grocery->list[grocery->Items].name))
            return EIO;
    
        //Consider something that creates a grocery list item (does malloc)
        //then appends that list item to the list
    
        //then this check would not be needed (well, it would change)
        if(grocery->list == NULL)
            return ENOMEM;
    

    提示:是否要添加到第一个列表元素?

    但还有一个更大的问题。您是否使用数组或列表来存储struct杂货项目?您将列表声明为指针,并在main中对其进行初始化。您是否分配了一些项目的数组,或者是否需要项目列表?结构杂货店类型没有指针,因此您可能不需要“列表”,而需要“数组”(命名很重要)。

    struct GList {
        size_t Items;
        struct Grocery *list;
    };
    

    因为addGrocery函数使用数组索引,所以假设您需要一个杂货项目数组,但您创建了多少?你在说哪一个?

    (这些问题应该为您指明正确的方向)

        2
  •  2
  •   autistic    7 年前

    首先,我相信你的老师会多次告诉你不要使用幻数:

    char file[PATH_MAX];
    

    为了程序未来的计算合理性,您可能希望避免此缓冲区溢出:

    if (snprintf(NULL, 0, "%s.txt", file) >= PATH_MAX - 1) {
        fputs("Filename too long!", stderr);
        exit(EXIT_FAILURE);
    }
    

    if (scanf("%s", grocery->list[grocery->Items].name) == 1)
    

    你不会知道你在使用 scanf 错误,直到您阅读 the scanf manual (这是我们作为软件开发人员工作的一部分)。事实上,粗略地看一眼,你似乎都不知道自己做错了什么。

    事实上,作为软件开发人员,我们不仅必须 仔细阅读手册、错误消息、代码 由其他人撰写(这可能不会很好地反映质量差的评论)。

    检查是否 scanf公司 返回0是确定是否读取了0个元素的好方法,但不是确定 EOF 或发生其他文件访问错误。

    你能找出为什么我(正确地)与1相比吗?如果你想读书 值来自 stdin 使用单个比较 scanf公司 ,应将返回值与哪个数值进行比较?


    void *temp = realloc(grocery->list, grocery->Items * sizeof *grocery->list);
    if (temp == NULL)
        return ENOMEM;
    grocery->list = temp;
    

    你不会知道你在使用 realloc 不正确。。。阅读 the realloc manual 雅达。。。雅达。。。等等等等等等

    我在这里做了另一个修改: 你错过了一个星号! 哦! 看看你是否能发现:)

    事实上,作为软件开发人员,我们不仅必须 仔细阅读手册、错误消息、代码 由其他人撰写(这可能不会很好地反映质量差的评论)。

    因此,我们必须从手册中进行一些扣除,以确定何时 realloc公司 可能不会 free 覆盖之前的旧指针(从而导致内存泄漏)。

    你能找出我为什么用 temp orary变量,有吗(应该有)?

    同样,您在此处遗漏了另一个星号:

    if((glist.list = malloc(sizeof glist.list[0])) == NULL)
    

    对不起,我忍不住要说得更清楚一点。。。您应该记下以下模式:

    pointer = malloc(count * sizeof *pointer);               // -- the `malloc` pattern; pointer could be NULL, so needs checking
    void *temp = realloc(pointer, count * sizeof *pointer);  // -- the `realloc` pattern; temp could be NULL (in which case you need to deal with `pointer`)
    

    记住这两种模式,你就不会再犯这些错误了。

    当我们讨论列表的主题时,空的列表包含0项,对吗?

    glist.Items = 0;
    

    P、 你听说过美国吗 valgrind ?...