GO学习笔记——数组与切片

一、Go中的数组

1、数组的声明

Go中的数组表示为T[n]的形式,表示长度为n的数组,下标从0开始到n-1。常见的数组声明方式有三种,分别如下:

第一种:

var 数组名 [数组长度]数组元素类型

示例如下:

import "fmt"
func main(){
    var a [4]int
    fmt.Println(a)
}

这样就声明了一个长度为4的int型数组a,默认初始值为0,输出结果就是[0 0 0 0]


第二种(简写声明,用:=,同时可完成数组的赋值,不赋值则为默认值0):

数组名 := [数组长度]数组元素类型{元素1,元素2,…}

示例如下:

import "fmt"
func main(){
    a := [4]int{}
    fmt.Println(a)
}

效果和第一种是一样的.


第三种(用“...”代替数组长度,让编译器自动计算,如果不进行赋值,则为一个长度为0的数组):

数组名 := [...]数组元素类型{元素1,元素2,…}

示例如下:

import "fmt"
func main(){
    a: = [...]int{}
    fmt.Println(a,len(a))
    b: = [...]int{1,2,3}
    fmt.Println(b,len(b))
}

输出就为:

[] 0

[1 2 3] 3

2、数组的使用说明

1)数组是值类型

Golang中数组是值类型,而不是引用类型,这意味着把数组赋值给一个新的变量时,新变量会得到一个原始数组的副本,而非原始数组的引用(指针),故修改新数组不会导致原始数组变化。

import "fmt"

func main(){
    a := [...]int{1,2,3}
    b := a
    b[0] = 0
    fmt.Println(a)
    fmt.Println(b)
}

上面示例代码的输出为:
[1 2 3]

[0 2 3]


2)使用for-range遍历数组

Golang中提供了一种方便简洁的数组遍历方法,即for循环的range方法来遍历数组。

range方法返回索引和该索引处的值,见如下代码:

import "fmt"

func main(){
    a :=[...]int{1,2,3}
    for i, v := range a{
        fmt.Printf("a[%d] = %d\n", i, v)
    }
}

上面示例代码的输出为:

a[0] = 1
a[1] = 2
a[2] = 3

如果只需要值,而忽略索引的话,可以用空白标识符 _ 来替换对应位置的变量即可。

for _, v := range a{

}


3)多维数组

Golang也支持多维数组的声明与使用,例如我们声明一个4行2列的二维数组就可以这么写:

import "fmt"

func main(){
    a := [4][2]int{
        {1, 2},
        {3, 4},
        {5, 6},
        {7, 8},
    }
    fmt.Println(a)
}

上面代码的输出为:

[[1 2] [3 4] [5 6] [7 8]]

多维数组声明时,第一维的长度也可以用“...”来代替,编译器会自动计算长度,但后面维度的长度需要显示的声明,否则会报错;同样,三维数组第一维可以用"..."替代,但第二、第三维就不可以,以此类推。

    a := [...][2]int{        //这样是可以的
        {1, 2},
        {3, 4},
        {5, 6},
        {7, 8},
    }


    a := [...][...]int{        //报错
        {1, 2},
        {3, 4},
        {5, 6},
        {7, 8},
    }


    a := [4][...]int{        //报错
        {1, 2},
        {3, 4},
        {5, 6},
        {7, 8},
    }


二、切片(slice)

切片是对数组一个连续片段的引用(该数组被称为底层数组,通常是匿名的),所以切片是一个引用类型。实际开发中切片更加常用,因为其动态性是数组不具备的。简而言之,切片可以理解为一个长度可以变化的数组。

可以认为切片自身有一些属性,例如切片的长度和容量,后文会介绍,以及指向引用开始元素的指针,这个指针导致了将切片传给函数时,函数内对切片的修改在函数外是可见的,即更改了对应地址的值。

1、切片的创建

具有T类型元素的切片表示为[ ]T,切片的创建方法也有三种。

第一种:

var 切片名 []切片元素类型 = 数组[begin : end]

表示创建了一个 “引用数组下标从begin到end-1元素” 的切片

示例如下:

import "fmt"

func main(){
    a := [5]int{1, 2, 3, 4, 5}
    var b []int = a[1:5]
    fmt.Println(b)
}

上述代码输出为:

[2 3 4 5]


第二种:
切片名  :=  []切片元素类型{元素1, 元素2,...,元素n}

表示创建了一个有n个元素的切片元素类型数组,并返回切片引用给对应切片

示例如下:

import "fmt"

func main(){
    a := []int{1,2,3,4}
    fmt.Println(a)
}

上述代码输出为:

[1 2 3 4]


第三种(使用make函数):

make函数定义为:

func make ([]T, len, cap) []T

        其中len和cap即为之前提到过的切片长度和容量(接下来马上介绍这两个概念),通过传递类型、长度和容量来创建切片,容量的默认值是切片长度。

        make函数的效果是创建一个数组,然后返回引用该数组的切片。

示例如下:

import "fmt"

func main(){
    a := make([]int, 4, 4)
    fmt.Println(a)
}

上面代码输出为:
 [0 0 0 0]

make函数创建切片时默认元素值为0


2、切片的长度和容量

切片的长度是指切片中的元素数,切片的容量是指从船舰切片索引开始算起,底层数组中元素的个数。可以用len()函数和cap()函数来返回切片的长度和容量,我们用下面一段代码来理解切片的长度和容量。

import "fmt"

func main(){
    a := [...]float64{1.2, 2.3, 3.4, 4.5, 5.6, 6.7}
    sliceA := a[2:4]
    fmt.Println(sliceA)
    fmt.Printf("length = %d, capacity = %d", len(sliceA), cap(sliceA)) 
}

上面代码的输出为:
[3.4 4.5]
length = 2, capacity = 4

长度很好理解,切片sliceA中有两个元素,故长度为2,但是容量是如何算出来的呢?我们更改代码再次看一下结果:

import "fmt"

func main(){
    a := [...]float64{1.2, 2.3, 3.4, 4.5, 5.6, 6.7}
    sliceA := a[3:5]
    fmt.Println(sliceA)
    fmt.Printf("length = %d, capacity = %d", len(sliceA), cap(sliceA)) 
}

上面代码的输出为:

[4.5 5.6]
length = 2, capacity = 3

发现容量发生了变化,我们可以看出,在这里,容量可以通俗的视作从切片开始下标在底层数组中的元素开始,到底层数组最后一个元素共计的元素个数。例如第一个例子中,我们从下标为2的元素开始切片,则容量为6-2=4,第二个例子中,我们从下标为2的元素开始切片,则容量为6-3=3。

切片的长度和容量是可以动态变化的,在追加切片元素时可以明显观察到。


3、追加切片元素与动态变化

追加切片元素我们使用append函数,其函数定义如下:

func append([]T, x ... T) T[]

x ... T在函数定义中表示该函数接受参数x的个数是可变的。这类函数被称为可变参函数。

Golang实现切片动态变化的方法十分“简单粗暴”。

当新的元素被添加至切片时,切片的长度会增加,增加的数量为新元素个数。长度增加后,如果当前切片的容量小于增加以后的切片长度,则切片会扩容切片的容量会翻一倍(2x原理来怎加数组长度),此时底层会自动创建一个扩容后的新数组,然后将原数组的值复制给新数组,并添加追加的元素,随后返回该数组的切片引用,不会影响原来的底层数组;若大于,则容量不变,切片继继续引用当前数组,并且修改底层数组对应位置元素。

我们用代码来说明。


import "fmt"

func main() {
	a := [...]float64{1.2, 2.3, 3.4, 4.5, 5.6, 6.7}

	sliceA := a[2:4]
	sliceA = append(sliceA, 7.8, 8.9, 9.1)
	sliceA[0] = 1.1
	fmt.Printf("切片A为")
	fmt.Println(sliceA)
	fmt.Printf("length = %d, capacity = %d\n当前原数组为:", len(sliceA), cap(sliceA))
	fmt.Println(a)

	sliceB := a[2:4]
	sliceB = append(sliceB, 7.8, 8.9)
	sliceB[0] = 1.1
	fmt.Printf("切片B为")
	fmt.Println(sliceB)
	fmt.Printf("length = %d, capacity = %d\n当前原数组为:", len(sliceB), cap(sliceB))
	fmt.Println(a)
}

上述代码的输出为:

可以发现,追加元素时:

当切片扩容,切片的修改并不会影响原底层数组的数据;而切片不扩容时,切片将继续引用原底层数组,所以数组的数据发生了改变。

这便于我们理解切片动态变化的原理

这一块可能有点难理解,可以上机实操辅助理解。


尝试以下代码,可以更深刻的理解切片与底层数组的关系。

func main() {
	a := [...]float64{1.2, 2.3, 3.4, 4.5, 5.6, 6.7}

	sliceA := a[2:4]
	sliceA = append(sliceA, 7.8, 8.9, 9.1)
	sliceA[0] = 1.1
	sliceA = sliceA[:cap(sliceA)]    //我们看一看新的底层数组未被引用部分是怎样的
	fmt.Printf("切片A为")
	fmt.Println(sliceA)
}

上面代码的输出为:

可以看到切片A的后面三个元素都是0,可以知道新的底层数组在未追加元素的情况下,默认值为0

4、多维切片

与数组类似,切片也可以有多个维度。

import "fmt"

func main(){
    sliceA = [][]int{
        {1, 2},
        {3},
        {4, 5},
    }
    fmt.Println(sliceA, len(sliceA), cap(sliceA))
}

上面代码的输出为:

[[1 2] [3] [4 5]] 3 3

  • 30
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值