go-数组、切面

182 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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)
   }
}