你没有提供很多信息来继续,但我认为这对社区来说是新事物。从您提供的内容来看,很明显,您至少有一个 Doubly-Linked-List,其中有一个字符串作为数据成员。同样清楚的是,您已将 head 和 tail 指针声明为 global 变量(这不是一个好习惯,因为您应该将函数所需的任何信息作为参数传递,但是对于这个学习练习提供了最小的简化)
根据您的描述,您希望您的 del 函数执行此操作,您想要遍历您的链表测试 name 成员是否包含子字符串 key,如果是,您想要删除该节点你的清单。
你有两个主要问题:
- 您使用了错误的字符串函数来检查
name 成员中的子字符串。 strcmp() 只会找到完整的name 与您的key 匹配的节点。相反,您需要strstr(),您可以在name 中的任何位置匹配key;
- 您尝试单独使用指向当前节点的 指针 来遍历列表,而不是同时使用 指针 和 地址。见Linus on Understanding Pointers
要纠正您的拳头问题,您只需将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)
始终确认您已释放已分配的所有内存并且没有内存错误。
我希望这与您的实现方式接近。如果您还有其他问题,请仔细查看并告诉我。