【Go编程学习】数组、切片

数组

数组是具有相同类型且长度固定的一组连续数据

定义数组

var arr1 = [5]int{
    
    }//长度为5,默认全是0
var arr2 = [5]int{
    
    1,2,3,4,5}//指定长度与元素
var arr3 = [5]int{
    
    3:10}//指定长度并通过索引值进行初始化
var arr4 = [...]int{
    
    1,2,3}//不指定长度,由后边的列表初始化
var arr5 = [...]int{
    
    3:10}//不指定长度并通过索引值进行初始化

操作数据

for i:=0;i<len(arr1);i++{
    
    
    //操作数据
}
for index, value := range arr1 {
    
    
	fmt.Printf("index: %d, value: %d\n", index, value)
}

不想要索引值的可以用_代替

多维数组

定义方式:

var arr6 = [5][5]int{
    
    
	{
    
    1, 2, 3, 4, 5},
	{
    
    6, 7, 8, 9, 10},
}

数组作为函数参数

go语言在传递数组时会对其进行拷贝,所以如果传递的是大数组的话会非常占内存,所以一般情况下很少直接传递一个数组,避免这种情况我们可以使用以下两种方式:

  • 传递数组的指针
  • 传递切片(具体内容见下一小节)

指针数组与数组指针

指针数组

go语言中数组默认是值传递的,所以如果我们在函数中修改传递过来的数组对原来的数组是没有影响的。

func main() {
    
    
	var a [5]int
	fmt.Println(a)
	test(a)
	fmt.Println(a)
}


func test(a [5]int) {
    
    
	a[1] = 2
	fmt.Println(a)
}

//[0 0 0 0 0]
//[0 2 0 0 0]
//[0 0 0 0 0]

如果我们一个函数传递的是指针数组:

func main() {
    
    
	var a [5]*int
	fmt.Println(a)
	for i := 0; i < 5; i++ {
    
    
		temp := i
		a[i] = &temp
	}
	for i := 0; i < 5; i++ {
    
    
		fmt.Print(" ", *a[i])
	}
	fmt.Println()
	test1(a)
	for i := 0; i < 5; i++ {
    
    
		fmt.Print(" ", *a[i])
	}
}


func test1(a [5]*int) {
    
    
	*a[1] = 2
	for i := 0; i < 5; i++ {
    
    
		fmt.Print(" ", *a[i])
	}
	fmt.Println()
}
//[<nil> <nil> <nil> <nil> <nil>]
// 0 1 2 3 4
// 0 2 2 3 4
// 0 2 2 3 4

初始化值全是nil,也就验证了指针数组内部全都是一个一个指针,之后我们将其初始化,内部的每个指针指向一块内存空间

然后将这个指针数组传递给test1函数,对于数组的参数传递仍然是复制的形式也就是值传递,但是因为数组中每个元素是一个指针,所以test1函数复制的新数组中的值仍然是这些指针指向的具体地址值,这时改变a[1]这块存储空间地址指向的值,那么原实参指向的值也会变为2。

数组指针

func main() {
    
    
	var a [5]int
	var aPtr *[5]int
	aPtr = &a
	//这样简短定义也可以aPtr := &a
	fmt.Println(aPtr)
	test(aPtr)
	fmt.Println(aPtr)
}


func test(aPtr *[5]int) {
    
    
	aPtr[1] = 5
	fmt.Println(aPtr)
}

先定义了一个数组a,然后定一个指向数组a的指针aPtr,然后将这个指针传入一个函数,在函数中改变了具体的值,程序的输出结果:

&[0 0 0 0 0]
&[0 5 0 0 0]
&[0 5 0 0 0]

虽然main和test函数中的aPtr是不同的指针,但是他们都指向同一块数组的内存空间,所以无论在main函数还是在test函数中对数组的操作都会直接改变数组.

切片

定义切片

//方法一
var s1 = []int{
    
    }
//方法二
var s2 = []int{
    
    1, 2, 3}
//方法三
var s3 = make([]int, 5)
//方法四
var s4 = make([]int, 5, 10)

切片操作

func main() {
    
    
	arr := [5]int{
    
    1, 2, 3, 4, 5}
	s := []int{
    
    6, 7, 8, 9, 10}

	s1 := arr[2:4]
	s2 := arr[:3]
	s3 := arr[2:]
	s4 := s[1:3]

	fmt.Println("s1:", s1) //[3 4]
	fmt.Println("s2:", s2) //[1 2 3]
	fmt.Println("s3:", s3) //[3 4 5]
	fmt.Println("s4:", s4) //[7 8]
}

切片的扩充与拼接

func main() {
    
    
	a := []int{
    
    1, 2, 3}
	b := a[1:3]

	b = append(b, 4)
	b = append(b, 5)
	b = append(b, 6)
	b = append(b, 7)
	fmt.Println(a) //[1 2 3]
	fmt.Println(b) //[2 3 4 5 6 7]
}

如果想要将两个切片进行拼接可以使用如下这种方式:

func main() {
    
    
	a := []int{
    
    1, 2, 3}
	b := a[1:3]

	fmt.Println(b)//[2 3]

	a = append(a, b...)
	fmt.Println(a)//[1 2 3 2 3]
}

如果想要将一个切片的值复制给另一个切片:

func main() {
    
    
	a := []int{
    
    1, 2, 3}
	b := make([]int, 3)
	copy(b, a)
	fmt.Println(a)
	fmt.Println(b)
}
  • 如果b的长度比a长,复制时不足的默认元素为0
  • 如果b的长度比a短,复制时会在相应位置截断
  • 如果b的长度为0,复制后b为空,不会报错

切片与数组的关系

对于任何一个切片来说,其都有一个底层数组与之对应,我们可以将切片看作是一个窗口,透过这个窗口可以看到底层数组的一部分元素,对应关系如下图所示:
在这里插入图片描述

  • 编写程序看看切片的容量与数组的大小有什么关系呢?
package main

import "fmt"

func main() {
    
    
	arr := []int{
    
    1, 2, 3,4,5}
	arrSlice := arr[1:3]

	arrSlice = append(arrSlice, 12, 13, 14)
    
	fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
    
	fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))
}

//输出
//切片 p = 0xc000098440,arr = [2 3 12 13 14],len = 5,cap = 8
//数组 p = 0xc000098420,arr = [1 2 3 4 5],len = 5,cap = 5

可以看到,数组的值并没有被修改,所以,当切片容量大于底层数组容量时,会自动创建一个新的底层数组,取消对原数组的引用

  • 如果我们在切片上再做切片那么他们会指向相同的底层数组吗?修改其中一个切片会影响其他切片的值么?其中一个切片扩容到容量大小之后会更换底层数组,那么之前的其他切片也会指向新的底层数组吗?
package main

import "fmt"

func main() {
    
    
	arr := []int{
    
    1, 2, 3, 4, 5}
	arrSlice := arr[1:4]
	arrSlice2 := arrSlice[2:]

	arrSlice2 = append(arrSlice2, 12)
	fmt.Printf("切片1 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))

	fmt.Printf("切片2 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice2, arrSlice2, len(arrSlice2), cap(arrSlice2))
	fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

	arrSlice2 = append(arrSlice2, 12,13,14,15)
	arrSlice = append(arrSlice, 12)
	fmt.Printf("切片1 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))

	fmt.Printf("切片2 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice2, arrSlice2, len(arrSlice2), cap(arrSlice2))
	fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))
}

第一次切片上的切片添加元素12,输出如下:

切片1 p = 0xc0000044a0,arr = [2 3 4],len = 3,cap = 4
切片2 p = 0xc0000044c0,arr = [4 12],len = 2,cap = 2
数组 p = 0xc000004480,arr = [1 2 3 4 12],len = 5,cap = 5

若改成对切片arrSlice添加元素12,输出如下:

切片1 p = 0xc0000044a0,arr = [2 3 4 12],len = 4,cap = 4
切片2 p = 0xc0000044c0,arr = [4],len = 1,cap = 2
数组 p = 0xc000004480,arr = [1 2 3 4 12],len = 5,cap = 5

说明两个切片指向同一个低层数组。修改其中一个切片的值都不会影响其他切片的值。

切片1 p = 0xc0000044a0,arr = [2 3 4 12],len = 4,cap = 4
切片2 p = 0xc0000044c0,arr = [4 12 12 13 14 15],len = 6,cap = 6
数组 p = 0xc000004480,arr = [1 2 3 4 12],len = 5,cap = 5

若改成修改arrSlice的长度超过原数组:

切片1 p = 0xc0000044a0,arr = [2 3 4 12 12 13 14 15],len = 8,cap = 8
切片2 p = 0xc0000044c0,arr = [4 12],len = 2,cap = 2
数组 p = 0xc000004480,arr = [1 2 3 4 12],len = 5,cap = 5

说明修改其中一个切片到超出长度,其他切片仍指向原数组。

  • 既然切片是引用底层数组的,需要注意的就是小切片引用大数组的问题,如果底层的大数组一直有切片进行引用,那么垃圾回收机制就不会将其收回,造成内存的浪费,最有效的做法是copy需要的数据后再进行操作。

参考

https://github.com/datawhalechina/go-talent

猜你喜欢

转载自blog.csdn.net/i0o0iW/article/details/111410838