指针详解

1.1. 关于指针

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

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

import "fmt"

func pointer() {
    a := 10
    // 值
    fmt.Printf("%d \n", a)
    // 指针地址
    fmt.Printf("%d \n", &a)
    // 去内存地址取值
    fmt.Printf("%d \n", *&a)
    // 指针类型
    fmt.Printf("%T \n", &a)
}

func main() {
    pointer()
}

1.2. &取变量地址

&符号取地址操作

  • 每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。
  • Go 语言中使用&字符放在变量前面对变量进行取地址操作。
  • Go 语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型
  • 取变量指针的语法如下:
ptr := &v      // 比如 v 的类型为 T
  • v: 代表被取地址的变量,类型为 T
  • ptr: 用于接收地址的变量,ptr的类型就为*T,称做 T 的指针类型。*代表指针。
package main

import "fmt"

func pointer2() {
    a := 10
    b := &a
    fmt.Printf("%d prt: %p \n", a, &a)
    fmt.Printf("%v type: %T \n", b, b)
    fmt.Printf("取b的地址 %d", &b)
}

func main() {
    pointer2()
}

b := \&a 的图示

1.3. 指针修改数据

*指针取值

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

import "fmt"

func pointer3() {
    a := 10
    b := &a
    fmt.Printf("type: %T \n", b)
    c := *b
    fmt.Printf("value: %d type: %[1]T \n", c)
}

func main() {
    pointer3()
}

  • 变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
    • 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
    • 指针变量的值是指针地址。
    • 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。

指针传值示例

package main

import "fmt"

func pointer5(x *int) {
    *x = 9
    fmt.Printf("value: %d type: %[1]T \n", x)
    fmt.Printf("value: %d type: %[1]T \n", *x)
}

func main() {
    var a = 10
    pointer4(a)
    fmt.Println(a)
    pointer5(&a)
    fmt.Println(a)
}

1.4. new 和 make

执行报错

  • 执行下面的代码会引发 panic,为什么呢?
  • 在 Go 语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。
  • 而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间。
  • 要分配内存,就引出来今天的 new 和 make。
  • Go 语言中 new 和 make 是内建的两个函数,主要用来分配内存。
package main

import "fmt"

func main() {
    var a map[string]string
    a["name"] = "张三"
    fmt.Println(a)
}

make和new比较

  • new 和 make 是两个内置函数,主要用来创建并分配类型的内存。
  • make和new区别
    • make 关键字的作用是创建于 slice、map 和 channel 等内置的数据结构
    • new 的作用是为类型申请一片内存空间,并返回指向这片内存的指针
package main

import "fmt"

func createMake() {
    a := make(map[string]string)
    a["name"] = "张三"
    fmt.Println(a)

    b := make([]int, 2, 5)
    b = append(b, 5)
    fmt.Println(b)

    b1 := new([]int)
    *b1 = append(*b1, 3)
    fmt.Println(b1)

}

func main() {
    createMake()
}

new函数

  • 一:系统默认的数据类型,分配空间
package main

import "fmt"

func createNew() {
    // 实例化int
    a := new(int)
    *a = 10
    fmt.Printf("value %d, type %[1]T \n", *a)
    // 实例化slice
    b := new([]int)
    *b = append(*b, 3)
    fmt.Printf("value %d, type %[1]T \n", *b)
    // 实例化map
    c := new(map[string]string)
    *c = map[string]string{}
    (*c)["name"] = "张三"
    fmt.Printf("value %v, type %[1]T \n", *c)
}

func main() {
    createNew()
}

  • 二:自定义类型使用 new 函数来分配空间
package main

import "fmt"

type people struct {
    name string
    age  int
}

func main() {
    var a *people
    // 分配空间
    a = new(people)
    a.name = "张三"
    fmt.Printf("value %v, type %[1]T \n", a)
}

make函数

  • make 也是用于内存分配的,但是和 new 不同,它只用于 chan、map 以及 slice 的内存创建
  • 而且它返回的类型就是这三个类型本身,而不是他们的指针类型
  • 因为这三种类型就是引用类型,所以就没有必要返回他们的指针了
package main

import "fmt"

func main() {
    a := make([]int, 2, 4)
    b := make(map[string]int)
    var c = make(chan int, 1)
    fmt.Printf("value %v, type %[1]T \n", a)
    fmt.Printf("value %v, type %[1]T \n", b)
    fmt.Printf("value %v, type %[1]T \n", c)
}

  • 当我们为slice分配内存的时候,应当尽量预估到slice可能的最大长度
  • 通过给make传第三个参数的方式来给slice预留好内存空间
  • 这样可以避免二次分配内存带来的开销,大大提高程序的性能。

1.5. 指针的指针

name := "武沛齐"

// 声明一个指针类型变量p1,内部存储name的内存地址
var p1 *string = &name

// 声明一个指针的指针类型变量p2,内部存储指针p1的内存地址
var p2 **string = &p1

// 声明一个指针的指针的指针类型变量p3,内部存储指针p2的内存地址
var p3 ***string = &p

因为有指针的指针存在,所以在使用指针进行重置值时,也需要将相应的*号设置好,例如:

package main

import "fmt"

func main() {
   name := "武沛齐"

   // 声明一个指针类型变量p1,内部存储name的内存地址
   var p1 *string = &name

   *p1 = "张三" // 将name的内存中的值由 武沛齐 改为 张三

   // 声明一个指针的指针类型变量p2,内部存储指针p1的内存地址
   var p2 **string = &p1
   **p2 = "啦啦啦" // 将name的内存中的值由 张三 改为 啦啦啦

   var p3 ***string = &p2
   ***p3 = "我靠" // 将name的内存中的值由 啦啦啦 改为 我靠
}

1.6. 指针小高级操作

数组的地址 == 数组的第一个元素的地址。

dataList := [3]int8{11, 22, 33}

fmt.Printf("数组的地址:%p;数组第一个元素的地址:%p \n", &dataList, &dataList[0])
// &dataList 和 &dataList[0] 的内存中存储的数据虽然相同gogo,但他们是两个不同类型的指针。
// &dataList 是 *[3]int8 类型
// &dataList[0] 是 *int8 类型

1.7. 指针的计算

gogopackage main

import (
    "fmt"
    "unsafe"
)

func main() {

    dataList := [3]int8{11, 22, 33}

    // 1.获取数组第一个元素的地址(指针)
    var firstDataPtr *int8 = &dataList[0]

    // 2.转换成Pointer类型
    ptr := unsafe.Pointer(firstDataPtr)

    // 3.转换成uintptr类型,然后进行内存地址的计算(即:地址加1个字节,意味着取第2个索引位置的值)。
    targetAddress := uintptr(ptr) + 1

    // 4.根据新地址,重新转换成Pointer类型
    newPtr := unsafe.Pointer(targetAddress)

    // 5.Pointer对象转换为 int8 指针类型
    value := (*int8)(newPtr)

    // 6.根据指针获取值
    fmt.Println("最终结果为:", *value)
}

results matching ""

    No results matching ""