2

说,我想添加两个数字的向量(在数学意义上)。我自然会做类似的事情:

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;
}

好吧,没关系,但我怀疑ab复制每个电话,这不是很好。所以我宣布他们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以避免复制吗?由于我不修改aand b,编译器可以为我优化吗?顺便说一句,我如何声明参数不可变(我尝试了immutable关键字,看起来我用错了)?在变通方法中真的会res被复制两次,还是通过移动获得回报?

4

1 回答 1

5

你真的应该阅读http://dlang.org/d-array-article.html。它详细介绍了 D 中的数组如何工作。但是对于一个简短的答案,在将参数传递给

T[] add(T)(T[] a, T[] b) {...}

是指针的底层指针和长度。没有任何元素被复制。相反,阵列是“切片的”。内部的结果数组是 's 参数add的切片add,这意味着它们引用与原始数组相同的内存。改变切片的元素会改变原始数组的元素,因为它们是相同的元素。然而,改变数组本身(例如分配另一个数组或附加到它)不会影响原始数组,并且如果附加到数组会导致其内存被重新分配以腾出空间(或者如果一个新数组被分配给数组),那么该数组将不再引用与原始数组相同的元素。在您的代码中制作数组副本的唯一位置是a.dup.

用标记数组的ref作用是使它们不被切片。相反,它们原始数组而不是切片。因此,如果将任何内容附加到本地数组或重新分配,那么这将影响原始数组(而如果你没有使用它就不会ref)。

此外,ref只接受左值(意思是可以放在赋值左侧的值),而数组文字是右值(意味着它们只能放在赋值的右侧),所以你不能将它们传递给以 为参数的函数ref。如果你想同时接受,你要么不接受 by ref,重载你的函数,以便你有一个refref版本(这似乎是你用作解决方案的版本),或者使用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;
}

或者更好的是,您可以使用数组向量操作并完全跳过循环:

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 。

于 2013-10-06T09:09:51.023 回答