ArcherWong博客
首页
博客
golang笔记---数组(array)和切片(slice)
作者:ArcherWong
分类:golang
时间:2019-01-03 21:34:24
阅读:353
[TOC] # 数组 ## 概念 数组是具有相同 **唯一类型** 的一组已编号且 **长度固定** 的数据项序列(这是一种同构的数据结构);数组长度必须是一个常量表达式,并且必须是一个非负整数。数组长度也是数组类型的一部分,所以[5]int和[10]int是属于不同类型的。 **▶ 声明方式1:** 声明的格式是: ``` var identifier [len]type ``` 例如: ``` var arr1 [5]int ```  每个元素是一个整型值,当声明数组时所有的元素都会被自动初始化为默认值 0。 arr1 的长度是 5,索引范围从 0 到 len(arr1)-1。 第一个元素是 arr1[0],第三个元素是 arr1[2];总体来说索引 i 代表的元素是 arr1[i],最后一个元素是 arr1[len(arr1)-1]。 **对索引项为 i 的数组元素赋值可以这么操作:arr[i] = value,所以数组是可变的**。 **▶ 声明方式2: var arr1 = new([5]int) 和 var arr2 [5]int 的区别** 数组可以通过 new() 来创建: `var arr1 = new([5]int)` ,得到的是一个**指向数组的指针**。 那么这种方式和 `var arr2 [5]int` 的区别是什么呢? ``` arr1 的类型是 *[5]int,而 arr2的类型是 [5]int。 ``` 结果如下: ``` var arr1 [5]int arr2 := arr1 arr2[2] = 100 fmt.Println(arr1) fmt.Println(arr2) //以下是指向数组的指针,使用new关键字,同样也可以直接操作 var a1 = new ([5]int) a2 := a1 a2[2] = 100 fmt.Println(a1) fmt.Println(a2) ``` ``` [0 0 0 0 0] [0 0 100 0 0] &[0 0 100 0 0] &[0 0 100 0 0] ``` 通过上面的结果,可以看出,在赋值后修改arr2不会arr1的值,修改a2同时会修改a1的值。 函数中数组作为参数传入时,如 func1(arr2),会产生一次数组拷贝,func1 方法不会修改原始的数组 arr2。如果你想修改原数组,那么可以使用下面的方式: **方法1**: arr2 必须**通过&操作符以引用方式传过来**,例如 func1(&arr2),下面是一个例子 示例 pointer_array.go: ``` package main import "fmt" func f(a [3]int) { fmt.Println(a) } func fp(a *[3]int) { fmt.Println(a) } //该函数内使用引用传递,可以修改原数组 func main() { var ar [3]int f(ar) // passes a copy of ar fp(&ar) // passes a pointer to ar } ``` 输出结果: ``` [0 0 0] &[0 0 0] ``` **方法2**: 另一种方法就是**生成数组切片**并将其传递给函数 后面关于切片章节会讲到 ## 数组常量 如果数组值已经提前知道了,那么可以通过 **数组常量** 的方法来初始化数组,而不用依次使用 []= 方法(所有的组成元素都有相同的常量语法) 第一种变化: ``` var arrAge = [5]int{18, 20, 15, 22, 16} ``` 注意 [5]int 可以从左边起开始忽略:[10]int {1, 2, 3} :这是一个有 10 个元素的数组,除了前三个元素外其他元素都为 0。 第二种变化: ``` var arrLazy = [...]int{5, 6, 7, 8, 22} ``` **... 可同样可以忽略,从技术上说它们其实变化成了切片**。 ``` var arrLazy = []int{5, 6, 7, 8, 22} //实际上是创建了一个长度为 5 的数组并且创建了一个相关切片。 ``` 第三种变化:key: value syntax ``` var arrKeyValue = [5]string{3: "Chris", 4: "Ron"} ``` 只有索引 3 和 4 被赋予实际的值,其他元素都被设置为空的字符串。 ## 多维数组 数组通常是一维的,但是可以用来组装成多维数组,例如:`[3][5]int,[2][2][2]float64`。 ``` a: = [2][3]int{ {1:1}, {2:2} } fmt.Println(a) ``` 结果 ``` [[0 1 0] [0 0 2]] ``` 内部数组总是长度相同的。Go 语言的多维数组是矩形式的(唯一的例外是切片的数组)。 示例 multidim_array.go ``` package main const ( WIDTH = 1920 HEIGHT = 1080 ) type pixel int var screen [WIDTH][HEIGHT]pixel func main() { for y := 0; y < HEIGHT; y++ { for x := 0; x < WIDTH; x++ { screen[x][y] = 0 } } } ``` ## 将数组传递给函数 把一个大数组传递给函数会消耗很多内存。有两种方法可以避免这种现象: - 传递数组的指针 - 使用数组的切片 **传递数组的指针在 Go 中并不常用,通常使用切片** # 切片 ## 概念 切片(slice)是对数组一个连续片段的引用(该数组我们称之为**相关数组**,通常是匿名的),所以切片是一个引用类型。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内,可以理解为数学中的 `[)`。 给定项的切片索引可能比相关数组的相同元素的索引小。**和数组不同的是,切片的长度可以在运行时修改**,最小为 0 最大为相关数组的长度:切片是一个 **长度可变的数组**。 切片提供了计算容量的函数 cap() 可以测量切片最长可以达到多少:它等于切片的长度 + 数组除切片之外的长度。如果 s 是一个切片,cap(s) 就是从 s[0] 到数组末尾的数组长度。切片的长度永远不会超过它的容量,所以对于 切片 s 来说该不等式永远成立:`0 <= len(s) <= cap(s)`。 多个切片如果表示同一个数组的片段,它们可以共享数据;因此**一个切片和相关数组的其他切片是共享存储的吗,其中一个值改变会影响全部**,相反,**不同的数组总是代表不同的存储**。 **优点** 因为切片是引用,所以它们不需要使用额外的内存并且比使用数组更有效率,所以在 Go 代码中 切片比数组更常用。 声明切片的格式是: ``` var identifier []type //(不需要说明长度,申明一个数组 var a [5]int)。 ``` 一个切片在未初始化之前默认为 nil,长度为 0。 切片的初始化格式是: ``` var slice1 []type = arr1[start:end]。 ``` 这表示 slice1 是由数组 arr1 从 start 索引到 end-1 索引之间的元素构成的子集(切分数组,start:end 被称为 slice 表达式) 让slice1等于完整的arr1数组,,可以用下面的方式 ``` var slice1 []type = arr1[0:len(arr1)] //表达式全范围 var slice1 []type = arr1[:] //上面的一种简写 slice1 = &arr1 //另一种表述方式 ``` 简写 ``` arr1[2:] 和 arr1[2:len(arr1)] 相同,都包含了数组从第三个到最后的所有元素。 arr1[:3] 和 arr1[0:3] 相同,包含了从第一个到第三个元素(不包括第三个)。 ``` 一个由数字 1、2、3 组成的切片可以这么生成: ``` s := [3]int{1,2,3}[:] //(注: 应先用s := [3]int{1, 2, 3}生成数组, 再使用s[:]转成切片) 甚至更简单的 s := []int{1,2,3}。 //创建了一个长度为 3 的数组并且创建了一个相关切片。 ``` `s2 := s[:]` 是用切片组成的切片,拥有相同的元素,但是仍然指向相同的相关数组,也就是原始的数组。 切片在内存中的组织方式实际上是一个有 3 个域的结构体:指向相关数组的指针,切片长度以及切片容量。下图给出了一个长度为 2,容量为 4 的切片y。 - y[0] = 3 且 y[1] = 5。 - 切片 y[0:4] 由 元素 3,5,7 和 11 组成。  **注意 绝对不要用指针指向 slice。切片本身已经是一个引用类型,所以它本身就是一个指针!!** ## 将切片传递给函数 如果你有一个函数需要对数组做操作,你可能总是需要把参数声明为切片。当你调用该函数时,把数组分片,创建为一个 切片引用并传递给该函数。 这里有一个计算数组元素和的方法: ``` func sum(a []int) int { s := 0 for i := 0; i < len(a); i++ { s += a[i] } return s } func main() { var arr = [5]int{0, 1, 2, 3, 4} sum(arr[:]) } ``` ## 使用make()创建一个分片 当相关数组还没有定义时,我们可以使用 make() 函数来创建一个切片 同时创建好相关数组: ``` var slice1 []type = make([]type, len) ``` 也可以简写为 ``` slice1 := make([]type, len) ``` 这里 len 是数组的长度并且也是 slice 的初始长度。所以定义 `s2 := make([]int, 10)`,那么 `cap(s2) == len(s2) == 10`。 如果你想创建一个 slice1,它不占用整个数组,而只是占用以 len 为个数个项,那么只要: ``` slice1 := make([]type, len, cap)。 ``` make 的使用方式是:`func make([]T, len, cap)`,其中 cap 是可选参数。 所以下面两种方法可以生成相同的切片: ``` make([]int, 50, 100) new([100]int)[0:50] ``` 下图描述了使用 make 方法生成的切片的内存结构:  ## new()和make()的区别 二者都在堆上分配内存,但是它们的行为不同,**适用于不同的类型**。 - new(T) 为每个新的类型T分配一片内存,初始化为 0 并且返回类型为**T的内存地址:这种方法 返回一个指向类型为 T,值为 0 的地址的指针**,它适用于值类型如数组和结构体;它相当于 &T{}。 - make(T) **返回一个类型为 T 的初始值**,它只适用于3种内建的引用类型:切片、map 和 channel。 换言之,new 函数分配内存,make 函数初始化;下图给出了区别:  实例分析: ``` var a = new ([5]int) lena := len(a) capa := cap(a) var b = make([]int, 5, 10) lenb := len(b) capb := cap(b) fmt.Println(a) fmt.Println(lena) fmt.Println(capa) fmt.Println(b) fmt.Println(lenb) fmt.Println(capb) ``` 结果 ``` &[0 0 0 0 0] 5 5 [0 0 0 0 0] 5 10 ``` ## 切片重组 我们已经知道切片创建的时候通常比相关数组小,例如: ``` slice1 := make([]type, start_length, capacity) ``` 其中 start_length 作为切片初始长度而 capacity 作为相关数组的长度。 这么做的好处是我们的切片在达到容量上限后可以扩容。**改变切片长度的过程称之为切片重组 reslicing**,做法如下:slice1 = slice1[0:end],其中 end 是新的末尾索引(即长度)。 将切片扩展 1 位可以这么做: ``` sl = sl[0:len(sl)+1] ``` 切片可以反复扩展直到占据整个相关数组。 另一个例子: ``` var ar = [10]int{0,1,2,3,4,5,6,7,8,9} var a = ar[5:7] // reference to subarray {5,6} - len(a) is 2 and cap(a) is 5 ``` 将 a 重新分片: ``` a = a[0:4] // ref of subarray {5,6,7,8} - len(a) is now 4 but cap(a) is still 5 ``` ## 切片的复制和追加 如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。下面的代码描述了从拷贝切片的 copy 函数和向切片追加新元素的 append 函数。 `func append(s[]T, x ...T) []T` 其中 append 方法将 0 个或多个具有相同类型 s 的元素追加到切片后面并且返回新的切片;追加的元素必须和原切片的元素同类型。如果 s 的容量不足以存储新增元素,append 会分配新的切片来保证已有切片元素和新增元素的存储。因此,返回的切片可能已经指向一个不同的相关数组了,这时就要注意不会影响到其它切片了。append 方法总是返回成功,除非系统内存耗尽了。 ``` a := []int{1,2,3} b := append(a, 4, 5) // [1 2 3 4 5] ``` `func copy(dst, src []T) int copy` 方法将类型为 T 的切片从源地址 src 拷贝到目标地址 dst,覆盖 dst 的相关元素,并且返回拷贝的元素个数。源地址和目标地址可能会有重叠。拷贝个数是 src 和 dst 的长度最小值。如果 src 是字符串那么元素类型就是 byte。如果你还想继续使用 src,在拷贝结束后执行 src = dst。 ``` package main import "fmt" func main() { sl := []int{1, 2, 3, 4, 5, 6} s2 := []int{7, 8, 9} copy(s1, s2) fmt.Println(s1) } ``` ``` [7, 8, 9, 4, 5, 6] ``` **参考资料** https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/directory.md
标签:
上一篇:
golang笔记---控制结构
下一篇:
golang笔记---字符串操作整理
文章分类
css
elasticsearch
git
golang
guacamole
javascript
letsencrypt
linux
nginx
other
php
python
vue
web
阅读排行
golang笔记---关联数组(map)
letsencrypt证书-管理工具certbot
centos7.3配置guacamole
golang笔记---template模板语法
nginx笔记-proxy_cache缓存详解
友情链接
node文件
laravel-vue
ArcherWong的博客园