在循环中,链表似乎是可以的,但在循环之外是空的

Inside a loop a linked list seems to be ok but outside it is empty

提问人:jvkloc 提问时间:12/12/2022 最后编辑:Ken Whitejvkloc 更新时间:12/12/2022 访问量:51

问:

我刚开始学习 C,我迷失了试图从文本文件中读取汽车及其制造年份的尝试。该程序显示,当我尝试使用包含以下行的 .txt 文件时,它找不到任何汽车:

Lada 1976
Ferrari 2005
Suzuki 1985
Volvo 1963
Toyota 1993
Honda 2011

我做了一个类似的链表程序,它从用户那里读取一个整数以添加到列表中,而不是一个文件。我认为这是完全相同的,只是现在程序从文件中读取。指针某处出错(我从调试打印中得出的最佳猜测,我添加到了几个地方),或者可能是我以某种方式设法仅在 while 循环内更改节点(另一个猜测)。有人可以指出这里出了什么问题吗?first

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

typedef struct car {
    char brand[15];
    int year; 
    struct car *prev; 
    struct car *next; 
} car;

void readCars(char *fname, car *newCar, car *first, car *last);
void printCars(car *ptr, car *first);
void freeMemory(car *ptr, car *first);

int main(int argc, char *argv[]) {
    car *first = NULL; // a pointer to the first node of the linked list
    car *last = NULL; // a pointer to the last node of the linked list
    car *newCar; // a pointer for new nodes
    car *ptr; // a pointer for iterating the linked list
    if(argc != 2) {
        printf("No filename provided.\n");
        exit(0);
    } 
    printf("Reading the file %s.\n", argv[1]);
    readCars(argv[1], newCar, first, last);
    printCars(ptr, first);
    freeMemory(ptr, first);
    printf("Program ended.\n");
    return(0);
}

void readCars(char *fname, car *newCar, car *first, car *last) {    
    FILE *tiedosto;
    char rivi[22];
    if ((tiedosto = fopen(fname, "r")) == NULL) {
        printf("Failed to open the file.\nProgram ended.\n");
        exit(0);
    }
    while (fgets(rivi, 22, tiedosto) != NULL) {
        if ((newCar = (car*)malloc(sizeof(car))) == NULL) { // allocate memory for a new node
                perror("Memory allocation failure.\n");
                exit(1);
        }
        sscanf(rivi, "%s %d", newCar->brand, &newCar->year); // set the car brand and age to the new node
        newCar->next = NULL; // set the pointer to next node to NULL as there is no next node
        newCar->prev = last; // set the pointer to the previous node to the previous last node (NULL if there was no previous)
        if (first == NULL) { 
            first = newCar; // the new node is the only node so it is the first node
            last = newCar; // the new node is the only node so it is also the last node
        } else {
            last->next = newCar; // the new node is next node of the previous last node
            last = newCar; // the new node is now the last node
        }
    }
    fclose(tiedosto);
    printf("File read into a linked list.\n");
}

void printCars(car *ptr, car *first) {
    if(first == NULL) {
        printf("No cars found.\n");
    } else {
        ptr = first;
        int count = 1;
        while (ptr != NULL) {
            printf("%d. car: %s from the year %d.\n", count, ptr->brand, ptr->year);
            count += 1;
            ptr = ptr->next;
        }
    }
}

void freeMemory(car *ptr, car *first) { 
    ptr = first;
    while (ptr != NULL) {
        first = ptr->next;
        free(ptr);
        ptr = first;
    }
    printf("Memory freed.\n");
}
c 指针 结构 while-loop malloc

评论


答:

1赞 Andreas Wenzel 12/12/2022 #1

线条

if (first == NULL) { 
    first = newCar; // the new node is the only node so it is the first node
    last = newCar; // the new node is the only node so it is also the last node
} else {
    last->next = newCar; // the new node is next node of the previous last node
    last = newCar; // the new node is now the last node
}

在函数中仅修改变量,在函数中修改变量。它们不会修改变量和函数。readCarsfirstlastreadCarsfirstlastmain

因此,函数中的变量将保留,因此,就该函数而言,链表保持为空。firstmainNULL

为了解决这个问题,函数不应该将变量的副本传递给函数,而应该传递指向这些变量的指针(即引用)。因此,您应该将函数的参数更改为以下内容:mainfirstlastreadCarsreadCars

void readCars(char *fname, car *newCar, car **first, car **last)

您还应该将上面引用的行更改为以下内容:

if ( *first == NULL ) { 
    *first = newCar; // the new node is the only node so it is the first node
    *last = newCar; // the new node is the only node so it is also the last node
} else {
    (*last)->next = newCar; // the new node is next node of the previous last node
    (*last) = newCar; // the new node is now the last node
}

此外,函数的参数不作为参数使用。相反,您将其用作局部变量,因此您应该将其声明为局部变量并将其从函数的参数列表中删除。newCarreadCars

编辑:如下面的评论中指出的那样,您正在对函数和中的参数执行相同的操作。您也应该从这些函数中删除该参数。ptrprintCarsfreeMemory

评论

0赞 TheNomad 12/12/2022
哎呀,安德烈亚斯,你把我打败了。@Jonne,为了进一步补充安德烈亚斯所说的内容,您似乎使用参数代替局部变量(您这样做了,这是一种非常奇怪的编码方式。这与安德烈亚斯说的最后一句话有关。通常,局部变量应该在本地提供,或者如果它们与以后调用的任何内容相关,则最多提供。如果你不返回或将它们作为 out 参数,你就不会使用 then 只是为了以后不声明它们。此外,开始使用 而不是 NULL,并习惯于将释放的指针设置为 nullptr(或至少 NULL)。car = newCarptr = ptrnullptr
0赞 Andreas Wenzel 12/12/2022
@TheNomad:大多数编译器还不支持,至少在默认设置下是这样。例如,编译器 gcc 和 clang 仅通过 or 命令行选项支持它。nullptrstd=c2xstd=gnu2x
0赞 TheNomad 12/12/2022
GCC 长期以来一直支持 C++17。它可能只需要 std::nullptr 而不是 nullptr。是的,它确实需要标准的标志,但这很正常。MSVC 也有同样的东西。选择要编译的标准。
0赞 jvkloc 12/12/2022
啊哈,指向指针的指针。我现在得到了他的,非常感谢。但是有一个问题。更正后的程序会在正确的汽车列表的末尾打印出一行额外的行:。如何阻止这种情况发生?7. car: from the year 0
1赞 Andreas Wenzel 12/12/2022
@Jonne:文件末尾是否有非空行。这可能会导致该行成功(即返回 non-),但失败。我建议你检查一下 的返回值,例如:fgetsNULLsscanfsscanfif ( sscanf(rivi, "%s %d", newCar->brand, &newCar->year ) != 2 ) { free( newCar ); continue; }
1赞 Angelo Mantellini 12/12/2022 #2

我的解决方案是:

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

typedef struct car {
    char brand[15];
    int year; 
    struct car *prev; 
    struct car *next; 
} car;

void readCars(char *fname, car *newCar, car **first, car **last);
void printCars(car *ptr, car *first);
void freeMemory(car *ptr, car *first);

int main(int argc, char *argv[]) {
    car *first = NULL; // a pointer to the first node of the linked list
    car *last = NULL; // a pointer to the last node of the linked list
    car *newCar; // a pointer for new nodes
    car *ptr; // a pointer for iterating the linked list
    if(argc != 2) {
        printf("No filename provided.\n");
        exit(0);
    } 
    printf("Reading the file %s.\n", argv[1]);
    readCars(argv[1], newCar, &first, &last);
    printCars(ptr, first);
    freeMemory(ptr, first);
    printf("Program ended.\n");
    return(0);
}

void readCars(char *fname, car *newCar, car **first, car **last) {    
    FILE *tiedosto;
    char rivi[22];
    if ((tiedosto = fopen(fname, "r")) == NULL) {
        printf("Failed to open the file.\nProgram ended.\n");
        exit(0);
    }
    while (fgets(rivi, 22, tiedosto) != NULL) {
        if ((newCar = (car*)malloc(sizeof(car))) == NULL) { // allocate memory for a new node
                perror("Memory allocation failure.\n");
                exit(1);
        }
        sscanf(rivi, "%s %d", newCar->brand, &newCar->year); // set the  car brand and age to the new node
        newCar->next = NULL; // set the pointer to next node to NULL as  there is no next node
        newCar->prev = *last; // set the pointer to the previous node to  the previous last node (NULL if there was no previous)
        if (*first == NULL) {
            *first = newCar; // the new node is the only node so it is the  first node
            *last = newCar; // the new node is the only node so it is also  the last node
        } else {
            (*last)->next = newCar; // the new node is next node of the previous last node
            *last = newCar; // the new node is now the last node
        }
    }
    fclose(tiedosto);
    printf("File read into a linked list.\n");
}

void printCars(car *ptr, car *first) {
    if(first == NULL) {
        printf("No cars found.\n");
    } else {
        ptr = first;
        int count = 1;
        while (ptr != NULL) {
            printf("%d. car: %s from the year %d.\n", count, ptr->brand,  ptr->year);
            count += 1;
            ptr = ptr->next;
        }
    }
}

void freeMemory(car *ptr, car *first) { 
    ptr = first;
    while (ptr != NULL) {
        first = ptr->next;
        free(ptr);
        ptr = first;
    }
    printf("Memory freed.\n");
}

主要区别在于 readCars 需要一个指向第一个和最后一个的指针的指针。这样,当函数结束时,first 和 last 的值不会更改为初始值(在 NULL 中)。 如果您已经理解,请告诉我。

评论

0赞 jvkloc 12/12/2022
是的,现在很清楚了。更正后的程序会在正确的汽车列表的末尾打印出一行额外的行:。如何阻止这种情况发生?7. car: from the year 0
1赞 Angelo Mantellini 12/12/2022
输入文件的末尾有空闲行吗?您可以做的是检查输入参数是否不为空。