一起学GO之数组和切片

本文详细介绍了Go语言中的数组、切片及其操作,包括数组的声明、初始化、访问和修改,以及切片的声明、访问、添加、删除元素。还探讨了数组和切片的区别,并通过示例展示了多维数组和多维切片的使用。此外,文章强调了在Go语言中数组是值类型,切片是引用类型,以及在传递和比较时的注意事项。
摘要由CSDN通过智能技术生成

数组

数组定义

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在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
最后一个元素变为: 60个值是:11个值是:22个值是:33个值是:44个值是:55个值是: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 

数组和切片的区别

  • 数组是不可变长度的序列,切片是可变长度的序列
  • 数组是值类型,切片是引用类型
  • 切片不可以直接进行比较,需要自己实现
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木子林_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值