数组
数组定义
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Go语言中很少直接使用数组。
数组的声明
声明语法: var 变量名 [元素数量]类型,默认情况下,一个新的数组中元素的初始值为元素类型的零值,对于数字类型来说就是0 。下面我们来看一个简单的例子
package main
import (
"fmt"
)
func main() {
var listName [5]int
fmt.Println(listName)
}
// 输出结果:[0 0 0 0 0]
数组的每个元素都可以通过索引下标来访问,索引下标的范围是从 0 开始到数组长度减 1 的位置,内置函数 len() 可以返回数组中元素的个数。
package main
import (
"fmt"
)
func main() {
// 我们还可以通过简略声明, 来定义整数数组
var listName = [6]int{1, 2, 3, 4, 5}
// 打印第一个元素
fmt.Println("第一个元素: ", listName[0])
// 打印最后一个元素。(len()可以得到数组的长度)
fmt.Println("最后一个元素", listName[len(listName)-1])
// 我们也可以通过索引来修改值
listName[5] = 6
fmt.Println("最后一个元素变为:", listName[5])
// 打印索引和元素
for i, v := range listName {
fmt.Printf("第%d个值是:%d\n", i, v)
}
// 仅打印元素 如果想忽略调一些返回值,就可以使用 _
for _, v := range listName {
fmt.Printf("%d\t", v)
}
}
//打印结果
第一个元素: 1
最后一个元素 0
最后一个元素变为: 6
第0个值是:1
第1个值是:2
第2个值是:3
第3个值是:4
第4个值是:5
第5个值是:6
1 2 3 4 5 6
另外如果在数组长度的位置出现“…”省略号,则表示数组的长度是根据初始化值的个数来计算。
数组是值类型
在GO语言里,数组是值类型而不是引用类型。这意味着当数组赋值给一个新的变量时,该变量会得到一个原始数组的一个副本。如果对新变量进行更改,则不会影响原始数组。
package main
import (
"fmt"
)
func main() {
// 用这种声明数组,数组长度是根据初始化值的个数来计算
oldList := [...]string{"a", "b", "c", "d", "e"}
newList := oldList
newList[0] = "a1"
fmt.Println("oldList: ", oldList)
fmt.Println("newList: ", newList)
}
// 输出结果
oldList: [a b c d e]
newList: [a1 b c d e]
所以当数组作为参数传递给函数时,每个传入的参数都会创建一个副本,然后赋值给对应的函数变量,函数接受的是一个副本,所以原始数组保持不变,数组是按值传递。
package main
import (
"fmt"
)
func way(arg [3]int) {
arg[0] = 55
fmt.Println("在函数里的数组", arg)
}
func main() {
nums := [...]int{1, 2, 3}
fmt.Println("开始的数组: ", nums)
way(nums)
fmt.Println("传递后的数组: ", nums)
}
//执行结果
开始的数组: [1 2 3]
在函数里的数组 [55 2 3]
传递后的数组: [1 2 3]
当然我们也可以显式传递一个数组的指针,那么函数内部的数组对数组的任何修改都会改变原是数组
package main
import (
"fmt"
)
func way(arg *[3]int) {
arg[0] = 55
fmt.Println("在函数里的数组", *arg)
}
func main() {
nums := [...]int{1, 2, 3}
fmt.Println("开始的数组: ", nums)
way(&nums)
fmt.Println("传递后的数组: ", nums)
}
//执行结果
开始的数组: [1 2 3]
在函数里的数组 [55 2 3]
传递后的数组: [55 2 3]
访问数组
前面都有类似代码就写伪代码了
第一种方式:下标
// 这里注意不要去下标大于数组长度的,否则会报错:
a := [3]int{1,2,3}
fmt.println(a[0])
第二种方式使用range遍历
//更简便的方式range上面有实例代码,可以翻上去看看
//这里就写这种复杂的
a := [3]int{1,2,3}
for i := 0; i < len(a); i++ {
fmt.Println(i, a[i])
比较两个数组是否相等
如果两个数组类型相同(包括数组的长度,数组中元素的类型)的情况下,我们可以直接通过较运算符(==和!=)来判断两个数组是否相等.只有当两个数组的所有元素都是相等的时候数组才是相等的,不能比较两个类型不同的数组,否则程序将无法完成编译。
a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"
d := [3]int{1, 2}
fmt.Println(a == d) // 编译错误:无法比较 [2]int == [3]int
多维数组
Go语言中允许使用多维数组,因为数组属于值类型,所以多维数组的所有维度都会在创建时自动初始化零值,定义多维数组和定义一维数组本质没有多大区别,下面我们通过实例来进行了解多维数组。
// 声明一个 2×2 的二维整型数组
var array [2][2]int
// 设置每个元素的整型值
array[0][0] = 10
array[0][1] = 20
array[1][0] = 30
array[1][1] = 40
或者我们可以通过下面的方式进行定义
// 使用数组字面量来声明并初始化一个二维整型数组
array1 := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 声明并初始化数组中索引为 1 和 3 的元素
array2 := [4][2]int{1: {20, 21}, 3: {40, 41}}
切片(slice)
slice定义
slice:一个拥有的相同类型元素的可变长度的序列,是对数组的一个连续片段的引用,相较于数组是值类型,slice是引用类型。slice和数组是紧密关联的,slice是一种轻量级的数据结构,可以用来访问数组的部分或全部的元素,而这个数组可以称为slice的底层元素。slice有三个属性:指针: 开始的位置,长度(len):使用的大小, 容量(cap):切片的大小
slice的声明
切片的声明有三种方式
- 从数组或切片生成新的切片
- var 关键字声明
- make 函数构造
声明的实例说明
1- 从数组或切片生成新的切片:
var a = [5]int{1,2,3,4,5}
// 整个a都赋给sliceA
sliceA := a[:]
// 下标1到2的数据
sliceB := a[1:3]
fmt.Println(sliceA)
fmt.Println(sliceB)
//返回结果
sliceA: [1 2 3 4 5]
sliceB: [2 3]
2- var 关键字声明
格式为: var name []Type
// 声明字符串切片
var strList []string
fmt.Println("strList: ",strList)
fmt.Println("strList type : ", reflect.TypeOf(strList))
//返回结果
strList: []
strList type : []string
3- make 函数构造
make 函数的格式:make( []Type, size, cap )
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(b, len(b))
fmt.Println(a, len(a))
//返回结果
[0 0] 2
[0 0] 2
注意:make 内置函数创建切片是一定发生了内存分配的;但是给定开始与结束位置(包含切片复位: [0:0])的切片只是将新的切片引用已经分配好的内存地址,所以设定开始与结束位置(第二种方式),不会发生内存分配操作。
slice的操作
访问元素
我们可以使用下标或range进行访问元素
data := []string{"one" , "two", "three"}
fmt.Println(data[0])
// index:索引值, val:数据
for index, val := range data {
fmt.Println(index, val)
}
//返回结果
one
0 one
1 two
2 three
添加元素(append)
slice可以使用append内置函数进行添加元素
var a []int
a = append(a, 1)
// 追加1个元素
fmt.Println("追加1个元素:", a)
a = append(a, 2, 3)
// 追加多个元素, 手写解包方式
fmt.Println("追加多个元素:", a)
a = append(a, []int{4,5,6}...)
// 追加一个切片, 切片需要解包
fmt.Println("追加一个切片:", a)
//返回结果
追加1个元素: [1]
追加多个元素: [1 2 3]
追加一个切片: [1 2 3 4 5 6]
我们在使用append的时候我们要知道一点,就是slice动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行扩容,扩容规则是:按容量的 2 倍数进行扩充
var slice []int
slice = append(slice, 0) // 追加1个元素
fmt.Printf("追加1个元素: len: %d cap: %d pointer: %p\n", len(slice), cap(slice), slice)
slice = append(slice, 1,) // 再追加1个元素
fmt.Printf("再追加1个元素: len: %d cap: %d pointer: %p\n", len(slice), cap(slice), slice)
slice = append(slice, 2, 3) // 追加多个元素, 手写解包方式
fmt.Printf("追加多个元素: len: %d cap: %d pointer: %p\n", len(slice), cap(slice), slice)
slice = append(slice, []int{4,5,6}...) // 追加一个切片, 切片需要解包
fmt.Printf("追加一个切片: len: %d cap: %d pointer: %p\n", len(slice), cap(slice), slice)
//返回结果
追加1个元素: len: 1 cap: 1 pointer: 0xc000016080
再追加1个元素: len: 2 cap: 2 pointer: 0xc0000160a0
追加多个元素: len: 4 cap: 4 pointer: 0xc00001a120
追加一个切片: len: 7 cap: 8 pointer: 0xc00001c080
删除元素
删除开头N个元素
sliceHead = []int{1, 2, 3, 4, 5}
sliceHead = sliceHead[N:] // 删除开头N个元素
sliceHead = sliceHead[1:] // 删除开头1个元素
删除尾部N个元素
sliceEnd = []int{1, 2, 3, 4, 5}
sliceEnd = sliceEnd[:N] // 删除尾部N个元素
sliceEnd = sliceEnd[:1] // 删除尾部1个元素
中间部分N个元素
sliceBODY = []int{1, 2, 3, 4, 5}
sliceBODY = append(sliceBODY[:i], sliceBODY[i+N:]...) // 删除中间N个元素
sliceBODY = append(sliceBODY[:i], sliceBODY[i+1:]...) // 删除中间1个元素
注意: 切片是对底层数组的引用,只要切片在内存中,数组是不被垃圾回收。所以我们在内存管理方块是需要注意的,如果我们有一个需求:有个很大的数组或切片,但是只需要处理其中一小部分。我们可以使用内置函数copy,将需要的那部分数据生成一个副本,去处理那个副本,让原来的那个数组的内存释放掉。
比较
我们这里需要记住一点slice是不能做比较的,因此不能用 ==来判断两个slice是否拥有相同元素。标准库里面提供了高度优化的函数bytes.Equal来比较两个字节slice([]byte),但是对于其他类型的slice,我们需要自己实现比较的函数。
比如说 判断int类型的切片是否为空,如果用 == 则存在漏洞,我们正确的应该使用len
var s []int. // len(s) == 0, s== nill
s = nill // len(s) == 0, s== nill
s = []int{nill} // len(s) == 0, s== nill
s = []int{} // len(s) == 0, s!= nill
多维切片
和数组一样,slice也可以有多个维度
slice := [][]string{
{"French fries", "hamburger"},
{"Rust", "Go", "Python"},
{"Tom", "Jack", "Mary"},
}
for i, v := range slice {
fmt.Printf("%d-%s ",i, v)
for i1, v1 := range v {
fmt.Printf("%d-%s ",i1, v1)
}
fmt.Println()
}
//返回值
0-[French fries hamburger] 0-French fries 1-hamburger
1-[Rust Go Python] 0-Rust 1-Go 2-Python
2-[Tom Jack Mary] 0-Tom 1-Jack 2-Mary
数组和切片的区别
- 数组是不可变长度的序列,切片是可变长度的序列
- 数组是值类型,切片是引用类型
- 切片不可以直接进行比较,需要自己实现