壹 介绍
Go 语言中指针是很容易学习的,Go 语言中使用指针可以更简单的执行一些任务。Go语言中的指针不能像C/C++语言一样进行偏移和运算,是安全指针。要搞明白Go语言中的指针需要先知道3个概念:指针地址、指针类型和指针取值。
任何程序数据载入内存后,在内存都有他们的地址,这个地址就是指针。而为了保存一个数据在内存中的地址,我们就需要一个变量,这个变量就是指针变量。
个人理解:A是一个变量,这个变量存储了一个值是1,那么如果需要得到A在内存的地址,就需要用另一个变量B存储A在内存的地址,这其中,A是存储1的变量,就是普通的变量,B是存储A这个变量在内存的地址的变量,又叫做指针变量,所以指针变量里面存储的只能是内存地址,所以A、B都能指向1这个值,但是A可以直接获取,B要通过A获取,至此我们可以看出指针本身就是一种引用。
那么如何获取地址和地址的值呢?Go语言中有两个符号:&
(取地址)和*
(根据地址取值)。即,&
是B获取A的地址使用的符号,*
是B获取1这个值的符号。
语法格式:
// 取地址
b := &a
// a:代表要被获取地址的变量,类型为T
// b:用于接收该a地址的指针变量,b的类型就为*T,称做T的指针类型。*代表这个变量是指针,存储地址的指针变量。
理解图示:
实例:
package main
import "fmt"
func main() {
var a int = 10
var b *int = &a
fmt.Printf("a的值: %v\ta的类型:%T\ta的地址:%p\n", a, a, &a)
// a的值: 10 a的类型:int a的地址:0xc000016098
fmt.Printf("b的值: %v\tb的类型:%T\tb的地址:%p\n", b, b, &b)
// b的值: 0xc000016098 b的类型:*int b的地址:0xc000006028
}
贰 指针声明
一个指针变量指向了一个值在内存中的地址。 类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:
var 指针变量名 *指针类型
// *号用于指定变量是作为一个指针
以下是有效的指针声明:
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */
需要注意:指针类型只能是值类型,不能是引用类型,这是没有意义的。
叁 使用指针
指针使用流程:
graph LR
A[定义指针变量] --> B[为指针变量赋值,用&] --> C[访问指针变量中指向地址的值,用*]
例子:
package main
import "fmt"
func main() {
var a int = 10
var b *int = &a
var c **int = &b
var d ***int = &c
// 可以看到对于上面的c是指向指针的指针变量,而d是指向指针的指针的指针变量,大概懂那个意思就行,每一次都加一个*
fmt.Printf("a的值: %v\ta的类型:%T\ta的地址:%p\n", a, a, &a)
// a的值: 10 a的类型:int a的地址:0xc000016098
fmt.Printf("b的值: %v\tb的类型:%T\tb的地址:%p\n", b, b, &b)
// b的值: 0xc000016098 b的类型:*int b的地址:0xc000006028
fmt.Printf("获取b指针的值: %v\n", *b)
// 获取b指针的值: 10
fmt.Printf("c的值: %v\tc的类型:%T\tc的地址:%p\n", c, c, &c)
// c的值: 0xc000006028 c的类型:**int c的地址:0xc000006030
fmt.Printf("获取c指针的值: %v\t获取c指针的指针的值: %v\n", *c, **c)
// 获取c指针的值: 0xc000016098 获取c指针的指针的值: 10
fmt.Printf("d的值: %v\td的类型:%T\td的地址:%p\n", d, d, &d)
// d的值: 0xc000006030 d的类型:***int d的地址:0xc000006038
fmt.Printf("获取d指针的值: %v\t获取d指针的指针的值: %v\t获取d指针的指针的指针的值: %v\n", *d, **d, ***d)
// 获取d指针的值: 0xc000006028 获取d指针的指针的值: 0xc000016098 获取d指针的指针的指针的值: 10
}
肆 空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil,nil 指针也称为空指针,nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值,这时候是不可以给该指针常用类型的值的,如:int、float、bool、string等,指针变量只能存储地址。
package main
import "fmt"
func main() {
var ptr *int
fmt.Printf("ptr 的值为 : %x\n", ptr )
//ptr 的值为 : 0
}
伍 指针数组
需要将数组保存到指针中,我们可以对整型指针数组进行以下声明:
var ptr [MAX]*int;
// ptr:是指针数组
// MAX是指针数组的数量
// *int:指针数组的类型
ptr 为整型指针数组。因此每个元素都指向了一个值。以下实例的三个整数将存储在指针数组中:
package main
import "fmt"
// 定义一个MAX,是方便统一管理数组的数量
const MAX int = 3
func main() {
a := []int{10,100,200}
var i int
var ptr [MAX]*int;
for i = 0; i < MAX; i++ {
ptr[i] = &a[i] /* 整数地址赋值给指针数组 */
}
for i = 0; i < MAX; i++ {
fmt.Printf("a[%d]地址 = %d,a[%d]值 = %d,\n", i, ptr[i], i, *ptr[i])
}
}
可以看到数组是引用类型,*号不在数组前面定义而是在其类型上定义。
陆 指针作为函数参数
Go 语言允许向函数传递指针,只需要在函数定义的参数上设置为指针类型即可。
以下实例演示了如何向函数传递指针,并在函数调用后修改函数内的值:
package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 100
var b int= 200
fmt.Printf("交换前 a 的值 : %d\n", a )
fmt.Printf("交换前 b 的值 : %d\n", b )
/* 调用函数用于交换值
* &a 指向 a 变量的地址
* &b 指向 b 变量的地址
*/
swap(&a, &b);
fmt.Printf("交换后 a 的值 : %d\n", a )
fmt.Printf("交换后 b 的值 : %d\n", b )
}
func swap(x *int, y *int) {
var temp int
temp = *x /* 保存 x 地址的值 */
*x = *y /* 将 y 赋值给 x */
*y = temp /* 将 temp 赋值给 y */
}
柒 new和make
我们先来看一个例子:
func main() {
var a *int
*a = 100
// 为什么可以b := 100,a = &b呢?
// 个人理解是因为a=&b表示&b讲地址的值赋值给a,而*a=100,表示不能直接将100给*a,因为*a本身没有存储100的地址,需要申请一块存储100的地址,才能存储100
fmt.Println(*a)
var b map[string]int
b["A7"] = 100
fmt.Println(b)
}
执行上面的代码会引发panic,为什么呢? 在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间。要分配内存,就引出来今天的new和make。 Go语言中new和make是内建的两个函数,主要用来分配内存。
7.1 new
new是一个内置的函数,是用于内存分配的,与make差不多,不过make只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。,它的函数格式如下:
func new(Type) *Type
// Type:表示类型,new函数只接受一个参数,这个参数是一个类型
//*Type:表示类型指针,new函数返回一个指向该类型内存地址的指针。
new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为类型零值。举个例子:
func main() {
a := new(int)
b := new(bool)
fmt.Printf("%T\n", a) // *int
fmt.Printf("%T\n", b) // *bool
fmt.Println(*a) // 0
fmt.Println(*b) // false
}
而var a *int
只是声明了一个指针变量a,但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。应该按照如下方式使用内置的new函数对a进行初始化之后就可以正常对其赋值了:
func main() {
var a *int
a = new(int)
*a = 10
fmt.Println(*a)
}
7.2 new与make的区别
a. 二者都是用来做内存分配的
b. new用于的内存分配,会为类型的新值分配已置零的内存空间,返回的是指向类型的指针
c. 而make只用于slice、map以及channel的初始化,会被编译器翻译成具体的创建函数,由其分配内存和初始化成员结构,说白了make返回初始化之后类型的值,这个值并不是这个类型的零值,也不是指针,是经过初始化之后的类型的引用,所以返回的还是这三个引用类型本身,new是代替不了make的
捌 什么情况下使用指针(注意)
- a.不要对 map、slice、channel 这类引用类型使用指针;
- b.如果需要修改参数的值或者内部数据时,也需要使用指针类型的参数;
- c.如果是比较大的数据类型时,每次参数传递或者调用方法都要内存拷贝,内存占用多,这时候可以考虑使用指针;
- d.像 int、bool 这样的小数据类型没必要使用指针;
- e.如果需要并发安全,则尽可能地不要使用指针,使用指针一定要保证并发安全;
- f.指针最好不要嵌套,也就是不要使用一个指向指针的指针,虽然 Go 语言允许这么做,但是这会使你的代码变得异常复杂。