当涉及到 Go 语言中的数据结构时,数组和切片无疑是两个不可或缺的角色。它们为我们提供了处理数据的强大工具,分别适用于不同的场景和需求。下文将深入探讨Go语言中数组和切片的特性、用法以及它们之间的区别。从固定长度的数组到动态可调整长度的切片,我们将揭开它们的面纱,帮助你更好地理解如何在Go中有效地利用这两种数据结构。
数组
定义
数组是一个长度固定、相同类型的元素组成的连续序列;数组类型不仅是逻辑上的连续序列,而且在实际内存分配时也占据着一整块内存。
声明语法
-
声明数组
govar name [size]type
name
为数组的名字。size
为数组元素的个数。type
为元素的类型。
-
初始化数组的三种方式
govar arr [3]string var numbers = [5]int{1,2,3,4,5} var skill = [...]string{"JS","TS","Go"}
-
数组中的元素个数不能超过中括号中的数字。
-
不显示指明数组元素的个数则可以使用
...
来代替数组长度,Go 编译器会根据大括号中的元素个数自动推导数组的长度。govar arr = [...]int{1,2,3,4,5} // Go 编译器自动计算长度, len = 5
-
声明一个数组类型变量的同时,也可以显式地对它进行初始化。如果不进行显式初始化,那么数组中的元素值就是它类型的零值。
govar arr1 [6]int // [0 0 0 0 0 0] // 不进行显示初始化 var arr2 = [6]int { 11, 12, 13, 14, 15, 16,} // 显示初始化
-
如果两个数组类型的元素类型 与数组长度都是一样时,那么这两个数组类型是等价的,如果有一个属性不同,它们就是两个不同的数组类型。
-
数组间的比较,长度和类型也是一个关键的条件。
-
数组是值传递,有较大的内存开销(指针可以解决内存开销的问题)。
-
-
访问数组元素
数组长度可以通过内置的
len
函数获取,数组的下标值是从 0 开始的。如果下标值超出数组长度范畴或者是负数,那么 Go 编译器会报错,防止访问溢出。govar arr = [6]int{1, 2, 3, 4, 5, 6} fmt.Println(arr[0], arr[5]) // 11 16 fmt.Println(arr[-1]) // 报错:下标值不能为负数 fmt.Println(arr[7]) // 报错:超出了 arr 的长度范围
切片
切片是对数组的一个连续"片段"的引用,切片是引用类型 ;切片的出现,解决了数组的不足:固定的元素个数不可变和传值机制下导致的开销较大的问题 ;切片中可以通过内置函数 append
,动态地向切片中添加元素。
一个切片在运行时,由指针、长度和容量3部分构成;切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。
go
type slice struct {
array unsafe.Pointer
len int
cap int
}
array:是指向底层数组的指针。
len:是切片的长度,即切片中当前元素的个数。使用
len()
获取长度。cap:是底层数组的长度,也是切片的最大容量,
cap
值永远大于等于len
值。使用cap()
获取容量。
声明语法
go
var name []type
name
表示切片的变量名;type
表示切片对应元素的类型;
go
package main
import "fmt"
func main() {
var sliceStr []string // 声明一个字符串切片
var sliceNum []int // 声明整型切片
var emptySliceNum = []int{} // 声明一个int类型的切片,默认值为int类型的零值 0
fmt.Printf("slice string length: %d\n", len(sliceStr)) // slice string length: 0
fmt.Printf("slice number length: %d\n", len(sliceNum)) // slice number length: 0
fmt.Printf("empty slice num length: %d\n", len(emptySliceNum)) // empty slice num length: 0
}
切片的创建方法
-
通过
make
函数来创建切片,并指定底层数组的长度。用
make
函数生成切片会发生内存分配操作;如果有显示声明开始和结束位置的切片,则只是将新的切片结构指向已经分配好的内存区域。goslice := make([]byte, 6, 10) // 6 -> 切片的初始长度, 10 -> cap 值,也就是底层数组的长度
如果没有在 make 中指定
cap
参数,那么底层数组长度cap
就等于len
。gosl := make([]byte, 6) // cap = len = 6
slice 容量小于 1024 时,当在切片中增加数据且容量不足时,会在当前容量的基础上进行翻倍扩容;当大于等于 1024 时,会在当前容量的基础上增加当前容量的四分之一。
-
基于数组创建切片
Go语言的切片原则:左含右不含,传入的数字是元素的索引
gopackage main import "fmt" func main() { arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} sl := arr[3:7] // 3 为起始下标,7 为结束下标 fmt.Printf("slice: %d", sl) // slice: [4 5 6 7] }
- 取出的元素数量为"结束索引减去开始位置"。
- 取出的元素不包含结束索引的值;切片最后一个元素可以使用
slice[len(slice)]
获取。 - 如果缺省开始位置,则表示从下标0到结束下标。
- 如果缺省结束位置,则表示从开始索引到整个切片的结尾。
- 如果两者同时缺省,则新生成的切片与原切片等效。
- 如果两者同时为0,则等效于一个空切片,一般用于切片复位。
根据索引位置去切片 slice 元素时,取值范围是
0 ~ len(slice)-1
;如果越界,则会报错;在生成切片时,结束位置可以填写len(slice)
,不会报错。
数组或切片中的常用方法
-
迭代
gopackage main import "fmt" func main() { var arr = []int{1, 2, 3} fmt.Printf("arr 的长度为: %d\n", len(arr)) // arr 的长度为: 3 // 如果不需要使用某个值,则可以使用下划线(_)来忽略 for k, v := range arr { fmt.Printf("第%d个值为: %d\n", k, v) } } // 第0个值为: 1 // 第1个值为: 2 // 第2个值为: 3
-
删除元素
gopackage main import "fmt" func main() { var skill = []string{"Go", "JavaScript", "Node.js", "TypeScript", "React", "React Native", "Vue"} // 指定索引(下标)删除 index := 2 // [:index] -> 从切片索引 0 开始,在 index 结束,不包含 index 的元素;[index+1:] -> 从index + 1 开始至切片的最后一个元素 skill = append(skill[:index], skill[index+1:]...) fmt.Printf("类型为: %T, 长度为:%d,结果为:%s", skill, len(skill), skill) // 类型为: []string, 长度为:6,结果为:[Go JavaScript TypeScript React React Native Vue] }
-
将数组转换为字符串
gopackage main import ( "fmt" "strings" ) func main() { var skill = []string{"Go", "JavaScript", "Node.js", "TypeScript", "React", "React Native", "Vue"} fmt.Printf("result: %s\n", strings.Join(skill, ",")) // result: Go,JavaScript,Node.js,TypeScript,React,React Native,Vue fmt.Printf("result: %s", sliceToString(skill)) // result: Go,JavaScript,Node.js,TypeScript,React,React Native,Vue } func sliceToString(arr []string) string { var result string for k, v := range arr { value := v if k < len(arr)-1 { value += "," } result += value } return result }
-
检查某个值是否在切片中
gopackage main import ( "fmt" ) func main() { var skill = []string{"Go", "JavaScript", "Node.js", "TypeScript", "React", "React Native", "Vue"} fmt.Printf("是否存在 Next.js:%t", exist("Next.js", skill)) // 是否存在 Next.js:false } func exist(target string, array []string) bool { if len(array) <= 0 { return false } for _, element := range array { if element == target { return true } } return false }
-
查找一个元素在数组中的位置
通过
reflect
包的ValueOf
函数获取数组的值,然后使用for
循环遍历数组对值进行比较,如果相等,则返回值的索引。gopackage main import ( "fmt" "reflect" ) func main() { var skill = []string{"Go", "JavaScript", "Node.js", "TypeScript", "React", "React Native", "Vue"} fmt.Printf("查找 React 的索引:%d", findPosition(skill, "react")) // 查找 react 的索引:-1 fmt.Printf("查找 React 的索引:%d", findPosition(skill, "React")) // 查找 React 的索引:4 } func findPosition(arr any, target any) int { array := reflect.ValueOf(arr) for i := 0; i < array.Len(); i++ { v := array.Index(i) if v.Interface() == target { return i } } return -1 }
-
查找最大值和最小值的元素
冒泡排序从小到大排序后,第一个是最小值,最后一个是最大值。
gopackage main import "fmt" func main() { numbers := []int8{-1, 40, 50, -23, 100, 88, -32, 56, 2, 6, 39} bubbleSort(numbers) fmt.Println("排序后:", numbers) // 排序后: [-32 -23 -1 2 6 39 40 50 56 88 100] fmt.Printf("最大值: %d, 最小值: %d\n", numbers[len(numbers)-1], numbers[0]) // 最大值: 100, 最小值: -32 } func bubbleSort(numbers []int8) { n := len(numbers) for i := 0; i < n-1; i++ { for j := 0; j < n-i-1; j++ { if numbers[j] > numbers[j+1] { numbers[j], numbers[j+1] = numbers[j+1], numbers[j] } } } }
-
删除重复的元素
gopackage main import "fmt" func main() { numbers := []int{-1, 6, 50, 2, 100, 9, -32, 56, 2, 6, 39} numbers = removeDuplicates(numbers) fmt.Printf("去重后: %d\n", numbers) // 去重后: [-1 6 50 2 100 9 -32 56 39] } func removeDuplicates(numbers []int) []int { result := []int{} for _, num := range numbers { if !contains(result, num) { result = append(result, num) } } return result } func contains(numbers []int, target int) bool { for _, num := range numbers { if num == target { return true } } return false }
-
排序
gopackage main import ( "fmt" "sort" ) func main() { numbers := []int{-1, 6, 50, 2, 100, 9, -32, 56, 2, 6, 39} sort.Sort(sort.IntSlice(numbers)) // 升序 fmt.Printf("numbers: %d\n", numbers) // numbers: [-32 -1 2 2 6 6 9 39 50 56 100] sort.Sort(sort.Reverse(sort.IntSlice(numbers))) // 降序 fmt.Printf("numbers: %d\n", numbers) // numbers: [100 56 50 39 9 6 6 2 2 -1 -32] }
总结
Go语言中的数组和切片是两种不同的数据类型,各自具有独特的特性和应用场景。数组是固定长度的数据结构,要求所有元素为相同类型 ,同时具备值语义。这意味着在传递和赋值时,会发生元素的复制。由于数组长度在创建时就已经确定,因此在编译时可以获得性能优势。数组通常用于需要高效访问固定大小数据的场景。
相较之下,切片提供了动态长度的抽象,基于数组实现。切片是引用类型 ,具有引用语义,这意味着多个切片可以共享同一底层数组 。通过使用make
函数,我们能够动态调整切片的长度和容量。append
函数允许在切片尾部追加元素,系统会在需要时重新分配底层数组。切片表达式允许我们根据现有切片创建新的切片。
在实际编程中,数组适用于固定大小且对性能要求较高的情境,而切片则为动态数据集提供了更灵活的管理方式,允许动态伸缩。合理地使用数组与切片将有助于提高代码的效率和可维护性。