此文章是个人学习归纳的心得,如有不对,还望指正,感谢!
如何判断是否有阅读本文章的必要,你可以观看下面的样例,并且分析最终打印的结果,如果答案正确,那就没有阅读本文的必要,答案在样例后面
package main
func one(s []int) {
s = append(s, 0)
for i := range s {
s[i]++
}
}
func tow() {
s1 := []int{1, 2}
s2 := s1
s2 = append(s2, 3)
one1(s1)
one1(s2)
fmt.Printf("%v,%v", s1, s2)
}
func main(){
tow()
}
如果和你预期的答案不一样,那么请接着往下看
如果要提append函数的话,我们不可避免的谈到切片,因此,我们就先来聊一下切片
go语言是一种强类型的语言,这种强不止体现在只能相同类型的元素进行运算,还体现在数组的身上,长度也是数组的类型的判断标准之一,这样可以规避很多风险,但也带来了不方便--数组的长度不可扩展,这对于我们操作数据来说,很不方便,因此就有了切片这一类型,其实切片可以类比其他语言中的数组,而go语言中的数组与其他类型的语言有很大的差距
在Go语言中,切片的底层是一个结构体,该结构体包含三个字段:
切片的结构体定义如下:
type slice struct {
ptr *elementType // 指向底层数组的指针
len int // 切片的长度
cap int // 切片的容量
}
其中,elementType是底层数组中元素的类型。
切片的底层数组可以是一个固定大小的数组,也可以是一个动态分配的数组。当切片的容量不足以容纳更多元素时,Go语言会自动分配一个更大的底层数组,并将切片的指针指向新的底层数组。这种自动扩容的机制使得切片在使用时非常灵活和方便。
我们可以从切片的创建来看:
第二种方法其实就是把第一种方法进行了封装
其实用make函数来创建的实际流程是,go编译器会先创建一个数组,然后再创建这个切片,并不是直接创建了切片,底层还是数组
package main
import "fmt"
//切片的创建
func main() {
// 方法一
// 1.先声明数组
arr := [8]int{1, 2, 3, 4, 5, 6, 7, 8}
// 2.声明该数组的切片
arrslice1 := arr[:] //直接把这个数组全部当做切片
arrslice2 := arr[0:] //第二个值不写的话,默认到最后
arrslice3 := arr[:8] // 第一个值不写的话,默认从0开始
arrslice4 := arr[2:3] // 切片是[2,3)的区间,所以就取下标为2的值
arrslice5 := arr[0:8] //可以简写成切片1的
fmt.Printf("数组的类型:%T\n", arr) //数组的类型:[8]int
fmt.Printf("数组切片1的类型:%T\n", arrslice1) //数组切片1的类型:[]int
fmt.Printf("数组切片1的值:%v\n", arrslice1) //数组切片1的值:[1 2 3 4 5 6 7 8]
fmt.Printf("数组切片2的值:%v\n", arrslice2) //数组切片2的值:[1 2 3 4 5 6 7 8]
fmt.Printf("数组切片3的值:%v\n", arrslice3) //数组切片3的值:[1 2 3 4 5 6 7 8]
fmt.Printf("数组切片4的值:%v\n", arrslice4) //数组切片4的值:[3]
fmt.Printf("数组切片5的值:%v\n", arrslice5) //数组切片5的值:[1 2 3 4 5 6 7 8]
//方法二
//切片其实也是一种数据类型,可以像一般类型那样进行声明创建
arrslice6 := []string{"yzc", "tjh", "tzr", "lcc"}
//arrslice7 := []string{}
fmt.Printf("字符串切片类型:%T\n", arrslice6)
fmt.Printf("字符串切片的值:%v", arrslice6)
}
上面的内容,其实我是想说,切片的底层还是数组,切片中元素的增加是与底层数组有关
,下面先介绍一下go语言内置的两个用来测量的函数 len(),cap()
arr := [7]int{}
fmt.Printf("长度:%v\n",len(arr))
fmt.Printf("容积:%v\n",cap(arr))
fmt.Printf("具体内容:%v\n",arr)
运行结果如下:
append()
是Go语言内置的函数,用于向切片中追加元素。
它的基本语法如下:
append(slice []T, elements ...T) []T
其中,slice
表示要追加元素的切片,elements
表示要追加的元素。
append()
函数会将元素追加到切片的末尾,并返回一个新的切片。如果原始切片的容量足够大,那么append()
函数会直接将元素追加到原始切片的末尾。如果原始切片的容量不够大,append()
函数会创建一个新的切片,并将原始切片的元素和新元素都复制到新的切片中。
需要注意的是,append()
函数返回的是一个新的切片,原始切片并没有被修改。如果想要修改原始切片,可以使用切片赋值的方式。
下面是一些append()
函数的示例:
slice := []int{1, 2, 3}
slice = append(slice, 4, 5) // 追加元素4和5到切片末尾
fmt.Println(slice) // 输出:[1 2 3 4 5]
slice2 := []int{6, 7, 8}
slice = append(slice, slice2...) // 将切片slice2追加到切片slice末尾
fmt.Println(slice) // 输出:[1 2 3 4 5 6 7 8]
需要注意的是,append()
函数可以一次追加多个元素,并且可以追加其他切片的元素,只需要在切片名后加上...
表示将切片打散作为参数传递。
其中还有一个值得关注的事情,就是当底层数组容积不够的时候,append函数会创建一个更大的数组,然后把这个原数组的内容拷贝到新数组里面去,其实我们大概认为是扩容后的容积是原容积的两倍就行.
具体的扩容策略如下:
这个扩容策略是为了平衡内存分配和性能,避免频繁地进行内存分配和拷贝操作。
需要注意的是,虽然append()
函数会创建一个新的更大的底层数组,但是返回的仍然是一个切片。这个切片会指向新的底层数组,原始切片并没有被修改。
下面是一个示例,演示了切片的扩容过程:
slice := []int{1, 2, 3}
fmt.Println("原始切片:", slice)
fmt.Println("原始切片长度:", len(slice))
fmt.Println("原始切片容量:", cap(slice))
slice = append(slice, 4, 5, 6, 7, 8, 9, 10)
fmt.Println("追加元素后的切片:", slice)
fmt.Println("追加元素后的切片长度:", len(slice))
fmt.Println("追加元素后的切片容量:", cap(slice))
输出结果如下:
原始切片: [1 2 3]
原始切片长度: 3
原始切片容量: 3
追加元素后的切片: [1 2 3 4 5 6 7 8 9 10]
追加元素后的切片长度: 10
追加元素后的切片容量: 12
可以看到,初始切片的容量是3,当追加了7个元素后,切片的容量已经扩大到12。
package main
func one(s []int) {
s = append(s, 0)
for i := range s {
s[i]++
}
}
func tow() {
s1 := []int{1, 2}
s2 := s1
s2 = append(s2, 3)
one(s1)
one(s2)
fmt.Printf("%v,%v", s1, s2)
}
func main(){
tow()
}
原来的底层数组可是没有一点变化
, 而函数外面的s1的底层数组可是仍然是没有变化的那个,所以后面打印的仍然是1,2原有切片的值不会发生改变!,切片的底层是一个结构体,其中有一个变量是用于存储切片长度的,还有一个指针用来指向数据,two调用one时发生了拷贝,这两个切片不是一个切片,但是指向的数据是同一片数据,虽然指向的数据变成了[2,3,4,1],但是在原来的切片s2中记录的长度仍然是3,容积仍然是4,通俗的讲,就是你的修改,它没有发现,所以没有呈现
所以s2最终的结果是长度3,容积4,内容:2,3,4,底层数组是2,3,4,1
所以最终的打印结果是1,2,2,3,4 我正在参与2023腾讯技术创作特训营第二期有奖征文,瓜分万元奖池和键盘手表
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。