Go 语言中,一个变量可以正常赋值。
通过 & 获取内存地址(指针),指针也可以赋值。
这使得我这种从 Java 语言跳出的小伙伴看的一头雾水,先看概念。
编写一个简单的实例来感受下变量和指针的特性。
func main() {
A := 3
fmt.Printf("A值:%v
", A)
fmt.Printf("A内存地址:%p
", &A)
// 变量取址(指针)
P := &A
fmt.Printf("P值(实际是A的地址):%v
", P)
fmt.Printf("P内存地址:%p
", &P)
// 指针取值
fmt.Printf("P实际值:%v
", *P)
}
输出结果如下:
A值:3
A内存地址:0xc00000a0c8
P值(实际是A的地址):0xc00000a0c8
P内存地址:0xc000006030
P实际值:3
直接上代码。
func main() {
A := 3
fmt.Printf("A值:%v
", A)
P := &A
fmt.Printf("P值(实际是A的地址):%v
", P)
fmt.Printf("P实际值:%v
", *P)
// 变量赋值
A = 4
fmt.Printf("A值:%v
", A)
// 指针赋值
*P = 5
fmt.Printf("P值(实际是A的地址):%v
", P)
fmt.Printf("P实际值:%v
", *P)
fmt.Printf("A值:%v
", A)
}
输出结果如下:
A值:3
P值(实际是A的地址):0xc000062090
P实际值:3
A值:4
P值(实际是A的地址):0xc000062090
P实际值:5
A值:5
通过变量赋值和指针赋值,可得大致得到如下流程图:
从上文中,我们可以看到变量的值修改可以使用变量赋值或者指针赋值。
表面上看起来,最终实现的效果都一致,那指针赋值的意义何在呢?感觉似乎绕了一圈实现了同一个结果?
这里就涉及到函数入参问题,在 Go 中函数的入参均是值传递,也就是说将入参对象(变量)的值 Copy 一份出来传到函数中。
既然是值传递就必然带来一个问题,无法修改入参变量原本的值,可以通过下方 DEMO 验证一下。
func main() {
A := 3
fmt.Printf("A值:%v
", A)
// A 值修改方法
changeA(A)
fmt.Printf("修改方法后的A值:%v
", A)
}
func changeA(A int) {
A = 4
fmt.Printf("修改方法,A值:%v
", A)
}
输出结果如下:
A值:3
修改方法,A值:4
修改方法后的A值:3
为了进一步验证为什么修改没有生效,我们输出各自的内存地址:
A值:3
A内存地址:0xc00000a0c8
修改方法,A内存地址:0xc00000a110
修改方法,A值:4
修改方法后的A内存地址:0xc00000a0c8
修改方法后的A值:3
我们发现,在 changeA() 函数中所修改的 A 变量的内存地址和 main() 函数的内存地址并不一样,因此可以确定函数中的 A 变量是一个全新的 Copy 出来的变量。
如何解决?
常见的形式就是函数直接返回修改后的变量。
func main() {
A := 3
fmt.Printf("A值:%v
", A)
// A 值修改方法
A = changeA(A)
fmt.Printf("修改方法后的A值:%v
", A)
}
func changeA(A int) int {
A = 4
fmt.Printf("修改方法,A值:%v
", A)
return A
}
输出结果如下:
A值:3
修改方法,A值:4
修改方法后的A值:4
如果我们不想函数有返回值,又希望修改入参变量的值,指针和指针赋值就可以达成这个目录。
func main() {
A := 3
fmt.Printf("A值:%v
", A)
// A 值修改方法
changeA(&A)
fmt.Printf("修改方法后的A值:%v
", A)
}
func changeA(A *int) {
*A = 4
fmt.Printf("修改方法,A值:%v
", A)
fmt.Printf("修改方法,A实际值:%v
", *A)
}
输出结果如下:
A值:3
修改方法,A值:0xc00000a0c8
修改方法,A实际值:4
修改方法后的A值:4
从上文我们大致感受到,函数在接受参数时均使用值传递,一定会执行一次 Copy 的过程。
如果直接 Copy 的是一个对象(变量),对 Copy 后的对象(变量)修改永远无法影响初始对象(变量),除非返回结果并对初始对象(变量)重新赋值。
如果 Copy 的是一个指针,本质上在对 Copy 后的指针赋值的时候,最终还是指向了初始对象(变量),因此能够实现初始对象(变量)的修改。
既然如此,在什么场景下选择什么入参形式就一目了然了。
当前还没有观点发布,欢迎您留下足迹!
GoLand 在保存代码时,可以自动调用 gofmt 和 goimports 实现自动格式化代码,在新版本中可以通过 File Watchers 插件来完成这些配置,配置位置位于File
Go1.11版本开始支持包依赖管理工具,新增了GOPROXY环境变量,用于配置依赖包下载代理,通过代理配置可以实现翻墙下载一些所需的依赖包,可以说相当实用
对于绝大多数新的 Go 项目而言,因为使用 go modules 管理包依赖从而无需要关注工程的目录位置,但是对于一些旧/历史工程在导入 GoLand 之后会出现全面飘红,这个时候就需要逐一排查问题
Go 中比较常见的 int、string、bool、float 基本数据类型之外还有其他的数据类型可以应用在特殊场景,比如 rune 就是类似于 int32,因为其可表示的字符范围更大,实际工作中可以用来计算字符串的真实长度
调试 Go 程序报错 xx is shadowed during return,方法在返回的时候不是预期的返回结果,错误的产生应当是在相同作用域中出现了同名的变量导致,根据实际业务场景进行修改
Go语言中的接口采用的是隐式实现,不需要去申明实现,只需要直接实现接口所定义的全部方法即可,同时区分了直接实现与指针实现两种形态,在实际使用时需要注意和关注