
本文共 6483 字,大约阅读时间需要 21 分钟。
数组相关
在Go
语言中,数组是一种容器相关的数据类型,用于存放多种相同类型的数据。
数组定义
在定义数组时,必须定义数组的类型以及长度,数组一经定义不可进行改变。
同时,数组的长度是按照元素个数进行统计的,并且数组长度是数组的一部分。
package mainimport ( "fmt")func main() { var arr [10]string // 定义一个长度为10byte的数组 fmt.Println(len(arr)) // 10 数组长度 fmt.Println(cap(arr)) // 10 数组容量 // 定义完成后进行赋值 arr = [10]string{"①","②","③"}}
定义赋值
数组可以在一经定义的时候就进行赋值,赋值的长度不可大于数组的长度。
数组也可以不定义长度,而是使用...
语法来动态计算填充的元素长度。
数组也可以在定义的时候通过索引对其进行赋值。
以下是定长的数组定义赋值
package mainimport ( "fmt")func main() { var arr = [10]string{"①", "②", "③", "④","⑤","⑥","⑦","⑧","⑨","⑩"} fmt.Printf("数组长度:%d\n", len(arr)) // 数组长度:10 fmt.Printf("数组容量:%d\n", cap(arr)) // 数组容量:10 fmt.Printf("第一个元素:%v\n", arr[0]) // 第一个元素:①}
以下是不定长的数组定义赋值
package mainimport ( "fmt")func main() { var arr = [...]string{"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩", "拾壹"} fmt.Printf("数组长度:%d\n", len(arr)) // 数组长度:11 fmt.Printf("数组容量:%d\n", cap(arr)) // 数组容量:11 fmt.Printf("最后一个元素:%v\n", arr[len(arr)-1]) // 最后一个元素:拾壹}
以下是定长的数组索引赋值:
package mainimport ( "fmt")func main() { var arr = [10]string{1: "②", 8: "⑨"} fmt.Printf("数组长度:%d\n", len(arr)) // 数组长度:11 fmt.Printf("数组容量:%d\n", cap(arr)) // 数组容量:11 fmt.Printf("数组元素:%v\n", arr) // 数组元素:[ ② ⑨ ]}
默认填充
当定义了一个数组并未填充数据时,会进行默认的数据填充:
布尔值是false
字符串是""
整形和浮点型都是0
多维数组
多维数组只有第一层可以使用...
来让编译器推导数组长度,其他层都需要手动指定长度。
如下定义了一个二维数组:
package mainimport ( "fmt")func main() { // 一维数组:不定长 二维数组:定长,2个 在一维数组中创建了3个二维数组 var arr = [...][2]string{ {"1-1", "1-2"}, {"2-1", "2-2"}, {"3-1", "3-2"}, } fmt.Println(arr) // [[1-1 1-2] [2-1 2-2] [3-1 3-2]]}
值类型
数组本身是值类型的,所以当重新复制一份数组时,不会受到前数组的影响。
这相当于深拷贝。
如下示例,单一的数组中存入值类型,互不影响。
package mainimport ( "fmt")func main() { var arr = [...]int{1, 2, 3} // 数组是值类型 newArr := arr arr[0] = 10 fmt.Println(arr) // [10 2 3] fmt.Println(newArr) // [1 2 3]}
示例二,二维数组的改变,也不会引发另一个数组的改变,故此说是深拷贝。
package mainimport ( "fmt")func main() { var arr = [...][1]int{ {10}, } // 数组是值类型 newArr := arr arr[0][0] = 100 fmt.Println(arr) // [[100]] fmt.Println(newArr) // [[10]]}
循环遍历
可以使用for
的索引循环,也可以使用range
来进行数组遍历。
package mainimport ( "fmt")func main() { var arr = [10]string{"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩"} // 索引循环 for index := 0; index < len(arr); index++ { fmt.Println(index) } // range循环 for index,ele := range arr{ fmt.Println(index) fmt.Println(ele) }}
切片相关
切片是基于数组的一层封装,非常灵活且支持扩容。
你可以将它当作一个更加高级的数组。
注意:切片是引用类型,不能直接做比较,只能和nil做比较。
nil则相当于null,是指没有分配内存的一个变量。只要被分配了内存,就非nil
切片声明
声明切片类型的基本语法如下:
var 变量名 []切片类型
以下是一些示例,对于单纯的创建切片来说,它没有像数组一样的容量限制,但是具有类型限制:
package mainimport ( "fmt")func main() { var a []string var b = []int{} var c = []bool{true,false} fmt.Println(a) // [] fmt.Println(b) // [] fmt.Println(c) // [true false] fmt.Println(a == nil) // true}
可以如同上面示例中,在定义阶段进行赋值,也可以在定义完成后再进行赋值
package mainimport ( "fmt")func main(){ var a []string a = []string{"①","②","③","④","⑤"}}
切片引用
如果对一个数组进行切片取值,那么切片的元素引用于原数组中的元素。
package mainimport ( "fmt")func main() { var arr = [...]string{"①", "②", "③", "④", "⑤"} var arrScile = arr[1:3] // 引用数组的②③ arr[1] = "二" arr[2] = "三" fmt.Println(arrScile) // [二 三] 跟随发生改变}
这里是常见的一些切片引用的操作,需要注意的是切片引用顾头不顾尾。
a[2:] // 等同于 a[2:len(a)]a[:3] // 等同于 a[0:3]a[:] // 等同于 a[0:len(a)]
长度容量
引用切片的len()
指的是切出来的元素数量。
而cap()
则是被切片的数组中,从第一个位置切的地方往后算还剩多少个元素。
长度就是当前切片或者数组中存在的元素个数
容量就是最多可以容纳的元素个数
package mainimport ( "fmt")func main() { var arr = [...]string{"①", "②", "③", "④", "⑤"} var arrSlice = arr[1:3] fmt.Println(len(arrSlice)) // 2 fmt.Println(cap(arrSlice)) // 4}
如何理解?这里有一幅图,因为有指针的关系。所以才是引用:
make切片
上面的切片都是经过数组创建出来的,切片的容量不能由我们自己进行控制。
但是使用make()
的话就可以动态的创建出一个切片。
make([]类型, size切片中元素数量, cap切片容量)
如下示例:
package mainimport ( "fmt")func main() { // 创建一个string类型的切片 默认存放3个元素 最大可存放5个 var slice = make([]string,3,5) fmt.Println(slice) // [ ]}
append
使用内置函数append()
,可以为切片添加元素。
可以添加一个元素,也可以添加多个元素,甚至可以利用解构语法进行切片合并。
需要注意的是,当使用append()
方法后,应该用一个变量来接收它。
以下是添加多元素的示例
package mainimport ( "fmt")func main() { var slice []string slice = append(slice, "一", "二") fmt.Println(slice) // [一 二]}
以下是合并两个切片的示例
package mainimport ( "fmt")func main() { var ( slice1 []string slice2 []string ) slice1 = append(slice1, "一", "二","三") slice2 = append(slice2, "四", "五") slice1 = append(slice1,slice2...) fmt.Println(slice1) // [一 二 三 四 五]}
copy
切片是引用类型,如果想将其作为值类型的传递可以用copy
。
它有两个参数,分别是destSlice
和srcSlice
。
destSlice:目标切片
srcSlice:数据来源切片
注意:目标切片必须是make创建的切片,否则将会失败
package mainimport ( "fmt")func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := make([]int, 5, 5) // 目标切片 数据来源切片 copy(slice2, slice1) slice1[0] = 100 fmt.Println(slice1) // [100 2 3 4 5] fmt.Println(slice2) // [1 2 3 4 5]}
删除元素
切片中没有提供任何删除元素的方法,但是可以利用append()
返回新切片的特性,来达到删除元素的目的。
package mainimport ( "fmt")func main() { slice1 := []int{1, 2, 3, 4, 5} // 删除3 slice1 = append(slice1[:2],slice1[3:]...) fmt.Print(slice1) // [1 2 4 5]}
扩容行为
切片不需要指定容量,它会自动进行扩容。如下示例:
func main() { var a []string fmt.Println(cap(a)) // 0 初始值 a = append(a,"1") fmt.Println(cap(a)) // 1 a = append(a,"2") fmt.Println(cap(a)) // 2 a = append(a,"3") fmt.Println(cap(a)) // 4 a = append(a,"4") fmt.Println(cap(a)) // 4 a = append(a,"5") fmt.Println(cap(a)) // 8 a = append(a,"6") fmt.Println(cap(a)) // 8 a = append(a,"7") fmt.Println(cap(a)) // 8 a = append(a,"8") fmt.Println(cap(a)) // 8}
可以通过查看$GOROOT/src/runtime/slice.go
源码,其中扩容相关代码如下:
newcap := old.capdoublecap := newcap + newcapif cap > doublecap { newcap = cap} else { if old.len < 1024 { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { newcap += newcap / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { newcap = cap } }}
从上面的代码可以看出以下内容:
- 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
- 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
- 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
- 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。
需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int
和string
类型的处理方式就不一样。
关于上面的示例,其实只看前两个判断就够了。因为旧的容量没有超过1024
循环遍历
可以使用for
的索引循环,也可以使用range
来进行切片遍历。
package mainimport ( "fmt")func main() { var arr = []string{"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩"} // 索引循环 for index := 0; index < len(arr); index++ { fmt.Println(index) } // range循环 for index,ele := range arr{ fmt.Println(index) fmt.Println(ele) }}
发表评论
最新留言
关于作者
