首页 > 编程笔记

Go语言指针切片(存储指针的切片)

切片是一种比较特殊的数据结构,这种数据结构更便于使用和管理数据集合。切片是围绕动态数组的概念构建的,可以按需自动增长和缩小,总的来说,切片可理解为动态数组,并根据切片里的元素自动调整切片长度。

Go 语言的切片指针是以切片表示的,切片的每个元素只能存放内存地址,切片指针的语法定义如下:
// 定义方式一
var name []*type
// 定义方式二
name := []*type{}
语法说明如下:
切片指针的定义与切片定义是相同的,只要在数据类型前面使用符号“*”即可变为切片指针。由于切片有多种不同的定义方式,因此切片指针也会有多种定义方式,这里只列举了常用的定义方式。

切片指针可以将多个变量的内存地址存放在切片中,这样方便管理多个变量,当需要修改某个变量的时候,由于变量的内存地址是不会改变的,直接修改变量或者从切片指针修改变量即可,修改后的数据都会同步到变量和切片指针中,示例如下:
package main

import "fmt"

func main() {
    // 定义一个空的字符串类型的切片指针
    var pslice []*string
    fmt.Printf("切片指针的元素:%v,内存地址:%v\n", pslice, &pslice)
    // 定义变量a、b、c并赋值
    var a, b string
    a, b = "a", "b"
    fmt.Printf("变量a、b的内存地址:%v、%v\n", &a, &b)
    // 使用内置函数方法append()将变量a、b、c的内存地址添加到切片指针
    pslice = append(pslice, &a)
    pslice = append(pslice, &b)
    fmt.Printf("切片指针的元素:%v\n", pslice)
    // 输出切片指针的元素所对应的数值
    // 使用取值操作符“*”从内存地址取值
    for _, k := range pslice{
         fmt.Printf("切片指针的元素所对应值:%v\n", *k)
    }
    // 从切片指针修改变量a的值,输出变量a
    *pslice[0] = "hello"
    fmt.Printf("修改后的变量值为:%v\n", a)
    // 修改变量b的值,输出切片指针的变量b的值
    b = "Golang"
    fmt.Printf("修改后的变量值为:%v\n", *pslice[1])
}
运行上述代码,运行结果为:

切片指针的元素:[],内存地址:0xc00000e028 
变量a、b的内存地址:0xc000010200、0xc000010210 
切片指针的元素:[0xc000010200 0xc000010210] 
切片指针的元素所对应值:a 
切片指针的元素所对应值:b 
修改后的变量值为:hello 
修改后的变量值为:Golang

分析上述代码,我们能得出以下结论:
如果不掌握切片指针的基本原理,在实际开发中程序很容易埋下难以寻找的 bug,示例如下:
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // 定义一个空的字符串类型的切片指针
    var pslice []*string
    // 定义字符串类型的变量a
    var a string
    // 循环5次,当前循环次数赋值给变量a,再写入切片指针
    for i := 0; i < 5; i++ {
         a = strconv.Itoa(i)
         pslice = append(pslice, &a)
    }
    // 输出切片指针的元素的数值
    for _, k := range pslice {
         fmt.Printf("切片指针的元素:%v,元素的值:%v\n", k, *k)
    }
}
分析上述代码,它定义了变量 a 和切片指针 pslice,再执行了两次 for 循环。第一次 for 循环是将每次循环次数赋值给变量 a,然后将变量 a 的内存地址写入切片指针 pslice;第二次 for 循环是输出切片指针 pslice 的元素和元素值,运行结果为:

切片指针的元素:0xc000010200,元素的值:4 
切片指针的元素:0xc000010200,元素的值:4 
切片指针的元素:0xc000010200,元素的值:4 
切片指针的元素:0xc000010200,元素的值:4 
切片指针的元素:0xc000010200,元素的值:4

从结果不难看出,切片指针 pslice 的所有元素都是同一个内存地址,元素值皆为 4,这说明在 for 循环中,每次循环都是将同一个内存地址的变量 a 写入切片指针 pslice,变量 a 的值不断被修改,直到最后一次循环为止,由于指针 pslice 的所有元素都是来自同一个变量 a,因此它们的内存地址和数值都是相同的。

如果要修改上述问题,只能在第一次 for 循环中重新定义变量 a,每次循环为变量 a 重新赋予新的内存地址,代码如下:
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // 定义一个空的字符串类型的切片指针
    var pslice []*string
    // 循环5次,当前循环次数赋值给变量a,再写入切片指针
    for i := 0; i < 5; i++ {
         // 定义字符串类型的变量a
         var a string
         a = strconv.Itoa(i)
         pslice = append(pslice, &a)
    }
    // 输出切片指针的元素的数值
    for _, k := range pslice {
         fmt.Printf("切片指针的元素:%v,元素的值:%v\n", k, *k)
    }
}
运行上述代码,运行结果为:

切片指针的元素:0xc000010200,元素的值:0 
切片指针的元素:0xc000010210,元素的值:1 
切片指针的元素:0xc000010220,元素的值:2 
切片指针的元素:0xc000010230,元素的值:3 
切片指针的元素:0xc000010240,元素的值:4

推荐阅读