欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-09-28 23:18:39
一、前言 学过 Go 的都知道在 Go 语言中有四种复合数据类型:数组、切片(Slice)、哈希表(Map)和结构体(Struct),而很多 Go 初学者也很容易把数组和切片弄混淆,所以要怎么把这两个数据类型分清楚呢? 二、数组 1.简介 数组是聚合类型,是一组同类型数据的集合,通过从0开始的下标索 ......

一、前言

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

   【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 中了。