我在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);
Run Code Online (Sandbox Code Playgroud)
当它们引用相同的数组时,更改另一个会改变另一个:
auto a = [1,2];
auto b = a;
a[1] = 20;
assert(a == [1,20]);
assert(a == b);
Run Code Online (Sandbox Code Playgroud)
从数组上的规范
为了最大限度地提高效率,运行时总是尝试调整阵列的大小以避免额外的复制.如果新的大小较大并且未通过new运算符或先前的resize操作分配数组,它将始终执行复制.
所以改变长度并不一定会打破参考:
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]);
Run Code Online (Sandbox Code Playgroud)
从数组上的规范
连接总是创建其操作数的副本,即使其中一个操作数是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
Run Code Online (Sandbox Code Playgroud)
但是当阵列互相踩踏时,值会被复制到一个新位置并且引用被破坏:
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);
Run Code Online (Sandbox Code Playgroud)
在进行更改之前更改两个数组的长度会得到与上面相同的结果(我希望如上所述):
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);
Run Code Online (Sandbox Code Playgroud)
改变长度或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);
Run Code Online (Sandbox Code Playgroud)
但是切片也会出现在画面中,突然间它变得更加复杂!切片可能是孤儿......
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]);
Run Code Online (Sandbox Code Playgroud)
那么......对同一动态数组进行多次引用是不好的做法?并传递切片等?或者我只是在这里,错过了D中动态数组的整个点?
Jon*_*vis 10
On the whole, you seem to understand things fairly well, but you appear to be misunderstanding the purpose of the ptr property. It does not indicate whether two arrays refer to the same instance. What it does is get you at the pointer to what is effectively the C array underneath. An array in D has its length as part of it, so it's more like it's a struct with a length and pointer to a C array than it is like a C array. ptr allows you to get at the C array and pass it to C or C++ code. You probably shouldn't be using it for anything in pure D code. If you want to test whether two array variables refer to the same instance, then you use the is operator (or !is to check that they're different instances):
assert(a is b); //checks that they're the same instance
assert(a !is b); //checks that they're *not* the same instance
Run Code Online (Sandbox Code Playgroud)
ptr对于两个数组而言,所有这些都表明它们的第一个元素在内存中位于相同的位置.特别是,他们length可能会有所不同.但是,它确实意味着如果在其中一个数组中更改它们,任何重叠元素都将在两个数组中被更改.
When changing the length of an array, D tries to avoid reallocating, but it could decide to reallocate, so you can't necessarily rely on whether it would reallocate or not. For instance, it's going to reallocate if not doing so will stomp on another array's memory (including those that have the same value for ptr). It could also reallocate if there isn't enough memory to resize itself in place. Basically, it will reallocate if not doing so will stomp on another array's memory, and it may or may not reallocate otherwise. So, it's generally not a good idea to rely on whether an array will reallocate or not when you set its length.
I would have expected appending to always copy per the docs, but per your tests, it does appear to act just like length does (I don't know whether that means that the docs need to be updated or whether it's a bug - my guess would be that the docs need to be updated). In either case, you certainly can't rely on other references to that array to still refer to the same array after appending.
至于切片,它们的工作方式与预期的一样,在D中也很常用 - 特别是在标准库Phobos中.切片是阵列的范围,范围是Phobos的核心概念.但是,就像许多其他范围一样,更改范围/切片所用的容器可能会使该范围/切片无效.这就是为什么当你使用可以在Phobos中调整容器大小的函数时,你需要使用前面带有stable(例如stableRemove()或者stableInsert())的函数,如果你不想冒这个容器的范围无效的风险.
此外,切片是一个数组,就像它指向的数组一样.因此,自然地,改变它length或附加它将遵循与改变length或附加到任何其他数组的规则相同的所有规则,因此它可以被重新分配并且不再是另一个数组的切片.
实际上,您只需要知道length以任何方式更改数组都可能导致重新分配,因此如果您希望引用继续引用相同的数组实例,则需要避免这样做.如果你绝对需要确保它们没有指向相同的引用,那么你需要使用dup来获取数组的新副本.如果你根本不搞乱length数组,那么数组引用(无论是切片还是对整个数组的引用)将继续愉快地引用相同的数组.
编辑:事实证明,文档需要更新.任何可以调整数组大小的东西都会尝试在适当的位置(如果它可能不会重新分配),但如果必须重新分配,以避免踩到另一个数组的内存或者它没有足够的空间重新分配到位.因此,通过设置其length属性来调整数组大小并通过追加它来调整大小,不应该有任何区别.
附录:任何使用D的人都应该阅读有关数组和切片的文章.它很好地解释了它们,并且应该让你更好地了解数组如何在D中工作.