golang 数组和切片解析

数组

数组:有限个类型相同的变量的集合。
go语言中的数组和其他大部分语言中的数组没什么区别,同样的内存存储地址连续,同样的创建之初就必须确定的元素个数以及类型。固定的长度让数组变得相当的不灵活,但在go语言中有了以数组为基础的引用类型切片Slice。因此大部分情况直接使用切片即可,不过在学习方面数组还是必不可少的一环。

数组的初始化

go中数组初始化非常麻烦 简便主要有以下方法:
仅声明不初始化会给值赋对应初始值如int:0,string:""

var arr [4]int

如果想要初始化可以用循环的方法来做

for i, _ := range arr {
	arr[i] = i
}
//or 2 method
for i := 0; i < len(arr); i++ {
	arr[i] = i
}

在声明时就显性赋初始值

arr := [4]int{0, 1, 2, 3}

如果是直接赋值可以选择省略长度[…]Type的方式来让编译器帮你数

arr := [...]int{0, 1, 2, 3}

最后还可以选择new()函数来创建并获取一个数组指针

arr := new([4]int)

需要注意的是在go语言中数组是一个值类型而非引用类型,此时用new出来的arr是一个数组的指针如有函数需要传值应当对数组进行解引用即*arr。

函数对数组的调用

//接收值,go会新建参数的一份副本给arr,效率较低
func arrValue(arr [4]int) {
	arr[0] = 11//原来的arr不会改
}
//接收地址(引用、指针都是一个东西叫法不同),直接将指针拿来用效率更高
func arrPoint(arr *[4]int) {
	arr[0] = 22//更改后原来的arr也会改
}
func main() {
	arr := new([4]int)
	arrValue(*arr)//new出来的是指针因此需要解引用
	fmt.Println("arr main in:", arr[0])
	arrPoint(arr)//new出来的是指针因此直接用就好了
	fmt.Println("arr main in:", arr[0])
	var arr2 [4]int
	arrValue(arr2) //正常初始化出来的是值
	fmt.Println("arr2 main in:", arr22[0])
	arrPoint(&arr2)//方法需要指针,对arr2进行取地址操作
	fmt.Println("arr2 main in:", arr22[0])
}

最后的结果是

arr main in: 0
arr main in: 22
arr2 main in: 0
arr2 main in: 22

上面这么多解引用、取地址的看着很难记,其实我们只要记住两种创建方式中的一种方法就够了。(推荐直接初始化,因为数组本身在go语言中就是一个值类型,想要指针直接&取地址就好了,没必要new出来)

切片

切片就是go为了解决有不确定长度数组的需求而实现的,切片本身就是一个指向数组的数据结构,因此切片是一个引用类型,这是切片和数组一大区别,用了切片以后就可以动态的增加长度了。

切片的初始化

声明格式

var sli []type//此时切片为nil,长度为0

切片的初始化
最直接的方法就是用函数make()来初始化切片了
这种方式是新建一个长度为cap的数组再由切片指向其[0,len)

var sli = make([]type, len,cap)
//也可省略cap,此时cap=len
var sli=make([]type,len)

当然我们也可以直接把一个现成的数组赋给一个切片

//声明初始化一次解决
sli1 := []int{1, 4, 6, 7, 0, 3, 4, 8, 2}
//定义一个数组
array := [9]int{1, 4, 6, 7, 0, 3, 4, 8, 2}
//将数组的[0:3)添加至切片sli
sli2 := array[0:3]//此时sli的len为3,cap为9
//也可以通过[:]的方式指向整个数组
sli3 := array[:]//[start:end]start和end可分别省略

切片的容量与长度

关于容量与长度其实很好理解,容量就是你切片所切向的数组的len,而len则是切片本身的长度,如果切片指向的是一个已经存在的数组,假设数组长度10,start=4,end=8,截取arr[start:end],则此时切片 容量=len(arr)-start=6,长度=end-start=4。数组[0,4)的部分相对于该切片来说是丢掉了。以下为图解
切片图解
由于切片是一个引用类型,因此当我们将一个切片赋给另一个切片时,两个切片所指向的数组将会是同一个,任意切片更改数据,所有指向该数组的切片数据都将改变。

	array := [...]int{1, 4, 6, 7, 0, 3, 4, 8, 2}
	var sli1 []int
	sli1 = array[0:3]  //根据数组创建的第一个切片array[0,3)
	sli2 := array[:]   //根据数组创建的第二个切片
	sli21 := sli2[1:5] //根据切片sli2创建的第三个切片sli2[1,5)
	fmt.Println("array:", array)
	sli21[0] = 21 //相对于切片sli21更改的是第一个元素,其实更改的是数组的第二个元素
	fmt.Println("sli1:", sli1, len(sli1), cap(sli1))
	fmt.Println("sli2:", sli2, len(sli2), cap(sli2))
	fmt.Println("sli21:", sli21, len(sli21), cap(sli21))
	fmt.Println("nowArray:", array)

输出结果为

array: [1 4 6 7 0 3 4 8 2]
sli1: [1 21 6] 3 9
sli2: [1 21 6 7 0 3 4 8 2] 9 9
sli21: [21 6 7 0] 4 8
nowArray: [1 21 6 7 0 3 4 8 2]

在go的gc系统中只有指向该数组的所有切片全部gg才会将这个数组回收,因此很有可能导致有一个很大的数组占用着内存但仅有一个切取其中一小部分的切片存活,这会导致很大的浪费,需要我们写代码时自己注意。

切片的基本操作

一个append(sli []type, elems …Type)函数就能满足我们对切片的大多数需求了,append内置函数将元素追加到切片的末尾。如果切片有足够的容量,将使用原有数组地址。否则,将分配一个新的底层数组。append返回一个更新的切片,通常用原切片本身接收。
以下列几个好用的公式:
删除索引[ i,j]的元素:

//删除不改变cap,对原数组有影响
a = append(a[:i+1], a[j+1:]...)

在索引 i 的位置插入切片 b 的所有元素:

//根据a的容量足够与否判断是否新建数组
a = append(a[:i], append(b, a[i:]...)...)

将元素 x 追加到切片 a:

//根据a的容量足够与否判断是否新建数组
a = append(a, x)

内置函数copy(dst,src []type),见名知意,根据索引覆盖值,若长度不够则舍弃如:

	array := [...]int{1, 4, 6, 7, 0, 3, 4, 8, 2}
	array2 := [...]int{23, 56, 89, 3, 34, 23}
	sli1 := array[0:3]
	sli2 := array2[:]  
	fmt.Printf("sli1:%p,%d,%d,%d\n", sli1, sli1, len(sli1), cap(sli1))
	fmt.Printf("sli2:%p,%d,%d,%d\n", sli2, sli2, len(sli2), cap(sli2))
	fmt.Println("array:", array)
	copy(sli1, sli2)
	fmt.Println("nowArray:", array)
	fmt.Printf("nowSli1:%p,%d,%d,%d\n", sli1, sli1, len(sli1), cap(sli1))

输出为

sli1:0xc00000c280,[1 4 6],3,9
sli2:0xc00000a3c0,[23 56 89 3 34 23],6,6
array: [1 4 6 7 0 3 4 8 2]
nowArray: [23 56 89 7 0 3 4 8 2]
nowSli1:0xc00000c280,[23 56 89],3,9

这里可以注意到sli1的地址并没有更改,反而是原来的数组被覆盖了,因此使用copy时要小心,你这边复制的是数组,这将影响到所有指向该数组的切片。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值