加入收藏 | 设为首页 | 会员中心 | 我要投稿 聊城站长网 (https://www.0635zz.com/)- 智能语音交互、行业智能、AI应用、云计算、5G!
当前位置: 首页 > 综合聚焦 > 编程要点 > 语言 > 正文

go语言中数组与切片有哪些差异

发布时间:2023-07-15 14:19:17 所属栏目:语言 来源:
导读:为大家详细介绍“go语言中数组与切片有哪些区别”,内容详细,步骤清晰,细节处理妥当,希望这篇“go语言中数组与切片有哪些区别”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来
为大家详细介绍“go语言中数组与切片有哪些区别”,内容详细,步骤清晰,细节处理妥当,希望这篇“go语言中数组与切片有哪些区别”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。
 
数组与切片的区别:1、切片是指针类型,数组是值类型;2、数组的赋值形式为值传递,切片的赋值形式为引用传递;3、数组的长度是固定的,而切片长度可以任意调整(切片是动态的数组);4、数组的长度是固定的,而切片长度可以任意调整(切片是动态的数组)。
 
undefined
 
Go语言中的数组大概相当与C/C++中的数组,固定大小,不能够动态扩展大小,而切片大概相当与C++中的Vector,可以动态扩展大小,当大小超过容量时,重新分配一块内存,然后将数据复制到新的内存区域。
 
一、数组
 
Go的切片是在数组之上的抽象数据类型,因此在了解切片之前必须要先理解数组。
 
1、数组的三种申明方式
 
var identifier [len]type
 
var identifier = [len]type{value1, value2, … , valueN}
 
var identifier = […]type{value1, value2, … , valueN}
 
相对应的:
 
identifier := [len]type{}
 
identifier := [len]type{value1, value2, … , valueN}
 
identifier := […]type{value1, value2, … , valueN}
 
例子:
 
var iarray1 [5]int32
 
var iarray2 [5]int32 = [5]int32{1, 2, 3, 4, 5}
 
iarray3 := [5]int32{1, 2, 3, 4, 5}
 
iarray4 := [5]int32{6, 7, 8, 9, 10}
 
iarray5 := [...]int32{11, 12, 13, 14, 15}
 
iarray6 := [4][4]int32{{1}, {1, 2}, {1, 2, 3}}  
 
fmt.Println(iarray1)
 
fmt.Println(iarray2)
 
fmt.Println(iarray3)
 
fmt.Println(iarray4)
 
fmt.Println(iarray5)
 
fmt.Println(iarray6)
 
结果:
 
[0 0 0 0 0]
 
[1 2 3 4 5]
 
[1 2 3 4 5]
 
[6 7 8 9 10]
 
[11 12 13 14 15]
 
[[1 0 0 0] [1 2 0 0] [1 2 3 0] [0 0 0 0]]
 
我们看数组 iarray1,只声明,并未赋值,Go语言帮我们自动赋值为0。再看 iarray2 和 iarray3 ,我们可以看到,Go语言的声明,可以表明类型,也可以不表明类型,var iarray3 = [5]int32{1, 2, 3, 4, 5} 也是完全没问题的。
 
2、数组的容量和长度
 
数组的容量和长度是一样的。cap() 函数和 len() 函数均输出数组的容量(即长度)
 
3、类型
 
数组是值类型,将一个数组赋值给另一个数组时,传递的是一份拷贝。而切片是引用类型,切片包装的数组称为该切片的底层数组。看下面的例子:
 
//a是一个数组,注意数组是一个固定长度的,初始化时候必须要指定长度,不指定长度的话就是切片了
 
a := [3]int{1, 2, 3}
 
//b是数组,是a的一份拷贝
 
b := a
 
//c是切片,是引用类型,底层数组是a
 
c := a[:]
 
for i := 0; i < len(a); i++ {
 
 a[i] = a[i] + 1
 
}
 
//改变a的值后,b是a的拷贝,b不变,c是引用,c的值改变
 
fmt.Println(a) //[2,3,4]
 
fmt.Println(b) //[1 2 3]
 
fmt.Println(c) //[2,3,4]
 
二、切片
 
Go语言中,切片是长度可变、容量固定的相同的元素序列。Go语言的切片本质是一个数组。容量固定是因为数组的长度是固定的,切片的容量即隐藏数组的长度。长度可变指的是在数组长度的范围内可变。
 
1、切片的四种创建方式
 
var slice1 = make([]int,5,10)
 
var slice2 = make([]int,5)
 
var slice3 = []int{}
 
var slice4 = []int{1,2,3,4,5}
 
相对应的:
 
slice1 := make([]int,5,10)
 
slice2 := make([]int,5)
 
slice3 := []int{}
 
slice4 := []int{1,2,3,4,5}
 
以上对应的输出
 
[0 0 0 0 0]
 
[0 0 0 0 0]
 
[]
 
[1 2 3 4 5]
 
从3)、4)可见,创建切片跟创建数组唯一的区别在于 Type 前的“ [] ”中是否有数字,为空,则代表切片,否则则代表数组。因为切片是长度可变的
 
2、隐藏数组
 
Go的切片是在数组之上的抽象数据类型,所以创建的切片始终都有一个数组存在。
 
举例说明:
 
slice0 := []string{"a", "b", "c", "d", "e"}
 
slice1 := slice0[2 : len(slice0)-1]
 
slice2 := slice0[:3]
 
fmt.Println(slice0, slice1, slice2)
 
slice2[2] = "8"
 
fmt.Println(slice0, slice1, slice2)
 
输出:
 
[a b c d e] [c d] [a b c]
 
[a b 8 d e] [8 d] [a b 8]
 
也说明,切片slice0 、 slice1 和 slice2是同一个底层数组的引用,所以slice2改变了,其他两个都会变
 
3、append追加切片
 
内置函数append可以向一个切片后追加一个或多个同类型的其他值。如果追加的元素数量超过了原切片容量,那么最后返回的是一个全新数组中的全新切片。如果没有超过,那么最后返回的是原数组中的全新切片。无论如何,append对原切片无任何影响。
 
举例说明:
 
slice1 := make([]int, 2, 5)
 
fmt.Println(len(slice1), cap(slice1))
 
for k := range slice1{
 
    fmt.Println(&slice1[k])
 
}
 
slice1 = append(slice1,4)
 
fmt.Println(len(slice1), cap(slice1))
 
for k := range slice1{
 
    fmt.Println(&slice1[k])
 
}
 
slice1 = append(slice1,5,6,7)
 
fmt.Println(len(slice1), cap(slice1))
 
for k := range slice1{
 
    fmt.Println(&slice1[k])
 
}
 
输出:
 
2 5 //长度和容量
 
0xc420012150
 
0xc420012158
 
3 5 //第一次追加,未超出容量,所以内存地址未发生改变
 
0xc420012150
 
0xc420012158
 
0xc420012160
 
6 10 //第二次追加,超过容量,内存地址都发生了改变,且容量也发生了改变,且是原来的2倍
 
0xc4200100f0
 
0xc4200100f8
 
0xc420010100
 
0xc420010108
 
0xc420010110
 
0xc420010118
 
再看一个例子:
 
slice1 := make([]int, 2, 5)
 
slice2 := slice1[:1]
 
fmt.Println(len(slice1), cap(slice1))
 
for k := range slice1{
 
    fmt.Println(&slice1[k])
 
}
 
fmt.Println(len(slice2), cap(slice2))
 
for k := range slice2{
 
    fmt.Println(&slice2[k])
 
}
 
slice2 = append(slice2,4,5,6,7,8)
 
fmt.Println(len(slice1), cap(slice1))
 
for k := range slice1{
 
    fmt.Println(&slice1[k])
 
}
 
fmt.Println(len(slice2), cap(slice2))
 
for k := range slice2{
 
    fmt.Println(&slice2[k])
 
}
 
以上输出:
 
2 5 //slice1的长度和容量
 
0xc4200700c0
 
0xc4200700c8
 
1 5  //slice2的长度和容量
 
0xc4200700c0
 
2 5 //slice2追加后,slice1的长度和容量、内存都未发生改变
 
0xc4200700c0
 
0xc4200700c8
 
6 10 //slice2追加后,超过了容量,所以slice2的长度和容量、内存地址都发生改变。
 
0xc42007e000
 
0xc42007e008
 
0xc42007e010
 
0xc42007e018
 
0xc42007e020
 
0xc42007e028
 
三、GO中数组与切片的区别
 
下面的主要看一些实际的例子来说明数组和切片的区别。
 
1、数组与切片的赋值形式
 
例子1
 
arr1 := [3] int {1,2,3}
 
arr2 := arr1
 
for k := range arr1 {
 
    fmt.Printf("%v ",&arr1[k]);
 
}
 
fmt.Println("");
 
for k := range arr2 {
 
    fmt.Printf("%v ",&arr2[k]);
 
}
 
fmt.Println("\n=================");
 
slice1 := [] int{1,2,3}
 
slice2 := slice1
 
for k := range slice1 {
 
    fmt.Printf("%v ",&slice1[k]);
 
}
 
fmt.Println("");
 
for k := range slice2 {
 
    fmt.Printf("%v ",&slice2[k]);
 
}
 
输出结果:
 
0xc420014140 0xc420014148 0xc420014150
 
0xc420014160 0xc420014168 0xc420014170
 
=================
 
0xc4200141a0 0xc4200141a8 0xc4200141b0
 
0xc4200141a0 0xc4200141a8 0xc4200141b0
 
从这个例子中可以看出,数组的赋值是值的拷贝,是一个全新的数组。而切片的赋值是引用。下面再来看一个例子。
 
例子2:
 
arr1 := [3] int {1,2,3}
 
arr2 := arr1
 
fmt.Printf("%v %v ",arr1,arr2);
 
arr1[0] = 11
 
arr2[1] = 22
 
fmt.Printf("\n%v %v ",arr1,arr2);
 
fmt.Println("\n================");
 
slice1 := [] int{1,2,3}
 
slice2 := slice1
 
fmt.Printf("%v %v ",slice1,slice2);
 
slice1[0] = 11
 
slice2[1] = 22
 
fmt.Printf("\n%v %v ",slice1,slice2);
 
输出结果:
 
[1 2 3] [1 2 3]
 
[11 2 3] [1 22 3]
 
================
 
[1 2 3] [1 2 3]
 
[11 22 3] [11 22 3]
 
这个例子再一次说明:数组是赋值是copy,而切片仅是引用。例子1和例子2中的切片的声明用到了隐藏数组。再来看下例子3,使用非隐藏数组的情况。
 
例子3:
 
arr1 := [5] int {1,2,3}
 
slice1 := arr1[0:3]
 
slice2 := slice1[0:4]
 
fmt.Printf("len: %d cap: %d %v\n",len(arr1),cap(arr1),arr1); //打印出非隐藏数组
 
for k := range arr1 {
 
    fmt.Printf("%v ",&arr1[k]);
 
}
 
fmt.Println("");
 
fmt.Printf("len: %d cap: %d %v\n",len(slice1),cap(slice1),slice1); //打印切片1
 
for k := range slice1 {
 
    fmt.Printf("%v ",&slice1[k]);
 
}
 
fmt.Println("");
 
fmt.Printf("len: %d cap: %d %v\n",len(slice2),cap(slice2),slice2); //打印切片2
 
for k := range slice2 {
 
    fmt.Printf("%v ",&slice2[k]);
 
}
 
fmt.Println("\n=================");
 
arr1[0] = 11   //非隐藏数组、切片1、切片2各自发生更改
 
slice1[1] = 22
 
slice2[2] = 33
 
fmt.Printf("len: %d cap: %d %v\n",len(arr1),cap(arr1),arr1); //再次打印非隐藏数组
 
for k := range arr1 {
 
    fmt.Printf("%v ",&arr1[k]);
 
}
 
fmt.Println("");
 
fmt.Printf("len: %d cap: %d %v\n",len(slice1),cap(slice1),slice1); //再此打印切片1
 
for k := range slice1 {
 
    fmt.Printf("%v ",&slice1[k]);
 
}
 
fmt.Println("");
 
fmt.Printf("len: %d cap: %d %v\n",len(slice2),cap(slice2),slice2); //再次打印切片2
 
for k := range slice2 {
 
    fmt.Printf("%v ",&slice2[k]);
 
}
 
输出结果:
 
len: 5 cap: 5 [1 2 3 0 0]0xc420012150 0xc420012158 0xc420012160 0xc420012168 0xc420012170
 
len: 3 cap: 5 [1 2 3]0xc420012150 0xc420012158 0xc420012160 len: 4 cap: 5 [1 2 3 0]0xc420012150 0xc420012158 0xc420012160 0xc420012168
 
=================
 
len: 5 cap: 5 [11 22 33 0 0]
 
0xc420012150 0xc420012158 0xc420012160 0xc420012168 0xc420012170
 
len: 3 cap: 5 [11 22 33]
 
0xc420012150 0xc420012158 0xc420012160
 
len: 4 cap: 5 [11 22 33 0]
 
0xc420012150 0xc420012158 0xc420012160 0xc42001216
 
综上三个例子,可以看出切片是数组的引用,这里包括对隐藏数组、非隐藏数组引用。
 
2、数组作为参数,被函数调用
 
func Connect() {
 
    arr1 := [5] int {1,2,3}
 
    fmt.Printf("%v ",arr1);
 
    for k := range arr1 {
 
        fmt.Printf("%v ",&arr1[k]);
 
    }
 
    fmt.Println("");
 
    f1(arr1)
 
    fmt.Println("");
 
    f2(&arr1)
 
}
 
func f1(arr [5]int) {
 
    fmt.Printf("%v ",arr);
 
    for k := range arr {
 
        fmt.Printf("%v ",&arr[k]);
 
    }
 
}
 
func f2(arr *[5]int) {
 
    fmt.Printf("%v ",arr);
 
    for k := range arr {
 
        fmt.Printf("%v ",&arr[k]);
 
    }
 
}
 
输出结果:
 
[1 2 3 0 0]  0xc420012150 0xc420012158 0xc420012160 0xc420012168 0xc420012170
 
[1 2 3 0 0]  0xc4200121b0 0xc4200121b8 0xc4200121c0 0xc4200121c8 0xc4200121d0
 
&[1 2 3 0 0] 0xc420012150 0xc420012158 0xc420012160 0xc420012168 0xc420012170
 
从上面的例子可以看出,数组在参数中,可以使用值传递和引用传递。值传递会拷贝新数组,引用传递则使用原数组。
 
func Connect() {
 
    slice1 := [] int {1,2,3}
 
    fmt.Printf("%v ",slice1);
 
    for k := range slice1 {
 
        fmt.Printf("%v ",&slice1[k]);
 
    }
 
    fmt.Println("");
 
    f1(slice1)
 
}
 
func f1(slice []int) {
 
    fmt.Printf("%v ",slice);
 
    for k := range slice {
 
        fmt.Printf("%v ",&slice[k]);
 
    }
 
}
 
输出结果:
 
[1 2 3] 0xc420014140 0xc420014148 0xc420014150
 
[1 2 3] 0xc420014140 0xc420014148 0xc42001415
 
从这个例子中可以看出,切片在参数中传递本身就引用。
 
 

(编辑:聊城站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!