Golang中指针切片和结构体切片的区别是什么?求通俗解释

Golang中指针切片和结构体切片的区别是什么?求通俗解释 我知道有大量的资料,但作为有C#背景的人,阅读所有这些内容只会让我更加困惑。对于一般用途,哪种方式更可取?每种方法各有什么优缺点?

将结构体指针切片作为参数传递或从函数返回:

func getAllAnimals() []*Animal  {
}

func printAllAnimals(animals []*Animal) {
}

对比

将结构体切片作为参数传递或从函数返回:

func getAllAnimals() []Animal  {
}

func printAllAnimals(animals []Animal) {
}

更多关于Golang中指针切片和结构体切片的区别是什么?求通俗解释的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复
  • 这类似于一个指针切片。切片持有一个地址(指针)列表,这些地址指向存储在内存中其他位置的实际结构体(盒子)。

更多关于Golang中指针切片和结构体切片的区别是什么?求通俗解释的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这完全解答了我所有的困惑。确实,我之前一直在为微小的优化而烦恼,但在大多数情况下,这很少成为性能瓶颈。不过,了解切片和指针的内部工作原理真的很有益。这非常有教育意义,非常感谢您花时间写出如此详细的回答!

Ester:

func getAllAnimals() []Animal  {
}

在 Go 语言中,你应该这样写:

func getAllAnimals() (Animal,error)  {
var(
x Animal
err error
)

return x,nil
}
data,err := getAllAnimals()
if err != nil {
}
type or paste code here

非常感谢您详细的解答。我之前也带着C#的思维模式在思考。为了100%确认,能否请您帮忙验证一下我的理解是否正确?

type Animal struct {
	Name   string
	Breed  string
	Weight float64
}

func Pointers(animals []*Animal) {
	for _, animal := range animals {
		fmt.Printf("%s %s %.2f", animal.Name, animal.Breed, animal.Weight)
	}
}

func Structs(animals []Animal) {
	for _, animal := range animals {
		fmt.Printf("%s %s %.2f", animal.Name, animal.Breed, animal.Weight)
	}
}

对于 Structs() 函数,在循环的每次迭代中,animal 实例都会被复制,但对于 Pointers() 函数则不会,对吗?因此,为了微小的性能优化,Pointers() 是更优的选择,这样理解正确吗?

在Go语言中,指针切片([]*T)和结构体切片([]T)的主要区别在于内存布局和语义,这直接影响了性能、修改行为和内存使用。

核心区别

  1. 内存布局

    • []Animal:切片中直接存储结构体的副本
    • []*Animal:切片中存储指向结构体的指针
  2. 修改行为

    • []Animal:修改切片元素需要索引赋值或使用指针
    • []*Animal:可直接修改指向的结构体

性能差异示例

type Animal struct {
    Name string
    Age  int
}

// 结构体切片 - 传递时复制整个切片头(但底层数组共享)
func processSlice(animals []Animal) {
    for i := range animals {
        animals[i].Age += 1 // 修改会影响原切片
    }
}

// 指针切片 - 传递指针切片头
func processPtrSlice(animals []*Animal) {
    for _, animal := range animals {
        animal.Age += 1 // 直接修改原结构体
    }
}

func main() {
    // 结构体切片
    slice := []Animal{{"Dog", 3}, {"Cat", 2}}
    processSlice(slice)
    fmt.Println(slice[0].Age) // 输出: 4
    
    // 指针切片
    ptrSlice := []*Animal{{"Dog", 3}, {"Cat", 2}}
    processPtrSlice(ptrSlice)
    fmt.Println(ptrSlice[0].Age) // 输出: 4
}

内存使用对比

type LargeStruct struct {
    Data [1024]byte
    ID   int
}

func memoryUsageDemo() {
    // 结构体切片 - 所有结构体在内存中连续存储
    structSlice := make([]LargeStruct, 1000)
    // 占用: 1000 * sizeof(LargeStruct) ≈ 1MB
    
    // 指针切片 - 存储指针+分散的结构体
    ptrSlice := make([]*LargeStruct, 1000)
    for i := range ptrSlice {
        ptrSlice[i] = &LargeStruct{ID: i}
    }
    // 切片本身: 1000 * 8字节(指针) = 8KB
    // 结构体: 分散在堆上,可能有额外开销
}

修改语义示例

func modifyElements() {
    // 结构体切片需要显式索引修改
    animals := []Animal{{"Dog", 3}}
    animals[0].Age = 4 // 直接修改元素
    // 或
    animal := &animals[0]
    animal.Age = 5
    
    // 指针切片可直接修改
    ptrAnimals := []*Animal{{"Dog", 3}}
    ptrAnimals[0].Age = 4 // 通过指针修改
}

选择建议

  • 使用[]T当:

    • 结构体很小(<64字节)
    • 需要内存局部性优化
    • 值语义更合适(如坐标、时间等不可变数据)
  • 使用[]*T当:

    • 结构体较大
    • 需要频繁修改或共享结构体
    • 需要nil元素表示缺失值
    • 结构体不可比较或包含互斥锁

函数参数传递

// 对于大型结构体,指针切片更高效
func filterAnimals(animals []*Animal) []*Animal {
    result := make([]*Animal, 0)
    for _, a := range animals {
        if a.Age > 5 {
            result = append(result, a) // 只复制指针
        }
    }
    return result
}

// 结构体切片会复制整个结构体
func filterAnimalsCopy(animals []Animal) []Animal {
    result := make([]Animal, 0)
    for _, a := range animals {
        if a.Age > 5 {
            result = append(result, a) // 复制结构体
        }
    }
    return result
}

从C#背景理解:[]Animal类似List<Animal>(值类型列表),[]*Animal类似List<Animal>(引用类型列表)。Go中没有引用类型的概念,但指针切片的行为最接近C#中的引用类型列表。

回到顶部