说,我想添加两个数字的向量(在数学意义上)。我自然会做类似的事情:
T[] add(T)(T[] a, T[] b) {
assert(a.length == b.length);
T[] res = a.dup;
foreach (i; 0 .. a.length) {
res[i] = a[i] + b[i];
}
return res;
}
嗯,没关系,但我怀疑a
和b
复制了每一个电话,但这并不是很好。所以我宣布他们为ref
。
T[] add(T)(ref T[] a, ref T[] b) { ...
传递变量时效果很好,但对于测试,我使用数组实例:
assert(add([1, 2, 3], [4, 5, 6]) == [5, 7, 9]);
这会失败,因为它不能推断出数组的引用。我设法找到了解决方法:
T[] add(T)(T[] a, T[] b) {
return add(a, b);
}
这似乎解决了这个问题,但看起来相当愚蠢。对我的问题有什么更好的设计?
或者把它放在较小的问题中:我是否真的必须将参数声明为ref
以避免复制?由于我不修改a
和b
,编译器可以为我优化吗?我如何通过这种方式声明参数不可变(我尝试immutable
关键字,看起来我使用它错了)? res
会在变通方法中真正复制两次,还是通过移动返回?
答案 0 :(得分:5)
你真的应该阅读http://dlang.org/d-array-article.html。它详细介绍了D中的数组是如何工作的。但是对于简短的回答,所有在将参数传递给
时都会被复制T[] add(T)(T[] a, T[] b) {...}
是指针的基础指针和长度。没有复制任何元素。相反,阵列是“切片”。 add
内部生成的数组是add
个参数的切片,这意味着它们引用的是与原始数组相同的内存。改变切片的元素将改变原始数组的元素,因为它们是相同的元素。但是,改变数组本身(例如,为其分配另一个数组或附加到它)不会影响原始数据,如果附加到数组会导致其内存被重新分配以腾出空间(或者如果是将新数组分配给数组),然后该数组将不再引用与原始数据相同的元素。代码中唯一一个正在创建数组副本的地方是a.dup
。
使用ref
标记的数组的作用是使它们不被切片。相反,它们是原始数组而不是切片。因此,如果有任何内容附加到本地数组或者它被重新分配,那么这将影响原始数组(如果你没有使用ref
则不会有这种情况。)
此外,ref
只接受左值(意思是可以在赋值左侧的值),而数组文字是右值(意味着它们只能在右侧)赋值),所以你不能将它们传递给一个以ref
为参数的函数。如果您想同时接受这两项内容,则必须ref
不要接受,重载您的功能,以便拥有ref
和非ref
版本(这看起来就像您所说的那样)用作您的解决方案),或者使用auto ref
代替ref
,在这种情况下它会接受两者(但auto ref
仅适用于模板化函数,而且它基本上只是简单的自己复制函数,因为这是auto ref
的作用。一般来说,如果你不想改变原作,你不应该经过ref
。
使您的代码更快的一个建议:没有理由dup
a
然后再次循环它并将其与b
一起添加。如果这就是你想要做的,你也可以只使用+=
并做更像
T[] add(T)(T[] a, T[] b)
{
assert(a.length == b.length);
auto res = a.dup;
foreach (i; 0 .. a.length)
res[i] += b[i];
return res;
}
甚至更好,您可以使用array vector operations并完全跳过循环:
T[] add(T)(T[] a, T[] b)
{
assert(a.length == b.length);
auto res = a.dup;
res[] += b[];
return res;
}
但是,如果你想正确理解数组在D中是如何工作的,你真的应该阅读http://dlang.org/d-array-article.html。