https://blog.csdn.net/weixin_52690231/article/details/123925880
GoLang之取地址符&、指针
注:本文以Windos系统上Go SDK1.8进行讲解
1.int取地址符&
变量是一种使用方便的占位符,用于引用计算机内存地址。
Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。
以下实例演示了变量在内存中地址:
func main() {
var a int
fmt.Printf("变量的地址: %x\n", &a) //变量的地址: c000016098
a = 4
fmt.Printf("变量的地址: %x\n", &a)//变量的地址: c000016098
}
2.指针声明
一个指针变量指向了一个值的内存地址。
类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:
*var-type 为指针类型,var_name 为指针变量名
var var_name *var-type
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */
3.int指针使用
指针使用流程:
定义指针变量。
为指针变量赋变量。
访问指针变量中指向地址的值。
func main() {
var a int = 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */
ip = &a /* 指针变量的存储地址 */
/* 使用指针访问变量 */
fmt.Printf("*ip 变量的值: %d\n", *ip) //*ip 变量的值: 20
/* 值的地址 */
fmt.Printf("a 变量的地址是: %p\n", &a) //a 变量的地址是: 0xc000102058
/* 指针变量里存的地址 */
fmt.Printf("ip 变量里储存的地址: %p\n", ip) //ip 变量里储存的地址: 0xc000102058
/* 指针变量的地址 */
fmt.Printf("ip 变量的地址: %p\n", &ip) //ip 变量的地址: 0xc00012e018
}
4.空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。
func main() {
var ptr *int
if ptr == nil {
fmt.Println("ptr==nil")
} else if ptr != nil {
fmt.Println("ptr!=nil")
}
fmt.Println(ptr)
fmt.Printf("ptr 的值为 : %x\n", ptr)
/*输出以下:
ptr==nil
<nil>
ptr 的值为 : 0
*/
}
5.指向指针的指针
如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。
当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址:
指向指针的指针变量声明格式如下:
var ptr **int;
package main
import "fmt"
func main() {
var a int
var ptr *int
var pptr **int
a = 3000
/* 指针 ptr 地址 */
ptr = &a
/* 指向指针 ptr 地址 */
pptr = &ptr
/* 获取 pptr 的值 */
fmt.Printf("变量 a = %d\n", a )//输出:变量 a = 3000
fmt.Printf("指针变量 *ptr = %d\n", *ptr )//输出:指针变量 *ptr = 3000
fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)//输出:指向指针的指针变量 **pptr = 3000
}
6.三重指针
pt3 - > pto - > ptr - >变量a
package main
import "fmt"
func main(){
var a int = 5
//把ptr指针 指向ss所在地址
var ptr *int = &a
//开辟一个新的指针,指向ptr指针指向的地方
var pts *int = ptr
//二级指针,指向一个地址,这个地址存储的是一级指针的地址
var pto **int = &ptr
//三级指针,指向一个地址,这个地址存储的是二级指针的地址,二级指针同上
var pt3 ***int = &pto
fmt.Println("a的地址:",&a,
"\n 值", a, "\n\n",
"ptr指针所在地址:",&ptr,
"\n ptr指向的地址:",ptr,
"\n ptr指针指向地址对应的值",*ptr,"\n\n",
"pts指针所在地址:",&pts,
"\n pts指向的地址:", pts,
"\n pts指针指向地址对应的值:",*pts,"\n\n",
"pto指针所在地址:",&pto,
"\n pto指向的指针(ptr)的存储地址:",pto,
"\n pto指向的指针(ptr)所指向的地址: " ,*pto,
"\n pto最终指向的地址对应的值(a)",**pto,"\n\n",
"pt3指针所在的地址:",&pt3,
"\n pt3指向的指针(pto)的地址:",pt3,//等于&*pt3,
"\n pt3指向的指针(pto)所指向的指针的(ptr)地址", *pt3, //等于&**pt3,
"\n pt3指向的指针(pto)所指向的指针(ptr)所指向的地址(a):",**pt3, //等于&***pt3,
"\n pt3最终指向的地址对应的值(a)", ***pt3)
}
/*
执行结果:
a的地址: 0xc00009a008
值 5
ptr指针所在地址: 0xc000092010
ptr指向的地址: 0xc00009a008
ptr指针指向地址对应的值 5
pts指针所在地址: 0xc000092018
pts指向的地址: 0xc00009a008
pts指针指向地址对应的值: 5
pto指针所在地址: 0xc000092020
pto指向的指针(ptr)的存储地址: 0xc000092010
pto指向的指针(ptr)所指向的地址: 0xc00009a008
pto最终指向的地址对应的值(a) 5
pt3指针所在的地址: 0xc000092028
pt3指向的指针(pto)的地址: 0xc000092020
pt3指向的指针(pto)所指向的指针的(ptr)地址 0xc000092010
pt3指向的指针(pto)所指向的指针(ptr)所指向的地址(a): 0xc00009a008
pt3最终指向的地址对应的值(a) 5
*/
7.引用变量
在一些开发语言中(比如 C++),对已存在的变量可以声明别名,这种别名称为引用变量。
可以看到 a、b 和 c 都指向相同的内存位置。对 a 的写操作会影响 b 和 c。
include <stdio.h>
int main() {
int a = 10;
int &b = a;
int &c = b;
printf("%p %p %p\n", &a, &b, &c); // 0x7ffe114f0b14 0x7ffe114f0b14 0x7ffe114f0b14
return 0;
}
8.Go语言没有引用变量
与 C++ 不同,Go 程序中定义的每个变量都占用一个惟一的内存位置。
func main() {
var a, b, c int
fmt.Println(&a, &b, &c) // 0x1040a124 0x1040a128 0x1040a12c
}
创建两个共享同一内存位置的变量是不可能的。但是可以创建两个指向同一内存位置的变量,不过这与两个变量共享同一内存位置是不同的。
下面这段代码,b 和 c 都具有相同的值 ,即变量 a 的地址,但 a、c 存储在内存中不同的位置。改变 b 的内容不会影响到 c。
func main() {
var a int
var b, c = &a, &a
fmt.Println(b, c) // 0x1040a124 0x1040a124
fmt.Println(&b, &c) // 0x1040c108 0x1040c110
}
9.&使用注意事项
9.1常量不可寻址
9.2字符串变量可寻址
func main() {
var s string = "qqqq"
fmt.Println(&s) //0xc00005a250
}
9.3函数或方法不可寻址
9.4基本类型字面量不可寻址
10 什么情况下使用指针
package main
import "fmt"
type animal struct {
name string
}
func (a animal) setName_v(name string) animal {
a.name = name
return a
}
func (a animal) setName_wrong(name *string) {
a.name = *name //作用域只在函数内部, 垃圾回收了所有内部参数
}
func (a *animal) setName(name *string) {
a.name = *name //作用域是外部的地址,垃圾并不会回收地址
}
func setName(p *animal, name string) {
p.name = name //自带的数据机构 可以传 地址可以传value,看具体情况节约内存
}
func main() {
an := animal{name: "jerry"}
fmt.Println(an)
setName(&an, "tommy")
fmt.Println(an)
strname := "zhangsan" //数据结构很大建议用地址,这样不用走栈内存。
an.setName(&strname)
fmt.Println(an)
an = an.setName_v("lili")
fmt.Println(an)
}
// 四个方法涉及了变量地址,数值 ,作用域,垃圾,内存开销。
从以上指针的详细分析中,我们可以总结出指针的两大好处:
- 可以修改指向数据的值;
- 在变量赋值,参数传值的时候可以节省内存。
不过 Go 语言作为一种高级语言,在指针的使用上还是比较克制的。它在设计的时候就对指针进行了诸多限制,比如指针不能进行运行,也不能获取常量的指针。所以在思考是否使用时,我们也要保持克制的心态。
我根据实战经验总结了以下几点使用指针的建议,供你参考:
如果接收者类型是 map、slice、channel 这类引用类型,不使用指针;
如果需要修改接收者,那么需要使用指针;
如果接收者是比较大的类型,可以考虑使用指针,因为内存拷贝廉价,所以效率高。
所以对于是否使用指针类型作为接收者,还需要你根据实际情况考虑。
- 不要对 map、slice、channel 这类引用类型使用指针;
- 如果需要修改方法接收者内部的数据或者状态时,需要使用指针;
- 如果需要修改参数的值或者内部数据时,也需要使用指针类型的参数;
- 如果是比较大的结构体,每次参数传递或者调用方法都要内存拷贝,内存占用多,这时候可以考虑使用指针;
- 像 int、bool 这样的小数据类型没必要使用指针;
- 如果需要并发安全,则尽可能地不要使用指针,使用指针一定要保证并发安全;
- 指针最好不要嵌套,也就是不要使用一个指向指针的指针,虽然 Go 语言允许这么做,但是这会使你的代码变得异常复杂。
总结
为了使编程变得更简单,指针在高级的语言中被逐渐淡化,但是它也的确有自己的优势:修改数据的值和节省内存。所以在 Go 语言的开发中我们要尽可能地使用值类型,而不是指针类型,因为值类型可以使你的开发变得更简单,并且也是并发安全的。如果你想使用指针类型,就要参考我上面讲到的使用指针的条件,看是否满足,要在满足和必须的情况下才使用指针。
这节课到这里就要结束了,在这节课的最后同样给你留个思考题:指向接口的指针是否实现了该接口?为什么?思考后可以自己写代码验证下。
最后编辑:秦晓川 更新时间:2025-02-22 15:31