单链表

时间:2009-06-10 04:12:54

标签: c data-structures linked-list

我创建了一个链表。一切正常。

我只想知道我的代码是否有任何潜在危险。我关注的代码片段是我的推送,弹出和清理。代码的各个部分仅供用户交互,因此不是很重要(无论如何我都发布了,以便我在做什么时更清楚)。只是链表应用程序。

非常感谢任何建议,因为这是我的第一次尝试。

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

typedef struct product_data product_data_t;
struct product_data
{
    int product_code;
    char product_name[128];
    int product_cost;
    product_data_t *next;
};

static product_data_t *head = NULL;
static product_data_t *tail = NULL;
static product_data_t *new_product = NULL;

// Push a product on to the list.
void push(int code, char name[], int cost); 
// Pop (delete) a product from the list.
void pop(int code);
// Display all product in the list.
void display_list();
// Delete all memory allocated on the list
void clean_up();
// Display menu
void menu();

int main(void)
{
    menu();

    getchar();

    return 0;
}

void push(int code, char name[], int cost)
{
    // Allocate memory for the new product
    new_product = calloc(1, sizeof(product_data_t));
    if(!new_product)
    {
        fprintf(stderr, "Cannot allocated memory");
        exit(1);
    }

    /* Populate new products elements fields */
    new_product->product_code = code;
    strncpy(new_product->product_name, name, sizeof(new_product->product_name));
    new_product->product_cost = cost;
    new_product->next = NULL;

    // Set the head and tail of the linked list
    if(head == NULL)
    {
        // First and only product
        head = new_product;
    }
    else
    {
        tail->next = new_product;
    }

    tail = new_product;
}

// Find the product by code and delete
void pop(int code)
{
    product_data_t *product = head;
    product_data_t *temp = NULL;
    product_data_t *previous = head;
    int found = 0; // 0 - Not Found, 1 - Found

    if(!head)
    {
        puts("The list is empty");
        return;
    }

    while(product)
    {
        if(product->product_code == code)
        {
            found = 1; // Found
            // Check if this is in the first node - deleting from head
            if(head->product_code == code)
            {
                temp = head;
                head = head->next;
                free(temp);

                // Finished Deleting product
                return;
            }

            // Check if this is the end node - deleting from the tail
            if(tail->product_code == code)
            {
                temp = tail;
                tail = previous;
                free(temp);

                // Finished deleting product
                return;
            }

            // delete from list if not a head or tail
            temp = product;
            previous->next = product->next;
            free(temp);

            // Finished deleting product
            return;
        }
        // Get the address of the previous pointer.
        previous = product;
        product = product->next;  
    }

    if(!found)
    {
        printf("code [ %d ] was not found\n", code);
    }

    // Set all to null after finished with them
    product = NULL;
    temp = NULL;
    previous = NULL;
}

// Traverse the linked list
void display_list()
{
    // Start at the beginning
    product_data_t *product = head;

    while(product)
    {
        printf("===================================\n");
        printf("Product code: \t\t%d\n", product->product_code);
        printf("Product name: \t\t%s\n", product->product_name);
        printf("product cost (USD): \t%d\n", product->product_cost);
        printf("===================================\n\n");

        // Point to the next product
        product = product->next;
    }
    // Finished set to null
    product = NULL;
}

// Release all resources
void clean_up()
{    
    product_data_t *temp = NULL;

    while(head)
    {
        temp = head;
        head = head->next;
        free(temp);    
    }
    head = NULL;
    temp = NULL;

    // End program - goodbye
    exit(0);
}

void menu()
{
    int choice = 0, code = 0, cost = 0;
    char name[128] = {0};

    do
    {
        fflush(stdin); // Flush the input buffer

        puts("========= Welecome to linked list ===============");
        puts("[1] Add new product to the list");
        puts("[2] Delete a product from the list");
        puts("[3] Display all products");
        puts("[4] Exit and clean up");
        printf("Enter your choice: ");
        scanf("%d", &choice);

        switch(choice)
        {
        case 1:
            printf("Enter product code: ");
            scanf("%d", &code);
            printf("Enter cost: ");
            scanf("%d", &cost);
            printf("Enter name: ");
            scanf("%s", name);
            push(code, name, cost);
            break;

        case 2:
            printf("Enter product code: ");
            scanf("%d", &code);
            pop(code);
            break;

        case 3:
            display_list();
            break;

        case 4:
            clean_up();
            break;

        default:
            puts("Incorrect choice");
            break;
        }
    }while(choice != 4);
}

7 个答案:

答案 0 :(得分:9)

来自pop()

            if(head->product_code == code)
            {
                    temp = head;
                    head = head->next;
                    free(temp);

                    // Finished Deleting product
                    return;
            }

如果只有一个项目,'head'和'tail'将指向同一个节点。但是,如果你弹出这一项,'head'将被调整,但'tail'仍将指向free'd节点。这将留下一个错误的指针,这可能会导致您的计算机爆炸。

附录:同样地,如果您弹出最后一个被推送的节点,'new_product'将悬空,而clean_up()也会使'tail'指针悬空。即使所提供的代码示例在它们被释放后也永远不会取消引用它们,C代码中的悬空指针应始终被视为“具有潜在危险”。

答案 1 :(得分:5)

strncpy(new_product->product_name, name, sizeof(new_product->product_name));

如果字符串长于您所拥有的字符,则不会正确终止。

答案 2 :(得分:4)

我认为没有理由new_product应该是全球性的,并且没有理由说明为什么它不应该是。

答案 3 :(得分:3)

看起来你走在正确的轨道上,但有问题。我会删除全局变量,而是将一个list_t结构(包含头部和尾部)传递给函数。正如其他人所指出的那样,您可能还希望通过使用(例如)node_t类型和void *数据指针来使列表具有通用性。

通常push和pop用于指在开头添加或删除项目,而不是任意位置(就像你一样);这只是一个命名问题。

如果你有product_name char * product_name,那么你可以删除长度限制以及strncpy的需要。您只需让调用者分配字符串,然后在clean_up中释放它。

您可以考虑使用枚举来提高菜单的可读性。对于“检查这是否在第一个节点 - 从头部删除”(尾部相同),您应该只比较头部与产品,而不是比较代码。

在“tail = previous”之后,你应该在NULL旁边设置tail-&gt;

答案 4 :(得分:2)

同意goldPseudo和thaggie / Steven提出的问题。

push()中,将strncpy()替换为strlcpy(),以确保目标字符串始终以NUL终止。

cleanup()中,我建议您删除exit(0);声明 - 您不需要它。从子程序中退出程序通常不是最好的事情。

你应该从创建第一个单链表中拿走一课,也就是说,单链表在现实世界中通常不是很有用,因为:

  • 他们太难操纵了。只需看看pop()子例程的复杂性。
  • 相对较慢,因为每次要从列表中检索元素时都必须从列表的开头开始。

您现在应该尝试编写您的第一个双向链表。虽然双链表实现起来比较复杂,但它们比单链表更容易操作(特别是在删除元素时)。

答案 5 :(得分:1)

你有没有理由从clean_up函数调用exit(0)?我认为这很有潜在危险,因为你没有机会让用户正确完成程序。

我建议您在构建数据结构时使用数据封装:

typedef struct
{
    int product_code;
    char product_name[128];
    int product_cost;
    list_node *next;
} list_node;

typedef struct
{
   list_node* head;
   list_node* tail;
   list_node* current;
   int        size;
} list;

最好在列表的开头使用跟踪虚拟节点,使代码更通用。

答案 6 :(得分:1)

按照正常的命名方式,push和pop与堆栈相关 - 即push()应该将一个项添加到堆栈的顶部(你添加到列表的尾部,这很好!)和pop()应该从堆栈顶部返回并删除该项目(您在列表中的任何位置搜索命名项目并将其删除。)
除了函数名,我建议列表的更通用(抽象)实现,其中节点的内容是指向任意数据的指针(在您的特殊情况下,稍后将是product_data)。这样,您的链接列表可以重复用于任何内容,并且更易于调试,阅读和维护 最好不要让全局内容,而是允许列表的多个实例。正常的C方法是将数据保存在结构中,然后将实例作为第一个参数传递给每个函数。