【问题标题】:Why can't you create a linked lists without creating nodes as pointers?为什么不创建节点作为指针就不能创建链表?
【发布时间】:2020-07-20 16:48:13
【问题描述】:

Creating a linked list without declaring node as a pointer 有答案。但是我想知道是否还有其他原因导致您无法将节点创建为指针,所以要清楚。

其中一个原因是新节点的作用域会死在函数之外——有没有办法解决这个问题? 还有其他原因吗?

【问题讨论】:

  • 如果你真的想,也许可以解决终身问题,但工作量很大,而且很容易出错,那你为什么要做呢?跨度>
  • 链接的问题标题不好,实际上是“为什么我需要动态分配的节点”,而不是“为什么需要指针”。
  • 使用全局变量可以解决作用域问题(使用extern
  • @melk,不,我只是在问。我只想知道是否不可能将节点声明为对象。或者,如果有办法,它比使用指针更复杂。例如,Landstalker 似乎已经给出了解决方案。所以,只是想知道使用节点作为对象(而不是指针)的其他问题是什么
  • 你无法避免使用某种间接方式。如果一个节点包含一个节点,它包含一个节点,它包含一个节点,以此类推,它会填满整个宇宙,一切都会爆炸。

标签: c++ pointers struct linked-list


【解决方案1】:

我能想到的另一个原因是遍历链表会更方便。例如:

Node* current;
while (current != nullptr) {
    // Do something
    current = current->next;
}

如果current 不是指针,您可能会复制链接列表中的每个节点,这确实是个坏主意。

【讨论】:

    【解决方案2】:

    我一直在使用很多链表(甚至更复杂的结构),其中没有在堆上单独分配节点,但所有节点都是单个数组中的元素。

    拥有指向节点的指针并在堆上分配单个节点很常见,但到目前为止还不是唯一的选择。例如,某些应用程序在“页面”中分配节点以提高效率或简化处理可能会更好。

    另一个非常常见且通常有用的选项是通过在数组中使用数字索引来创建根本没有指针(甚至在节点内部)的链表或树。例如

    struct Tree {
        struct Node {
            double value;
            int left, right; // Index of left/right child, -1 if missing
        };
        int root = -1;
        std::vector<Node> nodes;
    };
    

    【讨论】:

    • 如果使用数组作为底层存储,链表的目的是什么?
    • @Victor 有什么关系?
    • @Victor:您仍然可以插入和删除具有相同复杂性的元素(例如,删除一个元素只需要调整指针并且是 O(1))。如果您需要分配一个新节点并且数组已满,则需要进行重新分配,这是要付出代价的,但是如果数组中有空间,则单独分配比在堆上分配内存要快。您还可以使用更少的内存。使用整数作为指针存储在数组中的列表/树更容易序列化到磁盘或从磁盘序列化或通过网络发送;在很多情况下,使用索引而不是指针是合理的。
    【解决方案3】:

    如果你在共享内存或反射内存中有一个链表,那么指针的世界就不存在了。你回到了 1970 年代,使用数组索引而不是指针。

    对于那些习惯于指针的人来说,这是一个非常不同的世界和文化冲击。没有堆,您必须管理自己的垃圾回收。

    【讨论】:

      【解决方案4】:

      linked list 的字面意思是一种结构,其中对象指向彼此:

      在计算机科学中,链表是数据元素的线性集合,其顺序不是由它们在内存中的物理位置给出的。相反,每个元素指向下一个。

      在 C++ 中,一个对象可以使用指针或引用指向另一个对象。

      引用是可能的,但不方便,因为它必须在对象创建时设置并且不能修改。

      任何其他可以在不跟随指针的情况下从一个对象导航到另一个对象的结构都不是链表。一个典型的例子是一个数组:你可以通过计算它相对于当前元素的地址导航到下一个元素。

      现在如何分配节点完全取决于您 - 您可以在动态内存、自动存储(在堆栈上)、静态、共享内存等中分配它们。如果它们是链接的使用指针或引用,你仍然会有一个链表结构。

      【讨论】:

      • 好的,对象将包含指向下一个对象的指针。这足以满足链表的定义吗?但是为什么要创建“新节点”作为指针呢?如:为什么struct node* new_node 而不是struct node new_node
      • 是的。 node* new_node = new node; 在堆上分配一个节点,而函数内部的node new_node; 在堆栈上分配一个节点。如果节点相互指向,它仍然是一个链表。请注意,当函数结束时,堆栈分配的东西会自动释放。这就是为什么在堆上分配更方便。
      • 好的,所以你是说一旦程序的控制超出了功能,节点将不再存在?
      • 好的,所以你是说一旦程序的控制超出了功能,节点将不再存在?
      • 是的,这就是它被称为自动存储的原因——对象在块作用域的开头自动分配,在作用域结束时释放。
      【解决方案5】:

      为什么不创建节点作为指针就不能创建链表?

      你可以,但你绝对需要某种间接性。

      为什么通常使用指针来实现链表? 正如@6502 在他的回答中已经指出的那样,您可以通过数组或您保留为列表的页面/内存的任何其他容器来实现相同的目的。但是为了参数的缘故,让我们比较一个由指针实现的 List 和一个将节点保持为成员的 List。主要答案是:实现列表的功能更容易。 列表的好处是您可以轻松地前后推送/弹出并非常快速地删除项目。如何在列表中删除已完成实施明智?您只需将指向“要删除的对象”的指针更改为下一个节点(或在数组情况下分配另一个索引)。但是,如果您的“要删除的对象”包含另一个对象作为真正的对象而不是通过指针,那么您将不得不在删除它之前将其 move 从那里删除,否则它和列表的其余部分将消失。

      你也只能实现一个单链表,因为只有一个节点可以持有另一个。如果Node ANodeB,那么NodeB 不可能也有NodeA。但是使用指针NodeB 可以毫无问题地指向NodeA,反之亦然。

      TL;DR 如果您使用指针或数组,您的列表操作(如删除、推送等)只是切换指针或索引。如果您的节点包含您需要通过移动/复制来更新这些数据的数据。这比指针/引用间接实现起来很麻烦,而且更难掌握。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-14
        • 2018-10-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-01
        相关资源
        最近更新 更多