目录
一、数组
1、数组的三种申明方式
2、数组的容量和长度
3、类型
二、切片
1、切片的四种创建方式
2、隐藏数组
3、append追加切片
三、GO中数组与切片的区别
首页 后端开发 Golang go语言中数组与切片有什么区别

go语言中数组与切片有什么区别

Jan 13, 2023 pm 05:26 PM
golang go语言 数组 切片

数组与切片的区别:1、切片是指针类型,数组是值类型;2、数组的赋值形式为值传递,切片的赋值形式为引用传递;3、数组的长度是固定的,而切片长度可以任意调整(切片是动态的数组);4、数组的长度是固定的,而切片长度可以任意调整(切片是动态的数组)。

go语言中数组与切片有什么区别

本教程操作环境:windows7系统、GO 1.18版本、Dell G3电脑。

Go语言中的数组大概相当与C/C 中的数组,固定大小,不能够动态扩展大小,而切片大概相当与C 中的Vector,可以动态扩展大小,当大小超过容量时,重新分配一块内存,然后将数据复制到新的内存区域。下面我们通过几个问题来更好理解golang 的数组和切片,一起来看看吧。

一、数组

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
登录后复制

从这个例子中可以看出,切片在参数中传递本身就引用。

总结:

  • 切片是指针类型,数组是值类型

  • 数组的赋值形式为值传递,切片的赋值形式为引用传递

  • 数组的长度是固定的,而切片长度可以任意调整(切片是动态的数组)

  • 数组只有长度一个属性,而切片比数组多了一个容量(cap)属性

【相关推荐:Go视频教程编程教学

以上是go语言中数组与切片有什么区别的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
威尔R.E.P.O.有交叉游戏吗?
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Go语言中用于浮点数运算的库有哪些? Go语言中用于浮点数运算的库有哪些? Apr 02, 2025 pm 02:06 PM

Go语言中用于浮点数运算的库介绍在Go语言(也称为Golang)中,进行浮点数的加减乘除运算时,如何确保精度是�...

Go的爬虫Colly中Queue线程的问题是什么? Go的爬虫Colly中Queue线程的问题是什么? Apr 02, 2025 pm 02:09 PM

Go爬虫Colly中的Queue线程问题探讨在使用Go语言的Colly爬虫库时,开发者常常会遇到关于线程和请求队列的问题。�...

在 Go 语言中,为什么使用 Println 和 string() 函数打印字符串会出现不同的效果? 在 Go 语言中,为什么使用 Println 和 string() 函数打印字符串会出现不同的效果? Apr 02, 2025 pm 02:03 PM

Go语言中字符串打印的区别:使用Println与string()函数的效果差异在Go...

在Go语言中使用Redis Stream实现消息队列时,如何解决user_id类型转换问题? 在Go语言中使用Redis Stream实现消息队列时,如何解决user_id类型转换问题? Apr 02, 2025 pm 04:54 PM

Go语言中使用RedisStream实现消息队列时类型转换问题在使用Go语言与Redis...

GoLand中自定义结构体标签不显示怎么办? GoLand中自定义结构体标签不显示怎么办? Apr 02, 2025 pm 05:09 PM

GoLand中自定义结构体标签不显示怎么办?在使用GoLand进行Go语言开发时,很多开发者会遇到自定义结构体标签在�...

Go语言中哪些库是由大公司开发或知名的开源项目提供的? Go语言中哪些库是由大公司开发或知名的开源项目提供的? Apr 02, 2025 pm 04:12 PM

Go语言中哪些库是大公司开发或知名开源项目?在使用Go语言进行编程时,开发者常常会遇到一些常见的需求,�...

Go语言中`var`和`type`关键字定义结构体的区别是什么? Go语言中`var`和`type`关键字定义结构体的区别是什么? Apr 02, 2025 pm 12:57 PM

Go语言中结构体定义的两种方式:var与type关键字的差异Go语言在定义结构体时,经常会看到两种不同的写法:一�...

多进程日志写入如何保证并发安全又高效? 多进程日志写入如何保证并发安全又高效? Apr 02, 2025 pm 03:51 PM

高效处理多进程日志写入的并发安全问题多进程同时写入同一个日志文件,如何保证并发安全且高效?这是一个...

See all articles