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)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。

切片是一种引用类型,它有三个属性:指针长度容量

  1. 指针:指向 slice 可以访问到的第一个元素。
  2. 长度:slice 中元素个数。
  3. 容量: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 函数。

总结

最后来总结一下,面试时也可以这么来回答:

  1. 数组是一个长度固定的数据类型,其长度在定义时就已经确定,不能动态改变;切片是一个长度可变的数据类型,其长度在定义时可以为空,也可以指定一个初始长度。
  2. 数组的内存空间是在定义时分配的,其大小是固定的;切片的内存空间是在运行时动态分配的,其大小是可变的。
  3. 当数组作为函数参数时,函数操作的是数组的一个副本,不会影响原始数组;当切片作为函数参数时,函数操作的是切片的引用,会影响原始切片。
  4. 切片还有容量的概念,它指的是分配的内存空间。

以上就是本文的全部内容,如果觉得还不错的话欢迎点赞转发关注,感谢支持。


参考文章:

推荐阅读:

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 语言数组和切片的区别的主要内容,如果未能解决你的问题,请参考以下文章

Go 语言范围(Range)

Go语言系列之数组和切片

Go语言理论一

Go语言理论一

Go语言与Java语言的区别,必须区别。

Go语言:利用 TDD 测试驱动开发帮助理解数组与动态数组(切片)的区别