0
点赞
收藏
分享

微信扫一扫

15.Go复合类型-指针

15.Go复合类型-指针

7:指针

7.1 变量内存与地址

前面我们讲过存储数据的方式,可以通过变量,或者复合类型中的数组,切片,Map,结构体。

我们不管使用变量存储数据,还是使用符合类型存储数据,都有两层的含义:

  • 存储的数据(内存),对应的地址。

接下来,通过变量来说明以上两个含义。

例如,定义如下变量,打印变量的值以及存储的内存地址:

var i int
i = 100
fmt.Printf("i=%d\n", i)
fmt.Printf("&i=%v\n", &i)

// 执行如下
i=100
&i=0xc00000a0a8
  • 第一个​​Printf()​​函数的输出,大家都很熟悉,输出变量​​i​​的值,这个实际上就是输出内存中存储的数据。在前面的章节中,已经讲解过,定义一个变量,就是在内存中开辟一个空间,用来存储数据,当给变量​​i​​赋值为100,其实就是将100存储在该空间内。
  • 第二个​​Printf()​​函数的输出,输出的是变量​​i​​在内存中的地址。通过如下图来给大家解释:

15.Go复合类型-指针_指针

img

这张图,大家也应该非常熟悉,是在讲解变量时,画的一张图,​​0x100010​​​假设是变量​​i​​的内存地址(通过第二个输出可以获取实际的地址),内存地址的作用:在输出变量中存储的数据时,是通过地址来找到该变量内存空间的。

这个内存地址和实际生活中的地址也很相似,例如:大家可以将内存空间想象成,我们上课的教室,教室中存放有学生,那么现在要找一个学生,必须要知道具体的地址以及教室门牌号。

7.2 指针变量

现在已经知道怎样获取变量在内存中的地址,但是如果想将获取的地址进行保存,应该怎样做呢?

可以通过指针变量来存储,所谓的指针变量:就是用来存储任何一个值的内存地址。

指针变量的定义如下:

// 指针变量的定义
var i int = 100
var p *int // 定义指针变量
p = &i // 把变量i的地址赋值给指针变量p
fmt.Printf("i=%d, p=%v", i, p)

// 执行如下:
i=100, p=0xc00000a0a8

指针变量​​p​​​ 的定义是通过​​*​​​这个符号来定义,指针变量​​p​​​ 的类型为​​*int​​, 表示存储的是一个整型变量的地址。

如果指针变量​​p​​​存储的是一个字符串类型变量的地址,那么指针变量​​p​​​的类型为​​*string​

​p=&i​​​ : 该行代码的意思是,将变量​​i​​​的地址取出来,并且赋值给指针变量​​p​​​.也就是指针变量​​p​​​指向了变量​​i​​的存储单元。

可以通过如下图来表示:

15.Go复合类型-指针_内存管理_02


image-20210529105440322

在以上图中,一定要注意:指针变量​​p​​​存储的是变量​​i​​的地址。

大家可以思考一个问题:使用指针修改变量的值

既然指针变量​​p​​​指向了变量​​i​​​的存储单元,那么是否可以通过指针变量​​p​​​,来操作变量​​i​​中存储的数据?

答案是可以的,具体操作方式如下:

// 指针变量的定义
var i int = 100
var p *int // 定义指针变量
p = &i // 把变量i的地址赋值给指针变量p
*p = 80 // 使用指针修改值 《=====
fmt.Printf("i=%d, p=%v", i, p)

// 执行
i=80, p=0xc00000a0a8

注意:在使用指针变量​​p​​​来修改变量​​i​​​的值的时候,前面一定要加上​​*​​.(通过指针访问目标对象)

现在打印变量​​i​​的值已经有100变为80.

使用指针变量输出变量的值

当然,也可以通过指针变量​​p​​​来输出,变量​​i​​中的值,输出的方式如下所示:

// 通过指针变量`p`来输出,变量`i`中的值
fmt.Printf("i=%d, *p=%v", i, *p)

所以,​​*p​​的作用就是根据存储的变量的地址,来操作变量的存储单元(包括输出变量存储单元中的值,和对值进行修改)

7.3  注意事项

在使用指针变量时,要注意以下两点。

1:默认值为nil

var p *int
fmt.Println(p)

// 执行
<nil>

直接执行上面的程序,结果是:nil

2: 不要操作没有合法指向的内存。

例如,在上面的案例中,我们定义了指针变量p,但是没有让指针变量指向任何一个变量,那么直接运行如下程序,会出现异常。

var p *int
*p = 56 // 没有指向内存空间,直接赋值
fmt.Println(p)

出现的错误信息如下:

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x0 pc=0x14579a6]

所以,在使用指针变量时,一定要让指针变量有正确的指向。

以下的操作是合法的:

var a int
var p *int
p = &a // 指向变量a的内存地址
*p = 56
fmt.Println(a)

在该案例中,定义了一个变量a, 同时定义了一个指针变量p, 将变量a的地址赋值给指针变量p,也就是指针变量p指向了变量a的存储单元。给指针变量p赋值,影响到了变量a.最终输出变量a中的值也是56.

7.4 new( )函数:指针变量指向新的内存空间地址

指针变量,除了以上介绍的指向以外(​​p=&a​​​),还可以通过​​new( )​​函数来指向。

具体的应用方式如下:

// new函数
var p *int
p = new(int)
*p = 57
fmt.Println(*p)

​new(int)​​ 作用就是创建一个整型大小(4字节)的空间

然后让指针变量p指向了该空间,所以通过指针变量​​p​​​进行赋值后,该空间中的值就是​​57​​.

​new()​​函数的作用就是C语言中的动态分配空间。

但是在这里与C语言不同的地方,就是最后不需要关心该空间的释放。

GO语言会自动释放。这也是比C语言使用方便的地方。

也可以使用自动推导类型的方式:

q := new(int)
*q = 787
fmt.Println(*q)

7.5  指针做函数参数

指针也可以作为函数参数,那么指针作为函数参数在进行传递的时候,是值传递还是引用传递呢?

大家都知道,普通变量作为函数参数进行传递是值传递,如下案例所示:

定义一个函数,实现两个变量值的交换。

func Swap(num1, num2 int) {
num1, num2 = num2, num1
fmt.Printf("num1 = %d, num2 = %d\n", num1, num2)
}

func main() {
var a int = 10
var b int = 20
Swap(a, b)
fmt.Printf("a = %d, b = %d", a, b)
}

//执行如下:
num1 = 20, num2 = 10
a = 10, b = 20

通过以上案例,证实普通类型变量在传递时,为值传递。

使用指针作为函数参数,进行值交换

那么使用指针作为函数参数呢?现在将以上案例修改成,用指针作为参数,如下所示:

func Swap(num1, num2 *int) {
*num1, *num2 = *num2, *num1
fmt.Printf("num1 = %d, num2 = %d\n", *num1, *num2)
}

func main() {
var a int = 10
var b int = 20
Swap(&a, &b)
fmt.Printf("a = %d, b = %d", a, b)
}

//执行:
num1 = 20, num2 = 10
a = 20, b = 10

通过以上案例证实,指针作为参数进行传递时,为引用传递,也就是传递的地址。

在调用​​Swap()​​函数时,将变量a与变量b的地址传分别传递给指针变量num1,num2,这时num1和num2,分别指向了变量a,与变量b的内存存储单元,那么操作num1,num2实际上操作的就是变量a与变量b,所以变量a与变量b的值被交换。

7.6 数组指针

前面在讲解数组的时候,我们用数组作为函数参数,但是数组作为参数进行传递是值传递,如果想引用传递,可以使用数组指针。

具体使用方式如下:

func Swap(p *[5]int) { // 设置指针数组为参数
(*p)[0] = 89
}

func main() {
a := [5]int{1, 2, 3, 4, 5}
Swap(&a) // 传递数组地址
fmt.Println(a)
}

// 执行:
[89 2 3 4 5]

定义一个数组,作为函数Swap的实参进行传递,但是这里传递的是数组的地址,所以Swap的形参是数组指针。

这时指针​​p​​​,指向了数组​​a​​​,对指针​​p​​​的操作实际上是对数组​​a​​​的操作,所以如果直接执行如下语句:​​fmt.Println(*p)​​​,会输出数组​​a​​中的值。

也可以通过​​*p​​结合下标将对应的值取出来进行修改。

最终在​​main​​​函数中输出数组​​a​​,发现其元素也已经修改。

当然,我们也可以通过循环的方式来将数组指针中的数据打印出来:

for index, value := range *p{
fmt.Printf("index=%d, value=%d\n", index, value)
}

7.7 指针数组

上一小节,讲解到的是数组指针,也就是让一个指针指向数组 ,然后可以通过该指针来操作数组。

还有一个概念叫指针数组,这两个概念很容混淆,指针数组指的是一个数组中存储的都是指针(也就是地址)。

也就是一个存储了地址的数组。

指针数组的定义

下面通过一个案例,看一下指针数组的应用

// 指针数组的定义
var p [2]*int // 指针数组的定义
var i int = 10
var j int = 20
// 将地址赋值给指针数组
p[0] = &i
p[1] = &j
fmt.Println(p[0])
fmt.Println(p[1])

// 执行如下:
0xc00000a0a8
0xc00000a0c0

指针数组的定义方式,与数组指针定义方式是不一样的,注意指针数组是将​​“*”​​​放在了下标的后面 ​​var p [2]*int​​。

由于指针数组存储的都是地址,所以将变量​​i​​​,与变量​​j​​的地址赋值给了指针数组p。

最后输出指针数组p中存储的地址。

指针数组操作指向变量的值

思考:既然指针数组​​p​​​存储了变量​​i​​​ 和变量​​j​​​的的地址,那么怎样通过指针数组​​p​​​操作变量​​i​​​与变量​​j​​的值呢?

具体实现如下:

// 指针数组的定义
var p [2]*int // 指针数组的定义
var i int = 10
var j int = 20
// 将地址赋值给指针数组
p[0] = &i
p[1] = &j
fmt.Println(*p[0])
fmt.Println(*p[1])

// 执行:
10
20

注意这里输出 ​​fmt.Println(*p[0])​​ 要注意的问题是,没有加小括号。(注意运算顺序)

当然,我们也可以通过for循环的方式来输出指针数组中对应的值。

// 将地址赋值给指针数组
p[0] = &i
p[1] = &j

// for循环输出指针数组中对应的值
for k, v := range p {
fmt.Printf("k=%d, v=%d\n", k, *v)
}

// 执行:
k=0, v=10
k=1, v=20

7.8 结构体指针变量

我们前面定义了指针指向了数组,解决了数组引用传递的问题。那么指针是否可以指向结构体,也能够解决结构体引用传递的问题呢?完全可以。

结构体指针变量的定义:

下面我们先来看一下,结构体指针变量的定义:

type Student struct {
id int
name string
score float64
}

func main() {
var p *Student = &Student{1,"zhangsan", 90} // 定义结构体指针
fmt.Println(*p)
}

// 执行如下:
{1 zhangsan 90}

自动推导类型定义结构体指针变量:

也可以使用自动推导类型

type Student struct {
id int
name string
score float64
}

func main() {
p := &Student{1,"zhangsan", 90} // 自动推导,定义结构体指针
fmt.Println(*p)
}

使用结构体指针操作结构体成员属性

现在定义了一个结构体指针变量,那么可以通过该指针变量来操作结构体中的成员项。

type Student struct {
id int
name string
score float64
}

func main() {
p := &Student{1,"zhangsan", 90} // 自动推导,定义结构体指针
p.score = 89 // 直接通过 . 运算符操作结构体属性
fmt.Println(*p)
}

// 执行如下:
{1 zhangsan 89}

结构体指针作为形参

前面在讲解结构体时,用结构体作为函数的参数,默认的是值传递。

那么通过结构体指针,可以实现结构体的引用传递。

具体实现的方式如下:

type Student struct {
id int
name string
score float64
}

// 结构体指针变量作为形参
func Test(p *Student) {
p.id = 19
}

func main() {
p := &Student{1, "zhangsan", 90} // 自动推导,定义结构体指针
p.score = 89 // 直接通过 . 运算符操作结构体属性
Test(p) // 传递结构体指针
fmt.Println(*p)
}

// 执行如下:
{19 zhangsan 89}


举报

相关推荐

0 条评论