Go语言实战(四) | 数组、切片和映射 -- 切片

切片与动态数组

切片是围绕着动态数组的概念构建的,可以按需自动增长或缩小,还可以通过对切片再次切片来缩小一个切片的大小。
切片的底层实现是数组,因此可以索引、迭代以及优化垃圾回收过程。

内部实现

切片是对底层数组进行了抽象,并添加了相关操作方法。
切片是包含三个字段的数据结构:array、len和cap:

  • array是指针类型,指向底层数组
  • len是int64类型,表示切片的长度,即当前允许访问的长度
  • cap是int64类型,表示切片的容量,即切片允许增长的最大值
    切片的内部实现

创建和初始化

切片有三种创建方式:

  • 使用make函数,函数的第一个参数是切片类型,第二个参数是切片长度,第三个参数是切片容量:
    slice := make([]string, 3)slice := make([]string, 3, 5)
  • 使用切片字面量:
    slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}
  • 指定某一位置的元素值:slice := []string{99: ""}

nil切片 VS. 空切片
nil切片和空切片的长度和容量都是0,不同的是底层数组指针(array字段):nil切片的array字段是nil,空切片的array字段是一个地址指针,不过指向的是空数组。
nil切片用于描述一个不存在的切片,例如,函数要求返回一个切片但是发生异常的时候。

nil切片的表示

空切片在底层数组包含 0 个元素,也没有分配任何存储空间。想表示空集合时空切片很有用, 例如,数据库查询返回 0 个查询结果时。
空切片的表示

// 创建nil整型切片
var slice []int
// 创建空的整型切片
slice := make([]int, 0)

使用切片

我们可以像操作数组一样访问和修改切片元素。

slice := []int{10, 20, 30, 40, 50}
slice[1] = 100

使用切片创建切片

slice := []int{10, 20, 30, 40, 50}
// newSlice长度为2个元素,容量为4个元素
newSlice := slice[1:3]
slice与newSlice共享同一底层数组

切片之切片长度和容量的计算:
对底层数组容量为k的切片slice[i:j]来说,切片之切片的长度是j-i,即切片初始时可以访问的元素个数;容量是k-i,即与该切片相关联的所有元素的数量。

切片的增长

使用append()函数可以向切片中添加值(使得切片的长度变长,即切片中的len字段增加),返回一个包含修改结果的新切片。
切片的增长分为两种情况:

  1. 增长后的切片长度不大于切片容量
  2. 增长后的切片长度大于切片容量

对于前者,增长前后切片共享同一底层数组:

slice := []int{10, 20, 30, 40}
newSlice := slice[1:3]
newSlice = append(newSlice, 60)
append操作之后的底层数据

而当增长后的切片长度超出容量时,则返回的切片将会指向一个新建的底层数组,并拷贝原数组中的数据:

slice := []int{10, 20, 30, 40}
newSlice := append(slice, 50)

append 操作之后的新的底层数组

所以,内置函数 append 会首先使用可用容量。一旦没有可用容量,会分配一个新的底层数组。这导致很容易忘记切片间正在共享同一个底层数组。一旦发生这种情况,对切片进行修改,很可能会导致随机且奇怪的问题。对切片内容的修改会影响多个切片,却很难找到问 题的原因。
为此,可以在生成切片时将长度和容量设置为相同的值,这样后面进行append()操作时都会新建底层数组,而不是共享原切片的底层数组

slice := []int{10, 20, 30, 40}
newSlice := slice[2:3:3]  // 设置切片长度等于容量
newSlice = append(newSlice, 50)

迭代切片

迭代切片可以使用Golang中的range关键字和传统for循环两种方式。

可以使用range关键字

slice := []int{10, 20, 30, 40}
for index, value := range slice {
    fmt.Println("Index: %d; Value: %d: %d", index, value)
}

range关键字返回一个值时,返回的是Index;返回两个值时,第一个值是Index,第二个值是Value。

需要强调的是:range关键字返回的是每个元素的副本,而不是元素的引用:

slice := []int{10, 20, 30, 40}
for index, value := range slice {
    fmt.Printf("Value: %d Value-Addr: %X ElemAddr: %X\n", value, &value, &slice[index])
}
// Output :
// Value: 10 Value-Addr: C00001A0A0 ElemAddr: C0000160C0
// Value: 20 Value-Addr: C00001A0A0 ElemAddr: C0000160C8
// Value: 30 Value-Addr: C00001A0A0 ElemAddr: C0000160D0
// Value: 40 Value-Addr: C00001A0A0 ElemAddr: C0000160D8

可以看到,在for循环中value的地址和slice中元素的地址并不相同,且value地址在迭代过程中是不变的,即循环过程中是对同一变量的重复赋值。

使用传统for循环

slice := []int{10, 20, 30, 40}
for index := 2; index < len(slice); index++ {
    fmt.Printf("Index: %d Value: %d\n", index, slice[index])
}

有两个特殊的内置函数 len 和 cap,可以用于处理数组、切片和通道。对于切片,函数 len 返回切片的长度,函数 cap 返回切片的容量。

多维切片

和数组一样,切片是一维的。也可以像数组一样,组合多个一维切片实现多维切片。

slice := [][]int{{10}, {100, 200}}
多维切片的底层数据存储

多维切片的增长

增长多维切片的内层切片和增长一维切片的操作相同:

slice := [][]int{{10}, {100, 200}}
slice[0] = append(slice[0], 20)
多维切片的内层切片增长

在函数间传递切片

上一小节中提到在函数间传递数组的时间代价和空间代价都是巨大的,为此提出可以在函数间传递数组指针,但是这么做是极其危险的,可能导致对数组数据的非法修改且错误难以追踪。
其实,最好的方式是使用本节介绍的切片在函数间传递数组。
在 64 位架构的机器上,一个切片需要 24 字节的内存:指针字段需要 8 字节,长度和容量字段分别需要 8 字节。在函数间传递 24 字节的数据会非常快速、简单。这也是切片效率高的地方。
同时,也可以通过设置生成切片的容量来控制在调用函数中可以访问的底层数组范围和增长范围,防止错误的增长数据。


函数调用之后两个切片指向同一个底层数组
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355