指针
指针的创建
Go保留了指针,代表某个内存地址,默认值为nil,使用&取变量地址,通过*访问目标对象。
简单示例: 1
2
3
4
5v := "3"
ptr := &v
value := *ptr
fmt.Printf("指针地址为:%p\n", ptr) // 输出0x.....16进制数
fmt.Printf("指针地址内存储的值为:%s\n", value) // 输出3
指针类型的声明方式一 1
2
3
4var a int = 10
var p *int = &a //声明指针类型
fmt.Println(p) //输出 0xc.....16进制数
fmt.Println(*p) // 10
指针类型的声明方式二 1
2
3
4var p *int
p = new(int) //申请一个int类型的地址空间
*p = 666 //存储地址内内容为666
fmt.Println(p)
注意: - Go同样支持多级指针,如 **T - 空指针:声明但未初始化的指针
- 野指针:引用了无效的地址 - Go不支持->运算符指针运算,可以直接使用 . 访问目标成员
指针还可以使用new创建: 1
2
3str := new(string) //申请一个string类型的指针内存
*str = "hello"
fmt.Println(*str)
指针实现变量值交换
1 | func swap (p1,p2 *int) { |
结构体指针
结构体指针访问结构体字段仍然使用 . 语法,Go中没有 -> 操作符,案例如下: 1
2
3
4
5
6
7
8
9
10
11
12
13
14type User struct{
name string
age int
}
func main() {
var u = User{
name:"lisi",
age: 18,
}
p := &u
fmt.Println(u.name) //输出李四
fmt.Println(p.name) //输出李四
}
Go不支持指针运算
由于垃圾回收机制的存在,指针运算造成许多困扰,所以Go直接禁止了指针运算 1
2
3a := 1
p := &a
p++ //报错:non-numeric type *int
变量生命周期与栈逃逸机制
函数中允许返回局部变量的地址,Go编译器使用栈逃逸机制将这种局部变量分配在堆上: 1
2
3
4
5var p = f()
func f() *int {
v := 1
return &v // 返回函数中的局部变量地址是安全的,因为p仍然在引用他
}
变量的生命周期指在程序运行期间变量有效存在的时间段:
- 包级别声明的变量,其生命周期和整个程序的运行周期是一致的 - 局部变量的生命周期是动态的,每次从创建新变量的声明语句开始到不再引用为止,变量的存储空间可能被回收
函数的参数变量和返回值变量都是局部变量,它们在函数每次被调用的时候创建。
Go的GC判断变量是否回收的实现思路:从每个包级的变量、每个当前运行的函数的局部变量开始,通过指针和引用的访问路径遍历,是否可以找到该变量,如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在并不会影响后续计算结果。
示例: 1
2
3
4
5
6
7
8
9
10var global *int
func f() {
var x int
x = 1
global = &x
}
func g() {
y := new(int)
*y = 1
}