我在D2中看了一下动态数组,我发现它们很难理解。我似乎错误地解释了规范。 在更改数组时,处理动态数组的引用或切片似乎非常容易出错......或者我只是不了解基础知识?
引用同一个数组只共享实际项目:
auto a = [1];
auto b = a;
assert(&a != &b); // different instance; Doesn't share length
assert(a.ptr == b.ptr); // same items
assert(a == [1]);
assert(a == b);
当他们引用同一个数组时,更改另一个会改变另一个:
auto a = [1,2];
auto b = a;
a[1] = 20;
assert(a == [1,20]);
assert(a == b);
来自阵列上的规范
为了最大限度地提高效率,运行时总是尝试调整大小 阵列到位以避免额外复制。它总是会复制一份 如果新的大小更大并且数组未通过 新运营商或以前的运营商 调整操作。
因此改变长度并不一定会破坏参考:
auto a = [1];
auto b = a;
b.length = 2;
assert(b == [1,0]);
assert(a == [1]); // a unchanged even if it refers to the same instance
assert(a.ptr == b.ptr); // but still the same instance
// So updates to one works on the other
a[0] = 10;
assert(a == [10]);
assert(b == [10,0]);
来自阵列上的规范
连接总是创建其操作数的副本,即使其中一个操作数是0长度数组
auto a = [1];
auto b = a;
b ~= 2; // Should make a copy, right..?
assert(a == [1]);
assert(b == [1,2]);
assert(a != b);
assert(a4.ptr == b.ptr); // But it's still the same instance
a[0] = 10;
assert(b == [10,2]); // So changes to a changes b
但是当数组相互踩到一条时,值会被复制到一个新位置并且引用被打破:
auto a = [1];
auto b = a;
b ~= 2;
assert(a == [1]);
assert(b == [1,2]);
a.length = 2; // Copies values to new memory location to not overwrite b's changes
assert(a.ptr != b.ptr);
在进行更改之前更改两个数组的长度会得到与上面相同的结果(我希望如上所述):
auto a = [1];
auto b = a;
a.length = 2;
b.length = 2;
a[1] = 2;
assert(a == [1,2]);
assert(b == [1,0]);
assert(a.ptr != b.ptr);
同样在改变长度或cancatenating时(我希望如上所述):
auto a = [1];
auto b = a;
b.length = 2;
a ~= 2;
assert(a == [1,2]);
assert(b == [1,0]);
assert(a.ptr != b.ptr);
但是切片也会出现在图片中,突然间它变得更加复杂!切片可能是孤儿......
auto a = [1,2,3];
auto b = a;
auto slice = a[1..$]; // [2,3];
slice[0] = 20;
assert(a == [1,20,3]);
assert(a == b);
a.length = 4;
assert(a == [1,20,3,0]);
slice[0] = 200;
assert(b == [1,200,3]); // the reference to b is still valid.
assert(a == [1, 20, 3, 0]); // but the reference to a is now invalid..
b ~= 4;
// Now both references is invalid and the slice is orphan...
// What does the slice modify?
assert(a.ptr != b.ptr);
slice[0] = 2000;
assert(slice == [2000,3]);
assert(a == [1,20,3,0]);
assert(b == [1,200,3,4]);
所以...对同一动态数组进行多次引用是不好的做法?并传递切片等?或者我只是在这里,在D?
中错过了动态数组的整个点答案 0 :(得分:10)
总的来说,你似乎很了解事情,但你似乎误解了ptr
财产的目的。 not 表示两个数组是否引用同一个实例。它的作用是让你指向有效的C数组下面的指针。 D中的数组有length
作为其中的一部分,因此它更像是一个带有长度和指向C数组的指针的结构,而不像C数组。 ptr
允许您访问C数组并将其传递给C或C ++代码。您可能不应该在纯D代码中使用它。如果要测试两个数组变量是否引用同一个实例,那么可以使用is
运算符(或!is
来检查它们是不同的实例):
assert(a is b); //checks that they're the same instance
assert(a !is b); //checks that they're *not* the same instance
对于两个数组,ptr
相等的所有内容都表明它们的第一个元素位于内存中的相同位置。特别是,他们的length
可能会有所不同。但是,它确实意味着如果您在其中一个数组中更改它们,任何重叠元素都会在两个数组中被更改。
当更改数组的length
时,D会尝试避免重新分配,但它可能会决定重新分配,因此您不一定要依赖它是否会重新分配。例如,它会重新分配,如果不这样做会踩到另一个数组的内存(包括那些ptr
具有相同值的内存)。如果没有足够的内存来调整自身的位置,它也可以重新分配。基本上,它将重新分配,如果不这样做会踩到另一个数组的内存,它可能会或可能不会重新分配。因此,在设置length
时,依赖于数组是否会重新分配通常不是一个好主意。
我原本希望追加始终按照文档进行复制,但根据您的测试,它看起来就像length
一样(我不知道这是否意味着文档需要更新或是否是一个错误 - 我的猜测是文档需要更新)。在任何一种情况下,你肯定不能依赖对该数组的其他引用来在追加之后仍然引用相同的数组。
对于切片,它们的工作方式与预期的一样,并且在D中使用率很高 - 特别是在标准库Phobos中。切片是阵列的范围,范围是Phobos的核心概念。但是,就像许多其他范围一样,更改范围/切片所用的容器可能会使该范围/切片无效。这就是为什么当你使用可以在Phobos中调整容器大小的函数时,你需要使用前面带有稳定函数的函数(例如stableRemove()
或stableInsert()
),如果你不想冒险使范围无效你需要那个容器。
此外,切片是一个数组,就像它指向的数组一样。因此,自然地,更改其length
或附加到它将遵循与更改length
或附加到任何其他数组的规则相同的所有规则,因此可以重新分配,而不是更长的是切入另一个数组。
实际上,您只需要知道以任何方式更改数组的length
都可能导致重新分配,因此如果您希望引用继续引用它,则需要避免这样做数组实例。如果您确实需要确保它们 not 指向相同的引用,那么您需要使用dup
来获取该数组的新副本。如果你根本不弄乱数组的length
,那么数组引用(无论是切片还是对整个数组的引用)将继续愉快地引用相同的数组。
编辑:事实证明,文档需要更新。任何可以调整数组大小的东西都会尝试在适当的位置(如果它可能不会重新分配),但如果必须重新分配,以避免踩到另一个数组的内存或者它没有足够的空间重新分配到位。因此,通过设置其length
属性来调整数组大小并通过追加它来调整大小,不应该有任何区别。
ADDENDUM:任何使用D的人都应该在数组和切片上阅读this article。它很好地解释了它们,并且应该让你更好地了解数组如何在D中工作。
答案 1 :(得分:2)
我真的不想把它变成一个全面的答案,但我还不能对之前的答案发表评论。
我认为连接和追加是两种略有不同的操作。如果你对一个数组和一个元素使用〜,它就会附加;有两个数组,它是串联的。
您可以尝试这样做:
a = a ~ 2;
看看你是否得到了相同的结果。
此外,如果您想要定义行为,只需使用.dup(或.idup for immutables)属性。如果您有一组引用,这也非常有用;你可以修改主数组和.dup切片来处理,而不用担心竞争条件。
编辑:好吧,我觉得它有点不对,但无论如何它都存在。连接!=追加。//最大