携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情
数组
类型[n]T表示拥有n个T类型的值的数组。数组的长度是其类型的一部分,因此数组不能改变大小。
package main
import "fmt"
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
切片
每个数组的大小都是固定的。而切片则为数组元素提供动态的大小、灵活的视角。在实践中,切片比数组更常用。
类型[]T表示一个元素类型为T的切片。
它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
package main
import "fmt"
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
}
切片就像数组的引用
切片不存储任何数据,它只描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素。与它共享底层数组的切片都会观测到这些修改。
package main
import "fmt"
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}
切片文法
切片类似于没有长度的数组文法。
这是一个数组文法:
[3]bool{true,true,false}
下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片:
[]bool{true,true,false}
例子:
package main
import "fmt"
func main() {
q := []int{2, 3, 5, 7, 11, 13}
fmt.Println(q)
r := []bool{true, false, true, true, false, true}
fmt.Println(r)
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
}
切片的默认行为
在进行切片时,你可以利用它的默认行为来忽略上下界。 切片下界的默认值0。上届则是该切片的长度。 对于数组
var a [10]int
来说,以下切片时等价的:
a[0,10]
a[:10]
a[0:]
a[:]
例子:
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
s = s[1:4]
fmt.Println(s)
s = s[:2]
fmt.Println(s)
s = s[1:]
fmt.Println(s)
}
切片的长度与容量
切片拥有长度和容量。 切片的长度就是它所包含的元素个数。 切片的容量是从它的第一个元素开始,到其底层数组元素末尾的个数。 切片s的长度和容量可通过表达式len(s)和cap(s)来获取。
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
//截取切片使其长度为0,底层数组不变
s = s[:0]
printSlice(s)
//拓展其长度,底层数组不变 len=4,cap=6(cap为切片的第一个元素到底层数组元素的末尾)
s = s[:4]
printSlice(s)
//舍弃前两个值,底层舍弃了前面两个 len=2 cap=4(因为舍弃了两个值)
s = s[2:]
printSlice(s)
//上面的下边界移动了,所以底层数组舍弃了前面两个 len=2 cap=4 和上面的没有本质区别
s = s[0:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
下边界往右移动才会导致底层数组进行舍弃,切面只是一个视图。
nil切片
切片的零值是nil。 nil切片的长度和容量为0且没有底层数组。
package main
import "fmt"
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}
切片的切片
切片可包含任何类型,甚至可以包括其它的切片。
例子:
package main
import (
"fmt"
"strings"
)
func main() {
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
board[0][0] = "X"
board[2][2] = "O"
board[1][2] = "X"
board[1][0] = "O"
board[0][2] = "X"
for i := 0; i < len(board); i++ {
fmt.Printf("%s\n", strings.Join(board[i], " "))
}
}
用make创建切片
切片可以用内建函数make来创建,这也是你创建动态数组的方式。
make函数会分配一个元素为零值的数组并返回一个引用了它的切片:
a := make([]int, 5) // len(a)=5
要指定它的容量,需向 make 传入第三个参数:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
例子:
package main
import "fmt"
func main() {
//len(a)=5
a := make([]int, 5)
printSlice1("a", a)
//len(a)=5 cap(a)=10
b := make([]int, 5, 10)
printSlice1("b", b)
c := b[:2]
printSlice1("c", c)
d := c[2:5]
printSlice1("d", d)
}
func printSlice1(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n", s, len(x), cap(x), x)
}
向切片追加元素
为切片追加新的元素是种常用的操作,为此Go提供了内建的append函数。内建函数的文档对此函数有详细的介绍。
func append(s []T, vs ...T) []T
当s的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。(要了解关于切片的更多内容,请阅读文章 Go 切片:用法和本质。)
例子:
package main
import "fmt"
func main() {
var s []int
printSlice2(s)
s = append(s, 0)
printSlice2(s)
s = append(s, 1)
printSlice2(s)
s = append(s, 2, 3, 4)
printSlice2(s)
}
func printSlice2(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
注意:第三个append会进行扩容,具体可参考Go 切片:用法和本质。
Range
for循的range形式可遍历切片或映射。 每次迭代会返回两个值,一个是下标,一个是下标对应元素的一份副本。
例子:
package main
import "fmt"
var powvar = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range powvar {
fmt.Printf("2**%d = %d\n", i, v)
}
}
可以将下标或者值赋予_来忽略它。
for i, _ := range pow
for _, value := range pow
若你只需要索引,忽略第二个变量即可。
for i := range pow
例子:
package main
import "fmt"
func main() {
pow := make([]int, 10)
for i := range pow {
pow[i] = 1 << uint(i)
}
for _, value := range pow {
fmt.Printf("%d\n", value)
}
}