分水岭
不看内容,只看标题还以为这是一个小说呢。 如果哪天心血来潮,没准会写一篇小说。但自从参加朋友婚姻之后,就受到了打击。 同样进入30岁,有的同学已经年入上百万,前呼后拥。 而自己除了会写点"水货"代码,别无他长。 感慨良多,还是感觉自己不是一块能依靠写代码发家致富的料。 所以会分出一部精力,来考虑如何实现技术变现。 但这个系列仍然会写完,不会虎头蛇尾。因此本节起名:分水岭。(这里为cnblogs的乁卬杨同学发一封表扬信,这位同学大胆留言说第三式写的有些负面,我重看了一下第三式,的确有些负面。 所以汲取教训,写好才是王道,我的文章我说了算!再次谢谢乁卬杨同学)
如果没有C/C++语言经历的同学,估计很少会使用过指针,这个名词应该是在职业生涯当中经常会听到,但很少会使用到的。 尤其什么时候用*,什么时候用&,只靠死记硬背很难活学活用。而在Golang当中,指针的使用变得有些简单。像在前几节讲解函数参数之时,提到Golang参数默认是值传递,但有的时候通过指针可以达到引用传递的作用。而这也只是指针在Golang当中一个使用场景,本节将会具体聊聊Golang中如何使用指针。
正如前面所述,在Golang当中每个变量都是一个内存位置,每个内存位置都有它定义的地址,可以使用&运算符访问它,它表示内存中的地址。例如下面的例子:
var a int = 10
fmt.Printf("%x\n", &a )
这样通过&a,就输出了a的内存地址。每次输出的地址,有可能相同也有可能不相同。这取决于当时的内存使用状态。 如果内存资源不紧张,Golang的GC就不会回收这块内存,输出的就是同一块地址。 如果内存紧张,GC就会回收垃圾内存,然后运行时重新分配一块内存,那时就输出一块新地址。
在大学或者其他教程中,应该会提到过什么是指针。 为了一俗到底,这里再提一遍:指针是一个变量,保存的是内存地址。与任何变量或常量一样,必须先声明一个指针(变量),然后才能使用它来存储任何内存地址。通过下面语法可以声明指针变量:
var name *type
type指的是数据类型,name则是指针名称。*不能丢,表示这是一个指针类型的变量。如果没有*,就变成一个普通数据变量了。比如声明一个int类型,是这样:
var i int
而声明一个int类型的指针,则是:
var i *int
因为指针保存的是内存地址,所以其长度都是相同的,是一个表示内存地址的长十六进制数字。不同数据类型指针之间的唯一区别是指针指向的内存所保存的数据类型。举个例子:每家家门钥匙长得都一样(是同一批防盗门),但每家里面的户型就千奇百怪了。 这里面,钥匙就是指针,而户型就是数据类型。 我们可以说A钥匙指的是二居室,而B钥匙指的是四居室。
声明完指针类型之后,就可以使用指针了。因此,下面来看如何使用指针。
使用指针三板斧:
- 声明一个指针。
- 将真正数据的内存地址赋给这个指针
- 取出指针保存的内存地址,然后通过这个地址取出数据。
指针的使用,基本就是这三板斧。而赋值,取值操作就依靠*和&来操作。对照着例子来看:
package main
import "fmt"
func main() {
var a int = 20
var ip *int
ip = &a
fmt.Printf("Address of a variable: %x\n", &a )
fmt.Printf("Address stored in ip variable: %x\n", ip )
fmt.Printf("Value of *ip variable: %d\n", *ip )
}
var a int = 20
声明了一个int类型的普通变量,把20搁到里面去。
然后第一斧(声明指针) var ip *int。 声明了一个指针,准备用来保存int类型的变量。(内存地址长度都一样,但Golang是强类型语言,所以必须告诉编译器你准备保存什么类型的数据,因此声明成*int)
然后砍上第二斧,将a的地址给了ip。通过&a取出a的内存地址,然后ip = &a就把地址给了ip。
最后三板斧,取出地址里面的数据,也就是最后一个Printf里面的*ip。 这里面,绕了个弯。 如果直接用ip,那其实是将ip保存的数据打印出来(想想如果i=1, 直接输出i,是多少?)。这个地址本身没有用,我们需要的是这个地址所对应的数据(好比小偷不关注钥匙,关注的是钥匙能打开那扇门)。所以通过*ip,就直接取出ip所保存地址里面的数据了。
如果绕了,就把自个想象成小偷(意淫一下,别真去)。给你一个钥匙,你不心动。而如果告诉你这把钥匙能开那扇门,这个时候才最心动。所以ip保存的是钥匙,而*ip就是告诉你是那扇门。
既然说到钥匙了,就接着往下说。 配钥匙师傅那里都挂着一串空白钥匙,这个专业术语就是空指针。空指针也是一个指针变量类型,只不过没有保存任何地址。想想空白钥匙,那扇门都打不开。
当刚开始第一板斧的时候(var ip *int),其实就已经是空指针了。此时默认值就是0.在绝大多数的操作系统中,0都是一个很神奇的数字,介于天堂和地狱之间。 所以操作系统不让用户直接访问0地址(其实就是操作系统不放心用户,担心用户瞎捣乱)。在Golang当中,特意为空指针起了个名字,叫做nil。例如下面:
if(ptr != nil){ ... 不是空指针 }
if(ptr == nil){ ... 是空指针 }
如果你能熟练背出指针三板斧,那么你就掌握50%了。 如果不能.... 重新GOTO line1,然后Loop。下面开始剩余50%的指针大法。
指针在数组中的应用
莫紧张,不要看到指针就感觉是相亲。 指针比妹子容易对付多了。妹子的世界是太极拳,虚虚实实,拳无定式。对付妹子,就跟圣斗士一样,同样的招式不能用两遍(是不是车田正美也受到了妹子的启发)。而Golang大法,招招式式,都写的很清楚。先来看普通数组如何使用:
a := []int{10,100,200}
var i int
for i = 0; i < MAX; i++ {
fmt.Printf("Value of a[%d] = %d\n", i, a[i] )
}
初始化三个值,然后依次取出。 改造一下,把指针糅合进去。
const MAX int = 3
a := []int{10,100,200}
var i int
var ptr [MAX]*int;
for i = 0; i < MAX; i++ {
ptr[i] = &a[i] /* assign the address of integer. */
}
for i = 0; i < MAX; i++ {
fmt.Printf("Value of a[%d] = %d\n", i,*ptr[i] )
}
指针的数组使用起来,就和其它数组的使用一模一样。以前怎么用,现在还怎么用。唯一的变化就是,以前的数组保存的是数,而现在的指针数据保存的是地址。 除此之外,毫无区别。 两个例子一对比就够了,没啥说的。
指针的指针
这种场景不得不说。 你可以不去使用,但万一看到了,不能不知道。说实话,使用这个大招的,十有八九是从C++转过来的。不能说不好,个人习惯吧。 我是不喜欢用,毕竟要考虑代码的可读性。(学乖了,为了不找骂,我个人不喜欢而已。言多必失!)
指针的指针,术语叫做指针链。一听链,就表明至少有两个指针。 暂且称之为A针,B针。A针按三板斧的规则,保存的是一个数据的地址。而B针保存的是A针的地址。 绕?看下面:
B --> A --> Data
假设Data所在的地址是123,那么A保存的数据就是123. 同时A自己的地址是abc,那么B保存的数据就是abc。 如果还有C,那就保存B的地址,你高兴,后面可以跟着一群。
那么此时此刻,问题来了。 我知道A可以声明成 var a *int。那么B呢? *int? 好像不对吧。
大口吸气,冷静一下。 想想指针的数据类型。 int是数据类型,*int是指针类型。那么一个保存指针类型的指针,也就是叠加一个*int呗。因此B就变成了:
var b **int
如果还有C呢,自个儿写代码试试。指针链就这么点需要注意的,如果还不明白,手敲以下代码跑一下:
package main
import "fmt"
func main() {
var a int
var ptr *int
var pptr **int
a = 3000
ptr = &a
pptr = &ptr
fmt.Printf("Value of a = %d\n", a )
fmt.Printf("Value available at *ptr = %d\n", *ptr )
fmt.Printf("Value available at **pptr = %d\n", **pptr)
}
坚持住,已经99%了。看完下面,进度条就跑到100%了(绝不像X雷,卡你99%)。
指针在函数参数中的使用
这里只是为了凑齐那1%,其实内容已经在:二式<维密>一节,提到过了。只要看准参数是要值传递还是引用传递,就没问题。
最后忍不住,来点扯淡的话。 追求知识无可厚非,但现实社会中,金钱是成功与否的唯一标准。所以在学习工作过程中,还是要时时刻刻考虑如何依靠知识来变现。没有人看的书,等于厕纸。无法变现的知识,等于白学。如果你有好的想法,或者闲得无聊欢迎发邮件给我([email protected]),没准火花就迸发于扯淡之间。