Go语言中slice成员地址的问题


自己写了下《GO语言编程》第三章音乐库的例子,发现一个问题,MusicManager的Remove方法是删除某首歌,并且返回删除的那首歌,但是在删除前后removedMusic指针指向的元素确发生了改变,感觉removedMusic是&m.musics[index]的一个别名,slice的改变会影响到它,google了很多资料,但是好像也没有完全说清楚,希望大牛解释一下缘由

package main

import "fmt"

type MusicEntry struct {
Id string
Name string
}

type MusicManager struct {
musics []MusicEntry
}

func (m *MusicManager) Add(music MusicEntry) {
m.musics = append(m.musics, music)
}

func (m *MusicManager) Len() int {
return len(m.musics)
}

func (m *MusicManager) Remove(index int) *MusicEntry {
if index < 0 || index >= m.Len() {
return nil
}

removedMusic := &m.musics[index]
fmt.Println("removedMusic.Id:", removedMusic.Id)

if index == 0 {
m.musics = m.musics[1:]
} else if index == m.Len()-1 {
m.musics = m.musics[0 : index-1]
} else {
m.musics = append(m.musics[0:index], m.musics[index+1:]...)
}

''fmt.Println("removedMusic.Id:", removedMusic.Id)''

return removedMusic
}

func main() {
fmt.Println("Hello, player")

mm := &MusicManager{make([]MusicEntry, 0, 100)}
m0 := MusicEntry{"1", "My Heart will go on"}
m1 := MusicEntry{"2", "In your arms"}
m2 := MusicEntry{"3", "Someone like you"}

mm.Add(m0)
mm.Add(m1)
mm.Add(m2)

fmt.Println("The lib has", mm.Len(), "songs")

m := mm.Remove(1)

if m.Id != m1.Id {
fmt.Println("Mismatched")
}
}

执行结果:

Hello, player
The lib has 3 songs
removedMusic.Id: 2
removedMusic.Id: 3
Mismatched

go

大宇宙天使歌姬 11 years, 9 months ago

谢谢邀请!

你提到的这个问题是Remove函数包含的一个缺陷导致的。主要原因是这一个赋值语句: removedMusic := &m.musics[index] 只取得了该元素的地址,而在之后的数组重整操作中该元素地址会被后跟的一个元素前移覆盖,从而导致打印的是后一个元素的Id。

修正方法很简单,就是将上面提到的语句修改为 removedMusic := m.musics[index] 。当然,相应的最后一个返回语句需要做一个取地址操作。

完整代码如下:

func (m *MusicManager) Remove(index int) *MusicEntry {
    if index < 0 || index >= m.Len() {
        return nil
    }

    removedMusic := m.musics[index]
    fmt.Println("removedMusic.Id:", removedMusic.Id)

    if index == 0 {
        m.musics = m.musics[1:]
    } else if index == m.Len()-1 {
        m.musics = m.musics[0 : index-1]
    } else {
        m.musics = append(m.musics[0:index], m.musics[index+1:]...)
    }

    fmt.Println("removedMusic.Id:", removedMusic.Id)

    return &removedMusic
}
XxLoli answered 11 years, 9 months ago

Your Answer