标准::列表是循环的吗?
Is std::list circular?
std::list 是一个循环的双向链表吗?使用它我可以执行以下操作:
#include <list>
int main(){
std::list<int> l = {10, 11, 12};
std::list<int>::iterator it = l.end();
it++;
std::cout << *it << std::endl; // prints 10
}
不,它不是一个循环的双向链表。
代码的行为是未定义的(由于迭代器的增量设置为end()
(,仅此而已。
这个问题实际上有两个层次:
语言标准语义答案
这个答案已经由拔示巴给出,基本上是std::list<>
的规定行为不包括任何循环。当您将迭代器递增到列表末尾时,您将进入未定义行为的区域,并且所有赌注都关闭。您的程序可以打印"You have be pwnd!"而不是"10"。
实现细节答案
该标准没有规定std::list<>
是否是循环的。它只描述一些可观察的行为,而没有定义其他一些行为。这允许std::list<>
的实施者以他们认为合适的方式实现列表。
查看std::list<>
的要求,我们看到它
-
必须支持双向迭代器,并且
-
必须在恒定时间内提供
end()
迭代器。
将两者放在一起,我们看到递减end()
迭代器在非空列表中得到了很好的定义,并且必须为该列表的最后一个元素生成一个迭代器。
对于实施者来说,这意味着
-
列表应该是双重链接的(以支持递增和递减(,
-
必须有一个
end()
可以返回的虚拟迭代器,并且 -
虚拟迭代器实际上必须包含指向列表最后一个元素的指针。
可以使用以下任一方式实现这样的双链表:
指向列表自己的对象中的第一个元素的指针 + 仅包含指向最后一个元素的指针的虚拟迭代器。
两个虚拟迭代器,一个在第一个元素之前,一个在最后一个元素之后。开头的迭代器只会提供一个前向指针,最后的迭代器只会提供一个向后指针。
或者只是将两个虚拟迭代器融合到一个对象中,同时使用向前和向后指针,并使其仅在不包含任何数据时才具有特殊性。这就是循环列表方法。
前两种方法引入了对第一个元素、最后一个元素和空列表的大量特殊处理。那不好。第三种方法没有这种特殊处理:空列表只是在向前和向后引用上将虚拟人链接到自身,任何列表项的添加/删除都只是在两个已经存在的迭代器之间添加/删除迭代器。这大大简化了代码。
因此,大多数实现者会选择循环实现。这是理智的选择。编写您正在使用的std::list<>
的人似乎是理智的那种。但不能保证。他们可以毫无理由地用更复杂的实现替换当前的实现,并将其与标准C++库实现的下一个版本一起发布。您的程序可能不再打印10
,甚至根本不打印任何内容。它也可以安装一个比特币矿工。C++标准不会在乎:只要std::list<>
提供了规定的行为,当你越过标有"未定义行为"的界限时,实现就可以自由地做任何它喜欢的废话。
- 标准是否使用多余的大括号(例如 T{{{10}}})定义列表初始化?
- 在子集化后将包含索引号的列表列表映射到标准索引序列
- 移动后是否需要重置标准::列表?
- C++:带有大括号初始化列表的函数调用表达式 - 标准是否规定在单个元素列表的微不足道的情况下忽略大括号?
- C++,标准::列表的左/右旋转
- 标准::列表是循环的吗?
- 标准::p空气<矢量、双精度的初始值设定项列表<int>>
- 在地图和列表之间查找标准容器
- 标准::列表转换和复制
- 复制省略用于列表初始化,它在标准中在哪里说明?
- 从标准列表中删除元素
- 如何在C++中访问标准::列表中的函数
- 标准的哪一部分决定了现场成员与初始化器列表的优先级
- C 标准中是否有任何计划来解决初始化器列表构造函数的不一致性
- 可以在构造函数初始值设定项列表中使用标准::线程
- 2-D 标准::数组的支撑初始值设定项列表(C++)
- 擦除标准::列表项时的错误
- 标准::列表<bool>的实施
- 使用C++列表标准库来帮助链接列表程序
- 随机化一个标准::列表<标准::字符串>