指针

指针的创建

Go保留了指针,代表某个内存地址,默认值为nil,使用&取变量地址,通过*访问目标对象。

简单示例:

1
2
3
4
5
v := "3"
ptr := &v
value := *ptr
fmt.Printf("指针地址为:%p\n", ptr) // 输出0x.....16进制数
fmt.Printf("指针地址内存储的值为:%s\n", value) // 输出3
贴士:变量、指针和地址二者的关系是: 每个变量都拥有地址,指针的值就是地址。

指针类型的声明方式一

1
2
3
4
var a int = 10
var p *int = &a //声明指针类型
fmt.Println(p) //输出 0xc.....16进制数
fmt.Println(*p) // 10

指针类型的声明方式二

1
2
3
4
var p *int
p = new(int) //申请一个int类型的地址空间
*p = 666 //存储地址内内容为666
fmt.Println(p)

注意: - Go同样支持多级指针,如 **T - 空指针:声明但未初始化的指针
- 野指针:引用了无效的地址 - Go不支持->运算符指针运算,可以直接使用 . 访问目标成员

指针还可以使用new创建:

1
2
3
str := new(string)		//申请一个string类型的指针内存
*str = "hello"
fmt.Println(*str)

指针实现变量值交换

1
2
3
func swap (p1,p2 *int) {
*p1,*p2 = *p2,*p1
}

结构体指针

结构体指针访问结构体字段仍然使用 . 语法,Go中没有 -> 操作符,案例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type 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
3
a := 1
p := &a
p++ //报错:non-numeric type *int

变量生命周期与栈逃逸机制

函数中允许返回局部变量的地址,Go编译器使用栈逃逸机制将这种局部变量分配在堆上:

1
2
3
4
5
var p = f()
func f() *int {
v := 1
return &v // 返回函数中的局部变量地址是安全的,因为p仍然在引用他
}

变量的生命周期指在程序运行期间变量有效存在的时间段:
- 包级别声明的变量,其生命周期和整个程序的运行周期是一致的 - 局部变量的生命周期是动态的,每次从创建新变量的声明语句开始到不再引用为止,变量的存储空间可能被回收

函数的参数变量和返回值变量都是局部变量,它们在函数每次被调用的时候创建。

Go的GC判断变量是否回收的实现思路:从每个包级的变量、每个当前运行的函数的局部变量开始,通过指针和引用的访问路径遍历,是否可以找到该变量,如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在并不会影响后续计算结果。

示例:

1
2
3
4
5
6
7
8
9
10
var global *int
func f() {
var x int
x = 1
global = &x
}
func g() {
y := new(int)
*y = 1
}
上述的函数调用结果说明: - 虽然x变量定义在f函数内部,但是其必定在堆上分配,因为函数退出后仍然能通过包一级变量global找到,这样的变量,我们称之为从函数f中逃逸了 - g函数返回时,变量*y不可达,因此没有从函数g中逃逸,其内存分配在栈上,会马上被被回收。(当然也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间)

作者

ฅ´ω`ฅ

发布于

2021-06-10

更新于

2021-06-10

许可协议


评论