Go 语言中的slice类型可以理解为是数组array类型的描述符,包含了三个因素:
  1. 指向底层数组的指针
  2. slice目前使用到的底层数组的元素个数,即长度
  3. 底层数组的最大长度,即容量

因此当我们定义一个切片变量,s := make([]int, 5, 10),即为指向了一个最大长度为10的底层数组,目前切片s使用到的长度为5。

基于slice的定义,在使用slice时,有以下几点注意事项:

1.对slice进行切分操作
对slice进行切分操作会生成一个新的slice变量,新slice和原来的slice指向同一个底层数组,只不过指向的起始位置可能不同,长度及容量可能也不相同。
  • 当从左边界有截断时,会改变新切片容量大小
  • 左边界默认0,最小为0;右边界默认slice的长度,最大为slice的容量
  • 当然,因为指向同一个底层数组,对新slice的操作会影响到原来的slice
package main

import (
"fmt"
) func main() { a := make([]int, 5, 10) b := a[:3]
//b len: 3 cap: 10
fmt.Println("b-- ", "len:", len(b), " ,cap:", cap(b)) //当从左边界有截断时 会改变新切片容量大小
c := a[2:4]
//c len: 2 cap: 8
fmt.Println("c-- ", "len:", len(c), " ,cap:", cap(c)) //左边界默认0 最小为0 | 右边界默认slice的长度 最大为slice的容量
d := a[:cap(a)]
//d len: 10 cap: 10
fmt.Println("d-- ", "len:", len(d), " ,cap:", cap(d))
}

  输出:

b--  len: 3  ,cap: 10
c-- len: 2 ,cap: 8
d-- len: 10 ,cap: 10

  

2.slice的赋值及函数间传递

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

 

package main

import (
"fmt"
) func main() { a := []int{1, 2, 3, 4, 5}
b := a //对b进行操作会影响到a
b[0] = 10
// 10 2 3 4 5
fmt.Println(a)
}

  输出:

[10 2 3 4 5]

如上所示,则a, b指向同一个底层数组,且长度及容量因素相同,对b进行的操作会影响到a。

切片作为函数参数示例:

package main

import (
"fmt"
) func modifySlice(s []int) {
s[0] = 10
} func main() {
a := []int{1, 2, 3, 4, 5}
modifySlice(a)
//[10 2 3 4 5]
fmt.Println(a)
}

  输出:

[10 2 3 4 5]

  如上所示,将slice作为参数在函数间传递的时候是值传递,产生了一个新的slice,只不过新的slice仍然指向原来的底层数组,所以通过新的slice也能改变原来的slice的值

package main

import (
"fmt"
) func modifySlice(s []int) {
s = []int{10, 20, 30, 40, 50}
     s[0] = -1
}
func main() {
a := []int{1, 2, 3, 4, 5}
modifySlice(a)
//[1 2 3 4 5]
fmt.Println(a)
}

  如上所示,在调用函数的内部,将s整体赋值一个新的slice,并不会改变a的值,因为modifySlice函数内对s重新的整体赋值,让s指向了一个新的底层数组,而不是传递进来之前的a指向的那个数组,之后s的任何操作都不会影响到a了。

3.slice的append操作

append操作最容易踩坑,下面详细说明一下。
 
append函数定义:  
 
 func append(s []T, x ...T) []T 
 
Append基本原则:对于一个slice变量,若slice容量足够,append直接在原来的底层数组上追加元素到slice上;如果容量不足,append操作会生成一个新的更大容量的底层数组。 
 
package main

import (
"fmt"
) func main() {
a := make([]int, 2, 4)
fmt.Println("initialize.....")
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) //通常append操作都是将返回值赋值给自己,
//此处为了方便说明,没有这样做 //改变b的前2个元素值 会影响到a
a[0] = 99
fmt.Println("赋值之后.....")
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a))
fmt.Println("Append之后.....")
b := append(a, 2)
b[0] = 1
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a))
fmt.Println("b:", b, " &b[0]:", &b[0], " len:", len(b), " cap:", cap(b)) a = append(a, 3)
fmt.Println("a Append之后.....")
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a))
fmt.Println("b:", b, " &b[0]:", &b[0], " len:", len(b), " cap:", cap(b)) a = append(a, 4)
a = append(a, 5) fmt.Println("a 再次Append之后.....")
//a 扩容了,指向不同的底层数组
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a))
//a的扩容没有影响到b,b还是指向原来的底层数组
fmt.Println("b:", b, " &b[0]:", &b[0], " len:", len(b), " cap:", cap(b))
}

  输出:

initialize.....
a: [0 0] &a[0]: 0xc0000105a0 len: 2 cap: 4
赋值之后.....
a: [99 0] &a[0]: 0xc0000105a0 len: 2 cap: 4
Append之后.....
a: [1 0] &a[0]: 0xc0000105a0 len: 2 cap: 4
b: [1 0 2] &b[0]: 0xc0000105a0 len: 3 cap: 4
a Append之后.....
a: [1 0 3] &a[0]: 0xc0000105a0 len: 3 cap: 4
b: [1 0 3] &b[0]: 0xc0000105a0 len: 3 cap: 4
a 再次Append之后.....
a: [1 0 3 4 5] &a[0]: 0xc000014240 len: 5 cap: 8
b: [1 0 3] &b[0]: 0xc0000105a0 len: 3 cap: 4
a 再次Append之后,a 扩容了,指向不同的底层数组,a的扩容没有影响到b,b还是指向原来的底层数组
package main

import (
"fmt"
) func main() {
a := make([]int, 2, 4)
a[0] = 10
a[1] = 20
//a: [10 20] &a[0]: 0x10410020 len: 2 cap: 4
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) //进行append操作
b := append(a[:1], 1) //a: [10 1] &a[0]: 0x10410020 len: 2 cap: 4
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) //b: [10 1] &b[0]: 0x10410020 len: 2 cap: 4
fmt.Println("b:", b, " &b[0]:", &b[0], " len:", len(b), " cap:", cap(b))
}

  输出:

a: [10 20]  &a[0]: 0xc0000105a0  len: 2  cap: 4
a: [10 1] &a[0]: 0xc0000105a0 len: 2 cap: 4
b: [10 1] &b[0]: 0xc0000105a0 len: 2 cap: 4

 

如上所示,若append后的b的实际元素个数没有超过原来的a指向的底层数组的容量,那么a,b指向同一个底层数组。
注意此时append的操作对象是:对a进行切分之后的切片,只取了a的第一个值,相当于一个新切片,长度为1,和a指向同一个底层数组,我们称这个切分后的新切片为c吧,那么就相当于b其实是基于c切片进行append的,直接在长度1之后追加元素,所以append之后a的第二个元素变成了1。【所以切分操作和append操作放一起的时候,一定要小心】 

 

package main

import (
"fmt"
) func main() {
a := make([]int, 2, 4)
a[0] = 10
a[1] = 20
//a: [10 20] &a[0]: 0x10410020 len: 2 cap: 4
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) //进行append操作
//b := append(a[:1], 1)
c := a[:1]
fmt.Println("c:", c, " &c[0]:", &c[0], " len:", len(c), " cap:", cap(c))
b := append(c, 1) //a: [10 1] &a[0]: 0x10410020 len: 2 cap: 4
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) //b: [10 1] &b[0]: 0x10410020 len: 2 cap: 4
fmt.Println("b:", b, " &b[0]:", &b[0], " len:", len(b), " cap:", cap(b))
}

  输出:

a: [10 20]  &a[0]: 0xc000072140  len: 2  cap: 4
c: [10] &c[0]: 0xc000072140 len: 1 cap: 4
a: [10 1] &a[0]: 0xc000072140 len: 2 cap: 4
b: [10 1] &b[0]: 0xc000072140 len: 2 cap: 4

  

package main

import (
"fmt"
) func main() { a := make([]int, 2, 4)
a[0] = 10
a[1] = 20
//a: [10 20] &a[0]: 0x10410020 len: 2 cap: 4
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) //进行append操作
//append是在第一个元素后开始追加,所以要超过容量,至少要追加4个,而不是之前例子的3个
b := append(a[:1], 1, 2, 3, 4) //a: [10 20] &a[0]: 0x10410020 len: 2 cap: 4
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) //b: [10 1 2 3 4] &b[0]: 0x10454020 len: 5 cap: 8
fmt.Println("b:", b, " &b[0]:", &b[0], " len:", len(b), " cap:", cap(b))
}

  如果append的元素数较多,超过了原来的容量,直接采用了新的底层数组,也就不会影响到a了。

输出:

a: [10 20]  &a[0]: 0xc0000105a0  len: 2  cap: 4
a: [10 20] &a[0]: 0xc0000105a0 len: 2 cap: 4
b: [10 1 2 3 4] &b[0]: 0xc000014240 len: 5 cap: 8

  

package main

import (
"fmt"
) func testAppend(s []int) {
//进行append操作
s = append(s, 1)
//s: [10 1] &s[0]: 0x10410020 len: 2 cap: 4
fmt.Println("s:", s, " &s[0]:", &s[0], " len:", len(s), " cap:", cap(s))
}
func main() { a := make([]int, 2, 4)
a[0] = 10
a[1] = 20
//a: [10 20] &a[0]: 0x10410020 len: 2 cap: 4
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) testAppend(a[:1]) //a: [10 1] &a[0]: 0x10410020 len: 2 cap: 4
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) }

  输出:

a: [10 20]  &a[0]: 0xc0000105a0  len: 2  cap: 4
s: [10 1] &s[0]: 0xc0000105a0 len: 2 cap: 4
a: [10 1] &a[0]: 0xc0000105a0 len: 2 cap: 4

  

import (
"fmt"
) func testAppend(s []int) {
//进行append操作
s = append(s, 1, 2, 3, 4)
//s: [10 1] &s[0]: 0x10410020 len: 2 cap: 4
fmt.Println("s:", s, " &s[0]:", &s[0], " len:", len(s), " cap:", cap(s))
}
func main() { a := make([]int, 2, 4)
a[0] = 10
a[1] = 20
//a: [10 20] &a[0]: 0x10410020 len: 2 cap: 4
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) testAppend(a[:1]) //a: [10 1] &a[0]: 0x10410020 len: 2 cap: 4
fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) }

  输出

a: [10 20] &a[0]: 0xc0000105a0 len: 2 cap: 4

s: [10 1 2 3 4] &s[0]: 0xc000014240 len: 5 cap: 8

a: [10 20] &a[0]: 0xc0000105a0 len: 2 cap: 4

  

参考:

https://blog.golang.org/go-slices-usage-and-internals

 

golang学习笔记 ---slice的更多相关文章

  1. golang学习笔记20 一道考察对并发多协程操作一个共享变量的面试题

    golang学习笔记20 一道考察对并发多协程操作一个共享变量的面试题 下面这个程序运行的能num结果是什么? package main import ( "fmt" " ...

  2. golang学习笔记19 用Golang实现以太坊代币转账

    golang学习笔记19 用Golang实现以太坊代币转账 在以太坊区块链中,我们称代币为Token,是以太坊区块链中每个人都可以任意发行的数字资产.并且它必须是遵循erc20标准的,至于erc20标 ...

  3. golang学习笔记18 用go语言编写移动端sdk和app开发gomobile

    golang学习笔记18 用go语言编写移动端sdk和app开发gomobile gomobile的使用-用go语言编写移动端sdk和app开发https://blog.csdn.net/u01249 ...

  4. golang学习笔记17 爬虫技术路线图,python,java,nodejs,go语言,scrapy主流框架介绍

    golang学习笔记17 爬虫技术路线图,python,java,nodejs,go语言,scrapy主流框架介绍 go语言爬虫框架:gocolly/colly,goquery,colly,chrom ...

  5. golang学习笔记16 beego orm 数据库操作

    golang学习笔记16 beego orm 数据库操作 beego ORM 是一个强大的 Go 语言 ORM 框架.她的灵感主要来自 Django ORM 和 SQLAlchemy. 目前该框架仍处 ...

  6. golang学习笔记14 golang substring 截取字符串

    golang学习笔记14 golang substring 截取字符串golang 没有java那样的substring函数,但支持直接根据 index 截取字符串mystr := "hel ...

  7. golang学习笔记13 Golang 类型转换整理 go语言string、int、int64、float64、complex 互相转换

    golang学习笔记13 Golang 类型转换整理 go语言string.int.int64.float64.complex 互相转换 #string到intint,err:=strconv.Ato ...

  8. golang学习笔记12 beego table name `xxx` repeat register, must be unique 错误问题

    golang学习笔记12 beego table name `xxx` repeat register, must be unique 错误问题 今天测试了重新建一个项目生成新的表,然后复制到旧的项目 ...

  9. golang学习笔记11 golang要用jetbrain的golang这个IDE工具开发才好

    golang学习笔记11   golang要用jetbrain的golang这个IDE工具开发才好  jetbrain家的全套ide都很好用,一定要dark背景风格才装B   从File-->s ...

随机推荐

  1. 浅谈 Flask 框架

    一.框架对比 Django —— 教科书式框架 优势:组件全,功能全,教科书 劣势:占用资源,创建复杂度高 Flask —— 以简单为基准开发,一切从简,能省则省 优势:轻,块 劣势:先天不足,第三方 ...

  2. 电信NBIOT 7 - 源码下载

    电信NBIOT 1 - 数据上行(中国电信开发者平台对接流程) 电信NBIOT 2 - 数据上行(中间件获取电信消息通知) 电信NBIOT 3 - 数据下行 电信NBIOT 4 - NB73模块上行测 ...

  3. 其他综合-CentOS7 安装 Jumpserver 跳板机

    CentOS7 安装 Jumpserver 跳板机 1.实验描述 搭建 jumpserver 平台,实现有效的运维安全审计.完美做到事先防范,事中控制和事后溯源 2.实验环境 物理机系统:Window ...

  4. 10、shell编程+流程控制+分支嵌套

    SHELL 编程     shell 是一个命令解释器,侦听用户指令.启动这些指令.将结果返回给用户(交互式的shell)     shell 也是一种简单的程序设计语言.利用它可以编写一些系统脚本. ...

  5. FFMPEG+SDL实现视频播放器

    一. 前言 基于学习ffmpeg和sdl,写一个视频播放器是个不错的练手项目. 视频播放器的原理很多人的博客都有讲过,这里出于自己总结的目的,还是会做一些概况. 二. 视频播放器基本原理 2.1 解封 ...

  6. 201871010117 石欣钰《面向对象程序设计(Java)》第十二周学习总结

      内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.com/nwnu-daizh/p/ ...

  7. 201871010118-唐敬博《面向对象程序设计(java)》第八周学习总结

    博文正文开头格式:(2分) 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.co ...

  8. Node.js到底是做什么的?这是我看到最好的解释了

    作者:贾厂长链接:https://www.zhihu.com/question/33578075/answer/56951771来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明 ...

  9. 【Spring AOP】AOP的实现(三)

    一.Spring 对AOP的支持 Spring中AOP代理由Spring的IOC容器负责生成.管理,其依赖关系也由IOC容器负责管理.因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种 ...

  10. Nginx的代理配置(六)

    一.正向代理 1. 指令说明 (1) resolver 这个用于设置DNS服务器的ip .DNS服务器的主要工作是进行域名解析,将域名映射为对应IP地址. 语法:resolver address .. ...