[Go]Go的数组和切片

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sixdaycoder/article/details/80406185

数组

如果你之前比较熟悉C和C++,那么请忘掉C和C++的关于数组的定义,在Go里边发生了比较大的变化。

首先,我们看一下Go如何声明一个数组

    var array1   [10]int           //1.常规的数组声明方法,它的类型是[10]int,数组长度被认为是数组类型的一部分
    var array2 = [10]int{1, 2, 3}  //2.常用用法,给定初始化的值,未被初始化的值以该类型的零值填充
    array3 := [...]int{1, 2, 3, 4} //3.不给定长度,使用...关键词告知编译期根据初始化的元素个数推断长度

这是比较常用的三种声明数组的方法,值得注意的是,在Go中数组的长度属于数组类型的一部分,比如我们打印他们的类型,可以得到结果:

    fmt.Printf("array1 type:%T len:%d, cap:%d\n", array1, len(array1), cap(array1))
    fmt.Printf("array2 type:%T len:%d, cap:%d\n", array2, len(array2), cap(array2))
    fmt.Printf("array2 type:%T len:%d, cap:%d\n", array3, len(array3), cap(array3))

    Output:
     array1 type:[10]int len:10, cap:10
     array2 type:[10]int len:10, cap:10
     array2 type:[ 4]int len:4,  cap:4

可以看出array1和array2的类型是一致的,都是[10]int,而array3的类型是[4]int。
这一点在数组作为函数形参的时候可以更为方便的看出:

func PassFixLengthArray(array [10]int){
    fmt.Printf("Array Pass Type is %T\n", array)
}
PassFixLengthArray(array1)
PassFixLengthArray(array2)
//PassFixLengthArray(array3)//编译错误,因为类型不匹配

实际上,长度作为数组类型的一部分我个人觉得是非常合适的。

还有,Go的数组和C/C++的数组有很大的不同的是,在Go种数组是值类型的,不再具有指针的概念

nInts := array3
for i := range nInts {
    nInts[i]++
}
fmt.Println(nInts)  
fmt.Println(array3)

Output:
[2 3 4 5]
[1 2 3 4]

这意味着nInts是array3的一个副本而非引用,所以在函数调用时如果参数类型是数组,那么每次传参都会发生一次数组的copy,这个对性能的影响还是比较大的,所以我们一般在Go中都是使用slice这种数据结构。

切片

Slice,说白了就是一种可以动态增长的数组。
下面给出Go中常用的slice的构造方式

var slen int = 10
var scap int = 20

var slice1 = make([]int, slen, scap)//这种方法可读性比较好,显示指定了slice的长度和容量
slice2 := []int{1, 2, 3, 4}
slice3 := []int{}

切片的底层是一个数组,除此之外Go的切片和数组再无联系。
切片的长度和容量很可能是不一样的,但是容量一定大于等于长度,可以使用len和cap方法查看:

var slice1 = make([]int, slen, scap)
slice2 := []int{1, 2, 3, 4}
slice3 := []int{}

fmt.Printf("slice1 type:%T len:%d, cap:%d\n", slice1, len(slice1), cap(slice1))
fmt.Printf("slice2 type:%T len:%d, cap:%d\n", slice2, len(slice2), cap(slice2))
fmt.Printf("slice3 type:%T len:%d, cap:%d\n", slice3, len(slice3), cap(slice3))

Output:
slice1 type:[]int len:10, cap:20
slice2 type:[]int len:4, cap:4
slice3 type:[]int len:0, cap:0

从输出我们可以看出,切片的长度是不属于切片类型的一部分的(显然,因为切片的长度是变化的)
Go中切片是引用类型,这意味着,赋值时也是获得的引用,我们可以看下面一个小例子:

tmp := slice2[1:3]
tmp[0] += 2

fmt.Println(slice2)
fmt.Println(tmp)
Output:
[1 4 3 4]
[4 3]

我们可以发现,tmp[0]的val的修改引起了slice2的值的变化,简单的画一下他们的内存结构:

这里写图片描述

tmp是切片[1:3]得到的,获得的tmp的len和cap又是多少呢?

fmt.Printf("tmp type:%T len:%d, cap:%d\n",     tmp,   len(tmp), cap(tmp))
Output:
tmp type:[]int len:2, cap:3

长度是2没问题,cap是3其实表示的是slice2底层的容量。
如果我们给slice2尾部添加一个元素,再看看结果是怎么样的:

slice2 = append(slice2, 10)
fmt.Printf("slice2 type:%T len:%d, cap:%d\n", slice2, len(slice2), cap(slice2))
fmt.Printf("tmp type:%T len:%d, cap:%d\n",     tmp,   len(tmp), cap(tmp))

Output:
slice2 type:[]int len:5, cap:8
tmp type:[]int len:2, cap:3

slice2的cap变为8可以理解,实际上就是在append元素的时候发现原容量不够进行了扩容,Go旋转的扩容方案是两倍的scale。

tmp的cap意外的没有发生改变(出乎我的意料),看来tmp在声明的那一时刻就决定了它的cap,为了验证这个想法,我在语句的调用顺序上做出一些改变:

fmt.Println("---------------------------------------------------Split------------------------------------------------------------")
slice4 := []int{1, 2, 3, 4, 5, 6, 7, 8}
test := slice4[3:6]


fmt.Println("1")
fmt.Printf("slice4 type:%T len:%d, cap:%d\n", slice4, len(slice4), cap(slice4))
fmt.Printf("test   type:%T len:%d, cap:%d\n", test,   len(test), cap(test))
fmt.Println(slice4)
fmt.Println(test)

输出:
1
slice4 type:[]int len:8, cap:8
test   type:[]int len:3, cap:5
[1 2 3 4 5 6 7 8]
[4 5 6]



fmt.Println("2")
test = append(test, 100)
fmt.Printf("slice4 type:%T len:%d, cap:%d\n", slice4, len(slice4), cap(slice4))
fmt.Printf("test   type:%T len:%d, cap:%d\n", test,   len(test), cap(test))
fmt.Println(slice4)
fmt.Println(test)

输出:
2
slice4 type:[]int len:8, cap:8
test   type:[]int len:4, cap:5
[1 2 3 4 5 6 100 8]
[4 5 6 100]


fmt.Println("3")
test = append(test, 200)
fmt.Printf("slice4 type:%T len:%d, cap:%d\n", slice4, len(slice4), cap(slice4))
fmt.Printf("test   type:%T len:%d, cap:%d\n", test,   len(test), cap(test))
fmt.Println(slice4)
fmt.Println(test)
输出:
3
slice4 type:[]int len:8, cap:8
test   type:[]int len:5, cap:5
[1 2 3 4 5 6 100 200]
[4 5 6 100 200]


fmt.Println("4")
test = append(test, 300)
fmt.Printf("slice4 type:%T len:%d, cap:%d\n", slice4, len(slice4), cap(slice4))
fmt.Printf("test   type:%T len:%d, cap:%d\n", test,   len(test), cap(test))
fmt.Println(slice4)
fmt.Println(test)
输出:
4
slice4 type:[]int len:8, cap:8
test   type:[]int len:6, cap:10
[1 2 3 4 5 6 100 200]
[4 5 6 100 200 300]


fmt.Println("5")
test[0] = 400
fmt.Printf("slice4 type:%T len:%d, cap:%d\n", slice4, len(slice4), cap(slice4))
fmt.Printf("test   type:%T len:%d, cap:%d\n", test,   len(test), cap(test))
fmt.Println(slice4)
fmt.Println(test)
输出:
5
slice4 type:[]int len:8, cap:8
test   type:[]int len:6, cap:10
[1 2 3 4 5 6 100 200]
[400 5 6 100 200 300]


fmt.Println("6")
slice4 = append(slice4, 1000)
fmt.Printf("slice4 type:%T len:%d, cap:%d\n", slice4, len(slice4), cap(slice4))
fmt.Printf("test   type:%T len:%d, cap:%d\n", test,   len(test), cap(test))
fmt.Println(slice4)
fmt.Println(test)
输出:
6
slice4 type:[]int len:9, cap:16
test   type:[]int len:6, cap:10
[1 2 3 4 5 6 100 200 1000]
[400 5 6 100 200 300]

我们可以看到,从4之后,test和slice4就不再引用同一块内存了,这是因为在4的时候,test的容量不够了,发生了扩容操作,新启了一块内存。
在4之后test和slice4就是两块不同的内存。

我们使用切片作为函数的参数时,要小心的是,在函数里如果对切片进行了改变,那么函数外部它的值显然也会发生变化,这算是一个使用切片的副作用,一定要小心谨慎。

猜你喜欢

转载自blog.csdn.net/sixdaycoder/article/details/80406185