当我在 80 年代初学习编程时,这种东西曾经是一个标准练习。回忆一下这个解决方案可能会很有趣,并将其与 C++ 中可以做的事情进行比较。实际上那是在 Algol 68 中,但我不会把它强加给你并把它翻译成 C。鉴于
typedef ... T;
typedef struct node *link;
struct node { link next; T data; };
可以写,意识到如果可以断开第一个节点的链接,则需要传递列表头指针的地址:
void search_and_destroy(link *p_addr, T y)
{
while (*p_addr!=NULL && (*p_addr)->data!=y)
p_addr = &(*p_addr)->next;
if (*p_addr!=NULL)
{
link old = *p_addr;
*p_addr = old->next; /* unlink node */
free(old); /* and free memory */
}
}
那里出现了很多*p_addr;它是最后一个,它是赋值的 LHS,这就是首先需要指针地址的原因。请注意,尽管有明显的复杂性,p_addr = &(*p_addr)->next; 语句只是将指针替换为其指向的值,然后添加一个偏移量(此处为 0)。
可以引入一个辅助指针value来让代码轻一点,如下
void search_and_destroy(link *p_addr, T y)
{
link p=*p_addr;
while (p!=NULL && p->data!=y)
p=*(p_addr = &p->next);
if (p!=NULL)
{
*p_addr = p->next;
free(p);
}
}
但这基本上是相同的代码:任何体面的编译器都应该意识到指针值*p_addr 在第一个示例中连续多次使用,并将其保存在寄存器中。
现在有了std::forward_list<T>,我们不能访问链接节点的指针,而是让那些尴尬的“迭代器在实际操作之前指向一个节点”。我们的解决方案变成了
void search_and_destroy(std::forward_list<T> list, T y)
{
std::forward_list<T>::iterator it = list.before_begin();
const std::forward_list<T>::iterator NIL = list.end();
while (std::next(it)!=NIL && *std::next(it)!=y)
++it;
if (std::next(it)!=NIL)
list.erase_after(it);
}
同样,我们可以保留第二个迭代器变量来保存 std::next(it),而不必每次都将其拼写出来(当我们增加 it 时不要忘记刷新它的值),并且基本上得到 Daniel Frey 的答案。 (我们可以改为尝试使该变量成为*T 类型的指针,等于&*std::next(it),这足以我们使用它,但实际上要确保它成为空指针有点麻烦当std::next(it)==NIL,作为标准不会让我们采取&*NIL)。
我不禁感到,自古以来,这个问题的解决方案并没有变得更加优雅。