8.28Go之指针

8.28Go之指针

Go语言指针的特点

  • 不能进行指针运算--->这个在本质上又区别了指针和变量

  • 允许控制特定集合的数据结构、分配的数量以及内存访问模式

指针(Pointer)在Go语言当中的两个核心概念

类型指针
  • 允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据

  • 类型指针不能进行偏移和运算

固定的不会动的

切片

  • 由指向起始元素的原始指针、元素数量和容量组成

这样做的好处:

  • 高效访问,又不会发生指针偏移,避免了非法修改关键性数据的问题。

  • 垃圾回收也容易对不会发生偏移的指针进行检索和回收。

  • 切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃

指针的几个核心概念

  • 指针地址

  • 指针类型

  • 指针取值


指针地址

  • 一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。

  • 当一个指针被定义后没有分配到任何变量时,它的默认值为 nil。指针变量通常缩写为 ptr。

获取指针变量的内存地址的操作符:

&

ptr := &v    // v 的类型为 T
/*
变量是v,变量的地址使用Ptr接收
ptr的类型为*T指针类型
*代表指针
*/
package main

import "fmt"

func main() {
/*声明两个变量*/
var num int = 1
str := "香蕉"

fmt.Printf("%p,%p\n", &num, &str)
/*
这只是获取了变量的内存地址
*/

/*声明指针并且赋值*/
var ptr *int

ptr = &num

/*打印ptr观察是否与&num值一致*/
fmt.Printf("%p", ptr)
}

会发现ptr == &num

变量、指针和地址三者的关系是,每个变量都拥有地址,指针的值就是地址。

指针指向指针,从指针获取指针指向的值

指针指向指针
package main

import "fmt"

func main() {
var house = "home"

//地址赋值指针
ptr := &house

//打印指针类型
fmt.Printf("Type is:%T\n", ptr)
//打印地址
fmt.Printf("Address is:%p\n", ptr)

//声明一个指针
var ptr2 **string
ptr2 = &ptr

fmt.Printf("Type is:%T\n", ptr2)
fmt.Printf("Value is:%d", ptr2)
}
从指针获取指针指向的值
package main

import "fmt"

func main() {
var house = "home"

//地址赋值指针
ptr := &house

//打印指针类型
fmt.Printf("Type is:%T\n", ptr)
//打印地址
fmt.Printf("Address is:%p\n", ptr)

//使用指针指向指针
value := *ptr //这个ptr本身的类型是*string
/*实际上value的类型不是**string。这样赋值以后是直接拿到了指针ptr指向的内存地址的值*/

//打印类型和值
fmt.Printf("Value type is:%T\n", value)
fmt.Printf("Vlaue is:%s\n", value)
}

小结:

  • value := *ptr这样声明变量的形式不会把value声明成**string的形式,而是直接拿到*ptr指向的值本身。而且value的值是string

  • 声明var value2 **string这样的变量可以显示声明value2的类型,并且value2 = ptr会报错,因为ptr的类型不是地址的引用,value2 = &ptr这样才是正确的

  • value2的值是&ptr的内存地址

使用指针修改值

通过修改指针的指向来达到这个目的:

package main

import "fmt"

/*写一个函数,交换传入的指针变量的值*/
func changePointerValue(arr *int, brr *int) {
//指针之间进行值的交换。
//使用中间变量的方法存储值
temp := *arr
*arr = *brr
*brr = temp
}

func main() {
//设置两个变量,指定变量的值。通过交换指针的指向达到改变两个变量的值
a := 200
b := 100

fmt.Printf("指针交换前a的值:\n", a)
fmt.Println("\n")
fmt.Printf("指针交换前b的值:\n", b)

/* 调用 swap() 函数
* &a 指向 a 指针,a 变量的地址
* &b 指向 b 指针,b 变量的地址
*/
changePointerValue(&a, &b)

fmt.Println("\n")
fmt.Println("指针交换后a的值:", a)
fmt.Println("指针交换后b的值:", b)
}

小结:

  • *操作符作为右值时,意义是取指针的值,作为左值时,也就是放在赋值操作符的左边时,表示 a 指针指向的变量。

  • *`操作符的根本意义就是操作指针指向的变量。当操作在右值时,就是取指向变量的值,当操作在左值时,就是将值设置给指向的变量。

  • &*本质上是直接引用与简介引用的区别

    • &只能位于变量的前面,表示取变量的内存地址

    • *只能位于指针的前面,表示取指针的指向的内存地址的值

    • **表示取指针的内存地址

修改指针值

测试修改指针的值能否修改指针的指向

package main

import "fmt"

func changePointer(arr, brr *int) (*int, *int) {
fmt.Printf("Befor change value is:%d,%d\n", arr, brr)
temp := arr
arr = brr
brr = temp
return brr,arr
}

func main() {
x, y := 1, 2
changePointer(&x, &y)
fmt.Printf("After change value is:%d,%d\n", &x, &y)
fmt.Println(x, y)
}

arr 和 brr 的变量值确实被交换。但和 a、b 关联的两个变量并没有实际关联。--->说明在指针和值并不是单链表的形式

创建指针的另一种方法New()

特点:

  • new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值(nil)。

package main

import "fmt"

func main() {
str := new(string) //有指向的地址值

var str2 *string //指向nil

fmt.Println(str)
fmt.Println(str2)
}

使用指针变量获取命令行输入信息

Go语言内置的 flag 包实现了对命令行参数的解析。在运行时输入对应的参数,经过 flag 包的解析后即可获取命令行的数据

示例代码:

package main

import (
"flag"
"fmt"
)

var mode = flag.String("mode", "", "process mode")
/*
通过 flag.String,定义一个 mode 变量,这个变量的类型是 *string。后面 3 个参数分别如下:
参数名称:在命令行输入参数时,使用这个名称。
参数值的默认值:与 flag 所使用的函数创建变量类型对应,String 对应字符串、Int 对应整型、Bool 对应布尔型等。
参数说明:使用 -help 时,会出现在说明中。
*/

func main() {
//解析命令行参数
flag.Parse() //解析命令行参数,并将结果写入到变量 mode 中。

//输出命令行参数
fmt.Println(*mode) //打印 mode 指针所指向的变量。
}
/*
之前已经使用 flag.String 注册了一个名为 mode 的命令行参数,flag 底层知道怎么解析命令行,并且将值赋给 mode*string 指针
在 Parse 调用完毕后,无须从 flag 获取值,而是通过自己注册的这个 mode 指针获取到最终的值。
*/

底层调用的图解:

 

posted @ 2021-08-28 13:27  俊king  阅读(39)  评论(0编辑  收藏  举报