理解D语言中的引用

时间:2013-10-06 07:08:08

标签: return d ref

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

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以避免复制?由于我不修改ab,编译器可以为我优化吗?我如何通过这种方式声明参数不可变(我尝试immutable关键字,看起来我使用它错了)? res会在变通方法中真正复制两次,还是通过移动返回?

1 个答案:

答案 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