一、golang数组
数组的定义
var a [len]int,如:var a [5]int,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。
数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1,遍历数组一般有以下两种方式:
for i := 0; i < len(a); i++ {
}
for index, v := range a {
}
ps:数组支持比较,支持 “==”、"!=" 操作符,比较数组类型以及数组中的每一个元素,类型不同不能进行比较。
func main() {
a := [5]int{1, 2, 3, 4}
b := [5]int{1, 2, 3, 4}
c := [5]int{1, 2, 3}
fmt.Println(" a == b ", a == b) //true
fmt.Println(" a == c ", a == c) //false
}
初始化数组
1、一维数组
var arr1 = [5]int{1, 2, 3, 4, 5} 指定元素个数
var arr2 = […]int{1, 2, 3, 4, 5, 6} 不指定元素个数 可以使用 … 代替数组的长度,编译器会根据元素个数自行推断数组的长度
var str = [5]string{3: “hello world”, 4: “tom”}
通过指定下标初始化元素:将索引为3和4的元素初始化。ps:初始化数组中 {} 中的元素个数不能大于 [] 中的数字。如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小。
a := [3]int{1, 2} 未初始化元素值为 0
b := […]int{1, 2, 3, 4} 通过初始化值确定数组长度
c := [5]int{2: 100, 4: 200} 使用索引号初始化元素
数组元素可以通过索引来读取(或修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。
2、多维数组
var arr0 [5][3]int
var arr1 [2][3]int = […][3]int{{1, 2, 3}, {7, 8, 9}}
a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
b := […][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 “…”
a := [3][4]int{
{0, 1, 2, 3} , /* 第一行索引为 0 /
{4, 5, 6, 7} , / 第二行索引为 1 /
{8, 9, 10, 11}, / 第三行索引为 2 */
}
func main() {
var arr0 [5][3]int
var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
a := [3][4]int{ //a := [3][...]int{} 这样会报错
{0, 1, 2, 3} ,
{4, 5, 6, 7} ,
{8, 9, 10, 11},
}
fmt.Println(arr0)
fmt.Println(arr1)
fmt.Println(a)
}
结果:
3、数组遍历
for index,val := range nums{
}
for i:=0;i<len(a);i++ {
for j:=0;j<len(a[0]);j++ {
fmt.Printf("(%d,%d)=%d ",i,j,a[i][j])
}
}
结果:
二、golang切片
1、概念
切片和数组一样都是保存相同数组类型元素的容器,但切片的元素个数可变, 数组不可变,它是数组的引用,所以它是引用类型
2、创建切片
func main() {
//1.声明切片
var s1 []int
if s1 == nil {
fmt.Println("是空")
} else {
fmt.Println("不是空")
}
// 2.:=
s2 := []int{}
// 3.make()创建切片
var s3 []int = make([]int, 0, 0) //指定切片长度和容量
// 简写 s3 := make([]int, 0)
fmt.Println(s1, s2, s3)
// 4.从数组切片
arr := [5]int{1, 2, 3, 4, 5}
// 前包后不包
s6 := arr[1:4]
fmt.Println(s6)
}
结果:
func main() {
slice1 := make([]string, 3, 10)
slice2 := []string{"ZhangSan","LiSi","WangWu"}
fmt.Printf("slice1切片长度: %d\n", len(slice1))
fmt.Printf("slice1切片容量: %d\n", cap(slice1))
fmt.Printf("slice2切片数据: %s\n", slice2)
}
结果:
3、切片的初始化
arr := […]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[start:end]
slice6 := arr[:end]
slice7 := arr[start:]
切片常用操作和含义
func main() {
/* 创建切片 */
numbers := []int{0,1,2,3,4,5,6,7,8}
/* 打印原始切片 */
fmt.Println("numbers ==", numbers)
/* 打印子切片从索引1(包含) 到索引4(不包含)*/
fmt.Println("numbers[1:4] ==", numbers[1:4])
/* 默认下限为 0*/
fmt.Println("numbers[:3] ==", numbers[:3])
/* 默认上限为 len(s)*/
fmt.Println("numbers[4:] ==", numbers[4:])
/*读写操作实际目标是底层数组*/
nums := numbers[2:4]
nums[0] += 100
nums[1] += 200
/* 打印原始切片 */
fmt.Println("numbers ==", numbers)
}
结果:
4、空(nil)切片
一个切片在未初始化之前默认为 nil,长度和容量为 0 且没有底层数组。
unc main() {
/* 创建切片 */
var slice []int
if(slice == nil){
fmt.Printf("切片为空\n")
}
fmt.Printf("切片长度为: %d\n",len(slice))
}
结果:
5、append() 和 copy() 函数
如果要向切片中追加新元素,append 方法。如果要增加切片的容量,则必须创建一个新的更大的切片并把原切片的内容拷贝过来。
func main() {
/* 创建切片 */
var slice1 []int
slice1 = append(slice1,0)
printMsg(slice1)
slice1 = append(slice1,1,2,3)
printMsg(slice1)
/* 创建切片 slice2 是之前切片的两倍容量*/
slice2 := make([]int, len(slice1), (cap(slice1))*2)
/* 拷贝 slice1 的内容到 slice2 */
copy(slice2, slice1)
printMsg(slice2)
}
func printMsg(slice []int) {
fmt.Printf("len=%d cap=%d slice=%v\n",len(slice),cap(slice),slice)
}
结果:
超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满
func main() {
data := [...]int{0, 1, 2, 3, 4, 10: 0}
s := data[:2:3] //s := data[low:high:max] cap = max-low
s = append(s, 100) //append 一个值,没超出 s.cap 限制。
fmt.Println(s, data) // 没有重新分配底层数组。
fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。
data1 := [...]int{0, 1, 2, 3, 4, 10: 0}
s1 := data[:2:3] //s := data[low:high:max] cap = max-low
s1 = append(s1, 100,200) //append 两个值,超出 s.cap 限制。
fmt.Println(s1, data1) // 重新分配底层数组,与原数组无关。
fmt.Println(&s1[0], &data1[0]) // 比对底层数组起始指针。
}
结果:
ps:扩容通常以 2 倍容量重新分配底层数组,在大批量添加数据时,一次性分配足够大的空间比较好,以减少内存分配和数据复制开销。
6、切片的底层原理
切片底层包含3个字段:指向底层数组的指针、切片长度、切片允许增长到的元素的个数(容量),如下图。
基于同一个数组或切片创建的不同切片共享同一个底层数组。如果一个切片修改了该底层数组的共享部分,其他切片和原始数组或切片都能感知到。其底层数据结构如下面两个图所示:
func main() {
data := []int{10, 20, 30, 40, 50}
nums1 := data[:2]
nums2 := data[2:]
fmt.Println(data, nums1, nums2)
nums1[1] = 200
nums2[1] = 400
fmt.Println(data, nums1, nums2)
}
结果:
7、切片与数组相比具有哪些优点
切片可以动态扩容,长度可变,数组长度不可变
Go 数组是值类型,赋值和函数传参操作都会复制整个数组数据。把第一个大数组传递给函数会消耗很多内存,采用切片的方式传参可以避免上述问题。切片是引用传递,所以它们不需要使用额外的内存并且比使用数组更有效率。