指针为何物
当我们定义一个变量 name
var name string = "Edemao 博客"
当访问变量name时,机算机会返回给我们它指向的内存地址里存储的值:Edemao 博客
出于某些需要,我们会将这个内存地址赋值给另一个变量名,通常叫做 ptr(pointer的简写),而这个变量,我们称之为指针变量。换句话说,指针变量的值是内存地址。
根据变量指向的值,是否是内存地址,把变量分为两种:
-
普通变量:存数据值本身
-
指针变量:存值的内存地址
指针创建
第一种方法
先定义对应的其他变量,再通过普通变量取得内存地址(未声明则使用 :=
),创建指针:
// 定义普通变量
aint := 1
// 定义指针变量
ptr := &aint
第二种方法
先创建指定类型的指针变量(分配好内存),再给指针指向的内存地址写值。
// 创建指针
astr := new(string)
// 给指针赋值
*astr = "Edemao 博客"
第三种方法
先声明指针变量,再从其他变量取得内存地址赋值给它
aint := 1
var bint *int // 声明一个指针
bint = &aint // 初始化
上面的三段代码中,指针的操作都离不开这两个符号:
-
&
:从变量中取得内存地址 -
*
:当 * 在赋值操作的右边,是从一个指针变量中取值,当*
在赋值操作的左边,是该指针指向的变量
通过下面这段代码,你可以熟悉这两个符号的用法
要想打印指针指向的内存地址,方法有:
// 第一种
fmt.Printf("%p", ptr)
// 第二种
fmt.Println(ptr)
指针的类型
指针类型如何表示呢?看下面这段代码:
package main
import "fmt"
func main() {
astr := "hello"
aint := 1
abool := false
arune := 'a'
afloat := 1.2
fmt.Printf("astr 指针类型是:%T\n", &astr)
fmt.Printf("aint 指针类型是:%T\n", &aint)
fmt.Printf("abool 指针类型是:%T\n", &abool)
fmt.Printf("arune 指针类型是:%T\n", &arune)
fmt.Printf("afloat 指针类型是:%T\n", &afloat)
}
输出如下:
astr 指针类型是:*string
aint 指针类型是:*int
abool 指针类型是:*bool
arune 指针类型是:*int32
afloat 指针类型是:*float64
可以发现用 *
+所指向变量值的数据类型,就是对应的指针类型。
所以若我们定义一个接收指针类型的参数的函数,像这样:
func mytest(ptr *int) {
fmt.Println(*ptr)
}
指针的零值
指针声明后,若没有进行初始化,其默认为零值(nil
) 。
func main() {
a := 25
var b *int // 声明一个指针
if b == nil {
fmt.Println(b)
b = &a // 初始化:将a的内存地址给b
fmt.Println(b)
}
}
输出如下:
<nil>
0xc0000100a0
指针与切片
首先,切片与指针一样,都是引用类型。
比如,如果我们想通过一个函数改变一个数组的值,有两种方法
-
将这个数组的切片做为参数传给函数
-
将这个数组的指针做为参数传给函数
尽管二者都可以实现我们的目的,但是按照 Go 语言的使用习惯,建议使用第一种方法,因为第一种方法,写出来的代码会更加简洁,易读。看下面的示例代码:
使用切片
func modify(sls []int) {
sls[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(a[:])
fmt.Println(a)
}
使用指针
func modify(arr *[3]int) {
(*arr)[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(&a)
fmt.Println(a)
}