Golang 指针

任何程序数据载入后,在内存中都有它们的地址,这个地址就是指针

  • 寻址:对变量进行 &( 寻址 )操作,可以获取这个变量的内存地址
    • 指针变量:为了保存这个变量的内存地址,需要定义一个指针变量
  • 取值:对指针变量进行 * ( 取值 )操作,就可以获得原变量的值


  • 内存分配:make()  /  new(),二者都是用来做内存分配的,区别在于
    • make,只用于 slice、map 以及 channel 的( 引用类型 )初始化,返回的是这三个引用类型本身
    • new用于类型的内存分配,返回的是指向类型的指针,且内存对应的值为类型零值

Golang 中,指针不像 C 语言里,那么难理解,使用指针,可以更简单的执行一些任务


指针的声明与使用

( 常规操作 ):声明指针变量( *type ) — 为指针变量寻址( & ) — 获取指针变量对应的内存地址的值( * )

(1)声明指针变量

Go 语言中的值类型( int、float、bool、string、array、struct )都有对应的指针类型,如:*int、*int64、*string 等

	// (1)声明:var varName *varType
	var pointer1 *int               // 指向int类型的指针变量
	var pointer2 *float64           // 指向float64类型的指针变量
	fmt.Println(pointer1, pointer2) // <nil> <nil>

	// 空指针判断:当一个指针被定义后,没有分配任何变量时,它的值为 nil,即:空指针
	fmt.Println(pointer1 != nil) // false
	fmt.Println(pointer2 == nil) // true

(2)为指针变量寻址( & )和获取指针变量对应的内存地址的值( * )

	// (1)指针的寻址( & )和 取值( * )
	var a int = 10
	var aPointer *int = &a // 通过 & 寻址,内存地址 0x1400011c020
	fmt.Println(aPointer)
	fmt.Println(*aPointer) // 通过 * 指向,获取具体的值 10

	// (2)数组指针:首先,它是一个指针,然后,它指向数组      // 区别于(3)指针数组( 一个数组元素为指针的数组,具体见数组 )
	arr := [5]int{1, 2, 3, 4, 5}

	var arrPointer *[5]int = &arr
	fmt.Println(arrPointer)  // &[1 2 3 4 5]
	fmt.Println(*arrPointer) // [1 2 3 4 5]

	// (4)相应的,指针可以指向各种数据类型的内存地址 slice、map ...

	// (5)指向指针的指针( 可以有多级,以此类推 )
	var b int = 10 // 具体的值
	// 一级指针
	var bPointer *int = &b
	fmt.Println(bPointer, *bPointer) // 0x140000a4050 10
	// 二级指针:var ptr **int  使用 **ptr,可以获取具体的值
	var bbPointer **int = &bPointer
	fmt.Println(bbPointer, *bbPointer, **bbPointer) // 0x14000120020 0x14000126050 10

( 内存分配 ):使用 new 函数( 分配内存 ),直接初始化不同类型的指针

	// 对于值类型的声明,不需要分配内存空间,是因为,它们在声明的时候已经默认分配好了内存空间
	var a int
	a = 100
	fmt.Println(a) // 100

	// 对于引用类型的变量,在使用的时候不仅要声明它,还要为它分配内存空间,否则,值就没办法存储
	var b map[string]int
	//b["hello"] = 100
	//fmt.Println(b) // panic: assignment to entry in nil map

	// Golong 中,new 和 make 是内建的两个函数,主要用来分配内存
	b = make(map[string]int, 10)
	b["hello"] = 100 // map[hello:100]
	fmt.Println(b)

「 new 」new 函数,是一个内置函数,实际中,并不常用

	func new(Type) *Type
	// Type,表示类型,new 函数只接受这一个参数
	// *Type,表示指针类型,new 函数返回一个指向该类型内存地址的指针
	var a *int
	// 指针,作为一个引用类型,必须分配内存后,才能赋值
	// a = 100 // '100' (type untyped int) cannot be represented by the type *int
	a = new(int)
	// new 函数不太常用,使用 new 函数得到的是一个类型的指针,且,该指针对应的值为该类型的零值
	fmt.Println(a)  // 0x14000126008
	fmt.Println(*a) // 0
	// 使用 new 函数分配内存后,就可以正常赋值了
	*a = 100
	fmt.Println(*a) // 100

「 make 」make 函数,也是用于内存分配的,区别于 new,它只用于 slice、map 及 channel 这三个引用类型的内存创建,因为这三种类型本身就是引用类型,所以,就没必要返回指针类型了,而是返回这三个类型本身

func make(t Type, size ...IntegerType) Type

	var b map[string]int
	b = make(map[string]int, 10)
	b["hello"] = 100 // map[hello:100]
	fmt.Println(b)

使用指针作为函数参数( 推荐 )

Golang 中的传值方式,是值传递,这意味着给变量赋值、给函数传参时,都是直接拷贝一个副本,将副本赋值给对方

这样的拷贝方式,意味着:

  • 如果数据结构体积庞大,则要完整拷贝一个数据结构副本( 如,长度为 100w 的数组 )时,效率会很低
    • 如果传递给函数的是:数组的指针,只需要 8 个字节就可以了     // 数组,是复合类型,不是引用类型
  • 函数内部修改数据结构时,只能在函数内部生效,函数一退出就失效了,因为,它修改的是副本对象
    • 实际上,对于引用类型来说,所谓的副本,是“引用”,所以,修改函数参数,对原数据也会产生影响
func main() {
	// 使用指针作为函数参数
	c := 100
	d := 200
	fmt.Println(c, d) // 100 200
	swap(&c, &d)
	fmt.Println(c, d) // 200 100

}

// 交换函数
func swap(x *int, y *int) {
	// 使用指针传值,将会对原数据进行修改
	*x, *y = *y, *x
}