文章目录

  • 指针
  • 一 指针类型与指针变量
  • 二 指针取值
  • 三 new和make
  • 3.1 new函数
  • 3.2 make函数
  • 四 数组指针
  • 五 指针数组
  • 六 切片指针
  • 七 指针切片
  • 八 结构体指针
  • 九 指向指针的指针
  • 十一 内存模型


指针

区别于C/C++中的指针,Go语言中的指针不能进行偏移运算,是安全指针。

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

任何程序数据载入内存后,在内存都有他们的地址,这个地址就是指针。
而为了保存一个数据在内存中的地址,我们就需要指针变量。

比如,“永远不要高估自己”这句话是我的座右铭,我想把它写入程序中,
程序一启动这句话是要加载到内存(假设内存地址0x123456),
我在程序中把这段话赋值给变量A,把内存地址赋值给变量B
这时候变量B就是一个指针变量。通过变量A和变量B都能找到我的座右铭。

Go语言中的指针不能进行偏移和运算,因此Go语言中的指针操作非常简单,
我们只需要记住两个符号:&(取地址)和*(根据地址取值)。

一 指针类型与指针变量

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

var 指针变量 *类型

示例

package main

import "fmt"

func main()  {
	var a int = 10

	var p *int  // nil
	fmt.Println(a, p)  // 10 nil

	p = &a
	fmt.Printf("a:%d a ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
	fmt.Printf("p:%p type:%T\n", p, p) // b:0xc00001a078 type:*int
	fmt.Printf("&p:%p\n", &p)  // &p:0xc000006028
}

如下图所示:

go语言有core Go语言有指针吗_go语言有core

二 指针取值

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

package main

import "fmt"

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
}
  • 取地址操作符&和取值操作符*是一对互补操作符,
    &取出地址,*根据地址取出地址指向的值。
  • 变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
  • 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
  • 指针变量的值是指针地址
  • 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。

三 new和make

想要获得一个指针执行某种类型的地址空间,如果执行如下代码则会出错

package main

import "fmt"

func main()  {
	var a *int  // 初始化为 *int(nil)
	*a = 100  // 找不到地址, 出现错误
	fmt.Println(*a)
}
  • 由于a是一个指针变量,默认初始化为nil(地址为0x0)不知道地址在哪
  • 对无效的地址进行复制操作会引发panic错误

Go语言中对于 引用类型的变量 ,我们在使用的时候不仅要声明它,
还要为它分配内存空间,否则我们的值就没办法存储

而对于 值类型 的声明不需要分配内存空间,
是因为它们在声明的时候已经 默认分配好了内存空间

3.1 new函数

在堆区创建空间。释放空间是不需要管理。由引用计数自动回收内存

new函数用于分配值类型的指针,函数声明如下

func new(Type) *Type

其中,

  • Type表示类型,new函数只接受一个参数,这个参数是一个类型
  • *Type表示类型指针,new函数返回一个指向该类型内存地址的指针
package main

import "fmt"

func main()  {
	var a *int = new(int)
	fmt.Println(a)  // 0xc0000ac068
	*a = 100  // 向指针执行的内存地址保存数据
	fmt.Println(*a) // 100
}

var a *int只是声明了一个指针变量a但是没有初始化,
指针作为 引用类型 需要初始化后才会拥有内存空间,
才可以给它赋值。应该按照如下方式使用内置的new函数对a进行初始化之后
就可以正常对其赋值了

package main

import "fmt"

func main()  {
	var a *int
	a = new(int)  // 初始化
	fmt.Println(a)  // 0xc0000ac068
	*a = 100  // 向指针执行的内存地址保存数据
	fmt.Println(*a) // 100
}

3.2 make函数

make也是用于内存分配的,区别于new,它只用于slicemap以及channel 的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,
因为这三种类型就是引用类型,所以就没有必要返回他们的指针了

make函数的声明如下

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

make函数是无可替代的,我们在使用slicemap以及channel的时候,
都需要使用make进行初始化,然后才可以对它们进行操作。

package main

import "fmt"

func main()  {
	a := make([]int, 3, 10)
	fmt.Println(a)  // [0 0 0]
	fmt.Printf("%T\n", a)  // []int
}
  • make返回的是类型本身,初始化是slicemap以及channel三个类型,
    由与它们本身就是引用类型,没有必要返回他们的指针。

四 数组指针

前面接触到的都是执行类型的指针,对于数组我们也可以用指针去操作。由于go中指针不允许
进行运算,对于数组指针意义不大

var 指针变量*[长度]类型
package main

import "fmt"

func main()  {
    var a[5]int  // 声明一个数组
    a = [5]int{1,2,3,4,5}  // 初始化
  
    var p*[5]int  // 声明一个指向数组的指针 -- 数组指针
    p = &a  // 初始化
  
    for i := 0; i < 5; i++ {
      fmt.Println((*p)[i])
      fmt.Println(p[i])  // 与数组本身操作没有任何区别
    }
  
    fmt.Printf("%p\n", &a)  // 0xc0000d6060
    fmt.Printf("%p\n", &a[0]) // 0xc0000d6060
    fmt.Printf("%T\n", &a)  // *[5]int
    fmt.Printf("%T\n", &a[0])  // *int
    // &a与&a[0]虽然是同一个个地址,但是类型是不一样的。
}

五 指针数组

指针数组是用于存放指针的数组,定义方式如下

var 指针变量 [长度]*类型
  • 上述格式定义了一个存放某个类型指针的数组
package main

import "fmt"

func main()  {
	var a[5]*int   // 什么一个长度为5的数组,保存int类型的指针
	var b = [5]int{1,2,3,4,5}

	for i := 0; i < 5; i++ {
		a[i] = &b[i]
	}
	fmt.Println(a)  // [0xc00000a480 0xc00000a488 0xc00000a490 0xc00000a498 0xc00000a4a0]

	for i := 0; i < 5; i++ {
		fmt.Println(*a[i])
	}
}

六 切片指针

指向切片的指针, 切片名保存就是底层数组的首地址。
切片指针变量保存的切片名所在的地址

go语言有core Go语言有指针吗_python_02

package main

import "fmt"

func main() {
	var slice []int = []int{1,2,3,4,5}
	var p *[]int = &slice
	fmt.Printf("%p\n", p)  // 0xc000118060
	fmt.Printf("%p\n", *p)  // 0xc000156060
	fmt.Printf("%p\n", slice) // 0xc000156060
	fmt.Printf("%p\n", &slice[0]) // 0xc000156060
	fmt.Println((*p)[0])  // 通过切片指针获取切片底层数组保存的值 。
    *p = append(*p, 6, 7, 8, 9, 10,11,12,13,14,15,16,17,18)
    // *p == slice  修改切片对应的地址
    fmt.Println(*p)
    fmt.Println(slice)
    fmt.Printf("%p\n", slice)
}

切片指针作为函数参数

go语言有core Go语言有指针吗_指针_03

七 指针切片

指针切片,指切片的底层数组保存的是一个指针

package main

import "fmt"

func main() {
	a := 10
	b := 20
	var p []*int = []*int{&a, &b}
	fmt.Println(p)  // [0xc00000e0d8 0xc00000e100]
}

八 结构体指针

package main

import "fmt"

func main() {
  type Person struct { // 定义结构体类型
    name string
    city string
    age  int
  }
  var person Person = Person{"你好", "上海",12}

  fmt.Printf("%#v\n", person)
  var p * Person
  p = &person
  fmt.Printf("%p\n", p)  // 0xc0000743c0
  fmt.Printf("%p\n", &person.name)  // 0xc0000743c0
  fmt.Printf("%s\n", p.name)  // 通过结构体指针操作结构体成员
  fmt.Printf("%s\n", p.city)
  fmt.Printf("%d\n", p.age)
}

九 指向指针的指针

定义一个二级指针,二级指针变量保存一级指针变量的地址

var 变量名 **类型

go语言有core Go语言有指针吗_c++_04

package main

import "fmt"

func main() {
	a := 10

	p1 := &a
	p2 := &p1
	fmt.Printf("%p\n", &p1)  // 0xc0000d8018
	fmt.Printf("%p\n", p2)  // 0xc0000d8018
	fmt.Println(**p2)
}

十一 内存模型

go语言有core Go语言有指针吗_c++_05

  • 0~255: 系统占用,不允许使用
  • 代码区(只读): 保存计算机中的指令信息
  • 数据区: 存放程序需要使用的数据
  • 常量区域: const定义的常量,不能访问地址
  • 未初始化数据区域: 未初始化数据保存此位置,例如结构体
  • 初始化数据区域
  • 堆区: 保存一些数据,例如切片数据,new函数申请的内存等等
  • 栈区: 存放局部变量,函数信息
  • 最高地址段: 存放注册表