菜单

Golang中切片的用法与本质详解402.com:

2019年6月8日 - 计算机教程

前言

Go 语言切片(Slice)

Go
数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大

Go 语言切片是对数组的抽象。

Go的切片类型为处理同类型数据序列提供一个方便而高效的方式。
切片有些类似于其他语言中的数组,但是有一些不同寻常的特性。
本文将深入切片的本质,并讲解它的用法。

Go
数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

数组

声明Slice

Go的切片是在数组之上的抽象数据类型,因此在了解切片之前必须要先理解数组。

带有 T 类型元素的切片由 []T
表示,其中T代表slice中元素的类型。切片在内部可由一个结构体类型表示,形式如下:

数组类型定义了长度和元素类型。例如, [4]int
类型表示一个四个整数的数组。
数组的长度是固定的,长度是数组类型的一部分(  [4]int 和  [5]int
是完全不同的类型)。 数组可以以常规的索引方式访问,表达式  s[n]
访问数组的第 n 个元素。

type slice struct { 
 Length  int
 Capacity  int
 ZerothElement *byte
}
var a [4]int
a[0] = 1
i := a[0]
// i == 1

可见一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。通过len和cap函数分别返回slice的长度和容量。

数组不需要显式的初始化;数组的零值是可以直接使用的,数组元素会自动初始化为其对应类型的零值:

创建Slice

// a[2] == 0, int 类型的零值

直接声明创建 slice

类型 [4]int 对应内存中四个连续的整数:

[]<元素类型>{元素1, 元素2, …}

402.com 1

创建一个有 3 个整型元素的数组,并返回一个存储在 c 中的切片引用。

Go的数组是值语义。一个数组变量表示整个数组,它不是指向第一个元素的指针(不像
C 语言的数组)。
当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。
(为了避免复制数组,你可以传递一个指向数组的指针,但是数组指针并不是数组。)
可以将数组看作一个特殊的struct,结构的字段名对应数组的索引,同时成员的数目固定。

c := []int{6, 7, 8}

数组的字面值像这样:

make() 函数创建 slice

b := [2]string{"Penn", "Teller"}
s1 := make([]int, 5) //长度和容量都是 5
 s2 := make([]int, 3, 10) //长度是3,容量是10
 fmt.Println(cap(s1),s2)

当然,也可以让编译器统计数组字面值中元素的数目:

基于底层数组数组或切片创建

b := [...]string{"Penn", "Teller"}

基于现有的切片或者数组创建,使用[i:j]这样的操作符即可,她表示以i索引开始,到j索引结束,截取原数组或者切片,创建而成的新切片,新切片的值包含原切片的i索引,但是不包含j索引。注意i和j都不能超过原切片或者数组的索引

这两种写法, b 都是对应  [2]string 类型。

slice :=[]int{1,2,3,4,5}
 slice1 := slice[:]
 slice2 := slice[0:]
 slice3 := slice[:5]
 fmt.Println(slice1)
 fmt.Println(slice2)
 fmt.Println(slice3)

切片

新的切片和原数组或原切片共用的是一个底层数组,所以当修改的时候,底层数组的值就会被改变,所以原切片的值也改变了。

数组虽然有适用它们的地方,但是数组不够灵活,因此在Go代码中数组使用的并不多。
但是,切片则使用得相当广泛。切片基于数组构建,但是提供更强的功能和便利。

slice := []int{1, 2, 3, 4, 5}
 newSlice := slice[1:3]
 newSlice[0] = 10
 fmt.Println(slice)
 fmt.Println(newSlice)

切片类型的写法是 []T ,  T
是切片元素的类型。和数组不同的是,切片类型并没有给定固定的长度。

切片与数组的区别

切片的字面值和数组字面值很像,不过切片没有指定元素个数:

1.切片不是数组,但是切片底层指向数组

letters := []string{"a", "b", "c", "d"}

2.切片本身长度是不一定的因此不可以比较,数组是可以的。

切片可以使用内置函数 make 创建,函数签名为:

3.切片是变长数组的替代方案,可以关联到指向的底层数组的局部或者全部。

func make([]T, len, cap) []T

4.切片是引用传递(传递指针地址),而数组是值传递(拷贝值)

其中T代表被创建的切片元素的类型。函数 make
接受一个类型、一个长度和一个可选的容量参数。 调用  make
时,内部会分配一个数组,然后返回数组对应的切片。

5.切片可以直接创建,引用其他切片或数组创建

var s []byte
s = make([]byte, 5, 5)
// s == []byte{0, 0, 0, 0, 0}

6.如果多个切片指向相同的底层数组,其中一个值的修改会影响所有的切片

当容量参数被忽略时,它默认为指定的长度。下面是简洁的写法:

切片的修改

s := make([]byte, 5)

切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中。

可以使用内置函数 len 和  cap 获取切片的长度和容量信息。

package main
import (
 "fmt"
)
func main() {
 arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
 slice := arr[2:5]
 fmt.Println("array before", arr)
 for i := range slice {
  slice[i]++
 }
 fmt.Println("array after ", arr)
}
len(s) == 5
cap(s) == 5

在上述程序的第 9 行,我们根据数组索引 2,3,4 创建一个切片 dslice 。for
循环将这些索引中的值逐个递增。当我们使用 for
循环打印数组时,我们可以看到对切片的更改反映在数组中。该程序的输出是

接下来的两个小节将讨论长度和容量之间的关系。

array before [0 1 2 3 4 5 6 7 8 9]
array after [0 1 3 4 5 5 6 7 8 9]

切片的零值为 nil 。对于切片的零值,  len 和  cap 都将返回0。

当多个切片共用相同的底层数组时,每个切片所做的更改将反映在数组中。

切片也可以基于现有的切片或数组生成。切分的范围由两个由冒号分割的索引对应的半开区间指定。
例如,表达式 b[1:4] 创建的切片引用数组  b
的第1到3个元素空间(对应切片的索引为0到2)。

package main
import (
 "fmt"
)
func main() {
 array := [4]int{10, 20 ,30, 40}
 slice1 := array[:]
 slice2 := array[:]
 fmt.Println("array before change:", array)
 slice1[0] = 60
 fmt.Println("array after modification to slice slice1:", array)
 slice2[1] = 70
 fmt.Println("array after modification to slice slice2:", array)
}
b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
// b[1:4] == []byte{'o', 'l', 'a'}, sharing the same storage as b

在 9 行中, numa [:] 缺少开始和结束值。开始和结束的默认值分别为 0 和
len (numa) 。两个切片 nums1 和 nums2 共享相同的数组。该程序的输出是

切片的开始和结束的索引都是可选的;它们分别默认为零和数组的长度。

array before change: [10 20 30 40]
array after modification to slice slice1: [60 20 30 40]
array after modification to slice slice2: [60 70 30 40]
// b[:2] == []byte{'g', 'o'}
// b[2:] == []byte{'l', 'a', 'n', 'g'}
// b[:] == b

从输出中可以清楚地看出,当切片共享同一个数组时,每个所做的修改都会反映在数组中。

下面语法也是基于数组创建一个切片:

切片的长度和容量

x := [3]string{"Лайка", "Белка", "Стрелка"}
s := x[:] // a slice referencing the storage of x

切片的长度是切片中的元素数。
切片的容量是从创建切片索引开始的底层数组中元素数。

切片的内幕

package main
import (
 "fmt"
)
func main() {
 fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
 fruitslice := fruitarray[1:3]
 fmt.Printf("length of slice %d capacity %d", len(fruitslice), cap(fruitslice)) // length of is 2 and capacity is 6
}

一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度,
和容量(片段的最大长度)。

在上面的程序中, fruitslice 是从 fruitarray 的索引 1 和 2 创建的。
因此, fruitlice 的长度为 2 。

402.com 2

fruitarray 的长度是 7。 fruiteslice 是从 fruitarray 的索引 1
创建的。因此, fruitslice 的容量是从 fruitarray 索引为 1 开始,也就是说从
orange 开始,该值是 6 。因此, fruitslice 的容量为 6。该程序]输出切片的
**长度为 2 容量为 6 **。

前面使用 make([]byte, 5) 创建的切片变量  s 的结构如下:

切片可以重置其容量。任何超出这一点将导致程序运行时抛出错误。

402.com 3

package main
import (
 "fmt"
)
func main() {
 fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
 fruitslice := fruitarray[1:3]
 fmt.Printf("length of slice %d capacity %d\n", len(fruitslice), cap(fruitslice)) // length of is 2 and capacity is 6
 fruitslice = fruitslice[:cap(fruitslice)] // re-slicing furitslice till its capacity
 fmt.Println("After re-slicing length is",len(fruitslice), "and capacity is",cap(fruitslice))
}

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图