go语言渐入佳境[14]-指针

变量的地址

1
2
a :=10
fmt.Printf("a变量的地址为:%#X\n",&a)//a变量的地址为:0XC420092008

指针的声明

1
2
//声明
var p *int

空指针

1
2
3
if p==nil{
fmt.Println("p为空指针")
}

通过指针获取值

1
2
p = &a
fmt.Printf("p的类型为%T, p的值为:%v,p指向的int的值为:%v,a的值为:%d\n",p,p,*p,a)

通过指针修改值

1
2
*p = 99
fmt.Printf("p的类型为%T, p的值为:%v,p指向的int的值为:%v,a的值为:%d\n",p,p,*p,a)

完整例子1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

func main(){


//变量的地址
a :=10
fmt.Printf("a变量的地址为:%#X\n",&a)


//声明
var p *int

//空指针
if p==nil{
fmt.Println("p为空指针")
}

//通过指针获取值
p = &a
fmt.Printf("p的类型为%T, p的值为:%v,p指向的int的值为:%v,a的值为:%d\n",p,p,*p,a)


//通过指针修改值
*p = 99
fmt.Printf("p的类型为%T, p的值为:%v,p指向的int的值为:%v,a的值为:%d\n",p,p,*p,a)

}

指针作为函数参数

指针作为函数参数,修改原来的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func main() {
a := 10
fmt.Printf("1、变量a的内存地址是:%p ,值为:%v \n\n", &a, a)//10

b := &a
change(b)
fmt.Printf("3、change函数调用之后,变量a的内存地址是:%p ,值为:%v \n\n", &a, a)//20

change0(a)
fmt.Printf("5、change0函数调用之后,变量a的内存地址是:%p ,值为:%v \n\n", &a, a)//20

}

func change(a *int) {
fmt.Printf("2、change函数内,变量a的内存地址是:%p ,值为:%v \n\n", &a, a)//20
*a = 50
}

func change0(a int) {
fmt.Printf("4、change0函数内,变量a的内存地址是:%p ,值为:%v \n\n", &a, a)//20
a = 90
}

切片类型指针作为函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

func main() {
a := []int{1, 2, 3, 4}
fmt.Printf("1、变量a的内存地址是:%p ,值为:%v \n\n", &a, a)

modify(&a)
fmt.Printf("3、调用modify函数后,变量a的内存地址是:%p ,值为:%v \n\n", &a, a)

modify0(a)
fmt.Printf("5、调用modify0函数后,变量a的内存地址是:%p ,值为:%v \n", &a, a)
}

func modify(arr *[]int) {
fmt.Printf("2、modify函数中参数a的内存地址是:%p ,值为:%v \n", &arr, arr)
(*arr)[0] = 250
}

func modify0(arr []int) {
fmt.Printf("4、modify0函数中参数a的内存地址是:%p ,值为:%v \n", &arr, arr)
arr[0] = 99
}

指针作为函数参数例子2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func main() {
//定义两个局部变量
a, b := 100, 200

// 返回值的写法实现数据交换
a, b = swap0(a, b)
fmt.Println("第一次交换后:" , a, b)

// 使用指针实现交换
swap(&a, &b)
fmt.Println("第二次交换后:" , a, b)
}

//具有返回值的惯用写法,实现两个数据的交换
func swap0(x, y int) (int, int) {
return y, x
}

//使用指针作为参数的写法
func swap(x, y *int) {
*x, *y = *y, *x
}

指针数组

数组,数组中的元素存储的都是指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import "fmt"

const COUNT int = 4

func main() {
a := [COUNT]string{"abc", "ABC", "123", "一二三"}
//查看数组的指针的类型和值
fmt.Printf("%T , %v \n", &a, &a)

//定义指针数组
var ptr [COUNT]*string
fmt.Printf("%T , %v \n", ptr, ptr)

for i := 0; i < COUNT; i++ {
// 将数组中每个元素的地址赋值给指针数组的每个元素
ptr[i] = &a[i]
}
fmt.Printf("%T , %v \n", ptr, ptr)

fmt.Println(ptr[0])

// 根据指针数组元素的每个地址获取该地址所指向的元素的真实数值
for i:=0; i<COUNT ;i++ {
fmt.Println(*ptr[i])
}

for _,value :=range ptr {
fmt.Println(*value)
}
}

多级指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import "fmt"

func main() {
var a int
var ptr *int
var pptr **int

a = 123

// 为指针赋值
ptr = &a
fmt.Println("ptr:" , ptr)

//为pptr赋值
pptr = &ptr
fmt.Println("pptr" , pptr)

//获取指针对应的值
fmt.Printf("变量 a = %d \n" , a)
fmt.Printf("指针变量 *ptr = %d \n" , *ptr)
fmt.Printf("指向到指针的变量 **pptr = %d \n" ,**pptr)
}

那么垃Go语言的自动圾收集器是如何知道一个变量是何时可以被回收的呢?这里我们可以避开完整的技术细节,基本的实现思路是,从每个包级的变量和每个当前运行函数的每一个局部变量开始,通过指针或引用的访问路径遍历,是否可以找到该变量。如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在并不会影响程序后续的计算结果。
因为一个变量的有效周期只取决于是否可达,因此一个循环迭代内部的局部变量的生命周期可能超出其局部作用域。同时,局部变量可能在函数返回之后依然存在。
编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。

1
2
3
4
5
6
7
var global *int

func f() { func g() {
var x int y := new(int)
x = 1 *y = 1
global = &x }
}

这里的x变量必须在堆上分配,因为它在函数退出后依然可以通过包一级的global变量找到,虽然它是在函数内部定义的;用Go语言的术语说,这个x局部变量从函数f中逃逸了。相反,当g函数返回时,变量*y将是不可达的,也就是说可以马上被回收的。因此,*y并没有从函数g中逃逸,编译器可以选择在栈上分配*y的存储空间(译注:也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间),虽然这里用的是new方式。其实在任何时候,你并不需为了编写正确的代码而要考虑变量的逃逸行为,要记住的是,逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。
Go语言的自动垃圾收集器对编写正确的代码是一个巨大的帮助,但也并不是说你完全不用考虑内存了。你虽然不需要显式地分配和释放内存,但是要编写高效的程序你依然需要了解变量的生命周期。例如,如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时,会阻止对短生命周期对象的垃圾回收(从而可能影响程序的性能)。