我创建了一个链表。一切正常。
我只想知道我的代码是否有任何潜在危险。我关注的代码片段是我的推送,弹出和清理。代码的各个部分仅供用户交互,因此不是很重要(无论如何我都发布了,以便我在做什么时更清楚)。只是链表应用程序。
非常感谢任何建议,因为这是我的第一次尝试。
#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);
}
答案 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方法是将数据保存在结构中,然后将实例作为第一个参数传递给每个函数。