摘要
本文介绍了Go语言中数组的特点、定义方式与使用注意事项,然后介绍了其引申内容《切片》的内容,同时也分析了Go中的数组和切片与C、Java中数组的不同。
1、数组的定义与特点
定义
在Go中,首先明确数组是基本类型,而在java中数组是一个对象。
数组的类型是[n]T,表示该类型拥有n个T类型的值,其定义规则有以下几种:
var a [10]int //默认赋零值
a := []int{1, 2, 3} //数组长度有赋值格式决定
a := [10]int{1,2,3} //多余的部分直接赋零值
特点
Go语言的数组和java和c中是完全不同的,go中数组变量代表了整个数组,而不是c中指向数组的指针,或者java中代表一个对象的地址。其具体特点如下:
- 1、长度n是数组类型的固有属性,在定义是必须指定数组长度。同时[4]int和[5]int不是同一种类型,在=号复制和函数传参时都不能混用,案例如下:
func changeArr(arr [3]int) {
arr[0] = 100
return
}
arr1 := [5]int{1, 2, 3, 4, 5}
changeArr(arr4) //编译报错:cannot use arr4 (variable of type [5]int) as [3]int value in argument to changeArr
arr2 := [4]int{1, 2, 3}
arr2 = arr1 //编译报错:cannot use arr4 (variable of type [5]int) as [4]int value in assignment
- 2、由于数组在Go中是基本数据类型,因此无论是等号赋值还是函数传参都是值传递!!!,案例如下:
arr2 := [3]int{1, 2, 3}
arr3 := arr2
arr3[1] = 0
fmt.Println("arr2:", arr2)
fmt.Println("arr3:", arr3)
************输出结果*************
arr2: [1 2 3]
arr3: [1 0 3]
2、切片的引入与定义
小节1中数组的两个特性导致改变数组大小或对数组引用传递时非常不方便,因此在Go语言中一般使用切片而不是数组。
切片使用[]T来表示,表示类型T的切片。切片本身依赖与数组,是一种对数组的描述。这种描述关系是一对多的,多个切片可以共同描述同一个数组,切片是一种更灵活、动态的使用方式。它可以通过数组衍生而出,也可以直接进行定义,格式如下:
//通过数组衍生
arr := [5]int{1,2,3,4,5}
slice1 := arr[1:3]
slice2 := arr[1:5]
//直接定义
slice := []int{1,2,3,4,5}
其中,需要注意的是,通过数组衍生时其引用区间时前闭后开的,如slice1 = arr[a:b]的区间指[a,b)。
3、切片的特点
- 1、 每个切片对应一个数组,同一个数组的多个切片之间数据相互共享的,案例如下:
arr := [3]int{1,2,3}
slice = arr[0:3]
silce[0] = 1
fmt.Println(slice)
*******输出结果********
[1 1 3]
- 2、切片不是基本数据类型,类型本身固定占用24个byte,由3个8位byte组成,分别是指向数组的指针、切片的长度,切片的容量。
- 3、长度和容量是切片的固有属性,其含义分别为切片的实际大小、从该切片起始位置开始到数组结束位置的大小,可以通过内置函数len()、cap()来获取。
- 4、除了上述不同点,切片的使用方式与数组相同。总结来说其实切片与java、c中的数组其实是差不多。
4、测试代码与输出
代码
package main
import (
"fmt"
"unsafe"
)
func changeArr(arr [3]int) {
arr[0] = 100
return
}
func main() {
fmt.Println("**************************************************************************************************************")
fmt.Println("1、在go中,数组写法为[n]T,其拥有n个T类型的值,数组都是有内容的,数组不具有零值")
fmt.Println("数组通过var * [n]T = [n]T{...}方式定义,中间的[n]T可以忽略")
var arr [5]int
fmt.Printf("类型:%T,内容:%v\n", arr, arr)
fmt.Println("长度:", len(arr), "容量:", cap(arr))
fmt.Println()
fmt.Println("**************************************************************************************************************")
fmt.Println("2、不同于java或c,go中数组变量代表了整个数组,而不是c中指向数组的指针,或者java中代表一个对象的地址")
fmt.Println("这意味这每一次数组的赋值和传递都会复制整个数组,因此go中的数组真的就是一种基本数据类型")
fmt.Println("因此在go中数组一般不用")
arr2 := [3]int{1, 2, 3}
fmt.Printf("arr2类型:%T,内容:%v,变量本身占用%d个字节\n", arr2, arr2, unsafe.Sizeof(arr2))
arr3 := arr2
arr3[1] = 0
fmt.Println("arr2:", arr2)
fmt.Println("arr3:", arr3)
changeArr(arr3)
fmt.Println("函数改变后的arr3:", arr3, "说明arr3确实是基础变量类型,不会被函数改变")
fmt.Println()
fmt.Println("**************************************************************************************************************")
fmt.Println("3、在go中,长度是数组的固定属性这意味着[4]int 和 [5]int其实不是同一种类型")
fmt.Println("也就是说[4]int不能当作[5]int作为函数参数")
arr4 := [5]int{1, 2, 3, 4, 5}
fmt.Printf("arr4类型:%T,内容:%v,变量本身占用%d个字节\n", arr4, arr4, unsafe.Sizeof(arr4))
fmt.Println("changeArr(arr4)报错")
fmt.Println()
fmt.Println("**************************************************************************************************************")
fmt.Println("4、切片:切片是对数组的描述,用[]T来表示,它提供了动态的特性,和java和c中的数组类似,并且具有更方便的使用方式")
fmt.Println("切片可以直接定义,也可以通过数组衍生出,如var slice = arr4[a:b],区间是[a,b)")
fmt.Println("切片可以参考go官方博客 https://blog.go-zh.org/go-slices-usage-and-internals")
var slice = arr4[1:3]
fmt.Println()
fmt.Println("**************************************************************************************************************")
fmt.Println("5、每个切片对应一个数组,同一个数组的多个切片之间数据相互共享")
fmt.Println("切片的变量固定为24个字节,其中一个指向数组的指针,一个长度,一个容量,因此长度和容量都是它的属性")
fmt.Println("长度指该切片的实际大小,而容量指从该切片起始位置开始到数组结束之间的大小,因为通过该切片可以继续引用到切片之后的")
fmt.Printf("slice类型:%T,内容:%v,变量本身占用%d个字节\n", slice, slice, unsafe.Sizeof(slice))
slice = slice[0 : cap(slice)-1]
fmt.Printf("slice类型:%T,内容:%v,变量本身占用%d个字节\n", slice, slice, unsafe.Sizeof(slice))
fmt.Println()
fmt.Println("**************************************************************************************************************")
fmt.Println("6、切片具有零值,零值为nil")
var slice1 []int = []int{1, 2, 3, 4, 5}
fmt.Println(slice1)
fmt.Println()
fmt.Println("**************************************************************************************************************")
fmt.Println("7、go中有内置函数append来增加切片的长度,其实内部就是将数组扩大一倍,将元素组内容放进去这样的操作")
slice = append(slice, 6)
fmt.Println(slice)
fmt.Println()
for i, v := range arr4 {
fmt.Printf("第%d个元素 = %d\n", i, v)
}
}
输出
1、在go中,数组写法为[n]T,其拥有n个T类型的值,数组都是有内容的,数组不具有零值
数组通过var * [n]T = [n]T{…}方式定义,中间的[n]T可以忽略
类型:[5]int,内容:[0 0 0 0 0]
长度: 5 容量: 5
2、不同于java或c,go中数组变量代表了整个数组,而不是c中指向数组的指针,或者java中代表一个对象的地址
这意味这每一次数组的赋值和传递都会复制整个数组,因此go中的数组真的就是一种基本数据类型
因此在go中数组一般不用
arr2类型:[3]int,内容:[1 2 3],变量本身占用24个字节
arr2: [1 2 3]
arr3: [1 0 3]
函数改变后的arr3: [1 0 3] 说明arr3确实是基础变量类型,不会被函数改变
3、在go中,长度是数组的固定属性这意味着[4]int 和 [5]int其实不是同一种类型
也就是说[4]int不能当作[5]int作为函数参数
arr4类型:[5]int,内容:[1 2 3 4 5],变量本身占用40个字节
changeArr(arr4)报错
4、切片:切片是对数组的描述,用[]T来表示,它提供了动态的特性,和java和c中的数组类似,并且具有更方便的使用方式
切片可以直接定义,也可以通过数组衍生出,如var slice = arr4[a:b],区间是[a,b)
切片可以参考go官方博客 https://blog.go-zh.org/go-slices-usage-and-internals
5、每个切片对应一个数组,同一个数组的多个切片之间数据相互共享
切片的变量固定为24个字节,其中一个指向数组的指针,一个长度,一个容量,因此长度和容量都是它的属性
长度指该切片的实际大小,而容量指从该切片起始位置开始到数组结束之间的大小,因为通过该切片可以继续引用到切片之后的
slice类型:[]int,内容:[2 3],变量本身占用24个字节
slice类型:[]int,内容:[2 3 4],变量本身占用24个字节
6、切片具有零值,零值为nil
[1 2 3 4 5]
slice1类型:[]int,内容:[2 3 4],变量本身占用24个字节
7、go中有内置函数append来增加切片的长度,其实内部就是将数组扩大一倍,将元素组内容放进去这样的操作
[2 3 4 6]
第0个元素 = 1
第1个元素 = 2
第2个元素 = 3
第3个元素 = 4
第4个元素 = 6