【问题标题】:C++ linked list append methodC++链表追加方法
【发布时间】:2011-09-25 00:30:23
【问题描述】:

我正在用 C++ 编写一个链表模板类,作为我自己的练习,以帮助我重新开始 C++ 编程。我有以下类定义:

template <typename T>
class List
{
public:
    List();
    ~List();

    void append(T data);
    void display();
    int  count();

private:
    struct Node
    {
        T data;
        Node *next;
    } *head;
};

我有两个版本的 append 方法——一个有效,一个无效。我无法弄清楚在执行的操作方面有什么区别,以及为什么第二个不起作用。这是一个有效的方法:

template <typename T>
void List<T>::append(T data)
{
    if (head == NULL)
    {
        head = new Node;
        head->data = data;
        head->next = NULL;
    }
    else
    {
        Node *p = head, *q;
        while (p != NULL)
        {
            q = p;
            p = p->next;
        }

        p = new Node;
        p->data = data;
        p->next = NULL;
        q->next = p;
    }
}

这是一个似乎并没有真正将任何元素添加到列表中的元素:

template <typename T>
void List<T>::append(T data)
{
    Node *p = head, *q = head;

    while (p != NULL)
    {
        q = p;
        p = p->next;
    }

    p = new Node;
    p->data = data;
    p->next = NULL;
    if (q != NULL)
    {
        q->next = p;
    }
}

关于为什么第二个版本不添加任何元素的任何想法?我一直在尝试使用类型 T 作为 int。

附:这两个版本在编译期间和运行时都没有给出任何错误或警告。

【问题讨论】:

  • 你也可以保留一个指向列表最后一个元素的指针(例如tail),这样可以更容易地追加。

标签: c++ templates linked-list


【解决方案1】:

第二种方法只处理列表非空的情况。

当列表为空时,永远不会到达q-&gt;next = p; 行,因此在p 超出范围后,新元素会被泄露,并且不存在指向它的指针。

如果您想消除空列表的特殊情况,您想要的是Node **,如下所示:

template <typename T>
void List<T>::append(T data)
{
    Node** q = &head; /* head acts as the first Node::next link */

    /* invariant: q points to some Node::next field (or head, which acts like one) */
    while (*q)
        q = &(*q)->next;

    /* invariant: q points to the Node::next field at the end of the chain, which is currently NULL */
    *q = new Node { data, nullptr };
}

【讨论】:

  • 代码中的指针和双指针越少越好。您的代码表明您可以使用 C 来“耍花招”,但这是其他人难以阅读和维护的代码。亲,记得吗?
  • 谢谢,这说明了为什么它不起作用。现在很明显,仅仅说Node *p = head 并不足以让p = new Node 更新头部。
  • @littleadv:包含适当的 cmets 使此代码与原始代码一样易于理解,也许更容易。
  • 对于知道如何阅读 C 或 C++ 的人来说,代码不会更难阅读。我认为对双指针的更大反对是它可能产生的目标代码的质量。 (* 运算符不是免费的,如果在长时间运行的循环中重复,间接会花费很多。)另一方面,源代码比不使用双重间接的代码更短且更易于阅读。跨度>
  • @asveikau:主要成本来自缓存未命中,使用双指针的代码的缓存行为应该与原始工作代码相同,唯一的区别是除了访问@ 987654327@ 变量,它的地址也被保留。 @littleadv 已经指出了保留尾指针的性能权衡,尽管将 tail 设置为 Node** 也可以消除对特殊情况的需要。
【解决方案2】:

在第一个版本中您更改了 head,在第二个版本中 - 您没有。

更简单的是:

template <typename T>
void List<T>::append(T data)
{
    p = new Node;
    p->data = data;
    p->next = head;
    head = p;
}

这也更合乎逻辑,因为将项目输入到链表中不应该像它为您那样花费 O(n)...

如果您绝对必须添加到末尾,请执行以下操作:

template <typename T>
void List<T>::append(T data)
{
    p = new Node;
    p->data = data;
    p->next = NULL;
    if (tail)
        tail->next = p;
    else // first time
        tail = head = p;
}

【讨论】:

  • 首先,我的方法是尝试将一个节点附加到列表的末尾。你的在开始时插入一个。其次,由于 p 在方法开始时设置为 head,如果 p 为 null,则跳过 while 循环,这肯定是在头部添加一个新节点吗?
  • 是的,因为正如我所说 - 插入链表不应该花费 O(n)。如果您坚持添加到末尾 - 保留指向末尾的指针,然后执行相同操作。
  • 正题的答案是第一句话
  • 你看过我回答的第一句话吗?它直接回答了你的问题。我给你加粗。
  • 你知道吗?那你为什么问呢?好吧,随便。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-25
相关资源
最近更新 更多