Golang数组与切片使用介绍,通过示例演示Golang语言中如何使用数组、切片,以及介绍数组与切片异同,最后介绍排序模块。
数组
数组是具有相同数据类型的数据项,组成的长度固定的序列,数据项叫做数组的元素,数组的长度必须为非负整数常量,数组长度是类型的一部分。
声明
声明数组时需要指定数组元素的类型和数量,数组声明后长度不可修改,每个数据项的初始值为对应数据类型的零值。
格式:
var <变量名> [长度]<类型>
// 短声明
<变量名> := [长度]<类型>{}
说明:
package main
import "fmt"
func main() {
var names [5]string
var ages [5]int
var isBoy [5]bool
fmt.Printf("%T, %T\n", names, ages) // [5]string, [5]int
fmt.Printf("%q\n", names)
fmt.Println(ages)
fmt.Println(isBoy)
}
输出为:
[5]string, [5]int
["" "" "" "" ""]
[0 0 0 0 0]
[false false false false false]
赋值
为新声明的数组赋字面量值
package main
import "fmt"
func main() {
// 指定字面量的长度
var ages = [5]int{5, 7, 4: 6}
fmt.Println(ages) // [5 7 0 0 6]
var names = [5]string{0: "xiaoming", 4: "xiaowang"}
fmt.Printf("%q\n", names) // ["xiaoming" "" "" "" "xiaowang"]
// 自动推到长度
var isBoy = [...]bool{true, false, true, false, true}
fmt.Println(isBoy, len(isBoy)) // [true false true false true] 5
}
关系运算
package main
import "fmt"
func main() {
nums1 := [5]int{5, 7}
nums2 := [5]int{5, 7}
nums3 := [5]int{5, 8}
fmt.Println(nums1 == nums2) // true
fmt.Println(nums1 == nums3) // false
//nums4 := [...]int{5, 8, 2}
//fmt.Println(nums1 == nums4) // Invalid operation: nums1 == nums4 (mismatched types [5]int and [3]int)
}
索引
数组的索引值:0,1,2,…,len(array)-1
range
获取数组的 index
和 value
,index
的值可以使用空白标识符 _
接受
package main
import "fmt"
func main() {
nums1 := [5]int{5, 7}
nums1[0] = 8
fmt.Println(nums1[0], nums1[len(nums1)-1]) // 8 0
}
package main
import "fmt"
func ExampleArray() {
nums1 := [5]int{5, 7}
for i := 0; i < len(nums1); i++ {
fmt.Println(nums1[i])
}
for index, num := range nums1 {
fmt.Println(index, num)
nums1[index] += 1
}
fmt.Println(nums1)
// Output:
//5
//7
//0
//0
//0
//0 5
//1 7
//2 0
//3 0
//4 0
//[6 8 1 1 1]
}
多维数组
多维数组不使用 ...
定义可变长度
// 定义长度为3,每个元素是长度为2的int数组
var mularray [3][2]int
fmt.Printf("%T, %v\n", mularray, mularray)
// Output:
//[3][2]int, [[0 0] [0 0] [0 0]]
mularray := [3][2]int{{1, 2}}
fmt.Printf("%T, %v\n", mularray, mularray)
// Output:
//[3][2]int, [[1 2] [0 0] [0 0]]
切片
切片是长度可变的数组,组成包括:
type slice struct {
array unsafe.Pointer // 底层数组的指针地址
len int // 切片的长度
cap int // 切片的容量
}
定义:
// 初始化
[]type{v1, v2, ...}
// 空切片
[]type{}
// 指定长度和容量
[]type{index_x:v1, index_y:v2, ...}
// make 函数初始化
make([]type, len)
make([]type, len, cap) // len<=cap
// 数组切片
array[start:end]
array[start:end:cap]
array[start:]
array[:end]
说明:
- start、end 是整数,且 start<=end && end <= len(array)-1
- 切片的类型是切片,如 int 数组的切片是数组
[]int
- 如果省略 start、end,值为整个数组的所有元素
- cap 切片的容量,且 cap <= len(array)-1
- nil 切片
[]int
VS 空切片 []int{}
- 相关函数:
- 获取长度:len()
- 获取容量:cap()
- 追加元素:append()
- 复制:copy()
- go 语言中,for range 取得的是集合中元素的复制,可以一边遍历,一边修改切片
切片声明后,初始化值为 nil,表示暂不存在切片
示例
package main
import "fmt"
func ExampleSlice() {
str := "abcdefg"
fmt.Printf("%T, %v\n", str[1:5], str[1:5])
nums := []int{1, 2, 3, 4, 5, 6, 7, 8}
fmt.Printf("%T, %T, %v\n", nums, nums[1:5], nums[1:5])
// Output:
//string, bcde
//[]int, []int, [2 3 4 5]
}
示例2
package main
import "fmt"
func ExampleSlice() {
// 声明切片
var nums []int
fmt.Printf("%T\n", nums)
fmt.Printf("%#v %d %d\n", nums, len(nums), len(nums))
fmt.Println(nums == nil) // 指针,指向 nil
// 声明数组
var ages = [3]int{1, 2, 3}
fmt.Printf("%T\n", ages)
fmt.Printf("%#v\n", ages)
//fmt.Println(ages == nil) // Cannot convert 'nil' to type '[3]int'
// 字面量
nums = []int{1, 2, 3}
fmt.Printf("%#v %d %d\n", nums, len(nums), len(nums))
nums = []int{1, 2, 3, 4}
fmt.Printf("%#v %d %d\n", nums, len(nums), len(nums))
// 数组切片赋值
array := [5]int{1, 2, 3, 4, 5}
nums = array[0:3]
fmt.Printf("%#v %d %d\n", nums, len(nums), len(nums))
// Output:
//[]int
//[]int(nil) 0 0
//true
//[3]int
//[3]int{1, 2, 3}
//[]int{1, 2, 3} 3 3
//[]int{1, 2, 3, 4} 4 4
//[]int{1, 2, 3} 3 3
}
make 初始化
package main
import "fmt"
func ExampleSlice() {
// make 函数,对指针进行初始化
//nums := make([]int, 3, 3)
nums := make([]int, 3)
fmt.Printf("%#v %d %d\n", nums, len(nums), cap(nums))
nums = make([]int, 3, 5)
fmt.Printf("%#v %d %d", nums, len(nums), cap(nums))
// Output:
//[]int{0, 0, 0} 3 3
//[]int{0, 0, 0} 3 5
}
append 操作
- append 操作容空间容量不足,会重新申请内存空间。示例:
x = append(x, y)
,重新赋值原因:
- append 追加的元素放到预留的空间,同时 len 增加 1
- 若预留空间使用完,则会重新申请一块更大的内存空间,原内存空间的数据会拷贝到新空间在执行 append 操作,参考
- 若 capacity <= 1024;内存空间增加到原来的
2倍
- 若 capacity > 1024;内存空间增加到原来的
1.25倍
~ 1.4倍
- 自动扩容也是切片比数组高级的最大特点
package main
import "fmt"
func ExampleSlice() {
// make 函数,对指针进行初始化
//nums := make([]int, 3, 3)
nums := make([]int, 3, 4)
fmt.Printf("%#v %d %d\n", nums, len(nums), cap(nums))
// 修改值
fmt.Println(nums[0])
nums[0] = 8
fmt.Println(nums)
// 增加元素
nums = append(nums, 6)
fmt.Printf("%#v %d %d\n", nums, len(nums), cap(nums))
nums = append(nums, 8) // append 后,cap 由 4 增长到 8,若切片放不下,重新申请空间,容量增长一般是之前的1倍,因此需要 = 重新赋值
fmt.Printf("%#v %d %d\n", nums, len(nums), cap(nums))
// 切片操作,获取的仍然是一个切片
fmt.Printf("%T %#v\n", nums[0:2], nums[0:2])
// cap 计算,新切片的容量为 new_cap - start = 4,不指定 cap 时,为 org_cap - start = 1,且 new_cap 不能比原来大
nums2 := nums[1:2:5]
fmt.Printf("%T %#v %d %d\n", nums2, nums2, len(nums2), cap(nums2))
nums2 = nums[1:2]
fmt.Printf("%T %#v %d %d\n", nums2, nums2, len(nums2), cap(nums2))
// Output:
//[]int{0, 0, 0} 3 4
//0
//[8 0 0]
//[]int{8, 0, 0, 6} 4 4
//[]int{8, 0, 0, 6, 8} 5 8
//[]int []int{8, 0}
//[]int []int{0} 1 4
//[]int []int{0} 1 7
}
slice 空间分配规则:
package main
import (
"fmt"
"testing"
)
func slice_expansion() {
s := make([]int, 0, 4)
oldCap := cap(s)
for i := 0; i < 10000; i++ {
s = append(s, i)
currentCap := cap(s)
if currentCap > oldCap {
fmt.Printf("capacity change from %d to %d, expend %.2f\n", oldCap, currentCap, float32(currentCap)/float32(oldCap))
oldCap = currentCap
}
}
}
func Testslice_expansion(t *testing.T) {
slice_expansion()
}
//capacity change from 4 to 8, expend 2.00
//capacity change from 8 to 16, expend 2.00
//capacity change from 16 to 32, expend 2.00
//capacity change from 32 to 64, expend 2.00
//capacity change from 64 to 128, expend 2.00
//capacity change from 128 to 256, expend 2.00
//capacity change from 256 to 512, expend 2.00
//capacity change from 512 to 1024, expend 2.00
//capacity change from 1024 to 1280, expend 1.25
//capacity change from 1280 to 1696, expend 1.33
//capacity change from 1696 to 2304, expend 1.36
//capacity change from 2304 to 3072, expend 1.33
//capacity change from 3072 to 4096, expend 1.33
//capacity change from 4096 to 5120, expend 1.25
//capacity change from 5120 to 7168, expend 1.40
//capacity change from 7168 to 9216, expend 1.29
//capacity change from 9216 to 12288, expend 1.33
复制
与两个数组的长度有关
package main
import "fmt"
func ExampleSlice() {
nums1 := []int{1, 2, 3}
nums2 := []int{10, 20, 30, 40}
copy(nums2, nums1) // 仅覆盖nums2的前3个元素
fmt.Println(nums1, nums2)
nums1 = []int{1, 2, 3}
nums2 = []int{10, 20, 30, 40}
copy(nums1, nums2) // 仅覆盖nums1的前3个元素
fmt.Println(nums1, nums2)
// Output:
//[1 2 3] [1 2 3 40]
//[10 20 30] [10 20 30 40]
}
切片元素的删除
package main
import "fmt"
func ExampleSlice() {
nums := []int{10, 20, 30, 40}
fmt.Println(nums[1:])
fmt.Println(nums[:len(nums)-1])
// 删除第3个元素 30
// 通过可变参数,num[3:]... 表示 unpack slice nums[3:]
fmt.Println(append(nums[:2], nums[3:]...))
// 通过 copy
copy(nums[2:], nums[3:])
fmt.Println(nums)
fmt.Println(nums[:len(nums)-1])
// Output:
//[20 30 40]
//[10 20 30]
//[10 20 40]
//[10 20 40 40]
//[10 20 40]
}
切片共用地址导致的问题
切片截取时:
- 子切片和父切片共享底层的内存空间,修改子切片会反映到母切片上
- 如在子切片上执行 append 会把新元素追加到母切片预留的内存空间上,当 append 到父切片 capacity 使用完后,子切片重新分配内存空间,与父切片指向的地址分离
package main
import "fmt"
func ExampleSlice() {
// 共用地址导致的问题
nums := make([]int, 3, 5)
fmt.Printf("%#v %d %d\n", nums, len(nums), cap(nums))
nums1 := nums[1:3]
fmt.Println(nums, nums1)
nums1[0] = 8
fmt.Println(nums, nums1)
nums1 = append(nums1, 6)
fmt.Println(nums, nums1)
nums = append(nums, 1)
fmt.Println(nums, nums1)
// Output:
//[]int{0, 0, 0} 3 5
//[0 0 0] [0 0]
//[0 8 0] [8 0]
//[0 8 0] [8 0 6]
//[0 8 0 1] [8 0 1]
}
package main
import (
"fmt"
"testing"
)
func SubSlice() {
s := make([]int, 3, 5)
for i := 0; i < 3; i++ {
s[i] = i + 1
}
fmt.Printf("s[1] addr is %p\n", &s[1])
subSlice := s[1:3]
fmt.Printf("sub_slice len %d, cap %d\n", len(subSlice), cap(subSlice))
subSlice = append(subSlice, 6, 7)
subSlice[0] = 8
fmt.Printf("s=%v, subSlice=%v, s[1] addr is %p, subSlice[0] addr is %p\n", s, subSlice, &s[1], &subSlice)
subSlice = append(subSlice, 8)
subSlice[0] = 9
fmt.Printf("s=%v, subSlice=%v, s[1] addr is %p, subSlice[0] addr is %p\n", s, subSlice, &s[1], &subSlice)
}
func TestT(t *testing.T) {
SubSlice()
}
//s[1] addr is 0xc0000a00f8
//sub_slice len 2, cap 4
//s=[1 8 3], subSlice=[8 3 6 7], s[1] addr is 0xc0000a00f8, subSlice[0] addr is 0xc0000b2060
//s=[1 8 3], subSlice=[9 3 6 7 8], s[1] addr is 0xc0000a00f8, subSlice[0] addr is 0xc0000b2060
返回子切片导致内存泄漏
func subslice1() []int {
s := make([]int, 10000) // 长度为 10000 的数组
sub := s[2:4] // 只有 sub 没有被回收,10000 的内存空间不释放
return sub
}
func subslice1() []int {
s := make([]int, 10000) // 长度为 10000 的数组
sub := make([]int, 2) // 重新分配内存空间存 sub slice,才能释放 10000 的内存
for i:=2; i<4; i++ {
sub[i-2] = s[i]
}
return sub
}
队列
先进先出(在队列尾添加,在队列头部移除)
package main
import "fmt"
func ExampleQueue() {
queues := []int{}
// 入队列
queues = append(queues, 1)
queues = append(queues, 2)
queues = append(queues, 3)
fmt.Println(queues)
// 出队列
queues = queues[1:]
fmt.Println(queues)
// Output:
//[1 2 3]
//[2 3]
}
堆栈
先进后出(在队列头添加,在队列头部移除)
package main
import "fmt"
func ExampleStack() {
stacks := []int{}
// 入堆栈
stacks = append(stacks, 1)
stacks = append(stacks, 2)
stacks = append(stacks, 3)
fmt.Println(stacks)
// 出堆栈
stacks = stacks[:len(stacks)-1]
fmt.Println(stacks)
// Output:
//[1 2 3]
//[1 2]
}
多维切片
package main
import "fmt"
func ExampleManyStack() {
s1 := [][]int{}
s2 := make([][]int, 3)
fmt.Printf("%T\n", s2)
s1 = append(s1, []int{1, 2, 3})
s1 = append(s1, []int{6, 7, 8, 9})
fmt.Println(s1)
fmt.Println(s1[1][3])
// Output:
//[][]int
//[[1 2 3] [6 7 8 9]]
//9
}
数组 VS 切片
Golang 中所有赋值都是值传递
package main
import "fmt"
func ExampleArrayVsStack() {
// 切片赋值修改的是指针指向同一块内存空间,修改时同时发生改变
s1 := []int{1, 2, 3}
s2 := s1
s2[0] = 6
fmt.Println(s1, s2)
// 数组赋值时,会新开辟一段地址空间,修改值互不影响
a1 := [3]int{1, 2, 3}
a2 := a1
a2[0] = 6
fmt.Println(a1, a2)
// Output:
//[6 2 3] [6 2 3]
//[1 2 3] [6 2 3]
}
- 数组空间是预先分配的,不可变。切片是可以自增的
- 相同长度,数据因没有空间分配时间,速度更快
package main
import (
"fmt"
"testing"
"time"
)
func SliceTime() {
begin := time.Now()
arr := []int{}
for i := 0; i < 10000; i++ {
arr = append(arr, i)
}
fmt.Printf("slice append to %d use time %d ms\n", len(arr), time.Since(begin).Microseconds())
}
func ArrayTime() {
begin := time.Now()
arr := make([]int, 0, 10000)
for i := 0; i < 10000; i++ {
arr = append(arr, i)
}
fmt.Printf("array append to %d use time %d ms\n", len(arr), time.Since(begin).Microseconds())
}
func TestTime(t *testing.T) {
SliceTime()
ArrayTime()
}
//slice append to 10000 use time 208 ms
//array append to 10000 use time 54 ms
排序
sort
- sort 模块支持对切片和用户定义的集合进行排序。
package main
import (
"fmt"
"sort"
)
func ExampleSliceSort() {
s1 := []int{3, 1, 2}
sort.Ints(s1)
fmt.Println(s1)
str := []string{"ab", "sd", "321"}
sort.Strings(str)
fmt.Println(str)
// Output:
//[1 2 3]
//[321 ab sd]
}
container/heap
// https://pkg.go.dev/container/heap#pkg-examples
// This example demonstrates an integer heap built using the heap interface.
package main
import (
"container/heap"
"fmt"
)
// An IntHeap is a min-heap of ints.
type IntHeap []int
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x any) {
// Push and Pop use pointer receivers because they modify the slice's length,
// not just its contents.
*h = append(*h, x.(int))
}
func (h *IntHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
// This example inserts several ints into an IntHeap, checks the minimum,
// and removes them in order of priority.
func main() {
h := &IntHeap{2, 1, 5}
heap.Init(h)
heap.Push(h, 3)
fmt.Printf("minimum: %d\n", (*h)[0])
for h.Len() > 0 {
fmt.Printf("%d ", heap.Pop(h))
}
}