go中数组的长度是固定的,且不同长度的数组是不同类型,这样的限制带来不少局限性。于是切片就来了,切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。

定义切片

切片的定义跟数组很类似,区别就是不需要指定长度,如下:

var a []int

var 切片名 []切片值的类型

举例:

func main() {
var a []int // 定义一个整型切片
var b = []int{} // 定义一个整型切片并初始化
var c = []bool{true,false} // 定义一个布尔切片并初始化
fmt.Println(a) // []
fmt.Println(b) // []
fmt.Println(c) // [true false]
}

需要注意的是,定义的切片如果没有初始化,是不可以直接操作的。如:

func main() {
var a []int // 定义了切片,但没初始化
a[] = // 直接赋值
fmt.Println(a) // 报错
}

基于数组定义切片

切片的底层就是一个数组,所以可以基于数组定义切片:

func main() {
var a = []int{,,,,}
var b = a[:]
fmt.Println(b) // [2,3]
fmt.Printf("b的类型:%T",b) //b的类型:[]int
}

代码中 a[1:3] 表示获取从数组索引 1 到 3 的值,包含开始索引的值,不包含结束索引的值,这样就能获取到一个切片。还支持以下操作:

var a = []int{,,,,}
var b = a[:] // [2 3 4 5]
var c = a[:] // [1 2 3]
var d = a[:] // [1 2 3 4 5]

切片的三要素

所谓的三要素是我自己觉得的,分别是 大小,容量和地址。

切片大小

切片的大小指的就是当前切片的长度,可以使用 len() 方法来获取:

func main() {
var a = []int{,,,,}
fmt.Printf("a 的大小是:%v",len(a)) // a 的大小是:5
}

切片的容量

切片的容量可以理解为底层数组最大能存放的元素的个数,使用 cap() 方法来获取,例子:

func main() {
var a = [...]string{"北京","上海","广州","深圳","杭州","成都","重庆"} // 定义字符串数组
var b = a[:] // 基于数组定义切片
fmt.Printf("切片b的容量:%v",cap(b)) // 切片b的容量:6
}

切片b是从数组a的索引 1 开始的,所以切片的容量是 6。容量是可变的,即切片支持扩容,可以是使用 append() 方法向切片添加项,例子:

func main() {
var a = [...]string{"北京","上海","广州","深圳","杭州","成都","重庆"} // 定义字符串数组
var b = a[:] // 基于数组定义切片
fmt.Printf("切片b的长度:%v,切片b的容量:%v \n",len(b),cap(b))
b = append(b,"合肥")
fmt.Printf("切片b的长度:%v,切片b的容量:%v \n",len(b),cap(b))
b = append(b,"长沙")
fmt.Printf("切片b的长度:%v,切片b的容量:%v \n",len(b),cap(b))
b = append(b,"苏州")
fmt.Printf("切片b的长度:%v,切片b的容量:%v \n",len(b),cap(b))
} // 切片b的长度:4,切片b的容量:6
// 切片b的长度:5,切片b的容量:6
// 切片b的长度:6,切片b的容量:6
// 切片b的长度:7,切片b的容量:12

从结果分析,每一次append后,切片的长度加 1,而容量如果大于长度时则不变,如果长度超过容量时,容量则会扩容,且每一次扩容都是2倍增长(一个一个的append时是2倍,别的情况不一定)。

直接定义的切片是一样满足的以上条件的:

func main() {
var b = []string{"北京","上海","广州"} //直接定义切片
fmt.Printf("切片b的长度:%v,切片b的容量:%v \n",len(b),cap(b))
b = append(b,"合肥")
fmt.Printf("切片b的长度:%v,切片b的容量:%v \n",len(b),cap(b))
b = append(b,"长沙")
fmt.Printf("切片b的长度:%v,切片b的容量:%v \n",len(b),cap(b))
b = append(b,"苏州")
fmt.Printf("切片b的长度:%v,切片b的容量:%v \n",len(b),cap(b))
} // 切片b的长度:3,切片b的容量:3
// 切片b的长度:4,切片b的容量:6
// 切片b的长度:5,切片b的容量:6
// 切片b的长度:6,切片b的容量:6

切片的地址

切片地址是指切片中第一个元素指向的内存空间,本质为底层数组第一个元素的地址,可以使用占位符 %p 来打印,例子:

func main() {
var b = []string{"北京","上海","广州"} //直接定义切片
fmt.Printf("切片b的长度:%v,切片b的容量:%v,切片b的地址:%p \n",len(b),cap(b),b)
b = append(b,"合肥")
fmt.Printf("切片b的长度:%v,切片b的容量:%v,切片b的地址:%p \n",len(b),cap(b),b)
b = append(b,"长沙")
fmt.Printf("切片b的长度:%v,切片b的容量:%v,切片b的地址:%p \n",len(b),cap(b),b)
b = append(b,"苏州")
fmt.Printf("切片b的长度:%v,切片b的容量:%v,切片b的地址:%p \n",len(b),cap(b),b)
b = append(b,"南京")
fmt.Printf("切片b的长度:%v,切片b的容量:%v,切片b的地址:%p \n",len(b),cap(b),b)
} // 切片b的长度:3,切片b的容量:3,切片b的地址:0xc000072180
// 切片b的长度:4,切片b的容量:6,切片b的地址:0xc00005a0c0
// 切片b的长度:5,切片b的容量:6,切片b的地址:0xc00005a0c0
// 切片b的长度:6,切片b的容量:6,切片b的地址:0xc00005a0c0
// 切片b的长度:7,切片b的容量:12,切片b的地址:0xc00009c000

从结果可以看到,当切片容量改变时,切片地址也会改变。这是因为当切片要扩容时,底层数组容量也就不够了,要将原数组进行扩容,扩容后由于要求数组的地址空间连续,因此原地址不满足条件,会在开辟一片新的空内存区域存放扩容后的数组,因此地址也就变了。

切片是引用类型

数组是值类型,但基于数组封装的切片却是引用类型,例子:

func main() {
var a = []int{,,,,}
var b = a
fmt.Printf("b:%v\n",b)
b[] =
fmt.Printf("修改后的b: %v,此时a是:%v\nz",a,b)
} // b:[1 2 3 4 5]
// 修改后的b: [100 2 3 4 5],此时a是:[100 2 3 4 5]

引用类型保存的是内存地址,所以当b赋值为a时,引用的是同一个内存地址,当b修改时,a也会改变。

使用copy()函数复制切片

上面说到切片是引用类型,所以不能直接把一个切片赋值给另一个切片而到达复制切片的目的。好在go语言内置了 copy() 函数,可以迅速地将一个切片的数据复制到另外一个切片空间中。其使用方式如下:

copy(目标切片,数据源切片)

举个例子:

func main() {
a:=[]int{,,,}
b := make([]int,,)
copy(b,a)
fmt.Println(a) // [1 3 5 7]
fmt.Println(b) // [1 3 5 7]
a[] =
fmt.Println(a) // [100 3 5 7]
fmt.Println(b) // [1 3 5 7]
}

使用make()函数构造切片

切片可以是自定义,可以基于数组定义,也可以基于切片再切片。现在还可以使用make()函数来动态创建切片。格式如下:

make([]T,size,cap)

// T 切片中的元素类型
// size 切片中元素的数量
// cap 切片的容量

举个例子:

func main() {
a:=make([]int,,)
fmt.Println(a)
fmt.Printf("a 的长度:%v\n",len(a))
fmt.Printf("a 的容量:%v",cap(a))
} // [0 0 0]
// a 的长度:3
// a 的容量:5

遍历切片

切片的遍历跟数组是一致的,支持索引遍历和 for range遍历:

func main() {
a:=[]int{,,,} for i:=;i<len(a);i++{
fmt.Println(i)
} for idx,val := range a{
println(idx,val)
}
}

删除切片中的元素

Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。

func main() {
a:=[]int{,,,,,,,,}
// 删除索引为2的值
b:= append(a[:],a[:]...)
fmt.Println(b) // [1 3 7 9 11 13 15 17]
}

总结一下就是:要从切片 a 中删除索引为 index 的元素,操作方法是 a = append(a[:index], a[index+1:]...)

最后

切片的相关内容到此为止,可能说的不完整,后面学到再补充。

Go语言中的切片(十)的更多相关文章

  1. Go语言中使用切片(slice)实现一个Vector容器

    Go语言中的切片(slice)和一些内置函数能实现其他语言容器类Array.Vector的功能,但是Go内置语言包container里只提供了list.heap.ring三种容器,缺少vector容器 ...

  2. [Go] gocron源码阅读-go语言中的切片和类型综合

    在gocron.go文件的main函数中,有下面这一句,从这句代码中可以学习到切片和类型的综合运用 cliApp.Flags = append(cliApp.Flags, []cli.Flag{}.. ...

  3. [Go] gocron源码阅读-go语言中的切片接口和类型综合

    // getCommands func getCommands() []cli.Command { command := cli.Command{ Name: "web", Usa ...

  4. Go语言数组和切片的原理

    目录 数组 创建 访问和赋值 切片 结构 初始化 访问 追加 拷贝 总结 数组和切片是 Go 语言中常见的数据结构,很多刚刚使用 Go 的开发者往往会混淆这两个概念,数组作为最常见的集合在编程语言中是 ...

  5. go语言中的数组切片:特立独行的可变数组

    go语言中的数组切片:特立独行的可变数组 初看go语言中的slice,觉得是可变数组的一种很不错的实现,直接在语言语法的层面支持,操作方面比起java中的ArrayList方便了许多.但是在使用了一段 ...

  6. Go语言中底层数组和切片的关系以及数组扩容规则

    Go语言中底层数组和切片的关系以及数组扩容规则 demo package main import ( "fmt" ) func main() { // 声明一个底层数组,长度为10 ...

  7. GO语言数组和切片实例详解

    本文实例讲述了GO语言数组和切片的用法.分享给大家供大家参考.具体分析如下: 一.数组 与其他大多数语言类似,Go语言的数组也是一个元素类型相同的定长的序列. (1)数组的创建. 数组有3种创建方式: ...

  8. C语言中的经典例题用javascript怎么解?(一)

    C语言中的经典例题用javascript怎么解?(一) 一.1+2+3+……+100=?        <script type="text/javascript">  ...

  9. C语言中的内存压缩技术

    C语言中的内存压缩技术 前言 在整个研究生阶段我都在参与一个LTE协议栈实现的项目,在这个项目中,我们利用一个自己编写的有限状态机框架将协议栈中每一层实现为一个内核模块.我们知道,在编写内核代码时需要 ...

随机推荐

  1. 2019牛客暑期多校训练营(第二场)E 线段树维护dp转移矩阵

    题意 给一个\(n\times m\)的01矩阵,1代表有墙,否则没有,每一步可以从\(b[i][j]\)走到\(b[i+1][j]\),\(b[i][j-1]\),\(b[i][j+1]\),有两种 ...

  2. LOJ #2733 [JOI2016春季合宿]Sandwiches (DP)

    题目链接 https://loj.ac/problem/2733 题解 神仙题-- 首先可以观察到一个结论: 目标块的两块小三明治一定分别是最后和倒数第二个被吃的. 由此我们可以考虑这两块谁先被吃.这 ...

  3. CodeForces–830A--二分,贪心

    Office Keys time limit per test 2 seconds memory limit per test 256 megabytes input standard input o ...

  4. JAVA中随机生成确定范围内的随机数

    最近工作中的一个业务需要再确定范围内取出一个随机数,网上到时搜出来一堆可以实现的方法,但是鄙人仍是理解不了,虽说已经copy方法直接完成工作了.今天抽时间整理下,希望能够帮助大家更好的理解随机数的实现 ...

  5. [心得]暑假DAY 5

    好久没更新博客了 最近事情太多太多 tarjan进阶,点双边双 T2压力 最大坑点:点双缩点 它不是直接把割点连成树(割点会有环) 而是用割点作”中介“,联接点双构成一颗树(所谓圆方树) 接着在上面进 ...

  6. 解决分页插件ClassNotFoundException: org.springframework.boot.bind.RelaxedPropertyResolver

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.github.p ...

  7. java拦截器获取请求完整参数

    public class OptLogAspect implements HandlerInterceptor { @Override public boolean preHandle(HttpSer ...

  8. SRS之监听端口的管理:RTMP

    1. 监听端口管理的入口函数 监听端口的管理入口在 run_master 函数中,如下: int run_master() { ... if ((ret = _srs_server->liste ...

  9. @Transient的应用

    我今天分配的任务是为一个页面Debug,遇到了一个问题查询的实体类在数据库没有对应的表,这时最常用的是建立视图或者表,但是应用@Transient注释可以让你更简单,免除建立表还有视图需要找多表关联关 ...

  10. JS中在当前日期上追加一天或者获取上一个月和下一个月

    /** * 获取上一个月 * * @date 格式为yyyy-mm-dd的日期,如:2014-01-25 */ function getPreMonth(date) { var arr = date. ...