Golang|Golang——指针的使用、数组指针和指针数组、指针与切片、指针与结构体、多级指针

指针:

  • 指针是一个特殊的变量,因为存储的数据不仅仅是一个普通的数据,而是一个变量的内存地址,指针本身也是有内存地址的
  • 指针的数据类型有int、float、bool、string、数组、结构体
  • 指针的作用就是可以通过变量/对象的内存地址去操作变量/对象
  • 并非所有对象都能进行取地址操作,但变量总是能正确返回(addressable)。指针运算符为左值时,我们可更新目标对象状态,而为右值时则是为了获取目标状态。
Golang|Golang——指针的使用、数组指针和指针数组、指针与切片、指针与结构体、多级指针
文章图片

注意:
  • 空指针:未初始化的指针 使用*p获取
  • 野指针:被无效地址空间初始化
  • 取址运算符&用于获取对象地址
  • 指针运算符*用于间接引用目标对象
  • 二级指针(一个指针指向另一个指针)**T,或包含包名*package.T
指针定义: 定义格式:
var 指针名 *类型

获取内存地址格式:
&变量/指针

通过指针修改变量的值:
// *指针变量是获取到内存地址指向的变量值,拿到后可以再修改,或者其它操作 *指针变量 = 值

演示:
func pointerDemo01() { a := 10 // 定义一个指针变量p,并把a的内存地址赋值给p var p *int // &可以获取内存地址 p = &a // 对比a的内存地址和p变量存的数据是一样的 fmt.Println("变量a的内存地址:", &a) fmt.Println("指针p的数据:", p) fmt.Println("指针p存的数据:", *p) fmt.Println("指针p的内存地址:", &p) // 通过指针修改数据,a的数据也同步修改 *p = 222 fmt.Println(a) }

指针类型支持相等运算符,但不能做加减法运算和直接类型转换。如果两个指针指向同一地址,或都为nil,那么它们相等
func main() { x := 10 p := &x p++// 无效运算:p++ (non-numeric type *int) var p2 *int = p + 1 // 无效运算: p + 1 (mismatched types *int and int) p2 = &x println(p == p2) }

零长度(zero-size)对象的地址是否相等和具体的实现版本有关,不过肯定不等于nil
即便长度为0,可该对象依然是合法存在的,也拥有内存地址,这与nil语义完全不同。
在runtime/malloc.go里有个zerobase全局变量,所有通过mallocgc分配的零长度对象都使用该地址。不过下例中,对象a、b在栈上分配,并未调用mallocgc函数
func main() { var a, b struct{} println(&a, &b) println(&a == &b, &a == nil) }

打印结果:
0x14000096f68 0x14000096f68 false false

操作指针的3个注意事项:
  1. 空指针
var p *int fmt.Println(p) //

  1. 不要操作没有合法指向的内存
var p *int *p = 111 fmt.Println(p) //invalid memory address or nil pointer dereference // 这里虽然定义了指针并赋值,但是并没有将指针指向任何有效的变量

  1. new函数使用
// 创建了一个int类型的内存空间,然后让p指向内存空间,然后把222保存到了内存空间中 p = new(int) *p = 222 fmt.Println(*p)

指针作为函数参数:
在函数中修改变量值,是不影响原来的变量的,可以通过指针去修改
不通过指针的时候,修改原来的变量,虽然变量都是a,但是内存地址是不一样的,所以在函数中修改完以后会发现原来的变量并没有被修改,但是通过指针去修改的时候是因为指向的是内存地址,所以对函数中的a进行操作,其实就是对原来的a进行操作,所以可以将原来的变量值修改掉
演示:
func main() { a := 10 pointerDemo03(&a) fmt.Println(a) }// 注意,指针作为函数的时候,参数也要加上* func pointerDemo03(a *int) { *a = 20 }

数组指针:
要分清指针数组和数组指针的区别。指针数组是指元素为指针类型的数组,数组指针是获取数组变量的地址
var 数组指针变量 *[索引] 类型

演示:
func pointerDemo05() { arr := [10]int{1, 2, 3, 3, 4, 5} var p *[10]int p = &arr fmt.Println(*p)// 获取数组中的全部数据 fmt.Println((*p)[0]) // 获取指定数组中索引的数据,因为*p是先运算,所以要先加括号,否则编译保存 fmt.Println(p[0])// 获取指定数组中索引的数据,这个格式和加括号一样,但是简化的写法 for i := 0; i < len(p); i++ { fmt.Print(p[i], ",") } }

数组作为函数参数:
func main() { arr := [10]int{1, 2, 3, 3, 4, 5} var p *[10]int p = &arr pointerDemo06(p) }func pointerDemo06(p *[10]int) { p[0] = 111 fmt.Println(p) }

指针数组:
指针数组指的是元素为指针类型的数组(一个数组中存储的都是指针),它就是一个存储了地址的数组。
定义格式:
var 数组名 [索引] *类型

演示:
func pointerDemo07() { var p [2]*int a := 10 b := 20 // 变量a的内存地址保存在指针数组p的0索引,b保存在1索引 p[0] = &a p[1] = &b fmt.Println(p)// 获取p数组中的内存地址 fmt.Println(*p[0], *p[1]) // 获取p数组中的指定索引数据 for i := 0; i < len(p); i++ { fmt.Println(*p[i]) // 获取p数组中的所有的数据 } for key, value := range p { fmt.Println(key, *value) } }

指针与切片:
其实就是定义指针,指向切片
演示:
func pointerDemo08() { s := []int{1, 2, 3, 4, 5} var p *[]int p = &s fmt.Println(*p) fmt.Println((*p)[0]) // 指针切片中没有简化的写法,只能加括号先运算指针,再运算切片 for i := 0; i < len(*p); i++ { fmt.Print("fori方式:", (*p)[i], ",") } for key, value := range *p { fmt.Println("range方式:", key, value) } }

指针与结构体:
其实就是定义指针,指向结构体
【Golang|Golang——指针的使用、数组指针和指针数组、指针与切片、指针与结构体、多级指针】演示:
type Student struct { // 成员名称不加var关键字 idint name string ageint addr string }func pointerDemo09() { stu := Student{001, "itzhuzhu", 23, "广州"} var p *Student p = &stu fmt.Println(*p)// 获取全部 fmt.Println((*p).name) // 获取指定的数据 fmt.Println(p.name)// 结构体指针中也是有简化写法的 p.addr = "深圳" fmt.Println(*p) }

将结构体指针作为函数参数:
func main() { stu := Student{001, "itzhuzhu", 23, "广州"} var p *Student p = &stu pointerDemo10(p) fmt.Println(stu) }```go func pointerDemo10(p *Student) { p.addr = "深圳" }

多级指针:
多级指针指的是,存放的都是上一级指针的内存地址,二级指针存的是一级指针的内存地址,以此类推,多级指针的概念和二维数组的概念类似。多级指针可以无限定义级别,几级指针定义的时候就要写几个*
定义格式:
var 指针变量名 *类型 // 几级指针就写几个*一个* 就是一级指针

演示:
func pointerDemo11() { a := 10 var p *int p = &a // 定义二级指针, var p2 **int //var p3 ***int //var p4 ****int p2 = &p fmt.Println(**p2) // 10 }

    推荐阅读