go语言学习笔记(四)

一.go语言数组类型

一个数组(Array)就是一个可以容纳若干类型相同的元素的容器。这个容器的大小(即数组的长度)是固定的。
比如,声明了一个数组类型:type MyNumbers [3]int

注:类型声明语句由关键字type、类型名称和类型字面量组成。

类型字面量用于表示某个类型的字面表示(或称标记方法)。用于表示某个类型的值的字面表示被称为值字面量或字面量。比如3.7E-2可被称为浮点数字面量。

类型字面量[3]int由两部分组成。第一部分是由方括号包裹的数组长度,即[3]。一个数组的长度是该数组的类型的组成部分,是固定不变的。该类型字面量的第二个组成部分是int。它代表了该数组可以容纳的元素的类型。这条类型声明语句实际上是为数组类型[3]int声明了一个别名类型。我们可以把MyNumbers当做数组类型[3]int来使用。

在表示一个数组类型的值时,应该把该类型的类型字面量写在最左边,然后用花括号包裹该值包含的若干元素。各元素之间以(英文半角)逗号分隔,即:
[3]int{1, 2, 3} 把该数组字面量赋给一个名为numbers的变量:
var numbers = [3]int{1, 2, 3}
注:这是一条变量声明语句。它在声明变量的同时为该变量赋值。
另一种方法是,可以省略类型字面量的长度,像这样:
var numbers = […]int{1, 2, 3}
我们可以使用索引表达式来访问该变量的值中的任何一个元素,例如:
numbers[0] // 会得到第一个元素
numbers[1] // 会得到第二个元素
numbers[2] // 会得到第三个元素

注:在这里,索引值的有效范围是[0, 3)。也就是说,对于数组来说,索引值既不能小于0也不能大于或等于数组值的长度。另外要注意,索引值的最小有效值总是0,而不是1。

如果我们想修改数组值中的某一个元素值,可以使用赋值语句直接达到目的。例如要修改numbers中的第二个元素可以这样:numbers[1] = 4
获取数组长度的方法:len(数组名)
var length = len(numbers)

注:len是Go语言的内建函数的名称。该函数用于获取字符串、数组、切片、字典或通道类型的值的长度。我们可以在Go语言源码文件中直接使用它。
如果只声明一个数组类型的变量而不为它赋值,那么需要指定这个数组的长度,数组的每个值将会使用默认值0表示。如:
var numbers2 [5]int
则它的值会是
[5] int{0, 0, 0, 0, 0}
实例代码(一)go_array1.go 如下:

package main

import "fmt"

func main() {
    var arr1 = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    //遍历数组
    for a := 0; a < len(arr1); a++ {
        fmt.Printf("数组的第 %d 个值是:%d\n", a, arr1[a])
    }
}

实例代码(二)go_array.go 如下:

/*
数组是长度固定,数据类型一致的一个容器
数组的下标从0开始
获取数组的长度:len(数组名)
*/
package main

import "fmt"

func main() {
    //定义一个数组
    var numbers [5]int
    numbers[0] = 2              //2
    numbers[3] = numbers[0] - 3 //-1
    numbers[1] = numbers[2] + 5 //5
    numbers[4] = len(numbers)   //获取数组的长度
    sum := (11)
    // “==”用于两个值的相等性判断
    fmt.Printf("%v\n", (sum == numbers[0]+numbers[1]+numbers[2]+numbers[3]+numbers[4]))

    //定义一个字符串数组并赋值
    var arr = [5]string{"abc", "lhf", "bsg", "dhg", "yya"}
    fmt.Printf("字符串数组的长度:%d\n", len(arr))
    fmt.Printf("字符串的第3个值为:%s\n", arr[2])
}

https://img3.mukewang.com/5b290f650001e2cf06020341.jpg

二.go语言切片类型

切片(Slice)与数组一样,也是可以容纳若干类型相同的元素的容器。与数组不同的是,无法通过切片类型来确定其值的长度。每个切片值都会将数组作为其底层数据结构。我们也把这样的数组称为切片的底层数组。
表示切片类型的字面量如:[]int 或[]string

可以看到,它们与数组的类型字面量的唯一不同是不包含代表其长度的信息。因此,不同长度的切片值是有可能属于同一个类型的。相对的,不同长度的数组值必定属于不同类型。对一个切片类型的声明可以这样:

type MySlice []int

这时,类型MySlice即为切片类型[]int的一个别名类型。除此之外,对切片值的表示也与数组值相似,如:

[]int{1, 2, 3}

这样的字面量与数组(值)的字面量的区别也只在于最左侧的类型字面量。前面说到的操作数组值的方法也同样适用于切片值。另外一种操作数组值的方法,叫“切片”操作。实施切片操作的方式就是切片表达式。举例如下:

var numbers3 = [5]int{1, 2, 3, 4, 5} //数组

var slice1 = numbers3[1:4] //切片

请注意第二条赋值语句中在“=”右边那个部分。切片表达式一般由字符串、数组或切片的值以及由方括号包裹且由英文冒号“:”分隔的两个正整数组成。这两个正整数分别表示元素下界索引和元素上界索引。在本例中,切片表达式numbers3[1:4]的求值结果为[]int{2, 3, 4}。可见,切片表达式的求值结果相当于以元素下界索引和元素上界索引作为依据从被操作对象上“切下”而形成的新值。注意,被“切下”的部分不包含元素上界索引指向的元素。另外,切片表达式的求值结果会是切片类型的,且其元素类型与被“切片”的值的元素类型一致。实际上,slice1这个切片值的底层数组正是numbers3的值。

实际上,我们也可以在一个切片值上实施切片操作。操作的方式与上述无异。

var slice2 = slice1[1:3]

slice2的值为[]int{3, 4}。注意,作为切片表达式求值结果的切片值的长度总是为元素上界索引与元素下界索引的差值。

除了长度,切片值以及数组值还有另外一个属性——容量。数组值的容量总是等于其长度。而切片值的容量则往往与其长度不同。请看下图。
https://img1.mukewang.com/5b2913c300017e1004080223.jpg

如图所示,一个切片值的容量即为它的第一个元素值在其底层数组中的索引值与该数组长度的差值的绝对值。为了获取数组、切片或通道类型的值的容量,我们可以使用内建函数cap,如:

var capacity2 int = cap(slice2)

最后,要注意,切片类型属于引用类型。它的零值即为nil,即空值。如果我们只声明一个切片类型的变量而不为它赋值,那么该变量的值将会是nil。例如,若有这样一个变量:

var slice3 []int

则它的值会是 : nil

实例代码 go_slice.go 如下:

package main

import "fmt"

func main() {
    //定义一个数组
    var numbers3 = [5]int{1, 2, 3, 4, 5}
    //定义一个切片
    slice3 := numbers3[2:len(numbers3)]
    length := (3)
    capacity := (3)
    fmt.Printf("%v, %v\n", (length == len(slice3)), (capacity == cap(slice3)))
    fmt.Printf("切片slice3的容量为:%d\n", cap(slice3))
    fmt.Printf("数组numbers3的长度为:%d\n", len(numbers3))
}

https://img1.mukewang.com/5b2915dd0001b10c06020106.jpg

三.go语言——切片的操作方法

在进行“切片”操作的时候需要指定元素下界索引和元素上界索引,就像这样:numbers3[1:4]

我们还可以在方括号中放入第三个正整数,如numbers3[1:4:4]
这第三个正整数被称为容量上界索引。它的意义在于可以把作为结果的切片值的容量设置得更小。
换句话说,它可以限制我们通过这个切片值对其底层数组中的更多元素的访问。

前面讲到的numbers3和slice1。针对它们的赋值语句是这样的:
var numbers3 = [5]int{1, 2, 3, 4, 5}
var slice1 = numbers3[1:4]
这时,变量slice1的值是[]int{2, 3, 4}。
但是我们可以通过如下操作将其长度延展得与其容量相同:
slice1 = slice1[:cap(slice1)]
通过此操作,变量slice1的值变为了[]int{2, 3, 4, 5},且其长度和容量均为4。
现在,numbers3的值中的索引值在[1,5)范围内的元素都被体现在了slice1的值中。

这是以numbers3的值是slice1的值的底层数组为前提的。
这意味着,我们可以轻而易举地通过切片值访问其底层数组中对应索引值更大的更多元素。

如果我们编写的函数返回了这样一个切片值,那么得到它的程序很可能会通过这种技巧访问到本不应该暴露给它的元素。
这是确确实实是一个安全隐患。

如果我们在切片表达式中加入了第三个索引(即容量上界索引),如:
var slice1 = numbers3[1:4:4]

那么在这之后,无论我们怎样做都无法通过slice1访问到numbers3的值中的第五个元素。
因为这超出了我们刚刚设定的slice1的容量。如果我们指定的元素上界索引或容量上界索引超出了被操作对象的容量,那么就会引发一个运行时恐慌(程序异常的一种),而不会有求值结果返回。

虽然切片值在上述方面受到了其容量的限制,但是我们却可以通过另外一种手段对其进行不受任何限制地扩展。
这需要使用到内建函数append。
使用方法如下:slice1 = append(slice1, 6, 7)
通过上述操作,slice1的值变为了[]int{2, 3, 4, 6, 7}。
注意,一旦扩展操作超出了被操作的切片值的容量,那么该切片的底层数组就会被自动更换。
这也使得通过设定容量上界索引来对其底层数组进行访问控制的方法更加严谨了。

最后一种操作切片值的方法是“复制”。
该操作的实施方法是调用copy函数。该函数接受两个类型相同的切片值作为参数,并会把第二个参数值中的元素复制到第一个参数值中的相应位置(索引值相同)上。
这里有两点需要注意:

  1. 这种复制遵循最小复制原则,即:被复制的元素的个数总是等于长度较短的那个参数值的长度。
  2. 与append函数不同,copy函数会直接对其第一个参数值进行修改。

举例如下:

var slice4 = []int{0, 0, 0, 0, 0, 0, 0}
copy(slice4, slice1)

通过上述复制操作,slice4会变为[]int{2, 3, 4, 6, 7, 0, 0}。
实例代码 go_slice1.go 如下:

package main

import "fmt"

func main() {
    //定义一个切片
    var numbers4 = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    //为切片设置容量上界值为8
    slice5 := numbers4[4:6:8] //4是元素下界索引,6是元素上界索引,8是容量上界值
    length := (2)             //5,6   长度
    capacity := (4)           //容量 5,6,7,8
    fmt.Printf("切片slice5长度:%d\n", len(slice5))
    fmt.Printf("切片slice5容量:%d\n", cap(slice5))
    fmt.Printf("%v, %v\n", length == len(slice5), capacity == cap(slice5))
    //将其长度延展得与其容量相同
    slice5 = slice5[:cap(slice5)]

    //切片追加方法
    slice5 = append(slice5, 11, 12, 13) //5,6,7,8,11,12,13
    length = (7)
    fmt.Printf("%v\n", length == len(slice5))
    for i := 0; i < len(slice5); i++ {
        fmt.Printf("切片的第%d个值是:%d\n", i, slice5[i])
    }

    //定义一个切片
    //slice5 := []int[5,6,7,8,11,12,13]
    slice6 := []int{0, 0, 0}

    //切片复制方法 把第二个参数值中的元素复制到第一个参数值中的相应位置上
    copy(slice5, slice6) //0,0,0,8,11,12,13
    e2 := (0)
    e3 := (8)
    e4 := (11)
    fmt.Printf("%v, %v, %v\n", e2 == slice5[2], e3 == slice5[3], e4 == slice5[4])
}

https://img3.mukewang.com/5b2917b50001631406330291.jpg

好了,今天的分享就到这里,感谢各位朋友的支持!

点赞