Go 语言数组和切片的区别
Posted AlwaysBeta
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go 语言数组和切片的区别相关的知识,希望对你有一定的参考价值。
原文链接: Go 语言数组和切片的区别
在 Go 语言中,数组和切片看起来很像,但其实它们又有很多的不同之处,这篇文章就来说说它们到底有哪些不同。
另外,这个问题在面试中也经常会被问到,属于入门级题目,看过文章之后,相信你会有一个很好的答案。
数组
数组是同一种数据类型元素的集合,数组在定义时需要指定长度和元素类型。
例如:[4]int
表示一个包含四个整数的数组,数组的大小是固定的。并且长度是其类型的一部分([4]int
和 [5]int
是不同的、不兼容的类型)。
数组元素可以通过索引来访问,比如表达式 s[n]
表示访问第 n
个元素,索引从零开始。
声明以及初始化
func main()
var nums [3]int // 声明并初始化为默认零值
var nums1 = [4]int1, 2, 3, 4 // 声明同时初始化
var nums2 = [...]int1, 2, 3, 4, 5 // ...可以表示后面初始化值的长度
fmt.Println(nums) // [0 0 0]
fmt.Println(nums1) // [1 2 3 4]
fmt.Println(nums2) // [1 2 3 4 5]
函数参数
如果数组作为函数的参数,那么实际传递的是一份数组的拷贝,而不是数组的指针。这也就意味着,在函数中修改数组的元素是不会影响到原始数组的。
package main
import (
"fmt"
)
func Add(numbers [5]int)
for i := 0; i < len(numbers); i++
numbers[i] = numbers[i] + 1
fmt.Println("numbers in Add:", numbers) // [2 3 4 5 6]
func main()
// declare and initialize the array
var numbers [5]int
for i := 0; i < len(numbers); i++
numbers[i] = i + 1
Add(numbers)
fmt.Println("numbers in main:", numbers) // [1 2 3 4 5]
切片
数组的使用场景相对有限,切片才更加常用。
切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。
切片是一种引用类型,它有三个属性:指针,长度和容量。
- 指针:指向 slice 可以访问到的第一个元素。
- 长度:slice 中元素个数。
- 容量:slice 起始元素到底层数组最后一个元素间的元素个数。
底层源码定义如下:
type slice struct
array unsafe.Pointer
len int
cap int
声明以及初始化
func main()
var nums []int // 声明切片
fmt.Println(len(nums), cap(nums)) // 0 0
nums = append(nums, 1) // 初始化
fmt.Println(len(nums), cap(nums)) // 1 1
nums1 := []int1,2,3,4 // 声明并初始化
fmt.Println(len(nums1), cap(nums1)) // 4 4
nums2 := make([]int,3,5) // 使用make()函数构造切片
fmt.Println(len(nums2), cap(nums2)) // 3 5
函数参数
当切片作为函数参数时,和数组是不同的,如果一个函数接受一个切片参数,它对切片元素所做的更改将对调用者可见,类似于将指针传递给了底层数组。
package main
import (
"fmt"
)
func Add(numbers []int)
for i := 0; i < len(numbers); i++
numbers[i] = numbers[i] + 1
fmt.Println("numbers in Add:", numbers) // [2 3 4 5 6]
func main()
var numbers []int
for i := 0; i < 5; i++
numbers = append(numbers, i+1)
Add(numbers)
fmt.Println("numbers in main:", numbers) // [2 3 4 5 6]
再看一下上面的例子,把参数由数组变成切片,Add
函数中的修改会影响到 main
函数。
总结
最后来总结一下,面试时也可以这么来回答:
- 数组是一个长度固定的数据类型,其长度在定义时就已经确定,不能动态改变;切片是一个长度可变的数据类型,其长度在定义时可以为空,也可以指定一个初始长度。
- 数组的内存空间是在定义时分配的,其大小是固定的;切片的内存空间是在运行时动态分配的,其大小是可变的。
- 当数组作为函数参数时,函数操作的是数组的一个副本,不会影响原始数组;当切片作为函数参数时,函数操作的是切片的引用,会影响原始切片。
- 切片还有容量的概念,它指的是分配的内存空间。
以上就是本文的全部内容,如果觉得还不错的话欢迎点赞,转发和关注,感谢支持。
参考文章:
- https://go.dev/doc/effective_go#arrays
- https://go.dev/blog/slices-intro
- https://levelup.gitconnected.com/go-programming-array-vs-slice-5902b7fdd436
推荐阅读:
Go 语言范围(Range)
阅读目录
Go 语言范围(Range)
Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。
在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
for 循环的 range 格式可以对 slice、map、数组、字符串 等进行迭代循环。
格式如下:
for key, value := range oldMap
newMap[key] = value
以上代码中的 key 和 value 是可以省略。
如果只想读取 key,格式如下:
for key := range oldMap
或者这样:
for key, _ := range oldMap
如果只想读取 value,格式如下:
for _, value := range oldMap
range 关键字在 go 语言中是相当常用好用的语法糖,可以用在 for 循环中迭代 array、slice、map、channel、字符串所有涉及到遍历输出的东西。
切片迭代
nums := []int1, 2, 3
for k, v := range nums
fmt.Printf("key: %v , value: %v \\n", k, v)
key: 0 , value: 1
key: 1 , value: 2
key: 2 , value: 3
map迭代起始节点就随机了
kvs := map[string]string
"a": "a",
"b": "b",
for k, v := range kvs
fmt.Printf("key: %v , value: %v \\n", k, v)
PS E:\\go_test> go run .\\main.go
key: a , value: a
key: b , value: b
PS E:\\go_test>
字符串迭代
for k, v := range "hello"
//注意这里单个字符输出的是ASCII码,用 %c 代表输出字符
fmt.Printf("key: %v , value: %c\\n", k, v)
PS E:\\go_test> go run .\\main.go
key: 0 , value: h
key: 1 , value: e
key: 2 , value: l
key: 3 , value: l
key: 4 , value: o
PS E:\\go_test>
channel
ch := make(chan int, 10)
ch <- 11
ch <- 12
// 不用的时候记得关掉,不关掉又没有另一个goroutine存在会死锁哦,可以注释掉这一句体验死锁
close(ch)
for x := range ch
fmt.Println(x)
PS E:\\go_test> go run .\\main.go
11
12
PS E:\\go_test>
结构体
package main
import (
"fmt"
"reflect"
)
type Student struct
name string
age int
func main()
v := reflect.ValueOf(Student"乔峰", 29)
count := v.NumField()
for i := 0; i < count; i++
f := v.Field(i) //字段值
switch f.Kind()
case reflect.String:
fmt.Println(f.String())
case reflect.Int:
fmt.Println(f.Int())
PS E:\\go_test> go run .\\main.go
乔峰
29
PS E:\\go_test>
package main
import (
"fmt"
"reflect"
)
type LanType struct
s1, s2, s3 string
var language interface = LanType"Php", "Go", "Python3"
func main()
value := reflect.ValueOf(language)
for i := 0; i < value.NumField(); i++
fmt.Printf("字段索引 %d: %v\\n", i, value.Field(i))
PS E:\\go_test> go run .\\main.go
字段索引 0: Php
字段索引 1: Go
字段索引 2: Python3
PS E:\\go_test>
golang中数组与切片的区别详析
- 声明方式不同,数组需要指定大小,而切片不用
- 数组是值传递,切片是引用传递
Go切片和Go数组定义
Go 切片
又称动态数组,它实际是基于数组类型做的一层封装。
Go 数组
数组是内置(build-in)类型,是一组同类型数据的集合,它是值类型,通过从 0 开始的下标索引访问元素值。
在初始化后长度是固定的,无法修改其长度。
当作为方法的参数传入时将复制一份数组而不是引用同一指针。数组的长度也是其类型的一部分,通过内置函数 len(array)
获取其长度。
切片与数组的区别
- 数组的零值是元素类型的零值,切片的零值是nil;
- 数组是固定长度,切片是可变的长度。
- 数组是值类型,切片是引用类型。
1、 Go 中的数组是值类型,换句话说,如果你将一个数组赋值给另外一个数组,那么,实际上就是将整个数组拷贝一份。
因此,在 Go 中如果将数组作为函数的参数传递的话,那效率就肯定没有传递指针高了。
2、数组的长度也是类型的一部分,这就说明 [10]int
和 [20]int
不是同一种数据类型。
并且Go 语言中数组的长度是固定的,且不同长度的数组是不同类型,这样的限制带来不少局限性。
3、切片(slice)是一个拥有相同类型元素的可变长序列,可以方便地进行扩容和传递,实际使用时比数组更加灵活,这也正是切片存在的意义。
而且切片是引用类型,因此在当传递切片时将引用同一指针,修改值将会影响其他的对象。
数组
var arr1 [4]int
fmt.Printf("arr1 val:%d arr1 len:%d arr1 cap:%d\\n",
arr1, len(arr1), cap(arr1))
arr1 val:[0 0 0 0]
arr1 len:4
arr1 cap:4
arr := [4]int
fmt.Printf("val:%d len:%d cap:%d\\n",
arr, len(arr), cap(arr))
// val:[0 0 0 0] len:4 cap:4
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
// arr[4] = 5
// 报错:invalid array index 4
(out of bounds for 4-element array)
fmt.Printf("val:%d len:%d cap:%d\\n",
arr, len(arr), cap(arr))
// val:[1 2 3 4] len:4 cap:4
ss := arr[:]
ssPtr := (*reflect.SliceHeader)(unsafe.Pointer(&ss)).Data
fmt.Printf("ss val:%d len:%d cap:%d ptr:%v\\n",
ss, len(ss), cap(ss), ssPtr)
/*
ss val:[1 2 3 4]
len:4
cap:4
ptr:824633803136
*/
ss2 := arr[:]
ss2Ptr := (*reflect.SliceHeader)(unsafe.Pointer(&ss2)).Data
fmt.Printf("ss2 val:%d len:%d cap:%d ptr:%v\\n",
ss2, len(ss2), cap(ss2), ss2Ptr)
/*
ss2 val:[1 2 3 4]
len:4
cap:4
ptr:824633803136
*/
切片
注意:切片的容量可以根据元素的个数的增多自动扩容,但是不会根据元素的个数的减少自动缩容。
var s []int
if s == nil
fmt.Println("nil")
fmt.Printf("s val:%d len:%d cap:%d\\n",
s, len(s), cap(s))
/*
s val:[]
len:0
cap:0
*/
s = append(s, 1)
fmt.Printf("s val:%d len:%d cap:%d\\n",
s, len(s), cap(s))
/*
s val:[1]
len:1
cap:1
*/
s = append(s, 1)
s = append(s, 2)
fmt.Printf("s val:%d len:%d cap:%d\\n",
s, len(s), cap(s))
/*
s val:[1 2]
len:2
cap:2
*/
s = append(s, 1)
s = append(s, 2)
s = append(s, 3)
fmt.Printf("s val:%d len:%d cap:%d\\n",
s, len(s), cap(s))
/*
s val:[1 2 3]
len:3
cap:4
*/
代码
package main
import "fmt"
func main()
fmt.Println("--------------------数组声明与赋值-----------------")
// 指定数组大小
var a1 [5]int
// 自动推断数组大小
a2 := [...]int1, 2, 3
fmt.Println("a1=", a1, "a2=", a2)
// 按索引赋值
a3 := [...]int2: 2, 4: 4
fmt.Println("a3=", a3)
// 按索引赋值
a4 := [6]int2: 2, 4: 4
fmt.Println("a4=", a4)
fmt.Println("-------------------- 切片声明与初始化-----------------")
// 定义切片
var b1 []int
fmt.Println("b1=", b1)
// 初始化
b2 := make([]int, 3, 5)
fmt.Printf("b2=%v,len=%d,cap=%d\\n", b2, len(b2), cap(b2))
fmt.Println("--------------------值传递与引用传递-----------------")
a := [4]float6467.7, 89.9, 21, 78
b := []int2, 3, 5
fmt.Printf("变量a---地址:%p,类型:%T,数值:%v,长度:%d \\n", &a, a, a, len(a))
fmt.Printf("变量b---地址:%p,类型:%T,数值:%v,长度:%d \\n", &b, b, b, len(b))
c := a
d := b
fmt.Printf("变量c---地址:%p,类型:%T,数值:%v,长度:%d \\n", &c, c, c, len(c))
fmt.Printf("变量d---地址:%p,类型:%T,数值:%v,长度:%d \\n", &d, d, d, len(d))
a[1] = 200
fmt.Println("a=", a, "c=", c)
d[0] = 200
fmt.Println("b=", b, "d=", d)
PS E:\\go_test> go run .\\main.go
--------------------数组声明与赋值-----------------
a1= [0 0 0 0 0] a2= [1 2 3]
a3= [0 0 2 0 4]
a4= [0 0 2 0 4 0]
-------------------- 切片声明与初始化-----------------
b1= []
b2=[0 0 0],len=3,cap=5
--------------------值传递与引用传递-----------------
变量a---地址:0xc0000141a0,类型:[4]float64,数值:[67.7 89.9 21 78],长度:4
变量b---地址:0xc000008090,类型:[]int,数值:[2 3 5],长度:3
变量c---地址:0xc000014200,类型:[4]float64,数值:[67.7 89.9 21 78],长度:4
变量d---地址:0xc0000080d8,类型:[]int,数值:[2 3 5],长度:3
a= [67.7 200 21 78] c= [67.7 89.9 21 78]
b= [200 3 5] d= [200 3 5]
PS E:\\go_test>
数组声明与赋值
// 指定数组大小
var a1 [5]int
// 自动推断数组大小
a2 := [...]int1, 2, 3
fmt.Println("a1=", a1, "a2=", a2)
// a1= [0 0 0 0 0] a2= [1 2 3]
// 按索引赋值
a3 := [...]int2: 2, 4: 4
fmt.Println("a3=", a3)
// a3= [0 0 2 0 4]
// 按索引赋值
a4 := [6]int2: 2, 4: 4
fmt.Println("a4=", a4)
// a4= [0 0 2 0 4 0]
切片声明与初始化
// 定义切片
var b1 []int
fmt.Println("b1=", b1)
// b1= []
// 初始化
b2 := make([]int, 3, 5)
fmt.Printf("b2=%v,len=%d,cap=%d\\n",
b2, len(b2), cap(b2))
// b2=[0 0 0],len=3,cap=5
值传递与引用传递
a := [4]float6467.7, 89.9, 21, 78
fmt.Printf("变量a---地址:%p,类型:%T,数值:%v,长度:%d \\n",
&a, a, a, len(a))
变量a---地址:0xc000014180,
类型:[4]float64,
数值:[67.7 89.9 21 78],
长度:4
b := []int2, 3, 5
fmt.Printf("变量b---地址:%p,类型:%T,数值:%v,长度:%d \\n",
&b, b, b, len(b))
变量b---地址:0xc000008078,
类型:[]int,
数值:[2 3 5],
长度:3
b := []int2, 3, 5
a := [4]float6467.7, 89.9, 21, 78
c := a
d := b
fmt.Printf("变量c---地址:%p,类型:%T,数值:%v,长度:%d \\n",
&c, c, c, len(c))
变量c---地址:0xc000014180,
类型:[4]float64,
数值:[67.7 89.9 21 78],
长度:4
b := []int2, 3, 5
a := [4]float6467.7, 89.9, 21, 78
// c := a
d := b
fmt.Printf("变量d---地址:%p,类型:%T,数值:%v,长度:%d \\n",
&d, d, d, len(d))
变量d---地址:0xc000008078,
类型:[]int,
数值:[2 3 5],
长度:3
a := [4]float6467.7, 89.9, 21, 78
c := a
b := []int2, 3, 5
d := b
a[1] = 200
fmt.Println("a=", a, "c=", c)
d[0] = 200
fmt.Println("b=", b, "d=", d)
a= [67.7 200 21 78] c= [67.7 89.9 21 78]
b= [200 3 5] d= [200 3 5]
以上是关于Go 语言数组和切片的区别的主要内容,如果未能解决你的问题,请参考以下文章