一个 foreach
被编译器处理后
foreach (element; range)
{
// 循环体...
}
内部将重写成类似于以下内容:
for (auto __rangeCopy = range;
!__rangeCopy.empty;
__rangeCopy.popFront())
{
auto element = __rangeCopy.front;
// 循环体...
}
任何满足以下接口的对象被称为 range 范围(或者具体称为 InputRange
),它是一个可以迭代的类型:
interface InputRange(E)
{
bool empty();
E front();
void popFront();
}
查看右边的例子,InputRange 的具体实现和使用。
范围是 惰性 的。非必要不会对他们进行计算。所以可以对范围取无穷值:
42.repeat.take(3).writeln; // [42, 42, 42]
如果范围的内容是值类型,则范围会复制并且只使用副本:
auto r = 5.iota;
r.drop(5).writeln; // []
r.writeln; // [0, 1, 2, 3, 4]
如果范围的内容是引用类型(比如:class
或者 std.range.refRange
),则范围会被消费掉并且不会重置:
auto r = 5.iota;
auto r2 = refRange(&r);
r2.drop(5).writeln; // []
r2.writeln; // []
InputRanges
范围 ForwardRanges
大部分标准库的范围都是结构体,所以每次 foreach
通常都是带破坏性的,所以是没有保障的。如果这个保障很重要的话,InputRange
可以专门化为 forward 转发 范围,包含一个 .save
方法:
interface ForwardRange(E) : InputRange!E
{
typeof(this) save();
}
// by value (Structs)
auto r = 5.iota;
auto r2 = refRange(&r);
r2.save.drop(5).writeln; // []
r2.writeln; // [0, 1, 2, 3, 4]
ForwardRanges
可以被扩展到 Bidirectional 双向 + random access 随机访问范围可复制的 ForwardRange
有两种扩展:双向范围、随机访问范围。
双向范围允许从后面迭代:
interface BidirectionalRange(E) : ForwardRange!E
{
E back();
void popBack();
}
5.iota.retro.writeln; // [4, 3, 2, 1, 0]
随机访问范围有一个已知的 length
长度,并且每一个元素都可以直接访问:
interface RandomAccessRange(E) : ForwardRange!E
{
E opIndex(size_t i);
size_t length();
}
D 的数组实际上就是随机访问范围:
auto r = [4, 5, 6];
r[1].writeln; // 5
std.range
和
std.algorithm
提供了该接口的函数。范围允许在对象后自由组合复杂的迭代算法。此外,范围允许创建 惰性 对象,只在迭代真正需要时计算,例如:当下一个范围元素被访问时。特殊的范围算法将在后面的D's Gems 章节中呈现。