## 什么是指针 程序运行时的数据是存放在内存中的,而内存会被抽象为一系列具有连续编号的存储空间,那么每一个存储在内存中的数据都会有一个编号,这个编号就是内存地址。有了这个内存地址就可以找到这个内存中存储的数据,而内存地址可以被赋值给一个指针。 可以总结为:在编程语言中,指针是一种数据类型,用来存储一个内存地址,该地址指向存储在该内存中的对象。这个对象可以是字符串、整数、函数或者你自定义的结构体。 ## 指针的声明和定义 在 Go 语言中,获取一个变量的指针非常容易,使用取地址符 & 就可以,比如下面的例子: ``` func main() { strVal := "PHP is the best language in the world!" strPtr := &strPtr//取地址 fmt.Println("strPtrr变量的值为:",strPtrr) fmt.Println("strPtr变量的内存地址为:",strPtr) } ``` > 指针类型非常廉价,只占用 4 个或者 8 个字节的内存大小。 以上示例中 strPtr 指针的类型是 *string,用于指向 string 类型的数据。在 Go 语言中使用类型名称前加 `*` 的方式,即可表示一个对应的指针类型。比如 int 类型的指针类型是 *int,float64 类型的指针类型是 *float64,自定义结构体 A 的指针类型是 *A。总之,指针类型就是在对应的类型前加 * 号。 此外,除了可以通过简短声明的方式声明一个指针类型的变量外,也可以使用 var 关键字声明, `var intP *int ` > 通过 var 声明的指针变量是不能直接赋值和取值的,因为这时候它仅仅是个变量,还没有对应的内存地址,它的值是 nil。 和普通类型不一样的是,指针类型还可以通过内置的 new 函数来声明,如下所示: ``` intP := new(int) ``` 内置的 new 函数有一个参数,可以传递类型给它。它会返回对应的指针类型,比如上述示例中会返回一个 *int 类型的 intP。 ## 指针的操作 在 Go 语言中指针的操作无非是两种:一种是获取指针指向的值,一种是修改指针指向的值。 要获取指针指向的值,只需要在指针变量前加 * 号即可: ``` strVal := *strPtr fmt.Println("strPtr指针指向的值为:", strVal) ``` 修改指针指向的值也非常简单 ``` *strPtr = "hello world" //修改指针指向的值 fmt.Println("strPtr指针指向的值为:",*strPtr) // hello world fmt.Println("strPtr变量的值为:", str) // hello world ``` 通过打印结果可以看到,不光 strPtr 指针指向的值被改变了,变量 strVal 的值也被改变了,这就是指针的作用。因为变量 strVal 存储数据的内存就是指针 strPtr 指向的内存,这块内存被 strPtr 修改后,变量 strVal 的值也被修改了。 通过 var 关键字直接定义的指针变量是不能进行赋值操作的,因为它的值为 nil,也就是还没有指向的内存地址。比如下面的示例: ``` var intP *int *intP =10 ``` 运行的时候会提示 `invalid memory address or nil pointer dereference`。这时候该怎么办呢?其实只需要通过 new 函数给它分配一块内存就可以了,如下所示: ``` var intPtr *int = new(int) 或者 //intPtr := new(int) ``` ## 指针接收者 对于是否使用指针类型作为接收者,有以下几点参考: 1. 如果接收者类型是 map、slice、channel 这类引用类型,不使用指针; 2. 如果需要修改接收者,那么需要使用指针; 3. 如果接收者是比较大的类型,可以考虑使用指针,因为内存拷贝廉价,所以效率高。 ## 什么情况下使用指针 从以上指针的详细分析中,可以总结出指针的两大好处: 1. 可以修改指向数据的值; 2. 在变量赋值,参数传值的时候可以节省内存。 不过 Go 语言作为一种高级语言,在指针的使用上还是比较克制的。它在设计的时候就对指针进行了诸多限制,比如指针不能进行运行,也不能获取常量的指针。所以在思考是否使用时,我们也要保持克制的心态。 使用指针的建议: 1. 不要对 map、slice、channel 这类引用类型使用指针; 2. 如果需要修改方法接收者内部的数据或者状态时,需要使用指针; 3. 如果需要修改参数的值或者内部数据时,也需要使用指针类型的参数; 4. 如果是比较大的结构体,每次参数传递或者调用方法都要内存拷贝,内存占用多,这时候可以考虑使用指针; 5. 像 int、bool 这样的小数据类型没必要使用指针; 6. 如果需要并发安全,则尽可能地不要使用指针,使用指针一定要保证并发安全; 7. 指针最好不要嵌套,也就是不要使用一个指向指针的指针,虽然 Go 语言允许这么做,但是这会使你的代码变得异常复杂。