我是D编程语言的新手,刚开始阅读D编程语言书。
尝试一个关联数组示例代码时遇到错误
#!/usr/bin/rdmd
import std.stdio, std.string;
void main() {
uint[string] dict;
foreach (line; stdin.byLine()) {
foreach (word; splitter(strip(line))) {
if (word in dict) continue;
auto newId = dict.length;
dict[word] = newId;
writeln(newId, '\t', word);
}
}
}
DMD显示此错误消息:
./ vocab.d(11):错误:关联数组只能用不可变键赋值,而不是char []
我正在使用DMD编译2.051
我猜测关联数组的规则自TDPL书以来已经发生了变化。
如何将关联数组与字符串键一起使用?
感谢。
更新
我在本书的后半部分找到了解决方案。
在放入数组之前使用string.idup创建重复的不可变值。
所以
dict[word.idup] = newId;
会做这个工作。
但这样有效吗?
答案 0 :(得分:25)
关联数组要求它们的键是不可变的。当你想到如果它不是不可变的那个事实时它就有意义了,那么它可能会改变,这意味着它的哈希值会发生变化,这意味着当你再次获取值时,计算机将无法找到它。如果你去替换它,你将最终添加到关联数组的另一个值(所以,你将有一个具有正确的哈希值和一个具有不正确的哈希值)。但是,如果密钥是不可变的,则它不能改变,因此没有这样的问题。
在dmd 2.051之前,该示例有效(这是一个bug)。它现在已被修复,因此TDPL中的示例不再正确。然而,关联数组的规则已经发生了变化,因为它们中存在一个未被捕获的错误。这个例子在不应该编译时编译,而Andrei错过了它。它列在official errata for TDPL中,应该在将来的打印中修复。
更正后的代码应使用dictionary[word.idup]
或dictionary[to!string(word)]
。 word.idup
创建了word
的副本,它是不可变的。另一方面,to!string(word)
以最恰当的方式将word
转换为string
。由于word
在这种情况下是char[]
,因此可以使用idup
。但是,如果word
已经是string
,那么它只会返回传入的值,而不是不必要地复制它。因此,在一般情况下,to!string(word)
是更好的选择(特别是在模板化函数中),但在这种情况下,要么正常工作(to!()
在std.conv
)。
技术上可以将char[]
投射到string
,但这通常是一个坏主意。如果你知道 char[]
将永远不会改变,那么你可以侥幸逃脱它,但在一般情况下,你冒着问题,因为编译器会假设结果string
永远不会改变,它可能会生成不正确的代码。它甚至可能是段错误。所以,不要这样做,除非分析显示你真的需要避免副本的额外效率,否则你不能通过做一些事情来避免副本,比如首先使用string
(所以没有转换将是必要的),并且知道 string
将永远不会被更改。
一般来说,我不会过分担心复制字符串的效率。通常,您应该使用string
而不是char[]
,因此您可以复制它们(即复制它们的引用(例如str1 = str2;
),而不是复制它们的全部内容,如{{1并且dup
做)而不用担心它特别低效。该示例的问题是idup
返回stdin.byLine()
而不是char[]
(可能是为了避免在没有必要的情况下复制数据)。因此,string
会返回splitter()
,因此char[]
是word
而不是char[]
。现在,您可以执行string
或splitter(strip(line.idup))
而不是splitter(strip(line).idup)
密钥。这样,idup
将返回splitter()
而不是string
,但这可能基本上与char[]
idup
一样有效。无论如何,由于文本来自最初的文本,它是word
而不是char[]
,如果您打算将其用作{a} string
,它会迫使您idup
沿着该行的某个位置移动它关联数组中的键。但是,在一般情况下,最好只使用string
而不是char[]
。那么你不需要idup
任何东西。
修改强>
实际上,即使您发现从char[]
到string
的投射似乎既安全又必要,请考虑使用std.exception.assumeUnique()
(documentation)。当你需要并知道可以的时候,它本质上是将可变数组转换为不可变数组的首选方法。它通常会在你构造了一个你无法创建的数组的情况下完成,因为你必须分成几部分但没有其他引用,并且你不想创建它的深层副本。但是在你要问的例子中它没有用,因为你真的需要复制数组。
答案 1 :(得分:1)
不,它效率不高,因为它显然会复制字符串。如果您保证您创建的字符串将从不在内存中修改,请随意在其上显式使用强制转换cast(immutable)str
,而不是复制它。
(虽然,我注意到垃圾收集器运行良好,所以我建议你实际上不要尝试,除非你看到瓶颈,因为你可能决定稍后更改字符串。只需在你的代码中添加注释如果存在,可以帮助您找到瓶颈。)