【入门】Go语言切片详解

一、Go语言切片简介

1.1 切片的概念

数组和切片相比较切片的长度是不固定的,可以追加元素,在追加时可能会使切片的容量增大,所以可以将切片理解成 "动态数组",但是,它不是数组。

1.2 数组和切片的区别是什么?

  • 数组的长度是固定的,切片的长度是可变的。
  • 数组在声明时需要指定长度,切片不需要。
  • 数组的长度是数组类型的一部分,因此 [5]int 和 [10]int 是不同的类型,不能互相赋值或比较。而切片只需要考虑元素类型,因此 []int 和 []float64 是相同的类型。
  • 数组是值类型,当数组作为参数传递时,会被复制一份,因此对数组的修改不会影响原数组。而切片是引用类型,当切片作为参数传递时,只会传递一个指向底层数组的指针,因此对切片的修改会影响原切片。
  • 数组的长度是固定的,在内存中占用连续的空间。而切片的长度是可变的,在内存中是一个结构体,其中包含指向底层数组的指针、长度和容量三个字段。

二、切片声明

2.1 使用var声明切片

语法:

var 切片名称[]数据类型

举个例子:

var arr []int

2.2 使用自动推导类型声明切片

语法:

切片名称 := []类型{}

举个例子:

arr := []int{}

2.3 使用make()函数生成切片

语法:

make([]类型, 长度, 容量)
  • 长度是已初始化空间
  • 容量是已经开辟空间,包括已经初始化的空间和空闲空间。

举个例子:

package main

import "fmt"

func main() {
	arr := make([]int, 3, 5)
	fmt.Printf("长度:%d\t容量:%d", len(arr), cap(arr))
}

代码输出内容:

长度:3	容量:5

三、向切片中添加元素

3.1 切片中添加元素

package main

import "fmt"

func main() {
	arr := make([]int, 3, 5)
	arr[0] = 100  //添加100
	arr[1] = 200 // 添加200
	fmt.Println(arr)
}

代码输出内容:

[100 200 0]

3.2 append()函数切片添加元素

Go语言的内建函数 append() 可以为切片动态在末尾添加元素,代码如下所示:

package main

import "fmt"

func main() {
	var slice []int
	slice = append(slice, 1) // 追加1个元素
	slice = append(slice, 2, 3, 4) // 最加多个元素 
	slice = append(slice, []int{5, 6, 7}...) // 追加一个切片
	fmt.Println(slice)
}

代码输出内容:

[1 2 3 4 5 6 7]

注意:在使用 append() 函数为切片动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的长度会发生改变。切片在扩容时,容量的扩展规律是按容量的 2 倍数进行扩充,例如 1、2、4、8、16……,代码如下:

package main

import "fmt"

func main() {
	var slice []int
	for i := 0; i < 15; i++ {
		slice = append(slice, i)
		fmt.Printf("长度: %v 容量: %v 指针: %p\n", len(slice), cap(slice), slice)
	}
}

代码执行结果:

长度: 1 容量: 1 指针: 0xc000018098
长度: 2 容量: 2 指针: 0xc0000180e0
长度: 3 容量: 4 指针: 0xc00000e1e0
长度: 4 容量: 4 指针: 0xc00000e1e0
长度: 5 容量: 8 指针: 0xc000014280
长度: 6 容量: 8 指针: 0xc000014280
长度: 7 容量: 8 指针: 0xc000014280
长度: 8 容量: 8 指针: 0xc000014280
长度: 9 容量: 16 指针: 0xc000020180
长度: 10 容量: 16 指针: 0xc000020180
长度: 11 容量: 16 指针: 0xc000020180
长度: 12 容量: 16 指针: 0xc000020180
长度: 13 容量: 16 指针: 0xc000020180
长度: 14 容量: 16 指针: 0xc000020180
长度: 15 容量: 16 指针: 0xc000020180

除了可以在切片尾部追加,也可以在切片前面添加 示例如下:

package main

import "fmt"

func main() {
	var a = []int{1, 2, 3}
	a = append([]int{666}, a...) // 前面添加1个元素
	fmt.Println(a)
	a = append([]int{777, 888, 999}, a...) // 前面添加多个元素
	fmt.Println(a)
}

代码运行结果:

[666 1 2 3]
[777 888 999 666 1 2 3]

注意:在切片开头添加元素一般都会导致内存的重新分配,而且会导致已有元素全部被复制 1 次,因此,从切片的开头添加元素的性能要比从尾部追加元素的性能差很多。

3.3 copy()函数复制切片

Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。copy() 函数的使用格式如下:

copy( destSlice, srcSlice)
  • srcSlice:数据来源切片
  • destSlice:复制的目标
  • 目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致

下面代码展示了copy()函数的过程

package main

import "fmt"

func main() {
	slice1 := []int{1, 2, 3, 4, 5}
	slice2 := []int{5, 4, 3}
	copy(slice2, slice1) // 此时slice2内容为:[1 2 3]
	copy(slice1, slice2) //copy slice2对应索引的内容拷贝过来还是[1 2 3]

	fmt.Println(slice1)
	fmt.Println(slice2)
}

代码输出内容:

[1 2 3 4 5]
[1 2 3]

下面通过代码演示对切片的引用和复制操作后对切片元素的影响:

package main

import "fmt"

func main() {

	// 设置元素数量为1000
	const elementCount = 1000

	// 预分配足够多的元素切片
	// 预分配拥有 1000 个元素的整型切片,这个切片将作为原始数据
	srcData := make([]int, elementCount)

	// 将切片赋值
	// 将 srcData 填充 0~999 的整型值
	for i := 0; i < elementCount; i++ {
		srcData[i] = i
	}

	// 引用切片数据
	// 将 refData 引用 srcData,切片不会因为等号操作进行元素的复制
	refData := srcData

	// 预分配足够多的元素切片
	// 预分配与 srcData 等大(大小相等)、同类型的切片 copyData
	copyData := make([]int, elementCount)
	// 将数据复制到新的切片空间中
	// 使用 copy() 函数将原始数据复制到 copyData 切片空间中
	copy(copyData, srcData)

	// 修改原始数据的第一个元素
	// 修改原始数据的第一个元素为 999
	srcData[0] = 999

	// 打印引用切片的第一个元素
	// 引用数据的第一个元素将会发生变化
	fmt.Println(refData[0])

	// 打印复制切片的第一个和最后一个元素
	// 打印复制数据的首位数据,由于数据是复制的,因此不会发生变化
	fmt.Println(copyData[0], copyData[elementCount-1])

	// 复制原始数据从4到6(不包含)
	// 将 srcData 的局部数据复制到 copyData 中
	copy(copyData, srcData[4:6])

	// 打印复制局部数据后的 copyData 元素
	for i := 0; i < 5; i++ {
		fmt.Printf("%d ", copyData[i])
	}
}

四、遍历切片

和遍历数组其实是一样的

package main

import "fmt"

func main() {
	arr := make([]int, 3, 5)
	arr = append(arr, 200, 300)

	// // 传统for循环
	// for i := 0; i < len(arr); i++ {
	// 	fmt.Println(arr[i])
	// }
	for _, v := range arr {
		fmt.Println(v)
	}
}

五、切片截取

注意: 切片截取后返回新的切片,对新切片进行修改,会影响原切片,新切片只是指向原切片,并不是给新切片开辟内存空间。

简单举个例子:

s := []int{100, 200, 300, 400, 500, 6, 77}
	s1 := s[1:3:5]
  • 第一个值:起始索引位置
  • 第二个值:终止索引位置 终止索引位置不包括本身
  • 第三个值: 计算容量大小,容量=第三个值减去第一个值

案例:

package main

import "fmt"

func main() {
	s := []int{100, 200, 300, 400, 500, 6, 77}
	// 第一个值:起始索引位置
	// 第二个值:终止索引位置
	// 第三个值: 计算容量大小,容量=第三个值减去第一个值
	s1 := s[1:3:5]

	s2 := s[:]  // 截取所有元素
	s3 := s[3:] // 从第三个开始截取直到最后,如果没有指定第三个值,则使用s切片本身容量减去第一个值
	s4 := s[:2] // 从0开始截取,到索引2

	fmt.Printf("s1切片内容:%v\ts1切片长度:%v\ts1切片容量:%v\n", s1, len(s1), cap(s1))
	fmt.Printf("s2切片内容:%v\ts2切片长度:%v\ts2切片容量:%v\n", s2, len(s2), cap(s2))
	fmt.Printf("s3切片内容:%v\ts3切片长度:%v\ts3切片容量:%v\n", s3, len(s3), cap(s3))
	fmt.Printf("s4切片内容:%v\ts4切片长度:%v\ts4切片容量:%v\n", s4, len(s4), cap(s4))

}

代码输出内容:

s1切片内容:[200 300]	s1切片长度:2	s1切片容量:4
s2切片内容:[100 200 300 400 500 6 77]	s2切片长度:7	s2切片容量:7
s3切片内容:[400 500 6 77]	s3切片长度:4	s3切片容量:4
s4切片内容:[100 200]	s4切片长度:2	s4切片容量:7

六、拓展

6.1 切片作为函数参数

package main

import (
	"fmt"
)

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

	PrintFunc(a)
}

func PrintFunc(num []int) {
	for i := 0; i < len(num); i++ {
		fmt.Println(num[i])
	}
}

代码输出内容:

1
2
3
4
5
6

在函数中修改切片的值,会影响到原切片,如下案例:

package main

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

	PrintFunc(a)
	println(a[5])
}

func PrintFunc(num []int) {
	num[5] = 666 // 这里对切片进行重新赋值
}

代码输出内容:

666

6.2 切片案例: 计算一组整数之和

package main

import (
	"fmt"
)

func main() {
	var count int
	fmt.Println("请输入要求和的个数:")
	fmt.Scan(&count)
	s := make([]int, count)
	InitData(s)
	sum := SumAdd(s)
	fmt.Println(sum)
}

// 初始化函数
func InitData(num []int) {
	for i := 0; i < len(num); i++ {
		fmt.Printf("请输入第%d个数:\n", i+1)
		fmt.Scan(&num[i]) // 切片赋值操作
	}
}

// 求和函数
func SumAdd(num []int) int {
	var sum int
	for i := 0; i < len(num); i++ {
		sum += num[i]
	}
	return sum
}

代码输出内容:

请输入要求和的个数:
3
请输入第1个数:
100
请输入第2个数:
200
请输入第3个数:
200
500

6.3 切片案例: 输出一组整形数据中最大的值

package main

import (
	"fmt"
)

func main() {
	var count int
	fmt.Println("请输入要求和的个数:")
	fmt.Scan(&count)
	s := make([]int, count)
	InitData(s)
	max := Max(s)
	fmt.Println("最大值是:", max)
}

// 初始化函数
func InitData(num []int) {
	for i := 0; i < len(num); i++ {
		fmt.Printf("请输入第%d个数:\n", i+1)
		fmt.Scan(&num[i]) // 切片赋值操作
	}
}

// 比较
func Max(num []int) int {
	var max int = num[0]
	for i := 0; i < len(num); i++ {
		if num[i] > max {
			max = num[i]
		}
	}
	return max
}

代码输出内容:

请输入要求和的个数:
3
请输入第1个数:
100
请输入第2个数:
200
请输入第3个数:
400
最大值是: 400
posted @ 2023-03-29 14:35  乱七八糟博客备份  阅读(178)  评论(0编辑  收藏  举报