要搞明白 Go 语言中的指针需要先知道 3 个概念:指针地址、指针类型、指针取值

  • 指针地址(&a)
  • 指针取值(*&a)
  • 指针类型(&a) —> *int 改变数据传指针
  • 变量的本质是给存储数据的内存地址起了一个好记的别名。
  • 比如我们定义了一个变量 a := 10 ,这个时候可以直接通过 a 这个变量来读取内存中保存的 10 这个值。
  • 在计算机底层 a 这个变量其实对应了一个内存地址。
  • 指针也是一个变量,但它是一种特殊的变量,它存储的数据不是一个普通的值,而是另一个变量的内存地址。
  • Go 语言中的指针操作非常简单,我们只需要记住两个符号:&(取地址)和 *(根据地址取值)
func main() {
	var a = 10
	fmt.Printf("%d \n",&a)        // &a 指针地址 (824633761976)
	fmt.Printf("%d \n",*&a)       // *&a 指针取值 (10)
	fmt.Printf("%T \n",&a)        // %T 指针类型 (*int )
}

&符号取地址操作

  • 每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。
  • Go 语言中使用&字符放在变量前面对变量进行取地址操作。
  • Go 语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型

取变量指针的语法如下:

ptr := &v      // 比如 v 的类型为 T
  • v : 代表被取地址的变量,类型为 T
  • ptr : 用于接收地址的变量,ptr 的类型就为*T,称做 T 的指针类型。*代表指针。
func main() {
	var a = 10
	var b = &a
	fmt.Printf("a:%d ptr:%p\n", a, &a)    // a:10 ptr:0xc0000100a8
	fmt.Printf("b:%v type:%T\n", b, b)    // b:0xc0000100a8 type:*int
	fmt.Println("取 b 的地址:", &b)    // 取 b 的地址: 0xc000006028
}

b := &a的图示

*指针取值

在对普通变量使用&操作符取地址后会获得这个变量的指针,然后可以对指针使用操作,也就是指针取值

func main() {
	a := 10
	b := &a   // 取变量 a 的地址,将地址保存到指针 b 中
	fmt.Printf("type of b:%T\n", b)     // type of b:*int
	c := *b    // 指针取值(根据指针的值去内存取值)
	fmt.Printf("type of c:%T\n", c)    // type of c:int
	fmt.Printf("value of c:%v\n", c)   // value of c:10
}

变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

  • 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
  • 指针变量的值是指针地址。
  • 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。

指针传值示例

func modify1(x int) {
	x = 100
}
func modify2(x *int) {
	*x = 100
}
func main() {
	a := 10
	modify1(a)
	fmt.Println(a)    // 10
	modify2(&a)
	fmt.Println(a)    // 100
}

make 和 new

Go 语言中 new 和 make 是内建的两个函数,主要用来创建并分配类型的内存。

  • make 关键字的作用是创建于 slice、map 和 channel 等内置的数据结构
  • new 的作用是为类型申请一片内存空间,并返回指向这片内存的指针
func main() {
	a := make([]int, 3, 10)       // 切片长度为 1,预留空间长度为 10
	a = append(a,1)
	fmt.Printf("%v--%T \n",a,a)   // [0 0 0]--[]int   值----切片本身
	
	var b = new([]int)
	//b = b.append(b,2)          // 返回的是内存指针,所以不能直接 append
	*b = append(*b, 3)        // 必须通过 * 指针取值,才能进行 append 添加
	fmt.Printf("%v--%T",b,b)    // &[]--*[]string  内存的指针---内存指针
}

new函数

系统默认的数据类型,分配空间

func main() {
	// 1.new实例化int
	age := new(int)
	*age = 1

	// 2.new实例化切片
	li := new([]int)
	*li = append(*li, 1)

	// 3.实例化map
	userinfo := new(map[string]string)
	*userinfo = map[string]string{}
	(*userinfo)["username"] = "张三"
	fmt.Println(userinfo)    // &map[username:张三]
}

自定义类型使用 new 函数来分配空间

type Student struct {
	name string
	age  int
}

func main() {
	s := new(Student) //分配空间
	s.name = "李四"
	fmt.Println(s) //&{李四 0}
}

make函数

  • make 也是用于内存分配的,但是和 new 不同,它只用于 chan、map 以及 slice 的内存创建
  • 而且它返回的类型就是这三个类型本身,而不是他们的指针类型
  • 因为这三种类型就是引用类型,所以就没有必要返回他们的指针了
  • 当我们为slice分配内存的时候,应当尽量预估到slice可能的最大长度
  • 通过给make传第三个参数的方式来给slice预留好内存空间
  • 这样可以避免二次分配内存带来的开销,大大提高程序的性能。
func main() {
	a := make([]int, 3, 10)       // 切片长度为 1,预留空间长度为 10
	b := make(map[string]string)
	c := make(chan int, 1)
	fmt.Println(a,b,c)          // [0 0 0]  map[]  0xc0000180e0
}