大家好,我是 frank。

蓝色

文字「Golang 语言开发栈」关注公众号。

01

介绍

*

指针类型变量也需要一块内存空间存储值,指针变量的值就是它所指向数据的内存地址,而普通变量的值就是具体存放的数据。不同的指针类型变量之间无法互相赋值,在 Golang 语言中,指针不支持运算,也不能获取常量的指针。

02

指针定义

在 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

指针操作

*

示例代码:

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