【问题标题】:Linked Lists example - Why are they used?链接列表示例 - 为什么使用它们?
【发布时间】:2016-03-26 18:36:47
【问题描述】:

我正在查看有关如何获取 Unix / iOS / Mac OS X 的接口信息(IP 地址、接口名称等)的代码块,并想了解更多为什么使用链表。我不是全职程序员,但我可以编写代码并且一直在努力学习。我确实了解基本的 C/C++,但从来没有经验或不得不使用链表。

我正在尝试学习 OS X 和 iOS 开发并尝试获取网络接口信息并遇到了以下问题: https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/getifaddrs.3.html

如果我理解正确的话,似乎有一个链表用于将每个接口的一堆结构链接在一起。为什么在这种情况下使用链表?为什么结构不只是创建并存储在数组中?

谢谢

【问题讨论】:

标签: c++ ios macos linked-list bsd


【解决方案1】:

当您开始时不知道列表中有多少元素,或者您可能会随着时间的推移添加或删除元素时,链表算法非常好。如果您想在列表末尾以外的任何位置添加或删除元素,则链接列表特别强大。链表在 Unix 中很常见。可能最好的研究地点是Wikipedia,它讨论了优点、缺点和其他细节。但主要的教训是,链表非常适合动态数据结构,而数组往往更适合静态数据。

如果您将网络接口视为“网卡”,您可能会觉得它们非常静态,但它们用于许多其他事情,例如 VPN 连接,并且可以经常更改。

【讨论】:

  • 感谢所有回复的人。这很有意义。
【解决方案2】:

有多种存储数据的方法。在 c++ 中,首选通常是 std::vector,但也有 std::list 和其他容器 - 选择将取决于几个因素,例如插入/删除内容的频率和位置(向量非常适合删除/添加在最后,但在中间插入是不好的——链表在中间插入的时间要少得多,但迭代起来就不那么好了)。

然而,这个函数的 API 是一个经典的 C(而不是 C++),所以我们必须有一个“可变长度容器”,当然,我们可以在 C 中实现类似于std::vector(一个值它包含元素的数量和指向实际元素的指针)。我不确定为什么设计者在这种情况下不这样做,但链表具有很大的优势,即使用更多元素扩展它几乎是零成本。如果您事先不知道会有多少,这是一个很好的好处。而且我的猜测是这些对象中没有足够多的对象担心性能本身[调用者以后总是可以将其重新排列成更合适的形式]。

【讨论】:

    【解决方案3】:

    链表是非常完美的数据结构,可以存储大量未知元素的数据。它是一种非常灵活的数据结构,可以在运行时扩展和收缩。它还减少了额外的内存分配或浪费,因为它们使用动态内存来存储数据。当我们完成使用数据时,它会删除数据以及内存分配。

    【讨论】:

    • 这不能回答问题。 OP 询问为什么在这种特定情况下使用链表。
    • 使用链表是因为它的灵活性,当我们不知道要存储的数据总数时,如果您再多关注一点,我已经回答了它。
    【解决方案4】:

    如果你想学习 iOS,你必须从最底层学习指针和内存分配知识。 Objective-C 虽然是 C 编程语言的下一代编程语言,但在语法上略有不同,特别是在方法调用和定义方面。在进入 iOS/Mac OSX 之前,你应该了解 Pointers 知识,MVC 知识,并且了解 iOS Frameworks 的核心信息,才能成为一名专业的 iOS 开发者。 访问RayWenderLich iOS Tutiorials

    【讨论】:

    • 这与问题无关。
    • 如果你能读懂他还说他想学习iOS的问题,所以我帮助他学习iOS开发
    • 但问题不在于学习 iOS。这只是添加到问题中的额外信息。实际的问题是为什么将链表用于网络接口。
    • 实际问题是为什么我们使用链表而不是数组,我已经回答了这个问题,我不认为额外的帮助是犯罪或违反社区条款和条件。
    【解决方案5】:

    我同意这里的每个人关于链表优于数组对于动态数据长度的好处,但我需要添加一些东西

    如果 ifaddrs 分配的结构长度相同......使用链表比数组没有任何优势......如果是这样,我可以将其视为“糟糕的设计”

    但如果不是(可能是这种情况......请注意“ifaddrs 结构至少包含以下条目”......该数组将不是可变长度结构的正确表示 考虑这个例子

    struct ifaddrs
    {
         struct ifaddrs   *ifa_next;         /* Pointer to next struct */
         char             *ifa_name;         /* Interface name */
         u_int             ifa_flags;        /* Interface flags */
         struct sockaddr  *ifa_addr;         /* Interface address */
         struct sockaddr  *ifa_netmask;      /* Interface netmask */
         struct sockaddr  *ifa_dstaddr;      /* P2P interface destination */
         void             *ifa_data;         /* Address specific data */
    };
    
    struct ifaddrs_ofothertype
    {
         struct ifaddrs   ifaddrs;         /* embed the original structure */
         char             balhblah[256];         /* some other variable */
    };
    

    上述函数可以返回一个混合结构的列表,例如 (ifaddrs_ofothertype* casted to ifaddrs*) 和 (ifaddrs*) 而无需担心每个元素的结构长度

    【讨论】:

    • 结构是否相同长度与选择数组还是链表无关。如果它们的长度不同,您可以将它们维护为一个指针数组(这在 C 中很常见)。数组和链表的区别在于它们在处理任意修改(链表更简单、更快)、不可变数据共享(链表更强大)、迭代(数组更快)和索引(数组是更快更简单)。内存布局无关紧要。
    【解决方案6】:

    [...] 并想了解更多使用链表的原因。我不是全职程序员,但我可以编写代码并且一直在努力学习。我确实了解基本的 C/C++,但从来没有经验或不得不使用链表。

    链表实际上是一种极其简单的数据结构。它们有几种类型,但总体概念只是分配节点并通过索引或指针将它们链接在一起,如下所示:

    为什么在这种情况下使用链表?

    链表有一些有趣的属性,上图中提到了其中一个属性,例如从/到中间的恒定时间删除和插入。

    为什么不只是创建结构并将其存储在数组中?

    他们实际上可能是。在上图中,节点可以直接存储在数组中。然后链接节点的目的是允许快速插入和删除。元素数组不提供这种灵活性,但如果您存储一个节点数组,其中存储索引或指向next 和可能previous 元素的指针,那么您可以开始重新排列结构并删除内容并将内容插入到只需使用链接,就可以在恒定时间内完成所有操作。

    链表最有效的用法通常是连续或部分连续地存储节点(例如:使用空闲链表)并将它们链接在一起以允许快速插入和删除。您可以将节点存储在一个大数组中,例如vector,然后将它们链接起来并通过索引取消链接。链表的另一个有趣特性是,您只需更改几个指针即可将元素从一个链表的中间快速转移到另一个链表。

    它们还有一个属性,当每个节点都具有相同的大小时,它们在注意分配时可以非常有效地连续存储。例如,如果它们都使用自己的类似数组的容器,那么有效地表示一堆可变大小的存储桶可能会很棘手,因为每个存储桶都希望分配不同数量的内存。但是,如果他们只存储一个指向列表节点的索引/指针,他们可以轻松地将所有节点存储在一个巨大的数组中,用于所有存储桶。

    也就是说,在 C++ 中,链表经常被误用。尽管它们在算法上有好处,但如果节点没有以提供空间局部性的方式分配,其中很多实际上并不能转化为卓越的性能。否则,您可能会导致缓存未命中,并且可能会在访问每个节点时出现一些页面错误。

    尽管如此,小心使用节点在内存中的位置,它们可能非常有用。这是一个示例用法:

    在这种情况下,我们可能有一个粒子模拟,其中每个粒子都在每个帧周围移动,并带有碰撞检测,我们将屏幕划分为网格单元。这使我们能够避免二次复杂性碰撞检测,因为一个粒子只需要检查与同一单元中的其他粒子的碰撞。实际版本可能存储 100x100 个网格单元(10,000 个网格单元)。

    但是,如果我们对所有 10,000 个网格单元使用像 std::vector 这样的基于数组的数据结构,那将在内存中爆炸。最重要的是,将每个粒子从一个细胞转移到另一个细胞将是一项代价高昂的线性时间操作。通过在此处使用链表(以及仅将整数用于链接的数组),我们可以在这里和那里更改一些索引(指针),以便在粒子移动时将粒子从一个单元格转移到另一个单元格,而内​​存使用非常便宜(10,000 个网格单元意味着 10,000 个 32 位整数,转换为大约 39 KB,每个粒子的链接开销为 4 字节)。

    谨慎使用,链表是一种非常有用的结构。但是,它们经常会被滥用,因为想要针对通用内存分配器分别分配每个单个节点的幼稚实现往往会导致大量缓存未命中,因为节点在内存中会非常碎片化。链表的用处往往是最近被遗忘的一个细节,尤其是在 C++ 中,因为 std::list 实现,除非与自定义分配器一起使用,否则属于那种幼稚的缓存未命中类别。但是,它们在操作系统中的使用方式往往非常高效,在获得上述算法优势的同时又不会失去参考的局部性。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-14
      • 2012-04-17
      • 1970-01-01
      • 2017-09-08
      • 1970-01-01
      • 2021-05-26
      • 2012-04-27
      相关资源
      最近更新 更多