【Go入门学习】理解区分数组和切片

一、前言

  学过 Go 的都知道在 Go 语言中有四种复合数据类型:数组、切片(Slice)、哈希表(Map)和结构体(Struct),而很多 Go 初学者也很容易把数组和切片弄混淆,所以要怎么把这两个数据类型分清楚呢?

   

 

二、数组

1.简介

  数组是聚合类型,是一组同类型数据的集合,通过从0开始的下标索引访问元素值。在 Go 语言中,数组是值类型,这就意味着当你将一个数组赋值给另一个数组的时候,实际上是将这个数组拷贝了一份。

  数组的声明语法为:

var 数组变量名 [元素数量]Type

  语法说明如下所示:

  • 数组变量名:数组声明及使用时的变量名;
  • 元素数量:数组的元素数量,可以是一个表达式,但最终计算的结果必须是整型数值;
  • Type:可以是任意基本类型,包括数组本身,类型为数组本身时,可以实现多维数组。

2.初始化

  默认情况下,数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是0。在数组字面值中,如果在数组的长度位置出现的是“...”省略号,则表示数组的长度是根据初始化值的个数来计算。示例如下:

1 var a [3]int
2 a[0] = 1
3 b := [2]int{1, 2}
4 c := [...]int{3, 5, 7}
5 fmt.Println(a) // [1 0 0]
6 fmt.Println(b) // [1 2]
7 fmt.Println(c) // [3 5 7]

3.遍历数组

  数组通过下标访问元素,可修改其元素值。数组的遍历通过 for 循环实现:

1 arr := [3]int{2, 4, 6}
2 for i := 0; i < 3; i++ {
3     fmt.Printf("%d ", arr[i])
4 } // 2 4 6 
5 fmt.Println()
6 for _, v := range arr {
7     fmt.Printf("%d ", v)
8 } // 2 4 6 

 

三、切片

1.简介

  数组的长度不可改变,在一定场合下就不太适用了,Go 语言则提供了一种可以动态扩容的数据类型--切片(Slice)。一个切片类型通常会写作 []T,其中 T 代表切片中元素的数据类型,切片的语法和数组类似,只是没有固定长度。  

2.区别

  切片和数组有如下区别:

  1)和数组相比,切片除了有长度(len),还有容量(cap),容量指切片当前可容纳元素的最大数量。

  2)数组是值类型,切片是引用类型。

  值类型和引用类型有什么区别呢?在传递参数时,如果是值类型,对参数修改不会对原来的变量产生影响,但若是引用传递,对参数的修改也会影响到原始数据。示例如下:

 1 package main
 2 
 3 import (
 4     "fmt"
 5 )
 6 
 7 func change(a [3]int, s []int) {
 8     a[0] += 1
 9     s[0] += 1
10     s = append(s, 9)
11 }
12 
13 func main() {
14     arr := [3]int{2, 4, 6}
15     sli := []int{3, 5, 7}
16     change(arr, sli)
17     fmt.Println(arr) // [2 4 6]
18     fmt.Println(sli) // [4 5 7]
19 }

  在示例中,分别对数组 arr 和切片 sli 的第一个元素进行了+1操作,但从打印结果可以看出来只有切片的数据被修改了,而对数组的修改并没有改变原始数据。那为什么最后 sli 的结果不是 [4 5 7 9]呢?这是因为 append() 实际上是将切片 sli 复制了一份然后赋值给了 s,已经是一份新的数据了,也就不会对 sli 产生影响了。

3.初始化

  切片的初始化可以通过数组来实现,也可以通过内置函数 make() 来实现,在使用 make() 方法时还可以设置切片的容量,在追加元素时,若切片的容量不足,则会按切片的长度的二倍进行扩容。示例如下:

1 arr := [5]int{1, 2, 3, 4, 5}
2 s1 := arr[2:]
3 fmt.Println(s1) // [3 4 5]
4 s2 := arr[:]
5 fmt.Println(s2) // [1 2 3 4 5]
6 s3 := make([]int, 3)
7 s3[0], s3[1], s3[2] = 2, 4, 6
8 fmt.Println(s3) // [2 4 6]

4.追加元素

  在 Go 语言中有一个内置函数 append(),查看源码发现它是这么定义的:

func append(slice []Type, elems ...Type) []Type

  内置的 append() 函数用于向 slice 追加元素,示例为:

1 arr := [5]int{1, 2, 3, 4, 5}
2 var sli []int
3 for _, v := range arr {
4     sli = append(sli, v)
5 }
6 fmt.Println(sli) // [1 2 3 4 5]

  细心的人会发现源码中写的是 elems,这是不是就意味着可以一次添加多个元素呢?试一试:

1 var sli []int
2 sli = append(sli, 1, 2, 3)
3 fmt.Println(sli) // [1 2 3]

  例子很简单,append() 使用起来也很方便,但问题是如果要添加的元素数量超过了切片的容量,又会发生什么情况呢?看下面的例子:

1 var y []int
2 for i := 0; i < 10; i++ {
3     y = append(y, i)
4     fmt.Printf("%d cap=%d %v\n", i, cap(y), y)
5 }

  这几行代码的运行结果为:

0 cap=1 [0]
1 cap=2 [0 1]
2 cap=4 [0 1 2]
3 cap=4 [0 1 2 3]
4 cap=8 [0 1 2 3 4]
5 cap=8 [0 1 2 3 4 5]
6 cap=8 [0 1 2 3 4 5 6]
7 cap=8 [0 1 2 3 4 5 6 7]
8 cap=16 [0 1 2 3 4 5 6 7 8]
9 cap=16 [0 1 2 3 4 5 6 7 8 9]

  可以发现切片的容量从1慢慢增加为2、4、8、16,也就是说在使用 append 将元素添加至切片时,如果超出了容量,将会返回一个容量二倍与当前切片的切片

5.切片拷贝

  在 Go 语言中,切片的拷贝使用内置函数 copy() 来实现,可以放心的是,切片拷贝是深拷贝,不用像 Python 中纠结深浅拷贝真的很幸福呢!只不过拷贝的时候需要确保目的切片有足够的容量,否则会拷贝。示例如下:

1 sli := []int{3, 5, 7}
2 res := make([]int, 5)
3 copy(res, sli)
4 fmt.Println(res) // [3 5 7 0 0]
5 fmt.Println(&sli[0], &res[0]) // 0xc000012340 0xc00000c3c0
6 var s []int
7 copy(s, sli)
8 fmt.Println(s) // []

  这里 s 打印出来是空的,是由于 s 在初始化的时候没有分配内存空间,copy() 也不会为 s 分配空间,所以 sli 中的元素也就无法拷贝到 s 中了。

posted @ 2019-12-15 13:09  onionono  阅读(879)  评论(0编辑  收藏  举报