<p><strong>EDIT: Forgot to add a good title.</strong></p>
<p>Hey folks, I need some help with a slice. There’s something fundamental about how slices works underneath that I don’t get and I can’t deduce it from the “Go Slices: usage and internals” page. And sorry for not linking to playground, but the link feature seems to be removed (hopefully temporarly).</p>
<pre><code>package main
import "fmt"
import "errors"
type Item struct {
Title string
}
type Items []Item
func (i Items) RemoveAt(index int) (Item, error) {
if len(i) < index {
return Item{}, errors.New("Index out of range")
}
item := i[index]
tmp := append(i[:index], i[index+1:]...)
fmt.Printf("Tmp length: %d\n", len(tmp))
i = tmp
return item, nil
}
func main() {
fmt.Println("Hello, playground")
items := Items{Item{"index 0"}, Item{"index 1"}, Item{"index 2"}, Item{"index 3"}}
fmt.Printf("Items length: %d\n", len(items))
item, _ := items.RemoveAt(1)
fmt.Printf("Item: %s\n", item.Title)
fmt.Printf("Items length: %d\n", len(items))
fmt.Printf("Items: %v\n", items)
}
// OUTPUT:
// Hello, playground
// Items length: 4
// Tmp length: 3
// Item: index 1
// Items length: 4
// Items: [{index 0} {index 2} {index 3} {index 3}]
</code></pre>
<hr/>**评论:**<br/><br/>TheMerovius: <pre><p>So, here's a well too detailed explanation that contains hopefully everything you should now :)</p>
<p>The line <code>i = tmp</code> suggests what you misunderstand, as it doesn't actually do anything. Slices are value types, even though they reference an underlying array. So, assigning something to <code>i</code> will only change that copy. However, when you change the underlying array, this change is also visible from other slices.</p>
<p>You need to view slices essentially as a <code>type SliceHeader struct{ Len int, Cap int, Values *T}</code> (that is hidden from you by the language). You can extract <code>Len</code> and <code>Cap</code> via the <code>len</code> and <code>cap</code> functions. An index expression <code>X[n]</code> will first do a bounds-check (<code>n < X.Len</code>) and then resolve to <code>*(X.Values + n)</code>, in C-speak. Fundamentally, you can't change <code>X.Len</code> or <code>X.Cap</code> or <code>X.Values</code> (but you can change the array that <code>X.Values</code> <em>points to</em>). You can, however, derive new slices from this, which is what subslicing does: <code>i[a:b]</code> will first check, if the underlying array has enough room (<code>a <= b < X.Cap</code>) and then create a new slice <code>X2 SliceHeader{ Len: b-a, Cap: X.Cap - a, Values: X.Values + a}</code>. Fundamentally, this operation is the only way you can change a slice, that is by creating a new one and assigning it to the old variable (however, strictly speaking, you don't change the slice. You create a new slice and overwrite the old one).</p>
<p>When you call <code>RemoveAt</code>, this makes a copy of <code>items</code> (because it has a value receiver, instead of a pointer receiver) and assigning to <code>i</code> is only going to change this copy. Thus, whatever you do, <code>items</code> will never change, thus the observation that <code>len(items)</code> stays the same. However, the underlying array (pointed to by <code>items.Values</code>) <em>can</em> change. When you <code>append</code>, now, it will do the following:</p>
<ul>
<li>Check, if the passed slice (<code>i[:index]</code>) has room in it for the required elements, i.e. if <code>cap(i[:index]) - len(i[:index]) <= len(i[index+1:])</code>.</li>
<li>If not, allocate a new Array of sufficient size, copy <code>i[:index]</code> to the beginning of this array and using a slice on this new array instead of <code>i[:index]</code> in the next steps</li>
<li>Then (or if new allocation wasn't necessary) copy the elements of <code>i[index+1:]</code> after <code>i[index]</code>.</li>
<li>Return a new slice, with the <code>Values</code> pointing either to the newly allocated array and the <code>Len</code> and <code>Cap</code> set appropriately.</li>
</ul>
<p>As there is enough room in the underlying array of <code>i</code>, it will do the copy in-place without reallocation and as <code>i</code> and <code>items</code> use the same underlying array, this will also change the contents that <code>items</code> is pointing to, which is why append also affects the "contents" of <code>items</code> (other uses of <code>append</code>, e.g. if you would also like to implement an in-place method for appending stuff, might <em>not</em> make this visible, because they do a reallocation).</p>
<p>It is important to treat a slice and the underlying array it points to as different entities and to know that the slice itself is a value type (even though it contains a pointer to the underlying array) that is it is copied, if you don't use pointers to slices.</p>
<p>To round this off, here is a <a href="http://play.golang.org/p/prnkjt6sYp" rel="nofollow">working implementation</a> of your stuff. Using a pointer to a slice as a receiver means, we can make in-place changes (as with all methods: If you want to change the value, use a pointer :) ). However, I don't think you should do this at all. As you noticed, this is a somewhat subtle behavior (read: confusing and hard to read) and it speaks to the author (you) treating go slices like python lists, which you shouldn't. Also, you don't save a lot of code. You can inline this in one line with something like</p>
<pre><code>item, items := items[index], append(items[:index], items[index+1:])
</code></pre>
<p>which is idiomatic and (in my opinion) more readable :)</p></pre>lannor: <pre><p>Thanks for a great explanation! Now I get it, at least a lot better :)</p>
<p>I got a follow up about pointers (coming from PHP so this is kinda new to me). In your working implementation you use the expression <code>*i</code> (<code>len(*i)</code> and <code>(*i)[index]</code>). Does the <code>*</code> kinda mean go-to-the-value-of-this-pointer, and then what ever you want to do? </p>
<p>For example: <code>(*i)[index]</code> › Go to the value that resides where the pointer <code>i</code> points, and then grab the element on index <code>index</code>, correct? </p></pre>TheMerovius: <pre><p>Yes, that's correct :) This is called a "pointer dereference" most of the time and you can look up it's definition <a href="https://golang.org/ref/spec#Address_operators" rel="nofollow">here in the spec</a>.</p></pre>Mteigers: <pre><p>What's confusing you? That looks right. Slices are pointers under the hood. Modifications in the function reflect outside. </p></pre>lannor: <pre><p>The confusing this is that the item does get removed, but and item with Title "index 3" gets added at the end. Isn't that weird? Obviously the code I wrote does exactly that, but I dont want it to do that. I want the length of <code>items</code> to be one less after the .RemoveAt function. </p>
<p>About the "slices are pointers under the hood", that maybe the part I'm confused about. How would I go about removing an item from a slice then? </p></pre>GeneticGenesis: <pre><p>This can absolutely be confusing when you first come to it.</p>
<p>There's some good examples here <a href="https://github.com/golang/go/wiki/SliceTricks" rel="nofollow">https://github.com/golang/go/wiki/SliceTricks</a> for achieving what you need.</p></pre>champioj: <pre><p>A slice is a composition of a pointer, a length and a cap. All of that make the slice header.
As go is pass by value, when you pass a slice to a function the slice header is copied.
Remark that the pointer to the underlying array remain unmodified.</p>
<p>So, when you pass the slice header to the function, a copy is made.
Then you change the underlying array, changing it for both and modify the length of the slice header copy in the function.
But as you return in the main program, the original slice header remains unchanged, thus its length (and cap) are still the same.</p></pre>lannor: <pre><p>How can I then modify the data for the "internal structure" with a function? Can I get hold of the actual data somewhere? </p>
<p>Or do I have to return the modified <code>i</code> too? So that my function call returns <code>Items, Item, error</code> ? That feels a little weird. </p></pre>9nut: <pre><p>if you intend to change "i" in RemoveAt method, then you need to get a pointer to it; <a href="http://play.golang.org/p/nTxaeqlYo3" rel="nofollow">http://play.golang.org/p/nTxaeqlYo3</a></p></pre>lannor: <pre><p>That is what I was looking for! Thanks!</p>
<p>I didn't figure out the <code>old := *i</code> part, and that made it completely different. When I tried it I got all the errors of slice pointer not being indexable, and cannot read length from a pointer etc. But that row fixed it! </p>
<p>Thanks man! </p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传