作者:wuyunhua | 2023-09-16
这篇文章所讲述的值/引用类型,是golang开发人员必须清晰掌握,但又容易被忽略的知识。本文不讲原理,只讲实践。
chan
、map
、slice
、interface
、pointer
是引用类型,其他都是值类型。
package main
import (
"fmt"
)
func main() {
// int、float、bool、string、array、struct都是值类型
var a int = 1
var b int = a
b = 2
fmt.Println(a, b) // 1 2
// slice是引用类型
var c []int = []int{1, 2, 3}
var d []int = c
d[0] = 4
fmt.Println(c, d) // [4 2 3] [4 2 3]
// map是引用类型
var e map[string]int = map[string]int{"a": 1, "b": 2}
var f map[string]int = e
f["a"] = 3
fmt.Println(e, f) // map[a:3 b:2] map[a:3 b:2]
// chan是引用类型
var g chan int = make(chan int, 1)
var h chan int = g
h <- 1
fmt.Println(<-g, <-h) // 1 1
// interface是引用类型
var i interface{} = 1
var j interface{} = i
j = 2
fmt.Println(i, j) // 1 2
// 指针是引用类型
var k *int = &a
var l *int = k
*l = 3
fmt.Println(a, *k, *l) // 3 3 3
}
函数传递参数是值传递,这意味着当你将一个变量传递给函数时,实际上是将该变量的副本传递给了函数,而不是原始变量本身。但是如果参数是引用类型,那么传递的是引用的副本,也就是说,函数内部修改了引用类型的值,那么函数外部的引用类型的值也会被修改。
package main
import "fmt"
func main() {
value := 5
fmt.Println(value) // 5
// 传递的是值的副本
func(value int) {
value = 10
}(value)
fmt.Println(value) // 5
// 传递的是引用的副本
func(value *int) {
*value = 10
}(&value)
fmt.Println(value) // 10
}
截止当前版本1.21,在 for range 循环中,迭代变量的内存地址不会发生变化,它会在每次迭代中被重用,而不是为每次迭代创建一个新的变量
package main
import "fmt"
func main() {
slice := []int{1, 2, 3}
// v的内存地址不会发生变化
for _, v := range slice {
fmt.Printf("%p\n", &v) // 0xc0000b4008 0xc0000b4008 0xc0000b4008
}
for _, v := range slice {
v := v
fmt.Printf("%p\n", &v) // 0xc0000aa018 0xc0000aa030 0xc0000aa038
}
}
结构体是使用频率较高的类型,需要强调结构体是值类型,但是结构体字段可以是引用类型。
package main
import "fmt"
type User struct {
Name string
Age int
// 字段可以是引用类型
Friends []string
}
func main() {
var user User
// 结构体是值类型
fmt.Println(user == nil) // invalid operation: user == nil (mismatched types User and nil)
fmt.Println(user == User{}) // invalid operation: user == User{} (struct containing []string cannot be compared)
fmt.Println(user.Name) // ""
// 结构体字段可以是引用类型
user.Friends = append(user.Friends, "Tom")
var target = user
target.Friends[0] = "Jerry"
fmt.Println(user.Friends) // ["Jerry"]
}
slice的引用有限制,slice的cap发生变化时,引用会失效。
package main
import "fmt"
func main() {
s1 := make([]int, 0, 10)
s1 = []int{1, 2, 3} // <= cap现在是3
s2 := s1
s2 = append(s2, 4) // <= cap现在是6
s2[0] = 100
fmt.Println(s1, s2) // [1 2 3] [100 2 3 4]
}
for range循环中使用协程,很容易出错,因为迭代变量的内存地址不会发生变化,它会在每次迭代中被重用,而不是为每次迭代创建一个新的变量。
package main
import (
"log"
"sync"
)
type User struct {
Name string
}
func main() {
users := []User{
{"a1"},
{"a2"},
}
wg := sync.WaitGroup{}
for _, user := range users {
wg.Add(1)
go func() {
defer wg.Done()
log.Printf("%p\n", &user) // 0xc000118230 0xc000118230
}()
}
wg.Wait()
}
sync.Map中的引用类型,也是会被影响的
package main
import (
"fmt"
"sync"
)
type User struct {
Name string
}
func main() {
var s sync.Map
user := &User{Name: "a1"}
s.Store("a1", user)
// 存储之后再修改
user.Name = "a2"
name, _ := s.Load("a1")
fmt.Println(name.(*User).Name) // a2
}
了解一个数据类型是值类型还是引用类型非常重要,因为它直接影响了如何操作和处理数据,以及在内存中如何存储和传递数据。
了解数据类型是值类型还是引用类型对于编写高效、可维护和可预测的代码非常重要。不同的数据类型有不同的行为和特点,因此正确地理解和使用它们有助于避免潜在的问题和错误。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.wuyunhua.cn/golang-value-and-reference-types