Golang 数组和切片

1. Golang 数组和切片

1.1. 数组

数组初始化方式常用的有 3 种,至于其它的用的很少,就不用管了,常用方式如下:

var a[4]intb := [4]int{2, 4}
c := [...]int{2, 4}

Go 数组是值类型,赋值和传参会复制整个数组数据,为了避免数据复制,可以使用数组指针:

func test(x *[2]int) {
	x[1] += 1
}

func main() {
	a := [2]int{2, 3}
	test(&a)
}

最后需要区分指针数组和数组指针的区别:

func main() {
	x, y := 1, 2
	a := [...]*int{&x, &y} // 元素为指针的指针数组
	p := &a                // 存储数组地址的指针
}

Go 的数组,其实我们用的不多,一般大家都用切片,所以对于数组,掌握上述知识就可以了,其它关于数组的知识,需要用的时候,查阅相关资料即可。

1.2. 切片

1.2.1. 概念

切片出现的原因也是因为数组的可操作性不高。切片的长度是不固定的,可以追加数据,可以理解切片是一个动态数组,切片的底层是一个结构体。

type slice struct { 
	array unsafe.Pointer 
	len int 
	cap int 
}

切片类型 (slice) 本身并不是动态数组或数组指针。它内部通过指针引用底层数组,设定相关属性将操作限定在指定范围内。当需要时,会申请更大的内存,将当前数据复制过去,以实现类似动态数组的功能。

1.2.2. 切片创建

可直接创建切片对象,无需预先准备数组。因为是引用类型,须使用 make 函数或显式初始化语句,它会自动完成底层数组内存分配。

普通格式:

var 切片名 [] 数据类型

自动推导类型创建切片:

切片名 := [] 类型{}

make 函数创建切片:长度是已经初始化的空间,容量是已经开辟的空间,包括已经初始化的空间和空闲的空间。

// 长度是不能大于容量的,容量可以省略不写,不写时候就默认和长度的值一样
切片名称 := make ([] 切片类型,长度 容量)

// 返回切片的容量使用 cap, 返回切片的长度使用 len
fmt.Println(cap(切片名称))
// 演示
s1 := make([]int, 3, 5)    // 指定 len、cap, 底层数组初始化为零值
s2 := make([]int, 3)       // 省略 cap, 和 len 相等
s3 := []int{10, 20, 5: 30} // 按初始化元素分配底层数组,并设置 len、cap, 设置索引 5 的数据为 30

1.2.3. 切片初始化

三种创建格式,都是可以通过 append 向切片添加数据的,初始化格式:

// 普通格式创建的切片
切片名 [索引] =// 自动推导类型创建的切片
切片名 := [] 类型{数据 1, 数据 2, 数据 3}

// make 函数方式创建的切片可以通过 append 和循环初始化
切片名称 = append(切片名称,数据 1, 数据 2...)
// 演示
s1 := make([]int, 4, 6) // 由于 `len = 4`, 所以后面 2 个暂时访问不到,但是容量还是在,数组里面每个变量都是 0。
s2 := []int{10,20,30,40,50,60}

1.2.4. append 函数

  • append 函数是向切片的末尾 slice(len) 添加数据
  • 如果添加的内容超出了切片初始定义的容量,切片会自动扩容
  • 扩容机制是:上一次的容量 * 2
  • 如果超过 1024 字节,每次扩容上一次的 1/4
  • append 每次扩容都是一个新的内存,和原来的无关联,所以如果是通过参数传递的方式,使用 append 添加数据,但是不会影响到原切片的数据,原因就是 append 每次拓展都是一个新的空间,指向的内存不再是原切片。

1.2.5. copy 函数

  • 把切片 2 的数据 (0 索引到 len-1) 赋值到切片 1 中。
  • 注意:如果切片 1 的容量不够,则不赋值剩余的数据。如果切片 1 的数据比切片 2 的多,从切片 2 复制的数据是有多少,复制多少。
  • 总结:copy 只是复制索引相对应的数据,如果长度不够,不会覆盖原来的数据。

格式:

copy(切片 1, 切片 2)

演示:

// 从切片 2 复制到切片 1, 但是切片 2 的数据比切片 1 的多,所以,最终只是复制了一部分,也就是索引相对应的数据
func main() {
	slice := []int{1, 2, 3}
	slice2 := []int{4, 5, 6, 7, 8, 9}
	copy(slice, slice2)
	fmt.Println(slice) // [4 5 6]
}

// 从切片 1 复制到切片 1, 但是切片 1 的数据比切片 2 的少,所以,最终只是复制了一部分,也就是索引相对应的数据
func main() {
	slice := []int{1, 2, 3}
	slice2 := []int{4, 5, 6, 7, 8, 9}
	copy(slice2, slice)
	fmt.Println(slice2) // [1 2 3 7 8 9]
}

还可直接从字符串中复制数据到 []byte:

func main() {
	b := make([]byte, 3)
	n := copy(b, "abcde")
	fmt.Println(n, b)
}

1.2.6. 切片截取

切片截取就是从切片中获取指定的数据。如果初始化切片时,没有指定切片的容量,切片容量是跟随原切片的。

切片截取的操作:

操作含义
s[n]切片 s 中索引位置为 n 的项
s[:]从切片 s 的索引位置 0len(s)-1 处所获得的切片
s[low:]从切片 s 的索引位置 low 到 len(s)-1 处所获得的切片
s[:high]从切片 s 的索引位置 0 到 high 处所获得的切片,len=high
s[low:high]从切片 s 的索引位置 low 到 high 处所获得的切片,len=high-low
s[low:high:max]从切片 s 的索引位置 low 到 high 处所获得的切片,len=high-low, cap=max-low
len(s)切片 s 的长度,总是 <=cap(s)
cap(s)切片 s 的容量,总是 >=len(s)
/**
	第一个值:截取的起始索引
	第二个值:截取的终止索引(不包括该值)
	第三个值:用来计算切片的容量,可以省略,默认和长度一样
	容量 = 第三个值 - 第一个值
	长度 = 第二个值 - 第一个值
*/
newSlice := slice[0:3:3] // 切片的操作符 `s[i:j:k]`, `j` 和 `k` 是个开区间。

1.2.7. 切片值的修改

切片截取后返回新切片,对新切片的值进行修改,会影响原来的切片。

原因:切片截取后新的切片,不会给新的切片是指向了原来的切片,没有给新的切片开辟新的空间,所以对于新的切片操作会影响到原来的切片。

1.2.8. nil 和空切片

nil 切片的指针指向 nil, 表示一个不存在的切片:

var slice []int

空切片一般会用来表示一个空的集合。比如数据库查询,一条结果也没有查到,那么就可以返回一个空切片。

silce := make([]int , 0)  slice := []int{}

需要说明的一点:不管是使用 nil 切片还是空切片,对其调用内置函数 append, lencap 的效果都是一样的。然后切片只能和 nil 判等,不支持切片判等。

1.2.9. 切片扩容

Go 切片扩容策略:如果切片的容量小 1024 个元素,于是扩容的时候就翻倍增加容量。上面那个例子也验证了这一情况,总容量从原来的 4 个翻倍到现在的 8 个。一旦元素个数超过 1024 个元素,那么增长因子就变成 1.25, 即每次增加原来容量的 1/4。下面我们看一种情况,当扩容时没有新建一个新的数组的情况,这里容易出问题:

func main() {
	array := [4]int{10, 20, 30, 40}
	slice := array[0:2]           // 10 20
	newSlice := append(slice, 50) // 10 20 50
	newSlice[1] += 10             // 10 30 50
	// 这里 slice=[10 30], array=[10 30 50 40], 入坑!! ! 
	fmt.Printf("slice = %v\n", slice)       // [10 30]
	fmt.Printf("array = %v\n", array)       // [10 30 50 40]
	fmt.Printf("newSlice = %v\n", newSlice) // [10 30 50]
}

slicenewSlicearray 底层共用一个数组,当修改 newSlice[1] 时,因为底层数据被修改,其它也都被修改了,这样非常容易产生莫名的 Bug!

1.2.10. 切片遍历

遍历和数组一样可以使用普通的 for 循环和 range 遍历得到。

// 演示
func main() {
	slice := []int{1, 2, 3, 4, 5}
	for i := 0; i < len(slice); i++ {
		fmt.Print(slice[i])
	}
	
	for _, v := range slice {
		fmt.Println(v)
	}
}

如果用 range 的方式去遍历一个切片,拿到的 Value 其实是切片里面的值拷贝,每次打印 Value 的地址都不变,所以仅修改 Value 的值,是不会改变 Slice 中的数据,这点切记!! !

1.2.11. 切片作为函数参数

切片可以做为函数的参数,但是在函数中修改切片的值,会影响到原切片。

因为切片的底层是结构体,结构体里有个参数 Pointer, Pointer 会指向切片的内存地址,使用的是浅拷贝方式,所以会影响到原切片值。

func main() {
	slice := []int{1, 2, 3, 4, 5}
	SliceDemo10(slice)
}

func SliceDemo10(slice []int) {
	for _, v := range slice {
		fmt.Println(v)
	}
	slice = append(slice, 5, 6, 7)
	fmt.Println(slice)
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Golang数组切片是两种不同的数据类型,用于存储相同数据类型的容器。数组的长度是固定的,而切片的长度是可变的。在日常应用中,切片的使用更为普遍。 数组在声明时需要指定长度,并且在初始化时必须提供相同长度的元素。例如,`a := int{1, 2, 3}`就是一个长度为3的整数数组数组的长度一旦确定后就不能更改。 切片是基于数组的引用类型。它不需要指定固定的长度,并且可以根据需要动态扩展或缩小。切片包装着底层数组,通过指定起始索引和结束索引来指定子集。例如,`b := a[:]`就是一个切片,它包含了数组a的所有元素。 数组适用于需要固定长度的场景,而切片适用于长度可变的情况。在实际应用中,切片更常用,因为它提供了更大的灵活性和便利性。 总结: - 数组是长度固定的容器,切片是长度可变的容器; - 数组在声明时需要指定长度,切片则不需要; - 数组的长度一旦确定后就不能更改,而切片可以根据需要动态扩展或缩小; - 切片是基于数组的引用类型,可以通过指定起始索引和结束索引来指定子集。 参考资料: Golang中的「数组」和「切片」都是存储同一数据类型的容器,只不过Golang中的数组长度是固定的,而切片的长度是可变化的。 切片是引用类型,切片包装的数组称为该切片的底层数组。我们来看一段代码://a是一个数组,注意数组是一个固定长度的,初始化时候必须要指定长度,不指定长度的话就是切片了 a := int{1, 2, 3} //b是数组,是a...。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

云满笔记

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

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

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

打赏作者

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

抵扣说明:

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

余额充值