初探Golang数据结构之Slice的使用

 更新时间:2023年09月18日 16:47:36   作者:freesia13279  
在学习Go语言时,一直对数组和切片的使用场景好奇,不明白为什么推荐使用切片来代替数组,所以本文就来和大家梳理一下Slice切片的相关知识吧

在阅读Go语言圣经时,一直对数组和切片的使用场景好奇,不明白为什么推荐使用切片来代替数组。希望能通过一些梳理,能更好的理解切片和数组,找到他们合适的使用场景。

切片与数组

关于切片和数组怎么选择,我们来讨论下这个问题。

在Go中,数组是值类型,赋值和函数传参都会复制整个数组数据。

func main() {
    a := [2]int{100, 200}
    // 赋值
    var b = a
    fmt.Printf("a : %p , %v\n", &a, a)
    fmt.Printf("b : %p , %v\n", &b, b)
    // 函数传参
    f(a)
    f(b)
}
func f(array [2]int) {
    fmt.Printf("array : %p , %v\n", &array, array)
}

输出结果:

a : 0xc0000180a0 , [100 200]
b : 0xc0000180b0 , [100 200]
array : 0xc0000180f0 , [100 200]
array : 0xc000018110 , [100 200]

可以看到,四个内存地址都不相同,印证了前面的说法。当数组数据量达到百万级别时,复制数组会给内存带来巨大的压力,那能否通过传递指针来解决呢?

func main() {
    a := [1]int{100}
    f1(&a)
    fmt.Printf("array : %p , %v\n", &a, a)
}
func f1(p *[1]int) {
    fmt.Printf("f1 array : %p , %v\n", p, *p)
    (*p)[0] += 100
}

输出结果:

f1 array : 0xc0000b0008 , [100]
array : 0xc0000b0008 , [200]

可以看到,数组指针可以实现我们想要的效果,解决了复制数组带来的内存问题,不过函数接收的指针来自值拷贝,相对来说没有切片那么灵活。

func main() {
    a := [1]int{100}
    f1(&a)
    // 切片
    b := a[:]
    f2(&b)
    fmt.Printf("array : %p , %v\n", &a, a)
}
func f1(p *[1]int) {
    fmt.Printf("f1 array : %p , %v\n", p, *p)
    (*p)[0] += 100
}
func f2(p *[]int) {
    fmt.Printf("f2 array : %p , %v\n", p, *p)
    (*p)[0] += 100
}
//输出结果
f1 array : 0xc000018098 , [100]
f2 array : 0xc00000c030 , [200]
array : 0xc000018098 , [300]

可以看到,切片的指针和原来数组的指针是不同的。

总结

通常来说,使用数组进行参数传递会消耗较多内存,采用切片可以避免此问题。切片是引用传递,不会占用较多内存,效率更高一些。

切片的数据结构

切片在编译期是 cmd/compile/internal/types/type.go 包下的Slice类型,而它的运行时的数据结构位于 reflect.SliceHeader

type SliceHeader struct {
        Data uintptr // 指向数组的指针
        Len  int    // 当前切片的长度
        Cap  int    // 当前切片的容量,cap 总是 >= len
}
// 占用24个字节
fmt.Println(unsafe.Sizeof(reflect.SliceHeader{}))

切片是对数组一个连续片段的引用,这个片段可以是整个数组,也可以是数组的一部分。切片的长度可以在运行时修改,最小为0,最大为关联数组的长度,切片是一个长度可变的动态窗口

创建切片

使用make

slice := make([]int, 4, 6)

内存空间申请了6个int类型的内存大小。由于len=4,所以后面2个空间暂时无法访问到,但是容量是存在的。此时数组里每个变量都=0。

字面量

slice := []int{0, 1, 2}

nil切片和空切片

// nil 切片
var s []int
// 空切片 
s2 := make([]int, 0)
s3 := []int{}

空切片和 nil 切片的区别在于,空切片指向的地址不是nil,指向的是一个内存地址,但是它没有分配任何内存空间,即底层元素包含0个元素。

func main() {
    var s []int
    s2 := []int{}
    s3 := make([]int, 0)
    fmt.Println(s == nil)
    fmt.Println(s2 == nil)
    fmt.Println(s3 == nil)
}
// 输出结果
true
false
false

简单说,nil切片指针值为nil;而空切片的指针值不为nil,

需要说明的一点是,不管是使用 nil 切片还是空切片,对其调用内置函数 append,len 和 cap 的效果都是一样的。

但使用append时要注意:

  • 如果append追加的数据长度小于等于cap-len,只做一次数据拷贝。
  • 如果append追加的数据长度大于cap-len,则会分配一块更大的内存,然后把原数据拷贝过来,再进行追加。

特别当我们需要构建一个切片,是从原有切片复制而来时,要注意值覆盖问题。

func main() {
    s1 := []int{0, 1, 2, 3} // 先定义一个现有切片
    s2 := s1[0:1]           // 复制现有的切片
    s2 = append(s2, 100)
    fmt.Print(s1)
}

输出结果

[0 100 2 3]

原因还是切片本质上是数组的一个动态窗口,当cap够用时,不会新开辟内存空间进行复制,此时对数组的任何修改,都会对其他代理此数组的切片产生连带影响。

到此这篇关于初探Golang数据结构之Slice的使用的文章就介绍到这了,更多相关Go Slice内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 深入了解Golang中的格式化输出

    深入了解Golang中的格式化输出

    fmt是Go语言中用于控制文本输出的常用标准库,文中将通过示例详细讲解一下Go语言中不同形式的格式化输出,感兴趣的小伙伴可以了解一下
    2022-11-11
  • 浅谈Go数组比切片好在哪

    浅谈Go数组比切片好在哪

    Go1.17 会正式支持切片转换到数据,不再需要用以前那种骚办法了,本文就谈谈Go数组比切片好在哪,感兴趣的可以了解一下
    2021-09-09
  • Golang并发编程重点讲解

    Golang并发编程重点讲解

    这篇文章主要介绍了Golang并发编程,在许多环境中,实现对共享变量的正确访问所需要的微妙之处使并发编程变得困难。Go鼓励一种不同的方法,在这种方法中,共享值在通道中传递,实际上,从不由单独的执行线程主动共享
    2023-04-04
  • Kotlin编程基础语法编码规范

    Kotlin编程基础语法编码规范

    这篇文章主要为大家介绍了Kotlin编程条件控制示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Golang依赖注入工具digo的使用详解

    Golang依赖注入工具digo的使用详解

    这篇文章主要为大家详细介绍了Golang中依赖注入工具digo的使用,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-06-06
  • golang使用通道时需要注意的一些问题

    golang使用通道时需要注意的一些问题

    本文主要介绍了golang使用通道时需要注意的一些问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • Gin golang web开发模型绑定实现过程解析

    Gin golang web开发模型绑定实现过程解析

    这篇文章主要介绍了Gin golang web开发模型绑定实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Go实现字符串与数字的高效转换

    Go实现字符串与数字的高效转换

    在软件开发的世界里,数据类型转换是一项基础而重要的技能,尤其在Go语言这样类型严格的语言中,正确高效地进行类型转换对于性能优化和代码质量至关重要,本文给大家介绍了Go实现字符串与数字的高效转换,需要的朋友可以参考下
    2024-02-02
  • Go gRPC服务proto数据验证进阶教程

    Go gRPC服务proto数据验证进阶教程

    这篇文章主要为大家介绍了Go gRPC服务proto数据验证进阶教程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Go Error 嵌套实现创建方式

    Go Error 嵌套实现创建方式

    这篇文章主要介绍了Go Error 嵌套到底是怎么实现的?大家都知道创建error有两种方式分别是errors.new()另一种是fmt.errorf(),本文通过详细例子给大家介绍,需要的朋友可以参考下
    2022-01-01

最新评论