介绍
指针是一种数据类型,用来存储值的内存地址,为了便于理解,我们也可以把指针理解为内存地址,指针类型只占用内存 4 个或 8 个字节,在 Golang 语言中,类型名称前加 *
表示该类型的指针类型。
指针类型变量也需要一块内存空间存储值,指针变量的值就是它所指向数据的内存地址,而普通变量的值就是具体存放的数据。不同的指针类型变量之间无法互相赋值,在 Golang 语言中,指针不支持运算,也不能获取常量的指针。
指针定义
在 Golang 语言中,指针定义有 3 种方式:
第一种方式是使用取地址符 &
获取变量的指针(内存地址);
第二种方式是使用 var 关键字声明指针变量,使用 var 关键字声明的变量不能直接赋值和取值,因为它还没有内存地址,它的值是 nil;
第三种方式是使用内置的 new 函数来声明指针类型的变量,new 函数接收一个参数,可以传递类型给它,返回值是传递类型的指针类型。
示例代码:
func main () { // 方式 1 // 定义普通变量 a a := 1 // 定义指针变量 p p := &a fmt.Println("变量 a 的值为:", a) // 1 fmt.Println("变量 a 的内存地址为:", p) // 0xc0000ae008 fmt.Printf("变量 a 的类型为:%T\n", a) // int fmt.Printf("变量 p 的类型为:%T\n", p) // *int // 方式 2 var str string var p1 *int // 不同指针类型变量之间无法互相赋值 p1 = &str // ./main.go:29:5: cannot use &str (type *string) as type *int in assignment // 方式 3 p2 := new(int) fmt.Printf("%v %T\n", p2, p2) }
03
指针操作
在 Golang 语言中,指针操作包括取值和修改。取值就是获取指针指向的值,只需在指针变量前加 *
;修改就是修改指针指向的值,需要注意的是使用 var 关键字声明的指针变量不能直接赋值和取值,因为它还没有分配内存,它的值为 nil,可以使用内置函数 new 给它分配内存。
示例代码:
func main () { // 获取指针指向的值 b := 2 p3 := &b val := *p3 fmt.Println("变量 val 的值为:", val) // 修改指针指向的值 // 给 *p3 赋值,*p3 指向的值也被修改,因为 p3 指向的内存就是变量 b 的内存地址。 *p3 = 3 fmt.Println("*p3 指针指向的值为:", *p3) fmt.Println("变量 b 的值为:", b) var p4 *int = new(int) *p4 = 4 fmt.Println(*p4) }
04
指针应用
指针参数:
在 Golang 语言中,函数传递参数只有值传递,传递的实参都是参数原始值的拷贝副本,所以我们传递值类型的参数时,修改参数的值,原始数据不会被修改。但是,如果是指针类型的参数,修改参数的值,原始数据也会被修改,原因是指针类型的参数存储的是内存地址,并且和实参的内存地址相同。
示例代码:
func main () { // 值类型参数,实参的值未改变 mySalary := 80000 fmt.Printf("变量 mySalary 的内存地址为:%p\n", &mySalary) modifySalary(mySalary) fmt.Println(mySalary) // 指针类型参数,实参的值被改变 modifySalary2(&mySalary) fmt.Println(mySalary) } func modifySalary (salary int) { fmt.Printf("参数变量的内存地址为:%p\n", &salary) salary = 100000 } func modifySalary2 (salary *int) { fmt.Printf("参数变量的内存地址为:%p\n", salary) *salary = 100000 }
指针接收者:
在 Golang 语言中,定义一个方法,接收者可以是值类型和指针类型,二者都可以调用方法,因为 Golang 编译器会自动转换,所以二者是等价的。
示例代码:
type worker struct { name string salary uint } func (w *worker) raise () { w.salary += 1000 } func (w worker) raise1 () { w.salary += 1000 } func main () { // 值类型调用者 w := worker{ name: "frank", salary: 5000, } // 指针类型接收者 w.raise() fmt.Printf("w 的姓名是 %s,薪水是每月 %d\n", w.name, w.salary) // 值类型调用者 w1 :=worker{ name: "frank1", salary: 5000, } // 值类型接收者 w1.raise1() fmt.Printf("w1 的姓名是 %s,薪水是每月 %d\n", w1.name, w1.salary) // 指针类型调用者 w2 := &worker{ name: "lucy", salary: 5000, } // 指针类型接收者 w2.raise() fmt.Printf("w2 的姓名是 %s,薪水是每月 %d\n", w2.name, w2.salary) // 指针类型调用者 w3 := &worker{ name: "lucy1", salary: 5000, } // 值类型接收者 w3.raise1() fmt.Printf("w3 的姓名是 %s,薪水是每月 %d\n", w3.name, w3.salary) }
那么,应该在什么时候使用指针接收者呢?
我总结了以下两点:
- 如果需要修改接收者,可以使用指针修改指针指向数据的值。
- 如果接收者是非 map、slice 和 channel 类型,并且数据比较大,可以使用指针来节省内存。
05
总结
本文我们介绍了 Golang 语言中的指针,和指针定义与操作,并且介绍了指针作为指针参数和指针接收者的应用区别。使用指针虽然可以修改数据的值和节省内存,但是也给开发带来了复杂性,所以为了开发简单,在开发中除了必须使用指针类型外,尽量使用值类型,比如数据小的类型 int、bool 和需要并发安全的代码其实没有必要使用指针。
推荐阅读:
参考资料:
https://golang.org/doc/faq#methods_on_values_or_pointers