【问题标题】:Standard Template Library lists - doubly linked or circularly linked?标准模板库列表 - 双重链接或循环链接?
【发布时间】:2012-04-30 00:27:04
【问题描述】:

我刚刚注意到 C++ 中的 std::list 类,我对此感到好奇。简而言之,它涉及列表迭代器的工作方式。考虑以下代码:

std::list<int> alist;
alist.push_back(0);
alist.push_back(1);
alist.push_back(2);

很明显,这会创建一个包含三个整数元素的列表。我可以在列表的开头定义一个迭代器,并使用它来打印第一个元素中包含的值,如下所示:

std::list<int>::iterator iter = alist.begin();
std::cout << *iter << std::endl;  // Prints "0" to stdout

我觉得有点奇怪的是,如果我现在递减迭代器,它会“循环”并最终指向列表中的最后一个元素:

--iter;
std::cout << *iter << std::endl;  // Prints "2" to stdout

对于应该作为双向链表实现的东西,这种行为是否合理?如果列表是一个 循环 链接列表,我非常希望迭代器会出现类似的行为,但我觉得这很奇怪。

您过去使用过的这种迭代器行为有什么实际用途吗?我应该留意与此行为相关的任何问题吗?

(顺便说一句,这发生在 gcc 4.7.0 (MinGW) 上。我还没有用任何其他版本或编译器对其进行测试。)

【问题讨论】:

标签: c++ list stl iterator


【解决方案1】:

将迭代器递减到超过begin 会调用未定义的行为。您看到的行为很可能是巧合(实际上,看看使用不同的编译器会发生什么here)。

如果你想确认这一点,你可以简单地看一下GCC的list的实现;您通常可以在/usr/include/c++/4.x.y/bits/stl_list.h 找到源代码。

【讨论】:

  • 谢谢。阅读源代码有助于很好地解决问题。 (也感谢 ideone 的链接 - 以前没见过!)
  • 实际上我在一些实现中看到了使用隐藏节点来闭合圆圈。使用该 guard 可以更轻松地执行其余操作,插入/擦除不需要对列表的开头/结尾进行特殊处理,它们只需对 node-&gt;lastnode-&gt;next(但是它们在实现中被调用)知道它引用了一个有效的 object
【解决方案2】:

查看 stl_list.h,我注意到这条评论:

   *
   *  Second, a %list conceptually represented as
   *  @code
   *    A <---> B <---> C <---> D
   *  @endcode
   *  is actually circular; a link exists between A and D.  The %list
   *  class holds (as its only data member) a private list::iterator
   *  pointing to @e D, not to @e A!  To get to the head of the %list,
   *  we start at the tail and move forward by one.  When this member

这是在 4.2.1 gcc 中找到的。这不会改变@Oli 提供的答案,因为它恰好是在 gcc 4.2.1 中实现的答案。我会指望这个功能

【讨论】:

  • 该评论在实现方面略有误导。在该版本的 STL 中列表的实现定义了一个仅包含指针的节点基类型,以及扩展节点基并包含实际数据的节点实现。该列表包含一个节点基实例,它是关闭循环的对象(顺便说一下end() 迭代器)。造成差异的细节是关闭列表的 link 不能是保存数据的节点,否则不可能执行最简单的循环。
【解决方案3】:

我认为你得到的“2”是常数 2(在alist.push_back(2); 中)。 我觉得你的程序很短很简单,对吗?

【讨论】:

    猜你喜欢
    • 2014-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多