我正试图创建一个有名字的代码(例如,Andy、Barry、Matilda),当我输入某个子字符串时,这些名字将被删除(例如,子字符串是y,因此,Andy和Barry将被删除。剩下的只有Matilda)。有人能为我提供帮助吗?
我的代码是:
void del(char key[])
{
if(head == NULL)
{
printf("There's no datan");
}
else{
curr = head;
while(curr != NULL && strcmp(curr->name, key) != 0)
{
curr = curr->next;
}
if(curr == NULL)
{
printf("Node is not in the listn");
}
if(curr == head & curr == tail)
{
free(curr);
head = tail = NULL;
}
else if(curr == head)
{
head = head->next;
free(curr);
head->prev = NULL;
}
else if(curr == tail)
{
tail = tail->prev;
free(curr);
tail->next = NULL;
}
else
{
curr->prev->next = curr->next;
curr->next->prev = curr->prev;
free(curr);
}
}
}
您的代码正确地删除了单个列表节点,除非找不到key
——在这种情况下,printf("Node is not in the listn");
之后的return
丢失。
要删除多个节点,我们必须在列表上循环,如果找到并删除了匹配项,也要继续。为了只在没有发现任何内容的情况下打印消息Node is not in the list
,我们可以使用一个在循环后测试的标志。因此,您可以用替换外部else
块的内容
void *next;
int deleted = 0;
for (curr = head; curr; curr = next)
{ next = curr->next;
if (strstr(curr->name, key))
{ deleted = 1;
// the rest of this block is your code unchanged
if(curr == head & curr == tail)
{
free(curr);
head = tail = NULL;
}
else if(curr == head)
{
head = head->next;
free(curr);
head->prev = NULL;
}
else if(curr == tail)
{
tail = tail->prev;
free(curr);
tail->next = NULL;
}
else
{
curr->prev->next = curr->next;
curr->next->prev = curr->prev;
free(curr);
}
}
}
if (!deleted) printf("Node is not in the listn");
您没有提供太多信息,但我认为这对社区来说是新的。从您提供的内容来看,很明显,您至少有一个双链接列表,其中有一个字符串作为数据成员。同样清楚的是,您已经将head
和tail
指针声明为全局变量(这不是一个好的做法,因为您应该将函数所需的任何信息作为参数传递,但对于本学习练习,它提供了最小的简化)
根据您的描述,您希望del
函数执行的操作,您希望在链表上迭代,测试name
成员是否包含子字符串key
,如果包含,则希望从列表中删除该节点。
您有两个主要问题:
- 您使用了错误的字符串函数来检查
name
成员中的子字符串。strcmp()
将只查找完整的name
与您的key
匹配的节点。相反,您需要strstr()
,在name
中的任何位置都可以匹配key
- 通过尝试单独使用指向当前节点的指针来迭代列表,而不是同时使用当前节点的指针和地址,使节点删除过于复杂。参见Linus关于理解指针
要纠正您的第一个问题,您只需将strcmp()
更改为strstr()
即可在name
中的任何位置匹配key
,例如
...
if (strstr(curr->name, key) != NULL) { /* strstr, not strcmp, to find key */
...
要解决第二个问题,您需要重新生成del
函数。要做到这一点,您必须了解在删除多个节点的同时进行迭代与查找要删除的单个节点的迭代有何不同。当循环查找要删除的单个节点时,只需在每次迭代中前进到列表中的下一个节点,直到找到该节点,然后删除该节点。
当可能从列表中删除多个节点时,不能执行此操作。为什么?删除具有匹配子字符串的节点时,列表中的下一个节点将取代它。您不能在删除后直接前进到下一个节点,因为在删除第一个节点后,现在是当前节点的节点也可能包含要查找的子字符串。如果您只是盲目地前进到下一个节点,并且删除后的当前节点也包含子字符串,那么您将跳过删除该节点。
从代码的角度来看,这意味着你需要在if
下面添加一个else
子句,例如
if (strstr(curr->name, key) != NULL) { /* strstr, not strcmp, to find key */
...
else
...
根据if
子句,下一个节点将替换删除后的当前节点,因此您不会再次前进到列表中的下一节点。这样,新的当前节点将在下一次迭代中检查匹配的子字符串。如果key
不匹配,则仅前进到else
子句下的下一个节点。
当使用指向当前节点的指针以及当前节点的地址进行迭代时,不必处理特殊情况。您将始终将当前地址的内容设置为列表中的下一个节点。(head
不会更改,因为您已将该地址的结构设置为删除时的下一个)唯一需要的检查是检查是否正在删除tail
节点。在这种情况下,您需要更新tail
节点以指向上一个节点,因为您将删除tail
当前指向的节点。否则,您所需要做的就是更新移动到当前地址的节点的->prev
指针以指向列表中的前一个节点。
考虑到这一点,您的del
函数可以简化为:
void del (char key[])
{
if (head == NULL) { /* check empty */
puts ("list-empty");
return;
}
node_t **ppnode = &head, /* address of current node */
*curr = head; /* pointer to current node */
while (curr) {
if (strstr(curr->name, key) != NULL) { /* strstr, not strcmp, to find key */
*ppnode = curr->next; /* fill address w/next node */
if (curr != tail) /* if not tail */
(*ppnode)->prev = curr->prev; /* set ->prev to prev node */
else /* otherwise */
tail = curr->prev; /* update tail pointer */
free (curr); /* free node */
curr = *ppnode; /* set new current node */
}
else { /* node to keep */
ppnode = &curr->next; /* set address to addres of next */
curr = curr->next; /* advance to next node */
}
}
}
在不了解更多代码的情况下,我们所能做的就是编写一个简短的示例,将字符串添加为列表中的节点(使用固定的name
来简化事情)。例如:
int main (void) {
add ("Andy"); /* add nodes to list */
add ("Barry");
add ("Matilda");
prnfwd(); /* print forward and reverse */
prnrev();
putchar ('n');
del ("y"); /* delete nodes containing substring "y" */
prnfwd(); /* print forward and reverse */
prnrev();
del_list(); /* free allocated memory */
}
这会添加节点,在两个方向上对列表进行迭代,调用del ("y");
以删除字符串包含子字符串"y"
的所有节点(注意,这与字符'y'
不同),然后在释放列表中的所有内存之前,在两种方向上再次对列表进行迭代,输出剩余的内容。
示例使用/输出
给出示例字符串的结果是:
$ ./bin/lldglobaldelkey
Andy Barry Matilda
Matilda Barry Andy
Matilda
Matilda
示例的完整实现是:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXNM 64
typedef struct node_t {
char name[MAXNM];
struct node_t *prev, *next;
} node_t;
node_t *head, *tail;
/** add node at end of list, update tail to end */
node_t *add (const char *s)
{
node_t *node = malloc (sizeof *node); /* allocate node */
if (!node) /* validate allocation */
return NULL;
strcpy (node->name, s); /* initialize new node */
node->prev = node->next = NULL;
if (!head) /* if 1st node, node is head/tail */
head = tail = node;
else { /* otherwise */
node->prev = tail; /* set prev to tail */
tail->next = node; /* add at end, update tail pointer */
tail = node;
}
return node; /* return new node */
}
/* print list forward */
void prnfwd (void)
{
if (!head) { /* check empty */
puts ("list-empty");
return;
}
for (node_t *n = head; n; n = n->next) /* iterate over nodes - forward */
printf (" %s", n->name);
putchar ('n');
}
/* print list reverse */
void prnrev (void)
{
if (!head) { /* check empty */
puts ("list-empty");
return;
}
for (node_t *n = tail; n; n = n->prev) /* iterate over nodes - reverse */
printf (" %s", n->name);
putchar ('n');
}
/** delete all nodes in list */
void del_list (void)
{
node_t *n = head;
if (!head) { /* check empty */
puts ("list-empty");
return;
}
while (n) { /* iterate over nodes - forward */
node_t *victim = n; /* save ptr to node to delete */
n = n->next; /* advance to next */
free (victim); /* delete node */
}
head = tail = NULL; /* set pointers NULL */
}
void del (char key[])
{
if (head == NULL) { /* check empty */
puts ("list-empty");
return;
}
node_t **ppnode = &head, /* address of current node */
*curr = head; /* pointer to current node */
while (curr) {
if (strstr(curr->name, key) != NULL) { /* strstr, not strcmp, to find key */
*ppnode = curr->next; /* fill address w/next node */
if (curr != tail) /* if not tail */
(*ppnode)->prev = curr->prev; /* set ->prev to prev node */
else /* otherwise */
tail = curr->prev; /* update tail pointer */
free (curr); /* free node */
curr = *ppnode; /* set new current node */
}
else { /* node to keep */
ppnode = &curr->next; /* set address to addres of next */
curr = curr->next; /* advance to next node */
}
}
}
int main (void) {
add ("Andy"); /* add nodes to list */
add ("Barry");
add ("Matilda");
prnfwd(); /* print forward and reverse */
prnrev();
putchar ('n');
del ("y"); /* delete nodes containing substring "y" */
prnfwd(); /* print forward and reverse */
prnrev();
del_list(); /* free allocated memory */
}
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有2个责任:(1)始终为内存块保留一个指向起始地址的指针,因此,(2)当不再需要时,它可以被释放。
您必须使用内存错误检查程序来确保您不会试图访问内存或在分配的块的边界之外写入,尝试读取或基于未初始化的值进行条件跳转,最后确认您释放了所有分配的内存。
对于Linux,valgrind
是正常的选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行您的程序即可
$ valgrind ./bin/lldglobaldelkey
==10704== Memcheck, a memory error detector
==10704== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10704== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==10704== Command: ./bin/lldglobaldelkey
==10704==
Andy Barry Matilda
Matilda Barry Andy
Matilda
Matilda
==10704==
==10704== HEAP SUMMARY:
==10704== in use at exit: 0 bytes in 0 blocks
==10704== total heap usage: 4 allocs, 4 frees, 1,264 bytes allocated
==10704==
==10704== All heap blocks were freed -- no leaks are possible
==10704==
==10704== For counts of detected and suppressed errors, rerun with: -v
==10704== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
请始终确认您已经释放了分配的所有内存,并且没有内存错误。
我希望这与你的实现接近。仔细看看,如果你有进一步的问题,请告诉我。