阅读 651

Go语言Array和Slice的区别

Go语言中有着两个很容易混淆的概念: 数组Array和切片Slice。本篇文章将就这两个数据结构的相似与区别进行分析。

Array

Go语言中的Array即为数据的一种集合,需要在声明时指定容量和初值,且一旦声明就长度固定了,访问时按照索引进行访问。通过内置函数len可以获取数组中的元素个数。

初始化

数组在初始化时必须指定大小和初值,不过Go语言为我们提供了一些更为灵活的方式进行初始化。例如:使用...来自动获取长度;未指定值时,用0赋予初值;指定指定元素的初值等。下面给出一些数组初始化的方式示例。

var arr [5]int	//声明了一个大小为5的数组,默认初始化值为[0,0,0,0,0]
arr := [5]int{1}	//声明并初始化了一个大小为5的数组的第一个元素,初始化后值为[1,0,0,0,0]
arr := [...]int{1,2,3}	//通过...自动获取数组长度,根据初始化的值的数量将大小初始化为3,初始化后值为[1,2,3]
arr := [...]int{4:1}	//指定序号为4的元素的值为1,通过...自动获取长度为5,初始化后值为[0,0,0,0,1]
复制代码

函数参数

Go语言数组作为函数参数时,必须指定参数数组的大小,且传入的数组大小必须与指定的大小一致,数组为按值传递的,函数内对数组的值的改变不影响初始数组:

package main

import "fmt"

//PrintArray print the value of array
func PrintArray(arr [5]int) {
    arr[0] = 5
	fmt.Println(arr)
}

func main() {
	a := [...]int{4:1}
	PrintArray(a)	// [5,0,0,0,1]
    fmt.Println(a)	// [0,0,0,0,1]
}
复制代码

Slice

切片是Go语言中极为重要的一种数据类型,可以理解为动态长度的数组(虽然实际上Slice结构内包含了一个数组),访问时可以按照数组的方式访问,也可以通过切片操作访问。Slice有三个属性:指针、长度和容量。指针即Slice名,指向的为数组中第一个可以由Slice访问的元素;长度指当前slice中的元素个数,不能超过slice的容量;容量为slice能包含的最大元素数量,但实际上当容量不足时,会自动扩充为原来的两倍。通过内置函数lencap可以获取slice的长度和容量。

初始化

Slice在初始化时需要初始化指针,长度和容量,容量未指定时将自动初始化为长度的大小。可以通过直接获取数组的引用、获取数组/slice的切片构建或是make函数初始化数组。下面给出一些slice初始化的方式示例。

s := []int{1,2,3}	//通过数组的引用初始化,值为[1,2,3],长度和容量为3

arr := [5]int{1,2,3,4,5}
s := arr[0:3]	//通过数组的切片初始化,值为[1,2,3],长度为3,容量为5

s := make([]int, 3)	//通过make函数初始化,值为[0,0,0],长度和容量为3

s := make([]int, 3, 5)	//通过make函数初始化,值为[0,0,0],长度为3,容量为5
复制代码

其中特别需要注意的是通过切片方式初始化。若是通过对slice的切片进行初始化,实际上初始化之后的结构如下图所示:

此时x的值为[2,3,5,7,11],y的值为[3,5,7],且两个slice的指针指向的是同一个数组,也即x中的元素的值的改变将会导致y中的值也一起改变。

这样的初始化方式可能会导致内存被过度占用,如只需要使用一个极大的数组中的几个元素,但是由于需要指向整个数组,所以整个数组在GC时都无法被释放,一直占用内存空间。故使用切片操作进行初始化时,最好使用append函数将切片出来的数据复制到一个新的slice中,从而避免内存占用陷阱。

函数参数

Go语言Slice作为函数参数传递时为按引用传递的,函数内对slice内元素的修改将导致函数外的值也发生改变,不过由于传入函数的是一个指针的副本,所以对该指针的修改不会导致原来的指针的变化(例如append函数不会改变原来的slice的值)。具体可以根据下面的代码进行理解:

package main

import "fmt"

//PrintSlice print the value of slice
func PrintSlice(s []int) {
	s = append(s, 4)
	s[0] = -1
	fmt.Println(s)
}

func main() {
	s := []int{1,2,3,4,5}
	s1 := s[0:3]

    fmt.Println("s:",s)	//s: [1,2,3,4,5]
    fmt.Println("s1:",s1)	//s1: [1,2,3]
	PrintSlice(s1)	//[-1,2,3,4]
	fmt.Println("s:",s)	//[-1,2,3,4,5]
	fmt.Println("s1:",s1)	//[-1,2,3]
}
复制代码

总结

  • 数组长度不能改变,初始化后长度就是固定的;切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
  • 结构不同,数组是一串固定数据,切片描述的是截取数组的一部分数据,从概念上说是一个结构体。
  • 初始化方式不同,如上。另外在声明时的时候:声明数组时,方括号内写明了数组的长度或使用...自动计算长度,而声明slice时,方括号内没有任何字符。
  • unsafe.sizeof的取值不同,unsafe.sizeof(slice)返回的大小是切片的描述符,不管slice里的元素有多少,返回的数据都是24字节。unsafe.sizeof(arr)的值是在随着arr的元素的个数的增加而增加,是数组所存储的数据内存的大小。
  • 函数调用时的传递方式不同,数组按值传递,slice按引用传递。
文章分类
阅读
文章标签