如何在D语言中将字符串逐个字符作为范围读取?

5

如何在D语言中将一行读取为范围?

我知道D语言中有range概念,但我想知道如何使用这个概念来简单迭代字符串的每个字符?

为了说明我的需求,Go语言中类似的代码是:

for _, someChar := range someString {
    // Do something
}

1
http://ddili.org/ders/d.en/ranges.html - sigod
@sigod,是的,应该查看阿里的书!它绝对是现在了解D语言的首选资源。 - Samuel Lampa
2个回答

12

这取决于你想要迭代代码单元还是代码点。语言本身通过数组元素迭代数组,字符串是代码单元的数组,因此,如果你只是使用类型推断的foreach,那么就会迭代代码单元。

foreach(c; "La Verité")
    writeln(c);

最后打印的两个字符将是无意义的,因为é是由两个UTF-8代码单元组成的代码点,并且您正在打印单个代码单元(因为char是一个UTF-8代码单元)。相反,如果您执行以下操作:

foreach(dchar c; "La Verité")
    writeln(c);

然后运行时将把代码单元解码为代码点,é将作为最后一个字符打印出来。但这些都不是实际上在处理字符串范围。foreach原生地在数组上操作,无需使用输入范围API。但对于所有字符串类型,范围API看起来像:

@property bool empty();
@property dchar front();
void popFront();

它将字符串作为dchar范围处理 - 而不是它们的代码单元类型。这避免了像std.algorithm.filter这样的函数在操作单个代码单元时可能出现的问题,因为那没有意义。操作代码点也不完全正确,因为Unicode在组合代码点和字形方面变得非常复杂,但是操作代码点要更接近正确(我相信正在对标准库进行添加支持字形范围的工作,以便在需要并愿意支付性能代价的情况下使用)。因此,将范围API用于字符串,并将其作为dchar范围操作,更加正确,如果你执行以下操作:

foreach(c; filter!"true"("La Verité"))
    writeln(c);

你需要迭代dchar,这样é才会正确打印。当然,所有这些的缺点在于,默认情况下字符串上的foreach是按照代码单元级别操作的,而字符串的范围API则按照它们作为代码点进行操作,因此在混合使用数组操作和基于范围的字符串操作时必须小心。这也是为什么stringwstring不被视为随机访问范围 - 只是双向范围。当由多个代码单元组成时,无法以O(1)访问代码点,而dstring是一个随机访问范围,因为对于UTF-32,每个代码单元都是一个代码点。


如果我导入提供所需范围函数的std.array,那么在UFCS级别上,迭代字符串是否仍然相同? - dav1d
@dav1d 在数组上使用foreach将只使用数组API。对于数组,必须显式地使用基于范围的函数。实际上,尽管foreach的不一致性很烦人,但使其行为取决于是否导入了std.array会非常容易出错,因为仅添加或删除该导入可能会极大地改变代码的行为,具体取决于它正在做什么。 - Jonathan M Davis
是的,我真的希望它不会改变代码。但另一方面,UFCS允许迭代未实现opApply(可能有误)或range接口的对象。感谢澄清。 - dav1d

1
foreach(ch; str)
    do_something(ch);

字符串是一个 InputRange。一个 InputRange 实现了三个东西:

  • empty;它是否为空?
  • front;给我下一个项目。
  • popFront;推进范围,否则 front 将返回相同的值。

foreach 知道如何处理范围,所以它 "只是工作"。

但我不会说 Go 语言,所以我不确定我们是否在说同一种语言。


当然可以,唉,不知道我怎么能够忘记了这个,已经使用过几次了,所以我应该知道...得怪昨晚睡眠不足 :) - Samuel Lampa
我听到你说的话了,但此刻我的头脑也不是很清晰。 - 0b1100110
2
但是要注意类型推导在这里。那段代码正在迭代代码单元,而不是代码点,因此它不太可能正确地操作 Unicode。 - Jonathan M Davis
@JonathanMDavis 我很确定如果它正确地处理Unicode,我自己也无法处理它。= P但你说得对。 - 0b1100110

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接