Go语言数组和切片
前面的文章:
Go语言环境
go语言IDE安装与配置
Go语言Hello World实例
Go语言基础语法
一、数组
数组
go语言中的数组打大小也是数组类型的一部分,也就是说 [5]int 和 [10]int 是不同的类型
var 变量名 [数组长度]数组类型, 如:
var arr [10]int
也能像定义变量那样直接赋值:
arr2 := [10]int{1, 2, 3, 4, 5}
// 定义数组,数组长度为10,下标是0-9var arr1 [10]int// 定义循环,遍历数组for i := 0; i < len(arr1); i++ {fmt.Println(arr1[i])}// 定义数组 指定数组值arr2 := [10]int{1, 2, 3, 4, 5}/ / range for循环。类似于Java中的foreachfor index, value := range arr2 {fmt.Println("index= ", index, "value= ", value)}/*注意:在 Go 语言中,数组的大小是类型的一部分,因此不同大小的数组是不兼容的,也就是说 [5]int 和 [10]int 是不同的类型。以下定义了数组 balance 长度为 5 类型为 float32,并初始化数组的元素:*/// 如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}// 或balance1 := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
二、切片(slice)
示例中有提到,数组的大小是数组类型的一部分,不同大小的数组就是不同类型。那么如果需要一个函数定义形参,如果指定数组长度,那么这个函数就只能接收固定长度的数组入参,这在软件设计中显然是不可接受。
Go提供了一种灵活、功能强悍的内置类型解决这个问题(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
定义切片
var identifier []type
就像是不指定数组长度的数组定义。或使用 make() 函数来创建切片:
var slice1 []type = make([]type, len)
也可以简写为
slice1 := make([]type, len) / / len就是切片的初始长度
示例:
// 使用make定义slice,这种定义方式会分配slice的默认大小和初始值slice2 := make([]int, 12)for index, value := range slice2 {fmt.Println(index, value)}---------------执行结果------------------0 0
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
10 0
11 0
说明使用make函数创建切片,会按照给定的长度初始化数组
这种写法创建的slice是一个空切片:
var slice3 []intif slice3 == nil {fmt.Println("slice3是一个空切片")}-----------------------执行结果--------------------slice3是一个空切片
切片的传参是引用传递,普通数组的传参是值传递。
切片的容量
make()函数的第三个参数capacity是一个可选参数,如果指定了就是切片初始容量,如下示例:
// 定义一个有初始化容量的动态数组slice5 := make([]int, 3, 5)// 第三个参数指定的就是动态数组的初始话容量 capacity// 切片的容量和长度的区别fmt.Printf("len = %d, cap = %d, slice = %v\n", len(slice5), cap(slice5), slice5)// 追加一个元素试试slice5 = append(slice5, 2)fmt.Printf("len = %d, cap = %d, slice = %v\n", len(slice5), cap(slice5), slice5)// 继续追加,直到超出容量slice5 = append(slice5, 3)slice5 = append(slice5, 4)fmt.Printf("len = %d, cap = %d, slice = %v\n", len(slice5), cap(slice5), slice5)------------------------------执行结果------------------------------------------------------len = 3, cap = 5, slice = [0 0 0]
len = 4, cap = 5, slice = [0 0 0 2]
len = 6, cap = 10, slice = [0 0 0 2 3 4]
通过上面的示例,我们发现当长度小于容量的时候,向切片中添加元素只会增加切片的长度,不会改变切片的容量;
但是当长度大于容量的时候,切片会自动扩容,而一次自动扩容的大小正是当前容量的2倍。(类似Java的hashmap扩容)
那么切片的长度和容量到底应该如何理解?
切片的长度是指切片中当前包含的元素数量。
切片的容量是在不重新分配底层数组的情况下可以增长到的最大长度,是切片底层数组空间的长度。
切片容量总是大于等于切片长度。
切片的截取
基本语法
切片截取的基本语法是 slice[low:high],它返回一个新的切片,包含原切片从索引 low 到 high(不包含 high)的元素。
- 索引规则:
low和high都是可选的。如果省略low,默认为0(从开头开始);如果省略high,默认为原切片的长度len(slice)(直到末尾)。 - 长度与容量:新切片的长度为
high - low,容量通常为原切片的容量减去low。例如,对于s := []int{0,1,2,3,4,5},s1 := s[1:3]的长度是2,容量至少是5(假设原切片容量为6)。 - 三索引格式:Go还支持三索引的截取方式
slice[low:high:max],它可以用来精确控制新切片的容量,新切片的容量为max - low。这在需要限制后续操作对底层数组影响范围时很有用。
截取操作示例
假设有一个切片 s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},下面是一些常见的截取操作及其结果:
| 操作表达式 | 结果(元素) | 长度(len) | 容量(cap) | 说明 |
|---|---|---|---|---|
| s[:] | [0,1,2,3,4,5,6,7,8,9] | 10 | 10 | 获取整个切片 |
| s[:6] | [0,1,2,3,4,5] | 6 | 10 | 省略low,从索引0到6(不含) |
| s[3:] | [3,4,5,6,7,8,9] | 7 | 7 | 省略high,从索引3到结尾 |
| s[2:5] | [2,3,4] | 3 | 8 | 从索引2到5(不含) |
| s[3:6:8] | [3,4,5] | 3 | 5 | 指定新切片容量为8-3=5 |
关键特性:共享底层数组
切片截取最重要的特性是:新切片和原切片通常会共享同一个底层数组。
这意味着,如果你通过新切片修改了某个元素的值,原切片中对应位置的值也会随之改变,反之亦然。
arr := []int{0, 1, 2, 3, 4, 5}
s1 := arr[1:4] // s1 = [1, 2, 3]
s1[0] = 100 // 修改s1的第一个元素
fmt.Println(arr) // 输出: [0, 100, 2, 3, 4, 5],原数组也被修改了
切片截取注意事项
- 避免内存泄露:由于共享底层数组,如果你只是截取一个大切片中的一小部分使用,但保留了对这一小部分的引用,那么整个大的底层数组都无法被垃圾回收,这可能导致内存泄露。如果只需要处理部分数据,考虑使用
copy函数创建一个独立的新切片。 - 索引越界:截取时索引不能超出原切片的有效范围,否则会引发运行时异常(panic)。确保
low和high满足0 <= low <= high <= cap(slice)。 append操作的影响:对新切片进行append操作时,如果追加的元素没有超过其容量,修改会发生在共享的底层数组上,从而影响原切片。如果超过容量,Go会为新切片分配一个新的底层数组,此时两者就“分道扬镳”了。
切片截取之后,还是会指向原数组的地址,谨慎使用。我们可以使用copy(s2, s1)函数,将s1中的值依次拷贝到s2中,这个函数会将底层的数组一并拷贝成一个新的数组。
切片的排序
在Go语言中,你可以使用标准库中的 sort 包来对切片进行排序。sort 包提供了多种排序功能,包括按升序或降序排序整数、浮点数、字符串以及自定义类型的切片。
- 如果是基本数据类型,
sort包提供了对应的方法:sort.Ints()、sort.Float64s(floats)、sort.Strings(strs) - 如果是自定义类型,可以实现
sort.Interface接口后,通过sort.Sort()方法排序 - 切片还可以通过
sort.Slice()函数传入函数式比较器进行排序
以下是排序示例:
排序整数切片
package mainimport ("fmt""sort"
)
func main() {ints := []int{5, 2, 6, 3, 1, 4}sort.Ints(ints)fmt.Println(ints) // 输出: [1 2 3 4 5 6]
}
排序浮点数切片
package main
import ("fmt""sort"
)
func main() {floats := []float64{5.5, 2.2, 6.6, 3.3, 1.1, 4.4}sort.Float64s(floats)fmt.Println(floats) // 输出: [1.1 2.2 3.3 4.4 5.5 6.6]
}
排序字符串切片
package mainimport ("fmt""sort"
)
func main() {strs := []string{"banana", "apple", "cherry", "date"}sort.Strings(strs)fmt.Println(strs) // 输出: [apple banana cherry date]
}
排序自定义类型切片
对于自定义类型,你需要实现 sort.Interface 接口的三个方法:Len(), Less(i, j int) bool, 和 Swap(i, j int)。
package mainimport ("fmt""sort"
)// 定义自定义类型 Person
type Person struct {Name stringAge int
}// 实现 sort.Interface 接口
type ByAge []Personfunc (a ByAge) Len() int { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }func main() {people := []Person{{"Alice", 30},{"Bob", 25},{"Charlie", 35},}sort.Sort(ByAge(people))fmt.Println(people) // 输出: [{Bob 25} {Alice 30} {Charlie 35}]
}
反向排序
如果你想要反向排序,可以自定义一个 sort.Reverse 类型的排序器。例如,对于整数切片:
package mainimport ("fmt""sort"
)func main() {ints := []int{5, 2, 6, 3, 1, 4}sort.Sort(sort.Reverse(sort.IntSlice(ints)))fmt.Println(ints) // 输出: [6 5 4 3 2 1]
}
对于自定义类型,你可以类似地定义一个反向排序器:
package mainimport ("fmt""sort"
)// 定义自定义类型 Person
type Person struct {Name stringAge int
}// 实现 sort.Interface 接口
type ByAge []Personfunc (a ByAge) Len() int { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }// 定义反向排序类型
type ByAgeDesc struct{ ByAge }func (a ByAgeDesc) Less(i, j int) bool { return a.ByAge[i].Age > a.ByAge[j].Age }func main() {people := []Person{{"Alice", 30},{"Bob", 25},{"Charlie", 35},}sort.Sort(ByAgeDesc{people})fmt.Println(people) // 输出: [{Charlie 35} {Alice 30} {Bob 25}]
}
切片的函数式排序方法
// 定义自定义类型 Person
type Person struct {Name stringAge int
}func main() {people := []Person{{"Alice", 30},{"Bob", 25},{"Charlie", 35},}sort.Slice(people, func(i, j int) bool {return people[i].Age < people[j].Age})fmt.Println(people) // 输出: [{Bob 25} {Alice 30} {Charlie 35}]
}
