go切片和指针切片
转载请注明出处:
在Go语言中,切片(Slice)和指针的切片(即切片中每个元素都是指向某种数据类型的指针)是两个不同的概念,它们各自具有特定的用途和优势。
切片(Slice)
切片的一个重要特性是它提供了对底层数组的动态视图。这意味着你可以通过切片来访问、修改和操作数组的一部分或全部元素,而无需复制整个数组。此外,切片还可以方便地进行扩容和缩容操作,以满足不同场景下的需求。
package main
import "fmt"
func main() {
// 定义一个数组
array := [5]int{1, 2, 3, 4, 5}
// 创建一个切片,引用数组的前三个元素
slice := array[:3]
// 打印切片元素
fmt.Println(slice) // 输出: [1 2 3]
// 修改切片元素,也会修改底层数组对应位置的元素
slice[1] = 100
fmt.Println(array) // 输出: [1 100 3 4 5]
}
指针的切片
使用指针的切片可以节省内存空间,因为只需要存储指针而不是实际的数据。同时,通过指针可以方便地修改原始数据。然而,这也带来了额外的复杂性和风险,因为需要小心处理指针的解引用和内存管理。
示例:
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
// 创建几个Person实例的指针
person1 := &Person{Name: "Alice", Age: 30}
person2 := &Person{Name: "Bob", Age: 25}
// 创建一个指针的切片,存储这些指针
people := []*Person{person1, person2}
// 通过指针修改Person实例的属性
people[0].Age = 31
// 打印修改后的Person实例
fmt.Println(people[0].Name, people[0].Age) // 输出: Alice 31
}
结构体切片与结构体指针切片的区别
-
内存占用:
-
结构体切片:每个元素都是结构体的一个完整副本,因此内存占用较大。
-
结构体指针切片:每个元素只是一个指向结构体的指针,内存占用较小。但是,这并不意味着整体内存占用会小,因为还需要考虑实际结构体对象所占用的内存。
-
-
修改元素:
-
结构体切片:修改切片中的一个元素,将直接修改该元素的值,不会影响其他切片或原始结构体对象。
-
结构体指针切片:修改切片中的一个指针指向的元素,将影响所有指向该元素的指针。如果多个切片或变量指向同一个结构体对象,修改该对象的内容将影响所有引用。
-
-
初始化与赋值:
-
结构体切片:可以直接初始化并赋值。
-
结构体指针切片:需要先初始化结构体对象,然后将对象的地址赋值给切片。
-
-
-
对于结构体指针切片,nil切片和空切片(长度为0的切片)是不同的。nil切片没有分配底层数组,而空切片分配了底层数组但长度为0。
-
对于结构体切片,通常不会讨论nil切片,因为切片总是与底层数组相关联。
-
能否直接遍历结构体切片并赋值给结构体指针切片
不能直接遍历结构体切片并赋值给结构体指针切片。因为结构体切片中的元素是结构体的值,而结构体指针切片中的元素是指向结构体的指针。需要遍历结构体切片,并分别为每个元素创建指针,然后将这些指针添加到结构体指针切片中。
type Person struct {
Name string
Age int
}
// 创建并初始化一个Person结构体切片
peopleSlice := []Person{
{"Alice", 30},
{"Bob", 25},
}
// 修改切片中的一个元素
peopleSlice[0].Age = 31
// 遍历并打印切片中的元素
for _, person := range peopleSlice {
fmt.Println(person.Name, person.Age)
}
// 创建并初始化一些Person结构体对象
alice := Person{"Alice", 30}
bob := Person{"Bob", 25}
// 创建一个Person指针切片,并将结构体对象的地址添加到切片中
peoplePtrSlice := []*Person{&alice, &bob}
// 修改切片中的一个指针指向的元素
peoplePtrSlice[0].Age = 31 // 这将改变alice的年龄,因为peoplePtrSlice[0]指向alice
// 遍历并打印切片中的元素(通过解引用指针)
for _, personPtr := range peoplePtrSlice {
fmt.Println(personPtr.Name, personPtr.Age)
}
// 如果想要遍历结构体切片并赋值给结构体指针切片,你需要这样做:
peopleSlice := []Person{
{"Charlie", 28},
{"David", 35},
}
// 初始化一个空的Person指针切片,长度与peopleSlice相同
peoplePtrSlice = make([]*Person, len(peopleSlice))
// 遍历peopleSlice,为peoplePtrSlice分配新的指针
for i, person := range peopleSlice {
// 创建person的一个副本,并获取其地址,然后赋值给peoplePtrSlice
peoplePtrSlice[i] = &Person{Name: person.Name, Age: person.Age}
}
// 现在peoplePtrSlice包含了指向peopleSlice中元素副本的指针
能否直接遍历结构体指针切片并赋值给结构体切片
不能直接遍历结构体指针切片并赋值给结构体切片。结构体指针切片中的元素是指向结构体的指针,而结构体切片中的元素是结构体的值。因此,如果你尝试直接将指针切片中的指针赋值给结构体切片,你会得到的是指针的值(即内存地址),而不是结构体对象本身的值。
要遍历结构体指针切片并将指针指向的结构体对象赋值给结构体切片,你需要对每个指针进行解引用,获取其指向的结构体对象,然后将该对象赋值给结构体切片中的相应位置。
下面是一个详细的示例说明和分析:
package main
import (
"fmt"
)
// 定义Person结构体
type Person struct {
Name string
Age int
}
func main() {
// 初始化一些Person结构体对象,并获取它们的地址
alice := &Person{"Alice", 30}
bob := &Person{"Bob", 25}
// 创建一个Person指针切片
peoplePtrSlice := []*Person{alice, bob}
// 创建一个与peoplePtrSlice长度相同的Person结构体切片
peopleSlice := make([]Person, len(peoplePtrSlice))
// 遍历peoplePtrSlice,解引用指针并将结构体对象赋值给peopleSlice
for i, personPtr := range peoplePtrSlice {
peopleSlice[i] = *personPtr // 解引用指针,获取结构体对象
}
// 现在peopleSlice包含了peoplePtrSlice中指针指向的结构体对象的副本
fmt.Println(peopleSlice) // 输出: [{Alice 30} {Bob 25}]
// 修改peopleSlice中的元素,不会影响peoplePtrSlice或原始结构体对象
peopleSlice[0].Age = 31
fmt.Println(peopleSlice) // 输出: [{Alice 31} {Bob 25}]
fmt.Println(peoplePtrSlice) // 输出: [&{Alice 30} &{Bob 25}],peoplePtrSlice中的元素未改变
}
在这个示例中:
- 我们首先创建了两个
Person
结构体对象的指针alice
和bob
。 - 然后我们创建了一个包含这两个指针的
peoplePtrSlice
切片。 - 接着我们创建了一个与
peoplePtrSlice
长度相同的空Person
结构体切片peopleSlice
。 - 在遍历
peoplePtrSlice
时,我们使用*personPtr
来解引用指针,并将得到的结构体对象赋值给peopleSlice
中的对应位置。 - 最后,我们验证了修改
peopleSlice
中的元素不会影响peoplePtrSlice
或原始的结构体对象。
需要注意的是,这样做会创建结构体对象的副本。如果你只是想引用原始的对象而不是它们的副本,你应该直接使用指针切片,而不是创建结构体切片。如果你确实需要结构体切片,并且想保持对原始对象的引用,那么你需要重新考虑你的数据结构设计,因为结构体切片本身不保存对原始对象的引用。