• 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 ", &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 ", ptr)
    //打印地址
    fmt.Printf("Address is:%p ", ptr)

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

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

    import "fmt"

    func main() {
    var house = "home"

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

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

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

    //打印类型和值
    fmt.Printf("Value type is:%T ", value)
    fmt.Printf("Vlaue is:%s ", 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的值: ", a)
    fmt.Println(" ")
    fmt.Printf("指针交换前b的值: ", b)

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

    fmt.Println(" ")
    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 ", 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 ", &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 指针获取到最终的值。
    */

    底层调用的图解:

     

    It's a lonely road!!!
  • 相关阅读:
    Spark提交任务到集群
    在Spark中使用Kryo序列化
    Linux用户与用户组的详解
    Linux一键安装PHP/JAVA环境OneinStack
    Redis常用命令
    MySQL高效分页解决方案集
    linux 发邮件
    Linux 安全
    Linux Shell 文本处理工具集锦
    MySQL 获得当前日期时间(以及时间的转换)
  • 原文地址:https://www.cnblogs.com/JunkingBoy/p/15196623.html
Copyright © 2020-2023  润新知