Go语言数组和切片

前面的文章:
Go语言环境
go语言IDE安装与配置
Go语言Hello World实例
Go语言基础语法

一、数组

数组

go语言中的数组打大小也是数组类型的一部分,也就是说 5int 和 10int 是不同的类型

var 变量名 数组长度数组类型, 如:

var arr [10]int

也能像定义变量那样直接赋值:

arr2 := [10]int{1, 2, 3, 4, 5}

go 复制代码
	// 定义数组,数组长度为10,下标是0-9
    var 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中的foreach
    for 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就是切片的初始长度

示例:

go 复制代码
    // 使用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是一个空切片:

go 复制代码
    var slice3 []int
    if slice3 == nil {
        fmt.Println("slice3是一个空切片")
    }

-----------------------执行结果--------------------

slice3是一个空切片

切片的传参是引用传递,普通数组的传参是值传递。

切片的容量

make()函数的第三个参数capacity是一个可选参数,如果指定了就是切片初始容量,如下示例:

go 复制代码
    // 定义一个有初始化容量的动态数组
    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],它返回一个新的切片,包含原切片从索引 lowhigh(不包含 high)的元素。

  • 索引规则lowhigh 都是可选的。如果省略 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(不含)
s3: 3,4,5,6,7,8,9 7 7 省略high,从索引3到结尾
s2:5 2,3,4 3 8 从索引2到5(不含)
s3:6:8 3,4,5 3 5 指定新切片容量为8-3=5

关键特性:共享底层数组

切片截取最重要的特性是:新切片和原切片通常会共享同一个底层数组

这意味着,如果你通过新切片修改了某个元素的值,原切片中对应位置的值也会随之改变,反之亦然。

go 复制代码
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],原数组也被修改了

切片截取注意事项

  1. 避免内存泄露 :由于共享底层数组,如果你只是截取一个大切片中的一小部分使用,但保留了对这一小部分的引用,那么整个大的底层数组都无法被垃圾回收,这可能导致内存泄露 。如果只需要处理部分数据,考虑使用 copy 函数创建一个独立的新切片。
  2. 索引越界 :截取时索引不能超出原切片的有效范围,否则会引发运行时异常(panic)。确保 lowhigh 满足 0 <= low <= high <= cap(slice)
  3. 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()函数传入函数式比较器进行排序

以下是排序示例:

排序整数切片

go 复制代码
package main

import (
    "fmt"
    "sort"
)
func main() {
    ints := []int{5, 2, 6, 3, 1, 4}
    sort.Ints(ints)
    fmt.Println(ints) // 输出: [1 2 3 4 5 6]
}

排序浮点数切片

go 复制代码
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]
}

排序字符串切片

go 复制代码
package main

import (
    "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)

go 复制代码
package main

import (
    "fmt"
    "sort"
)

// 定义自定义类型 Person
type Person struct {
    Name string
    Age  int
}

// 实现 sort.Interface 接口
type ByAge []Person

func (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 类型的排序器。例如,对于整数切片:

go 复制代码
package main

import (
    "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]
}

对于自定义类型,你可以类似地定义一个反向排序器:

go 复制代码
package main

import (
    "fmt"
    "sort"
)

// 定义自定义类型 Person
type Person struct {
    Name string
    Age  int
}

// 实现 sort.Interface 接口
type ByAge []Person

func (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}]
}

切片的函数式排序方法

go 复制代码
// 定义自定义类型 Person
type Person struct {
	Name string
	Age  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}]
}
相关推荐
weixin_44672916几秒前
java实现发送邮件
java·开发语言
无相求码4 分钟前
为什么你的第二个 scanf 还没输入就直接跳过了?C语言输入缓冲区陷阱
c语言·后端
叼馒女友郭芙蓉4 分钟前
Function Calling理解
后端
玉宇夕落5 分钟前
LangChain RAG Retriever 的学习
后端
用户2986985301411 分钟前
Python 一键导出 Excel 中的图表和形状为图片
后端·python·excel
用户81300778163712 分钟前
PageHelper分页查询步骤
后端
是小李呀14 分钟前
MyBatis、MyBatis-Plus 与全自动 ORM 的本质区别
后端
米尔的可达鸭26 分钟前
深入操作系统 Socket 底层:EPOLLOUT 可写事件管理 + 非阻塞异步
开发语言·网络·数据结构·经验分享·websocket·网络协议
小堂子这厢有礼了27 分钟前
Chet.Admin 权限模型:菜单级 + 按钮级 + 行级三层防护
前端·后端·信息可视化·前端框架·.net
SimonKing28 分钟前
Spring Boot 4.0 + gRPC 极简集成:4步搞定,比REST还丝滑
java·后端·程序员