Go语言中的Slice学习总结

概念

Slice切片是对底层数组Array的封装,在内存中的存储本质就是数组,体现为连续的内存块,Go语言中的数组定义之后,长度就已经固定了,在使用过程中并不能改变其长度,而Slice就可以看做一个长度可变的数组进行使用,最为关键的,是数组在使用的过程中都是值传递,将一个数组赋值给一个新变量或作为方法参数传递时,是将源数组在内存中完全复制了一份,而不是引用源数组在内存中的地址,为了满足内存空间的复用和数组元素的值的一致性的应用需求,Slice出现了,每个Slice都是都源数组在内存中的地址的一个引用,源数组可以衍生出多个Slice,Slice也可以继续衍生Slice,而内存中,始终只有源数组,当然,也有例外,后边再说。

用法

1.Slice的定义

Slice可以通过两种方式定义,一种是从源数组中衍生,一种是通过make函数定义,本质上来说都一样,都是在内存中通过数组的初始化的方式开辟一块内存,将其划分为若干个小块用来存储数组元素,然后Slice就去引用整个或者局部数组元素。
直接初始化一个Slice:

代码如下:

s := []int{1, 2, 3}

注意,这与初始化数组有一点点区别,有的同学认为这个写法是定义和初始化一个数组,事实上这个写法是现在内存中构建一个包括有3个元素的数组,然后将这个数组的应用赋值给s这个Slice,通过以下数组的定义进行区别:

代码如下:

a := [3]int{1, 2, 3}
 b := [...]int{1, 2, 3}
 c := []int{1, 2, 3}
 fmt.Println(cap(a), cap(b), cap(c))
 a = append(a, 4)//Error:first argument to append must be slice; have [3]int
 b = append(b, 4)//Errot:first argument to append must be slice; have [3]int
 c = append(c, 4)//正常,说明变量c是Slice类型

可以看出,强调了数组定义的规则:长度和类型必须指定,若是根据实际元素个数自动计算数组长度,需要使用[...]定义,而不能只使用[]。

从数组中切片构建Slice:

代码如下:

a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
 s := a[2:8]
 fmt.Println(s) //输出:[3 4 5 6 7 8]

定义一个数组a,截取下标为2到8之间部分(包括2不包括8),构建一个Slice。

通过make函数定义:

代码如下:

s := make([]int, 10, 20)
 fmt.Println(s) //输出:[0 0 0 0 0 0 0 0 0 0]

make函数第一个参数表示构建的数组的类型,第二个参数为数组的长度,第三个参数可选,是slice的容量,默认为第二个参数值。

2.Slice的长度和容量

Slice有两个比较混淆的概念,就是长度和容量,何谓长度?这个长度跟数组的长度是一个概念,即在内存中进行了初始化实际存在的元素的个数。何谓容量?如果通过make函数创建Slice的时候指定了容量参数,那内存管理器会根据指定的容量的值先划分一块内存空间,然后才在其中存放有数组元素,多余部分处于空闲状态,在Slice上追加元素的时候,首先会放到这块空闲的内存中,如果添加的参数个数超过了容量值,内存管理器会重新划分一块容量值为原容量值*2大小的内存空间,依次类推。这个机制的好处在能够提升运算性能,因为内存的重新划分会降低性能。

代码如下:

a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
 s := a[0:]
 s = append(s, 11, 22, 33)
 sa := a[2:7]
 sb := sa[3:5]
 fmt.Println(a, len(a), cap(a))    //输出:[1 2 3 4 5 6 7 8 9 0] 10 10
 fmt.Println(s, len(s), cap(s))    //输出:[1 2 3 4 5 6 7 8 9 0 11 22 33] 13 20
 fmt.Println(sa, len(sa), cap(sa)) //输出:[3 4 5 6 7] 5 8
 fmt.Println(sb, len(sb), cap(sb)) //输出:[6 7] 2 5

可以看出,数组的len和cap是永远相等的,并且是在定义的时候就已经指定的,不能改变。切片s引用这个数组的全部元素,初始长度和容量都为10,继续追加3个元素后,其长度变为13容量为20,。切片sa截取下标2到7的数组片段,长度为5,容量为8,这个容量的改变规则为原容量值减掉起始下标,此时若追加元素,会覆盖掉原内存地址中存在的值。切片sb截取切片sa下标3到5的数组片段,注意,这里的下标指的是sa的下标,不是源数组的下标,长度为2,容量为8-3=5。

3.Slice是引用类型

上边已经提到过,Slice是对源数组的一个引用,改变Slice中的元素的值,实质上就是改变源数组的元素的值。

代码如下:

a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
 sa := a[2:7]
 sa = append(sa, 100)
 sb := sa[3:8]
 sb[0] = 99
 fmt.Println(a)  //输出:[1 2 3 4 5 99 7 100 9 0]
 fmt.Println(sa) //输出:[3 4 5 99 7 100]
 fmt.Println(sb) //输出:[99 7 100 9 0]

可以看到,不管是append操作,还是赋值操作,都影响了源数组或者其他引用同一数组的Slice的元素。Slice进行数组引用的时候,其实是将指针指向了内存中具体元素的地址,如数组的内存地址,事实上是数组中第一个元素的内存地址,Slice也是如此。

代码如下:

a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
 sa := a[2:7]
 sb := sa[3:8]
 fmt.Printf("%p\n", sa)   //输出:0xc084004290
 fmt.Println(&a[2], &sa[0])      //输出:0xc084004290 0xc084004290
 fmt.Printf("%p\n", sb)   //输出:0xc0840042a8
 fmt.Println(&a[5], &sb[0])      //输出:0xc0840042a8 0xc0840042a8

4.Slice引用传递发生“意外”

上边我们一直在说,Slice是引用类型,指向的都是内存中的同一块内存,不过在实际应用中,有的时候却会发生“意外”,这种情况只有在像切片append元素的时候出现,Slice的处理机制是这样的,当Slice的容量还有空闲的时候,append进来的元素会直接使用空闲的容量空间,但是一旦append进来的元素个数超过了原来指定容量值的时候,内存管理器就是重新开辟一个更大的内存空间,用于存储多出来的元素,并且会将原来的元素复制一份,放到这块新开辟的内存空间。

代码如下:

a := []int{1, 2, 3, 4}
 sa := a[1:3]
 fmt.Printf("%p\n", sa) //输出:0xc0840046e0
 sa = append(sa, 11, 22, 33)
 fmt.Printf("%p\n", sa) //输出:0xc084003200

可以看到执行了append操作后,内存地址发生了变化,说明已经不是引用传递。

(0)

相关推荐

  • GO 语言学习指南

    1.导入包,一个主函数中只有一个main函数,包含一个main包,主函数中我们要申明main的包! 复制代码 代码如下: package main func main(){ } 2.引入包文件,譬如我们使用的是fmt包,打印helloword用,golang有一个包可见性的定义,就譬如其他语言中的private .protected这些声明.当手写字母是小写的就是包内可用,包外就不可以用了,手写字母大写,引用包的就可以使用!举个例子:fmt.Println();我们会发现我们使用的和引用的函数都

  • 五步让你成为GO 语言高手

    Francesc (@francesc) 是 Go 核心团队的一员, 是提倡 Google Cloud 平台的开发者. 他是一个编程语言的爱好者, Google的技术指导大师, Go tour的创造者之一. 这个讨论的灵感来自于另一个 Raquel Vélez 在 JSConf. Slides 的讨论,这个讨论已经发到了这里. Sourcegraph 是下一代编程协作工具, 用于搜索, 探索, 和审查代码. 我们参加GopherCon India 来分享我们是怎样使用 Go 并学习别人是怎样使用

  • Go语言中的Slice学习总结

    概念 Slice切片是对底层数组Array的封装,在内存中的存储本质就是数组,体现为连续的内存块,Go语言中的数组定义之后,长度就已经固定了,在使用过程中并不能改变其长度,而Slice就可以看做一个长度可变的数组进行使用,最为关键的,是数组在使用的过程中都是值传递,将一个数组赋值给一个新变量或作为方法参数传递时,是将源数组在内存中完全复制了一份,而不是引用源数组在内存中的地址,为了满足内存空间的复用和数组元素的值的一致性的应用需求,Slice出现了,每个Slice都是都源数组在内存中的地址的一个

  • Go语言中slice作为参数传递时遇到的一些“坑”

    前言 相信看到这个题目,可能大家都觉得是一个老生常谈的月经topic了.一直以来其实把握一个"值传递"基本上就能理解各种情况了,不过最近遇到了更深一点的"小坑",与大家分享一下. 首先还是从最简单的说起,看下面代码: func main() { a := []int{7,8,9} fmt.Printf("len: %d cap:%d data:%+v\n", len(a), cap(a), a) ap(a) fmt.Printf("le

  • 详解 Go 语言中 Map 类型和 Slice 类型的传递

    Map 类型 先看例子 m1: func main() { m := make(map[int]int) mdMap(m) fmt.Println(m) } func mdMap(m map[int]int) { m[1] = 100 m[2] = 200 } 结果是 map[2:200 1:100] 我们再修改如下 m2: func main() { var m map[int]int mdMap(m) fmt.Println(m) } func mdMap(m map[int]int) {

  • Go语言中slice的用法实例分析

    本文实例讲述了Go语言中slice的用法.分享给大家供大家参考.具体如下: slice 指向数组的值,并且同时包含了长度信息. []T 是一个元素类型为 T 的 slice. 复制代码 代码如下: package main import "fmt" func main() {  p := []int{2, 3, 5, 7, 11, 13}  fmt.Println("p ==", p)  for i := 0; i < len(p); i++ {   fmt.

  • go语言中slice,map,channl底层原理

    目录 0. 前序 1. slice 1.1 slice的创建 1.2 数据结构 1.3 扩容机制 2. map 2.1 map创建 2.2 数据结构 2.3 扩容机制 3. channl 3.1 数据结构 3.2 过程详解 0. 前序 slice,map,channl是我们Go语言中最最常用的几个数据结构,对于这些做到知根知底,对于我们建立知识体系以及优化代码都有着很重要的意义,所以本文,我们深入这三个数据结构的底层,剖析其设计思想. 1. slice 1.1 slice的创建 slice的创建

  • Go语言中Slice常见陷阱与避免方法详解

    目录 前言 slice 作为函数 / 方法的参数进行传递的陷阱 slice 通过 make 函数初始化,后续操作不当所造成的陷阱 性能陷阱 内存泄露 扩容 前言 Go 语言提供了很多方便的数据类型,其中包括 slice.然而,由于 slice 的特殊性质,在使用过程中易犯一些错误,如果不注意,可能导致程序出现意外行为.本文将详细介绍 使用 slice 时易犯的一些错误,帮助读者更好的使用 Go 的 slice,避免犯错误. slice 作为函数 / 方法的参数进行传递的陷阱 slice 作为参数

  • 浅析Go语言中的Range关键字

    前言 相信用过Range的朋友们都知道,Go语言中的range关键字使用起来非常的方便,它允许你遍历某个slice或者map,并通过两个参数(index和value),分别获取到slice或者map中某个元素所在的index以及其值. 比如像这样的用法: for index, value := range mySlice { fmt.Println("index: " + index) fmt.Println("value: " + value) } 上面的例子足够

  • C语言中printf()缓冲问题详解

    前言 缓冲区又称为缓存,它是内存空间的一部分.也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区. 缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区. 为什么要引入缓冲区 比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度. 又比如,

  • Go 语言中gin使用gzip压缩遇到的问题

    最近学习go语言写了个 成都房地产薪酬 网站,抓取网上的招聘信息并进行统计.中间遇到一些坑在这里记录下来方便以后查阅 gzip压缩是每个web应用必不可少的,这项目使用gin作为web框架,gin支持使用中间件,github上有实现好的gzip中间件:gin-contrib/gzip 安装: go get github.com/gin-contrib/gzip 使用: func main() { r := gin.Default() r.Use(gzip.Gzip(gzip.DefaultCom

  • Go语言中append函数用法分析

    本文实例分析了Go语言中append函数用法.分享给大家供大家参考.具体如下: Go语言中append的功能十分强大,使用它可以使很多功能的实现变得更加简洁.以下为简单对比: .将一个slice插入到另一个slice的指定位置: 不使用append: 复制代码 代码如下: func insertSliceAtIndex(slice_origin []int, slice_to_insert []int,      insertIndex int) (result []int, err error

随机推荐