在Golang中,函数参数传递默认采用值传递的方式,也就是说传递参数时会拷贝一份数据给函数内部使用,函数内对参数的修改不会影响原数据。如果想要在函数内部修改外部变量的值,或者避免大对象拷贝带来的性能损耗,就需要把指针传递给函数。指针本质上是一个变量,存储的是另一个变量的内存地址,传递指针相当于把目标变量的地址传给函数,函数通过这个地址可以直接操作原变量。
值传递和指针传递的核心区别
值传递会把参数的副本传入函数,函数内操作的是副本数据,原数据不会发生变化。而指针传递传入的是变量的内存地址,函数内通过解引用操作可以直接修改原变量的内容,两者的差异可以通过下面的示例直观看到。
package main
import "fmt"
// 值传递函数,修改的是参数副本
func changeByValue(num int) {
num = 100
fmt.Println("值传递函数内num的值:", num)
}
// 指针传递函数,修改的是原变量
func changeByPointer(num *int) {
*num = 100
fmt.Println("指针传递函数内num的值:", *num)
}
func main() {
a := 10
fmt.Println("调用值传递函数前a的值:", a)
changeByValue(a)
fmt.Println("调用值传递函数后a的值:", a)
b := 10
fmt.Println("调用指针传递函数前b的值:", b)
changeByPointer(&b)
fmt.Println("调用指针传递函数后b的值:", b)
}
执行上述代码后,值传递场景中a的值始终是10,而指针传递场景中b的值被修改为100,这就是两种传递方式最明显的区别。
传递指针给函数的具体步骤
要把指针传递给函数,需要完成两个步骤:一是获取变量的内存地址,二是函数参数声明为对应类型的指针类型。
第一步:获取变量的内存地址
在Golang中,使用&运算符可以获取变量的内存地址,比如变量name的地址可以通过&name获取。
第二步:函数参数声明为指针类型
函数定义时,参数类型需要写成*类型的形式,比如要接收int类型的指针,参数就声明为num *int。
下面是一个传递结构体指针的示例,结构体属于比较大的数据类型,传递指针可以避免整个结构体的拷贝:
package main
import "fmt"
// 定义结构体
type User struct {
Name string
Age int
}
// 指针传递函数,修改结构体字段
func updateUser(u *User) {
u.Name = "张三"
u.Age = 20
}
func main() {
user := User{Name: "李四", Age: 18}
fmt.Println("修改前user:", user)
// 获取user的地址传递给函数
updateUser(&user)
fmt.Println("修改后user:", user)
}
指针传递的注意事项
- 传递指针时要确保指针不为空,避免出现空指针解引用的panic问题,如果不确定指针是否有效,可以先做非空判断。
- 不要为了传递指针而传递指针,对于小类型比如int、bool等,值传递的开销很小,使用值传递反而更清晰,不需要额外使用指针。
- 如果函数只需要读取数据不需要修改,传递指针时可以使用const相关的方式(Golang本身没有const参数,但是可以通过逻辑约定避免修改),防止误修改原数据。
下面是一个空指针判断的示例:
package main
import "fmt"
func printName(name *string) {
// 先判断指针是否为空
if name != nil {
fmt.Println("名字:", *name)
} else {
fmt.Println("指针为空,无法打印名字")
}
}
func main() {
var name1 *string
printName(name1)
name2 := "王五"
printName(&name2)
}