ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] ### 数组 **** #### OverView 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列。这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。**数组长度**必须是一个**常量**表达式,并且必须是一个**非负整数**。 数组长度也是数组类型的一部分,所以\[5\]int和\[10\]int是属于不同类型的。 ` ` #### 示例 一维数组声明以及初始化常见方式如下: ``` var arrAge = [5]int{18, 20, 15, 22, 16} var arrName = [5]string{3: "Chris", 4: "Ron"} //指定索引位置初始化 // {"","","","Chris","Ron"} var arrCount = [4]int{500, 2: 100} //指定索引位置初始化 {500,0,100,0} var arrLazy = [...]int{5, 6, 7, 8, 22} //数组长度初始化时根据元素多少确定 var arrPack = [...]int{10, 5: 100} //指定索引位置初始化,数组长度与此有关 {10,0,0,0,0,100} var arrRoom [20]int var arrBed = new([20]int) ``` Go 语言中的数组是一种值类型(不像 C/C++ 中是指向首元素的指针),所以可以通过 new() 来创建: ``` var arr1 = new([5]int) ``` 那么这种方式和 var arr2 \[5\]int 的区别是什么呢?arr1 的类型是 \*\[5\]int,而 arr2的类型是 \[5\]int。在Go语言中,数组的长度都算在类型里。 由于把一个大数组传递给函数会消耗很多内存(值传递),在实际中我们通常有两种方法可以避免这种现象: ``` 传递数组的指针 使用切片 ``` ` ` #### 多维数组 在定义多维数组时,仅第一维允许使用“…”,而内置函数len和cap也都返回第一维度长度。定义数组时使用“…”表示长度,表示初始化时的实际长度来确定数组的长度。 ``` b := [...][5]int{ { 10, 20 }, { 30, 40, 50, 60 } } fmt.Println(b[1][3], len(b)) //60 2 ``` 遍历数组 ``` var arrAge = [5]int{18, 20, 15, 22, 16} for i, v := range arrAge { fmt.Printf("%d 的年龄: %d\n", i, v) } ``` ` ` ### 切片 ***** **切片(slice)**是对底层数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个**引用类型**(和数组不一样)。切片提供对该数组中编号的元素序列的访问。 切片类型表示其元素类型的所有数组切片的集合。未初始化切片的值为nil。 切片具有长度和容量,**与数组不同,切片的长度可能在执行期间发生变化**,切片提供了计算容量的函数 cap() ,可以测量切片最大长度。切片的长度永远不会超过它的容量,所以对于切片 s 来说,这个不等式永远成立:0 <= len(s) <= cap(s)。 #### 优点 因为切片是引用,所以它们不需要使用额外的内存并且比使用数组更有效率: ``` var x = []int{2, 3, 5, 7, 11} var slice1 []type = make([]type, len,cap) ``` 也可以简写为 slice1 := make([]type, len),这里 len 是数组的长度并且也是 slice 的初始长度。cap是容量,其中 cap 是可选参数。 ``` v := make([]int, 10, 50) ``` 如果从数组或者切片中生成一个新的切片,我们可以使用下面的表达式: **a[low : high : max]** max-low的结果表示容量,high-low的结果表示长度。 ``` a := [5]int{1, 2, 3, 4, 5} t := a[1:3:5] ``` #### 切片重组 ``` slice1 := make([]type, start_length, capacity) ``` 通过改变切片长度得到新切片的过程称之为切片重组 reslicing,做法如下:slice1 = slice1[0:end],其中 end 是新的末尾索引(即长度)。 ` ` 当我们在一个slice基础上重新划分一个slice时,新的slice会继续引用原有slice的数组,我们应该避免分配大量临时的slice用于创建新的slice 来引用原有数据的一小部分。 错误示范: ``` package main import "fmt" func get() []byte { raw := make([]byte, 10000) fmt.Println(len(raw), cap(raw), &raw[0]) // 显示: 10000 10000 数组首字节地址 return raw[:3] // 10000个字节实际只需要引用3个,其他空间浪费 } func main() { data := get() fmt.Println(len(data), cap(data), &data[0]) // 显示: 3 10000 数组首字节地址 } ``` 为了避免这个陷阱,我们需要从临时的slice中使用内置函数copy(),拷贝数据(而不是重新划分slice)到新切片。 ``` package main import "fmt" func get() []byte { raw := make([]byte, 10000) fmt.Println(len(raw), cap(raw), &raw[0]) // 显示: 10000 10000 数组首字节地址 res := make([]byte, 3) copy(res, raw[:3]) // 利用copy 函数复制,raw 可被GC释放 return res } func main() { data := get() fmt.Println(len(data), cap(data), &data[0]) // 显示: 3 3 数组首字节地址 } ``` ` ` append()内置函数: ``` func append(s S, x ...T) S // T是S元素类型 ``` Append()函数将相同类型S的元素追加到切片s后面并且返回新的切片。 如果 s 的容量不足以存储新增元素,append 会分配新的切片来保证已有切片元素和新增元素的存储。 append()函数返回的切片可能已经指向一个不同的相关数组了。append()函数总是返回成功,除非系统内存耗尽了。