我们知道数组定义好之后其长度就无法再修改,但是,在实际开发过程中,有时候我们并不知道需要多大的数组,我们期望数组的长度是可变的,

在 Go 中有一种数据结构切片(Slice) 解决了这个问题,它是可变长的,可以随时向Slice 里面添加数据。

1 什么是切片(Slice)

在 Go 源码中是这样定义切片的,源码地址:https://github.com/golang/go/blob/master/src/runtime/slice.go

type slice struct {
array unsafe.Pointer
len int
cap int
}

从源码中我们可以看到 Slice 也是一种结构体,这个结构体的名字是:Slice,这个结构体包含三个属性:array、len、cap。

第1个属性是指向底层数组的指针(Pointer),指向数组中 Slice 开始的位置;

第2个属性是切片中元素的个数(len),也就是这个 Slice 的长度;

第3个属性是这个切片的容量(cap),也就是 Slice 从开始位置到底层数组最后位置的长度;

2 切片的创建

2.1 切片的创建方式有很多种,一个比较通用的创建方式,使用 Go 的内置函数 make() 创建

package main

import "fmt"

func main() {
var s1 []int = make([]int,5,8)
var s2 []int = make([]int,8) fmt.Println(s1, s2)
}

输出结果

[root@VM_81_181_centos golang]# go run slice01.go
[0 0 0 0 0] [0 0 0 0 0 0 0 0]
[root@VM_81_181_centos golang]#  

make() 函数创建切片,需要提供三个参数,切片的类型、切片的长度、切片的容量。其中第3个参数是可选的,如果第三个参数不提供的话,

则代表创建的是满容切片,也就是长度和容量相等。另外切片也可以通过类型自动推导,省去类型定义和 var 关键字。比如:

package main

import "fmt"

func main() {
var s1 []int = make([]int, 5, 8)
s2 := make([]int, 8) fmt.Println(s1, s2)
}

另外,我们可以使用 len()、cap() 函数获取切片的长度和容量

package main

import "fmt"

func main() {
numbers := make([]int,3,5) printSlice(numbers)
} func printSlice(x []int) {
fmt.Printf("len=%d cap=%d slice=%v\n",len(x), cap(x),x)
}  

输出结果

[root@VM_81_181_centos golang]# go run slice01.go
len=3 cap=5 slice=[0 0 0]
[root@VM_81_181_centos golang]#

2.2 用已有的数组生成切片

package main

import "fmt"

func main() {
// 1.通过数组生成切片
// 定义一个数组
arr1 := [8]int{1,2,3,4,5,6,7,8}
fmt.Println(arr1) // 定义一个切片
s1 := arr1[1:4]
fmt.Println(s1)
}

输出结果

[root@VM_81_181_centos golang]# go run slice01.go
[1 2 3 4 5 6 7 8]
[2 3 4]
[root@VM_81_181_centos golang]#  

2.3 用已有的切片生成切片

package main

import "fmt"

func main() {
s := []int{1,2,3}
s1 := s[1:3] // s1 为 [2,3]
s2 := s1[1:2]
fmt.Println(s2)
}

输出结果

[root@VM_81_181_centos golang]# go run slice01.go
[3]
[root@VM_81_181_centos golang]#

3 切片的初始化

使用 make() 函数创建的切片是零值切片,Go 语言还提供了另外一种创建切片的方法,允许我们给它赋初值,使用这种方式创建的切片

是满容的。

3.1 通过数组初始化切片:

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

直接初始化切片s

s := arr[:]

 初始化切片s,是数组 arr 的引用

s := arr[startIndex:endIndex]

 从已有数组中创建一个新的切片,新切片元素是从数组 arr 下标 startIndex 到 endIndex-1

s := [startIndex:]

 缺省 endIndex 表示一直取到数组的最后一个元素

s :=arr[:endIndex]

 缺省 startIndex 表示从数组的第一个元素开始取值

3.2 通过切片初始化切片

s1 := s[startIndex:endIndex]

在这里我们把原切片称之为父切片,新切片称之为子切片,子切片在语法上要提供起始位置和结束位置,这两个位置都是可选值,

如果不提供起始位置则默认为是从父切片的初始位置开始;如果不提供结束位置则默认到父切片尾部结束,如下

package main

import "fmt"

func main() {
s1 := []int{1,2,3,4,5,6,7}
// 不提供起始位置
s2 := s1[:5]
// 不提供结束位置
s3 := s1[3:]
// 提供起始位置和结束位置
s4 := s1[1:5]
// 起始位置和结束位置都不提供
s5 := s1[:]
fmt.Println(s1,len(s1),cap(s1))
fmt.Println(s2,len(s2),cap(s2))
fmt.Println(s3,len(s3),cap(s3))
fmt.Println(s4,len(s4),cap(s4))
fmt.Println(s5,len(s5),cap(s5))
}

输出结果

[root@VM_81_181_centos golang]# go run test01.go
[1 2 3 4 5 6 7] 7 7
[1 2 3 4 5] 5 7
[4 5 6 7] 4 4
[2 3 4 5] 4 6
[1 2 3 4 5 6 7] 7 7
[root@VM_81_181_centos golang]#

我们发现当我们没有提供起始位置时子切片 s1 和父切片 s2 的容量一致;不提供结束位置的时候子切片的长度和容量都是到

父切片结束位置;起始位置和结束位置都提供的时候,子切片的容量是从起始位置到父切片结束位置;综合这几种情况可以说

明父子切片共享底层数组。

再看一下子切片 s5 ,起始位置和结束位置都没有提供并且输出结果和父切片 s1 是一致的,给人的感觉好像是切片的赋值一样,

那这种形式和切片的赋值有什么区别呢?看一个例子

package main

import "fmt"

func main() {
// 定义切片s
s := []int{1,2,3,4,5,6,7} // 使用切片赋值的形式将s赋值给s1
s1 := s
// 使用[:]
s2 := s[:]
fmt.Println(s,len(s),cap(s))
fmt.Println(s1,len(s1),cap(s1))
fmt.Println(s2,len(s2),cap(s2)) // 修改父切片s
s[0] = 10
fmt.Println(s,len(s),cap(s))
fmt.Println(s1,len(s1),cap(s1))
fmt.Println(s2,len(s2),cap(s2))
}

输出结果

[root@VM_81_181_centos golang]# go run test01.go
[1 2 3 4 5 6 7] 7 7
[1 2 3 4 5 6 7] 7 7
[1 2 3 4 5 6 7] 7 7
[10 2 3 4 5 6 7] 7 7
[10 2 3 4 5 6 7] 7 7
[10 2 3 4 5 6 7] 7 7
[root@VM_81_181_centos golang]#

事实证明这两种形式没有区别,从这个地方也可以证实切片拷贝前后共享底层数组,修改其中一个会影响另一个切片的内容,

这一点和数组值拷贝是不一样的。

4 切片的值拷贝

切片作为参数传递给函数的时候,虽然也是通过值拷贝的形式传递,但是依然引用的是同一个底层数组。所以,当切片作为参

数传递给函数的时候,在函数内对切片的内容更改也会在函数外可见。如下

package main

import "fmt"

func main() {
s1 := []int{6,7,8}
// 打印切片s1
fmt.Println(s1) subtactone(s1)
fmt.Println(s1)
} func subtactone(s []int) {
for i := range s{
s[i] -= 2
}
}

输出结果

[root@VM_81_181_centos golang]# go run slice03.go
[6 7 8]
[4 5 6]
[root@VM_81_181_centos golang]#

函数 subtactone 对传入的切片循环并将切片中的每个元素减去 2 。在函数调用后输出切片的值,发现

函数外切片的值也发生了变化,这一点是不同于数组的,数组作为参数传递给函数时在函数内部对数组

修改,在函数外是不可见的。

5 切片的遍历

切片在遍历上的方式和数组是一样的,支持 for 和 使用 range 关键字遍历

package main

import "fmt"

func main() {
s := []int{1,2,3,4,5} // for 遍历
for i := 0;i < len(s);i++ {
fmt.Println(s[i])
} // 使用 range 关键字
for index,value := range s {
fmt.Println(index, value)
}
}

输出结果

[root@VM_81_181_centos golang]# go run slice01.go
1
2
3
4
5
0 1
1 2
2 3
3 4
4 5
[root@VM_81_181_centos golang]#  

6 切片的扩容(追加)
   我们知道切片的长度是可变化的,这个可变其实就是追加操作(append)导致的,我们使用 append 操作追加元素到切片时,如果容

量不够,则会创建新的切片,意味着内存地址也会发生变化,如果底层数组没有扩容,那么追加前后的两个切片共享底层数组,当底

层数组是共享的,一个切片的内容变化会影响到另一个切片的内容。如果底层数组扩容了,那么追加前后的两个切片是不共享底层数组

的。

package main

import "fmt"

func main() {
// 定义切片s1 且切片s1是满容的
s1 := []int{1,2,3,4,5}
// 打印切片s1
fmt.Printf("内存地址:%p \t\t长度:%v \t\t容量%v \t\t包含的元素:%v\n",s1,len(s1),cap(s1),s1) // 对满容的切片追加元素
s2 := append(s1,6)
// 打印切片s2
fmt.Printf("内存地址:%p \t\t长度:%v \t\t容量%v \t\t包含的元素:%v\n",s2,len(s2),cap(s2),s2)
// 修改s2的值
s2[1] = 10
fmt.Println(s1,s2) // 对没有满容的切片追加元素
s3 := append(s2,7)
// 打印切片s3
fmt.Printf("内存地址:%p \t\t长度:%v \t\t容量%v \t\t包含的元素:%v\n",s3,len(s3),cap(s3),s3)
// 修改s3的值
s3[1] = 20
fmt.Println(s2,s3)
}

输出结果

[root@VM_81_181_centos golang]# go run slice01.go
内存地址:0xc420010150 长度:5 容量5 包含的元素:[1 2 3 4 5]
内存地址:0xc420042050 长度:6 容量10 包含的元素:[1 2 3 4 5 6]
[1 2 3 4 5] [1 10 3 4 5 6]
内存地址:0xc420042050 长度:7 容量10 包含的元素:[1 10 3 4 5 6 7]
[1 20 3 4 5 6] [1 20 3 4 5 6 7]
[root@VM_81_181_centos golang]#

我们可以看到输出结果中 s1、s2 的内存地址发生了变化,是因为我们将元素 6 追加至切片 s1 中,超过了切片的最大容量 5 ,会创

建一个新的切片并将 s1 原有的元素拷贝一份至新的切片中,并且我们修改 s2 的值,发现 s1 并没有发生变化,说明s1、s2不在共享底

层数组。

扩容后的切片 s2 容量为 10 ,我们再向 s2 追加元素 7 后,没有超过 s2 的最大容量,且s2、s3 的内存地址一致,并且修改 s3 的值,

s2也会发生变化,说明s2、s3 共享底层数组。

所以,初始化切片的时候给出了足够的容量,append 操作的时候不会创建新的切片。

这里可能还会有一个疑问,为什么追加一个元素后容量由原来的 5 变成了 10?这里牵涉到 Slice 的扩容机制,可以参考这篇文章写得非

常详细:http://blog.realjf.com/archives/217 (点击无法访问可以将这个链接粘贴至地址栏访问)

7 copy 函数

func copy(dst, src []Type) int
用于将源slice的数据(第二个数据),复制到目标slice(第一个参数)
返回值为拷贝了的数据个数,是 len(dst)和len(src)中的最小值

通过一段代码看下实例  

package main

import "fmt"

func main() {
// 切片a、切片s
a := []int{0,1,2,3,4,5,6,7}
s := make([]int,6) // 源长度8 目标长度6
// 只会将a前6个复制
n1 := copy(s,a)
fmt.Println("s -",s)
fmt.Println("n1 -",n1) // a[1:]长度7 s长度6
// 只会将a[1:]前6个复制
n2 := copy(s,a[1:])
fmt.Println("s -",s)
fmt.Println("n2 -",n2) // a[5:]长度3 s长度6
// 只会将a[5:]前3个复制
n3 := copy(s,a[5:])
fmt.Println("s -",s)
fmt.Println("n3 -",n3) n4 := copy(s[3:],a[5:])
fmt.Println("s - ", s)
fmt.Println("n4 - ", n4)
}

8 空切片和 nil 切片的区别

nil 切片和空切片,两者的区别在于前者指针为nil,后者指针指向一个地址

var slice [] int // nil 切片
slice := make([]int,0) // 使用make创建空切片
slice := []int{} // 使用切片字面量创建空切片

空切片在底层数组包含0个元素,也没有分配任何存储空间。想表示空集合时空切片很有用。不管是nil切片还是空切片,对其调用内置函数 append, len 和 cap 的效果是一样的

判断一个切片是否为空使用的是 len(s) == 0 而不是 s == nil

参考文章:https://juejin.im/post/5be8e0b1f265da614d08b45a

Go 灵活多变的切片Slice的更多相关文章

  1. go 数组(array)、切片(slice)、map、结构体(struct)

    一 数组(array) go语言中的数组是固定长度的.使用前必须指定数组长度. go语言中数组是值类型.如果将数组赋值给另一个数组或者方法中参数使用都是复制一份,方法中使用可以使用指针传递地址. 声明 ...

  2. golang切片slice

    切片slice是引用类型 len()函数获取元素的个数 cap()获取数组的容量 1.申明方式 (1)var a []int 与数组不同的是他不申明长度(2)s2 := make([]int, 3, ...

  3. 在python&numpy中切片(slice)

     在python&numpy中切片(slice) 上文说到了,词频的统计在数据挖掘中使用的频率很高,而切片的操作同样是如此.在从文本文件或数据库中读取数据后,需要对数据进行预处理的操作.此时就 ...

  4. Golang 入门 : 切片(slice)

    切片(slice)是 Golang 中一种比较特殊的数据结构,这种数据结构更便于使用和管理数据集合.切片是围绕动态数组的概念构建的,可以按需自动增长和缩小.切片的动态增长是通过内置函数 append( ...

  5. 7 切片slice

    include 切片 切片的日常用法 切片slice 其本身并不是数组,它指向底层的数组 作为变长数组的替代方案,可以关联底层数组的局部或者 为引用类型. 可以直接创建或从底层数组获取生成. 使用le ...

  6. go递归函数如何传递数组切片slice

    数组切片slice这个东西看起来很美好,真正用起来会发现有诸多的不爽. 第一,数组.数组切片混淆不清,使用方式完全一样,有时候一些特性又完全不一样,搞不清原理很容易误使用. 第二,数组切片的appen ...

  7. [PY3]——内置数据结构(9)——线性结构与切片/命名切片slice()

    线性结构的总结 列表list  元组tuple  字符串str  bytes  bytearray的共同点: 都是顺序存储.顺序访问的: 都是可迭代对象: 都可以通过索引访问 线性结构的特征: 可迭代 ...

  8. Go语言【第十二篇】:Go数据结构之:切片(Slice)、范围(Range)、集合(Map)

    Go语言切片(Slice) Go语言切片是对数组的抽象,Go数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数 ...

  9. go中的数据结构切片-slice

    1.部分基本类型 go中的类型与c的相似,常用类型有一个特例:byte类型,即字节类型,长度为,默认值是0: bytes = []btye{'h', 'e', 'l', 'l', 'o'} 变量byt ...

随机推荐

  1. nginx tomcat https

    .首先确保机器上安装了openssl和openssl-devel #yum install openssl #yum install openssl-devel . server { listen s ...

  2. Eclipse在Debug模式下经常进入ThreadPoolExecutor解决办法

    1.进入Window-->搜索:debug

  3. redmine3.2 的部署

    安装libyaml [root@ ~]#wget http://pyyaml.org/download/libyaml/yaml-0.1.4.tar.gz -O /dist/dist/yaml-0.1 ...

  4. mysql库、表、索引

    创建和删除数据库,同一个数据库下的不同表可以采用不同的引擎 mysql> create database oldboy default character set utf8 collate ut ...

  5. VMware Workstation Pro14安装

    1. 下载VMware Workstation Pro14,注意,这个链接支持win7 64及以上系统 2.  点击进入安装 3. 接受许可协议 4. 选择安装目录,是否选择增强型键盘驱动程序 5. ...

  6. What is probabilistic programming? | 中文翻译

    What is probabilistic programming? | 中文翻译 Probabilistic languages can free developers from the compl ...

  7. 理解 neutron

    之前大师发个结构图. understanding_neutron.pdf 自己走读了代码: 1.  get_extensions_path() # 在/opt/stack/neutron/neutro ...

  8. Codeforces Round #479 (Div. 3)题解

    CF首次推出div3给我这种辣鸡做,当然得写份博客纪念下 A. Wrong Subtraction time limit per test 1 second memory limit per test ...

  9. Codeforces Round #424 (Div. 2, rated, based on VK Cup Finals) Problem E (Codeforces 831E) - 线段树 - 树状数组

    Vasily has a deck of cards consisting of n cards. There is an integer on each of the cards, this int ...

  10. bzoj 3993 星际战争 - 二分答案 - 最大流

    3333年,在银河系的某星球上,X军团和Y军团正在激烈地作战.在战斗的某一阶段,Y军团一共派遣了N个巨型机器人进攻X军团的阵地,其中第i个巨型机器人的装甲值为Ai.当一个巨型机器人的装甲值减少到0或者 ...