Go 数组与切片

发布时间: 更新时间: 总字数:3318 阅读时间:7m 作者: IP上海 分享 网址

Golang数组与切片使用介绍,通过示例演示Golang语言中如何使用数组、切片,以及介绍数组与切片异同,最后介绍排序模块。

数组

数组是具有相同数据类型的数据项,组成的长度固定的序列,数据项叫做数组的元素,数组的长度必须为非负整数常量,数组长度是类型的一部分。

声明

声明数组时需要指定数组元素的类型和数量,数组声明后长度不可修改,每个数据项的初始值为对应数据类型的零值。 格式:

var <变量名> [长度]<类型>

// 短声明
<变量名> := [长度]<类型>{}

说明:

  • 长度可以使用 ... 自动计算数组长度

  • 获取数组的长度:len(<数组变量名>)

  • var x []int 其中 x == nil 为真,可以直接 append 元素到数组

    • nil 切片是有效的长度为 0 的切片
  • 示例

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 获取数组的 indexvalueindex 的值可以使用空白标识符 _ 接受

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]]

切片

切片是长度可变的数组,组成包括:

  • 指针:指向切片第一个元素指向的数组数组元素的地址

  • 长度:切片元素的数量,可使用方法 len() 获取

  • 容量:切片开始到结束位置元素的数量,可使用方法 cap() 获取

  • 定义源码

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

  • 参考 container/heap,通过实现 heap.Interface 接口,使用 tree 实现优先级队列
// 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))
	}
}
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数