数组和切片
数组
数组是指一系列同一类型数据的集合。数组中包含的每个数据被称为数组元素(element),这种类型可以是意的原始类型,比如int、string等,也可以是用户自定义的类型。一个数组包含的元素个数被称为数组的长度。
在Go中数组是一个长度固定的数据类型,数组的长度是类型的一部分,也就是说[5]int和[10]int是两个不同的类型。
数组的另一个特点是占用内存的连续性,也就是说数组中的元素是被分配到连续的内存地址中的,因而索引数组元素的速度非常快。
数组定义
一维数组
var 数组变量名 [元素数量] T
示例
// 数组的长度是类型的一部分
var arr1 [3]int
var arr2 [4]string
fmt.Printf("%T, %T \n", arr1, arr2)
// 数组的初始化 第一种方法
var arr3 [3]int
arr3[0] = 1
arr3[1] = 2
arr3[2] = 3
fmt.Println(arr3)
// 第二种初始化数组的方法
var arr4 = [4]int {10, 20, 30, 40}
fmt.Println(arr4)
// 第三种数组初始化方法,自动推断数组长度
var arr5 = [...]int{1, 2}
fmt.Println(arr5)
// 第四种初始化数组的方法,指定下标
a := [...]int{1:1, 3:5}
fmt.Println(a)
二维数组
Go语言支持多维数组,我们这里以二维数组为例(数组中又嵌套数组):
var 数组变量名 [元素数量][元素数量] T
示例
// 二维数组
var array5 = [2][2]int{{1,2},{2,3}}
fmt.Println(array5)
另外我们在进行数组的创建的时候,还可以使用类型推导,但是只能使用一个 ...
// 二维数组(正确写法)
var array5 = [...][2]int{{1,2},{2,3}}
错误写法
// 二维数组
var array5 = [2][...]int{{1,2},{2,3}}
数组操作
在 Go 语言中,数组类型支持一些基本的操作,如遍历、修改、拷贝等。
遍历数组
可以使用for
循环遍历数组,也可以使用range
关键字来遍历数组。
a := [3]int{1, 2, 3}
// 使用for循环遍历数组
for i := 0; i < len(a); i++ {
fmt.Println(a[i])
}
// Output:
// 1
// 2
// 3
// 使用range关键字遍历数组
for i, v := range a {
fmt.Println(i, v)
}
// Output:
// 0 1
// 1 2
// 2 3
修改数组
要修改数组中的元素,只需使用下标运算符([]
)来指定要修改的元素的位置,并将新值赋给它即可。
a := [3]int{1, 2, 3}
a[1] = 4
fmt.Println(a) // Output: [1 4 3]
数组是值类型,赋值和传参会赋值整个数组,因此改变副本的值,不会改变本身的值
var array1 = [...]int {1, 2, 3}
array2 := array1
array2[0] = 3
fmt.Println(array1, array2) // Output: [1 2 3] [3 2 3]
内置函数
-
len(array)
:返回数组的长度 -
cap(array)
:返回数组的容量,对于数组类型,容量和长度是相等的
以下是一些使用数组内置函数的示例:
a := [3]int{1, 2, 3}
fmt.Println(len(a)) // Output: 3
fmt.Println(cap(a)) // Output: 3
切片
切片(Slice)是一个动态数组,是对数组的封装。与数组相比,切片具有更高的灵活性和便利性,可以动态地增加或减少其容量和长度。
切片本身并不存储数据,它只是一个对底层数组的引用。它的内部结构包含地址、长度和容量。
切片定义
直接定义
在Go语言中,切片类型的定义格式为:[]T
,其中,T
表示切片中元素的类型。
以下是一些初始化和定义切片的示例:
// 声明切片,把长度去除就是切片
// 定义一个字符串切片,并初始化2个元素
a := []string{"hello", "world"}
// 定义一字符串切片,但不初始化
var c []string
// 声明一个整数切片,但不初始化
var d []int
基于数组定义切片
由于切片的底层就是一个数组,所以我们可以基于数组来定义切片
// 基于数组定义切片
a := [5]int {55,56,57,58,59}
// 获取数组所有值,返回的是一个切片
b := a[:] // Out: [55 56 57 58 59]
// 从数组获取指定的切片
c := a[1:4] // Out: [56 57 58]
// 获取 下标3之前的数据(不包括3)
d := a[:3] // Out: [55 56 57]
// 获取下标3以后的数据(包括3)
e := a[3:] // Out: [58 59]
同理,我们不仅可以对数组进行切片,还可以切片在切片
切片操作
遍历切片
切片的遍历和数组是一样的
var slice = []int{1,2,3}
for i := 0; i < len(slice); i++ {
fmt.Print(slice[i], " ")
}
修改切片
要修改数组中的元素,只需使用下标运算符([]
)来指定要修改的元素的位置,并将新值赋给它即可。
a := []int{1, 2, 3}
a[1] = 4
fmt.Println(a) // Output: [1 4 3]
切片是引用类型,赋值和传参会实际都是对同一个数组的引用,因此改变副本的值,也会改变本身的值
package main
import "fmt"
func main() {
var array1 = []int {1, 2, 3}
array2 := array1
array2[0] = 3
fmt.Println(array1, array2) // Output: [3 2 3] [3 2 3]
}
内置函数
-
len(array)
:返回切片的长度 -
cap(array)
:返回切片的容量,对于数组类型,容量和长度是相等的 -
copy(destSlice, srcSlice []T) int
:将源切片中的元素复制到目标切片中,返回实际复制的元素个数,数组是不支持该操作的 -
make ([]T, size, cap)
:动态创建一个指定长度和容量的切片 -
append(srcSlice []T, num1, num2, ...)
:可以为切片动态添加元素,返回一个新切片,在为切片动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的容量会发生改变。
以下是一些使用切片内置函数的示例:
package main
import "fmt"
func main() {
var a = []int{1, 2, 3, 4}
fmt.Println(len(a)) // Output: 3
fmt.Println(cap(a)) // Output: 3
var b = make([]int, 7, 10)
fmt.Println(len(b)) // Output: 7
fmt.Println(cap(b)) // Output: 10
copy(b, a)
fmt.Println(b) // Output: [1 2 3 4 0 0 0]
b = append(b, 1) // 追加1个元素
b = append(b, 1, 2, 3) // 追加多个元素, 手写解包方式
b = append(b[:1], b[2:]...) // 追加一个切片, 切片需要解包, 切片本身不支持删除操作,但是可以模拟实现
fmt.Println(b) // Output: [1 3 4 0 0 0 1 1 2 3]
fmt.Println(len(b)) // Output: 10
fmt.Println(cap(b)) // Output: 20
}
排序操作
冒泡升序排序
package main
import "fmt"
func main() {
var numSlice = []int{9, 8, 7, 6, 5, 4}
for i := 0; i < len(numSlice); i++ {
flag := false
for j := 0; j < len(numSlice)-i-1; j++ {
if numSlice[j] > numSlice[j+1] {
var temp = numSlice[j+1]
numSlice[j+1] = numSlice[j]
numSlice[j] = temp
flag = true
}
}
if !flag {
break
}
}
fmt.Println(numSlice)
}
选择升序排序
package main
import "fmt"
func main() {
// 编写选择排序
var numSlice2 = []int{9, 8, 7, 6, 5, 4}
for i := 0; i < len(numSlice2); i++ {
for j := i + 1; j < len(numSlice2); j++ {
if numSlice2[i] > numSlice2[j] {
var temp = numSlice2[i]
numSlice2[i] = numSlice2[j]
numSlice2[j] = temp
}
}
}
fmt.Println(numSlice2)
}
切片扩容
当切片的长度不足以容纳新的元素时,Go 语言会自动扩容切片的容量
当创建多个切片时,如果它们底层引用的是同一个数组,则它们之间会共享底层数组。以下是一个例子:
// 创建一个长度为 5,容量为 10 的切片
numbers := make([]int, 5, 10)
// 创建两个切片,它们共享同一个底层数组
slice1 := numbers[1:3]
slice2 := numbers[2:5]
// 修改切片中的元素
slice1[0] = 10
slice2[1] = 20
// 遍历切片中的所有元素,发现它们都已经被修改了
for _, v := range numbers {
fmt.Println(v)
}
在上面的例子中,我们创建了一个长度为 5,容量为 10 的切片 numbers
,然后创建了两个切片 slice1
和 slice2
,它们都共享同一个底层数组 numbers
。然后我们修改了切片 slice1
和 slice2
中的元素,最后遍历切片 numbers
中的所有元素,发现它们都已经被修改了。
需要注意的是,如果修改了其中一个切片的长度或容量,那么它们之间就不再共享同一个底层数组了。例如,如果我们将 slice1
的长度扩展到 3,那么它就不再共享底层数组了:
slice1 = append(slice1, 30)
// 修改 slice1 后,它就不再共享底层数组了
for _, v := range numbers {
fmt.Println(v)
}
当一个切片的长度超过了其容量时,底层数组就会重新分配内存,新的底层数组会有足够的容量来容纳新的元素,然后将原来的元素复制到新的底层数组中,并将切片指向新的底层数组。因此,无论是使用数组还是使用长度为0的切片作为底层数组,在进行切片扩容操作时,都有可能失去与其他切片共享底层数组的效果。所以在使用多个切片共享底层数组时,需要特别注意切片的容量和扩容操作。