1、数组
特别需要注意的是:在 Go 语言中,数组长度也是数组类型的一部分!所以尽管元素类型相同但是长度不同的两个数组,它们的类型并不相同。
1.1、数组的初始化
1.1.1、通过初始化列表{}来设置值
var arr [3]int // int类型的数组默认会初始化全为 0 var addr = [3]string{"beijing","wuhan","shanghai"} // 指定初始化值
1.1.2、自动推断数组长度
var addr = [...]string{"beijing","wuhan","shanghai"}
1.1.3、通过索引初始化部分元素
// 数组类型 [6]string var addr = [...]string{0:"beijing",3:"wuhan",5:"shanghai"} // 数组类型 [3]int nums := [...]int{0:1,2:3}
1.2、数组的遍历
1.2.1、一维数组的遍历
var arr = [3]int{1,2,3} arr[1] = 3 // 通过索引修改数组的值 for i:=0;i<len(arr);i++{ fmt.Printf("%d ",arr[i]) // 1 3 3 }
1.2.2、二维数组的遍历
二维数组的定义
注意:在二维数组中,列数必须指定,无法自动推导! 行数可以用 [...] 来自动推导。
// 二维数组的初始化 var table = [2][3]int{ {1,2,3}, {4,5,6} } fmt.Println(table) // [[1 2 3] [4 5 6]]
普通遍历
这种遍历方式就是利用索引来遍历:
for i := 0; i < len(table); i++ { for j := 0; j < len(table[i]); j++ { fmt.Print(table[i][j]," ") } fmt.Println() } // 1 2 3 // 4 5 6
使用 range 遍历
range 关键字是专门用来遍历数组或切片的,它会返回两个值:索引和元素值。
for _,i := range table{ for _,j := range i{ fmt.Print(j," ") } fmt.Println() }
对于索引我们不需要,所以直接赋值给 _ ,而外层的元素值 i 代表的是二维数组 table 的每一行,相当于是一个一维数组。
所以,对于上面的一维数组遍历,我们同样可以采用这种方式:
for _,i := range arr{ fmt.Print(i," ") }
1.3、数组是值类型的
数组是值类型的!这是非常重要的一点。这意味着如果把数组作为参数传递给函数进行处理,那么实际的数组并不会发生改变。而且数组的容量是固定的,在定义时必须就确定!
但是 Go 语言提供了一种可以引用类型的特殊数组——切片(slice)。对于切片 ,它不仅是引用类型(作为函数参数时,如果形参被修改,那么实参也将被修改),同时也是可动态扩容的,也就是说我们不需要向数组那样在声明时就初始化大小。
func main(){ s1 := [2]int{0,0} // [0,0] fmt.Println(s1) // [0,0] addOne(s1) fmt.Println(s1) // [0,0] } func addOne(s [2]int){ for i:=0;i<len(s);i++{ s[i] += 1 } }
可以看到,正因为数组是值类型的,所以当把数组传递给函数的时候,函数中操作的形参相当于是拷贝的这么一个数组,所以操作完毕之后实参并不受影响。(这里的形参在 Java 中就像存在 addOne 方法自己的栈区,而实参存在 mian 方法的栈区,所以操作的就不是同一个内存地址)
1.3.1、数组的比较
也正因为数组是值类型的,所以它支持比较
var arr1 = [3]int{1,2,3} var arr2 = [3]int{1,2,3} fmt.Println(arr1 == arr2) // true
可以看到,两个相同类型元素相同的数组是相同的。
2、切片
正因为数组的长度固定,并且数组长度属于数组类型的一部分,所以使用数组非常局限。比如我们定义一个遍历数组的方法,那么我们必须指定数组的类型!
比如下面这个方法只能遍历存放三个元素的数组:
func printArr(arr [3]int){ for _,i := range arr{ fmt.Print(i," ") } }
2.1、切片的定义
切片定义时不需要指定容量,所以也就没有什么初始值:
var arr [] string
需要注意的一点是:因为切片是引用类型而不是值类型的,所以它不能直接比较(和其它切片用 == 进行比较,只能和 nil 进行比较)
2.1.1、切片的长度和容量
切片拥有自己的长度和容量,我们可以通过使用内建的 len() 函数求长度,使用内建的 cap() 函数求切片的容量。
- 长度(Length):长度表示切片中当前可以访问的元素个数,即从第一个元素到最后一个元素的数目。
- 容量(Capacity):容量是指切片的底层数组的大小,即从切片的第一个元素到底层数组的最后一个元素之间的元素总数。容量代表了在不重新分配内存的情况下,切片可以增长到的最大大小。
2.1.2、简单切片表达式
通过数组来给切片初始化:
s := a[low : high]
注意:切片表达式中的low和high表示一个索引范围()左包含,右不包含)
var a = [5]int{1,2,3,4,5} s := a[1:3] fmt.Println(a) // [1,2,3,4,5] fmt.Println(s) // [2,3] fmt.Printf("cap(s)=%v,len(s)=%v",cap(s),len(s)) // cap(s)=4,len(s)=2
此外
- s := a[1:] :代表从索引 [0,len(s))
- s := a[:2]:代表从索引 [0,2)
- s := a[:]:代表整个数组
注意:对于数组或字符串,如果 0 <= low <= high <= len(a),则索引合法,否则就会索引越界(out of range)。
2.1.3、完整切片表达式
完整的切片表达式是这样的:
s := a[low : high : max]
除了比普通切片表达式多了一个参数 max (这个 max 的作用是将得到的结果切片的容量设置为 max-low),此外,在完整切片表达式中,只有第一个索引值 low 可以省略,它默认为 0.
var a = [5]int{1,2,3,4,5} s := a[:3:5] fmt.Println(a) // [1,2,3,4,5] fmt.Println(s) // [1,2,3] fmt.Printf("cap(s)=%v,len(s)=%v",cap(s),len(s)) // cap(s)=5,len(s)=3
注意:完整切片表达式需要满足的条件是 0 <= low <= high <= max <= cap(a),其他条件和简单切片表达式相同。
2.1.4、使用 make 函数构造切片
我们上面都是基于已有的数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内建的 make()函数,格式如下:
// T: 切片元素类型 make([]T, size, cap)
比如:
arr := make([]int,2,10) fmt.Println(arr) // [0,0] fmt.Println(cap(arr)) // 10 fmt.Println(len(arr)) // 2
上面代码中切片 arr 的内部存储空间已经分配了10个,但实际上只用了2个。 容量并不会影响当前元素的个数,所以len(a)返回2,cap(a)则返回该切片的容量 10。
注意:对于未开辟的空间是不能初始化赋值的,比如上面我们指定了 2 个长度,如果此时对 arr[3] := 1 进行赋值,那么会报错,因为切片必须使用特定的方法来进行元素的添加(append)。
2.1.5、切片的本质
切片的本质就是对底层数组的封装,它包含了三个信息:
- 底层数组的指针
- 切片的长度(len)
- 切片的容量(cap)
a := [8]int{0, 1, 2, 3, 4, 5, 6, 7} s1 := a[:5]
s2 := a[3:6]
Go 语言基础之指针、复合类型【数组、切片、指针、map、struct】(2)https://developer.aliyun.com/article/1534258