Skip to content

数组 array

与其他动态类型的数组不一样的是:

  1. 数组一开始就必须确定长度且不能更改
  2. 一个数组只能存储一种类型的数据
  3. 不能使用变量指定数组长度

定义数组

go
package main

import "fmt"

func main() {
	// 定义一个空数组
	var scores [5]int

	// 通过索引赋值
	scores[0] = 66
	scores[1] = 77

	// 定义一个数组, 并直接赋值
	nums := [5]int{11, 2, 33, 4, 55}
	fmt.Printf("nums data type: %T\n", nums) // nums data type: [5]int

	// 不能这样定义数组, 无法通过编译, 因为长度必须是固定的
	// arrLen := 5
	// nums2 := [arrLen]int{11, 2, 33, 4, 55}
	// fmt.Printf("nums2 data type: %T\n", nums2)
	// 错误信息如下:
	// ./main.go:7:12: invalid array length arrLen

	// 但是可以这样定义数组, 可以通过编译, 因为是长度常量
	const arrLen int = 5
	nums3 := [arrLen]int{11, 22, 33, 44, 55}
	fmt.Printf("nums3 data type: %T\n", nums3) // nums3 data type: [5]int

	nums3[0] = 111 // 可以正常赋值, 因为 111 是 int 类型
	// nums3[1] = 2.2 // 不能赋值, 因为 2.2 不是 int 类型
	// 错误信息如下:
	// # command-line-arguments
	// ./main.go:30:13: cannot use 2.2 (untyped float constant) as int value in assignment (truncated)
}

数组使用技巧

go
package main

import "fmt"

func main() {
	nums := [5]int{11, 2, 33, 4, 55}

	// 0.通过索引获取值
	fmt.Println("nums[0]:", nums[0]) // 11

	// 1.通过索引赋值
	nums[1] = 100
	fmt.Println("nums[1]:", nums[1]) // 100

	// 2.获取数组的长度(元素个数)
	fmt.Println("nums length:", len(nums)) // 5

	// 3.获取数组的容量
	fmt.Println("nums capacity:", cap(nums)) // 5
}

遍历数组

go
package main

import "fmt"

func main() {
	nums := [5]int{11, 2, 33, 4, 55}

	// 使用 for 语法
	for i := 0; i < len(nums); i++ {
		fmt.Printf("index:%d, value:%d \n", i, nums[i])
	}

  // 使用 range 语法(推荐)
	for index, value := range nums {
		fmt.Printf("index:%d, value:%d \n", index, value)
	}
}

切分数组

切割数组的格式为 arr[startIndex:endIndex],切割的区间为 左闭右开, 所谓的 左闭右开 意思是 arr[startIndex:endIndex] 包括 startIndex 位置, 但是不包括 endIndex 位置

  1. 这个截取的语法与 python 的一模一样
  2. startIndex 和 endIndex 都是可以省略的

截取数组语法

go
package main

import "fmt"

func main() {
	nums := [5]int{11, 2, 33, 4, 55}

	// 从 1 的位置开始截取到 3-1 的位置
	copiedNums1 := nums[1:3]
	fmt.Println("slicedNums:", copiedNums1) // [2,33]

	// 从 1 的位置开始截取到最后
	copiedNums2 := nums[1:]
	fmt.Println("slicedNums:", copiedNums2) // [2,33,4,55]

	// 从 0 的位置开始截取到最后
	copiedNums3 := nums[:]
	fmt.Println("slicedNums:", copiedNums3) // [11,2,33,4,55]
}

截取数组的细节

go
package main

import "fmt"

func main() {
	nums := [5]int{11, 2, 33, 4, 55}

	// 截取是返回一个新的数组, 不会影响原来的数组
	arr2 := nums[1:3]
	fmt.Println("nums:", nums) // [11,2,33,4,55]
	fmt.Println("arr2:", arr2) // [2,33]

	// 被截取的元素如果是: 整形/浮点型/字符型/布尔型
	// 则会直接复制一份新的, 是值传递
	fmt.Printf("nums: %p \n", &nums[0]) // 0xc000124000
	fmt.Printf("arr2: %p \n", &arr2[0]) // 0xc000124030
	arr2[0] = 111

	// 修改 arr2, nums 并没有受到影响
	fmt.Println("nums:", nums) // [11,2,33,4,55]
	fmt.Println("arr2:", arr2) // [111,33]
	fmt.Println("---------------")

	// 被截取的元素如果是: 字符串型
	// 则会直接传递内存地址, 是引用传递
	strs := [2]string{"hello", "world"}
	str2 := strs[:]
	fmt.Printf("strs: %p \n", &strs[0]) // 0xc0001a2060
	fmt.Printf("str2: %p \n", &str2[0]) // 0xc0001a2060

	// 修改 str2, strs 的元素也会被更新
	strs[0] = "hi"
	fmt.Println("strs:", strs) // [hi world]
	fmt.Println("str2:", str2) // [hi world]
}

二维数组

go
package main

import "fmt"

func main() {
	var students [3][4]string
	students[0] = [4]string{"Judy", "18", "stu-001", "class-001"}
	students[1] = [4]string{"Jack", "19", "stu-002", "class-002"}
	students[2] = [4]string{"Lily", "20", "stu-003", "class-003"}

	fmt.Println(students)
	// [[Judy 18 stu-001 class-001] [Jack 19 stu-002 class-002] [Lily 20 stu-003 class-003]]
}

练习:输出杨辉三角

go
package main

import "fmt"

const TriangleSize = 5

func main() {
	// build datas
	var arr [TriangleSize][]int
	for i := range len(arr) {
		row := make([]int, i+1) // 手动创建切片
		row[0] = 1
		row[i] = 1
		arr[i] = row

		for j := 1; j < len(row)-1; j++ {
			// prevRow     : 当前行的上一行
			// prevRow[j-1]: 上一行的当前位置的前一个位置的值
			// prevRow[j]  : 上一行的当前位置的值
			prevRow := arr[i-1]
			row[j] = prevRow[j-1] + prevRow[j]
		}
	}

	// render
	for i, row := range arr {
		for j := 0; j < (TriangleSize-(i+1))*2; j++ {
			fmt.Printf(" ")
		}

		for _, col := range row {
			fmt.Printf("%d   ", col)
		}

		fmt.Println()
	}
}

切片 slice

在上面的练习中, 使用 make 函数创建了一个切片, 那么什么是切片呢?

什么是切片?

可以简单的理解为: 一种动态长度的特殊数组

定义切片

  1. 使用定义数组的语法(不指定长度)来定义切片
  2. 使用定义数组的语法(不指定长度)并且直接赋值来定义切片
  3. 使用 make 方法定义指定长度的切片
go
package main

import "fmt"

func main() {
	// 1.使用 []int 不设置数组长度的方式
	// 在声明切片时: 一定不能设置长度, 一旦设置
	// 长度就会被编译器识别为数组, 而不是切片
	var nums []int

	// 1.1 不设置长度, 就不会分配存储空间, 直接赋值会报错
	// panic: runtime error: index out of range [0] with length 0
	// nums[0] = 1

	// 1.2 想要给切片动态分配存储空间, 需要使用 append 方法
	// 根据第一个参数的类型, 分配所需要的内存空间
	// 后续的参数是需要存储的值, 可以是多个: append(nums, 1, 2, 3)
	// 返回值是新的切片, 并不会改变原来的切片
	nums2 := append(nums, 1)
	fmt.Printf("类型:%T, 值:%v \n", nums, nums)   // 类型: []int, 值:[]
	fmt.Printf("类型:%T, 值:%v \n", nums2, nums2) // 类型: []int, 值:[1]
}
go
package main

import "fmt"

func main() {
	// 2. 直接定义切片并赋值(注意不能指定长度,否则就会被识别为数组)
	nums := []int{1, 2, 3}
	fmt.Printf("type:%T, value:%v \n", nums, nums)        // type: []int, value:[1,2,3]
	fmt.Printf("len:%v, cap:%v \n", len(nums), cap(nums)) // len:3, cap:3
}
go
package main

import "fmt"

func main() {
	// 3.使用 make 内置函数定义指定长度的切片
	// 第一个参数是切片的类型, 第二个参数是长度
	chars := make([]byte, 2)
	chars[0] = 'h'
	chars[1] = 'i'

	// 3.1 元素超出 make 参数指定的长度就会报错
	// chars[2] = '!'
	// panic: runtime error: index out of range [2] with length 2
  // 3.2 虽然指定了长度, 但是可以使用 append 扩容切片
	chars = append(chars, '!')

	fmt.Printf("类型:%T, 值:%v \n", chars, chars) // 类型: []uint8, 值: [104 105]
}

读写元素

go
package main

import "fmt"

func main() {
	students := []string{"张三", "李四", "王五"}

	// 访问某个元素
	fmt.Println("第一个元素:", students[0])

	// 修改某个元素的值
	students[0] = "张3"
	fmt.Println("修改后的第一个元素:", students[0])

	// 访问整个切片(变量)
	fmt.Println("切片:", students)

	// 修改整个切片(变量)
	students = []string{}
	fmt.Println("切片:", students)
}

遍历元素

go
package main

import "fmt"

func main() {
	students := []string{"张三", "李四", "王五"}

	// 遍历: 依次访问切片的的所有元素
	for index, student := range students {
		fmt.Printf("index:%v, student:%v \n", index, student)
	}
}

截取元素

切片的截取和数组的截取一样: [startIndex:endIndex] 都是 左闭右开

go
package main

import "fmt"

func main() {
	nums := []int{11, 2, 33, 4, 55}

	// 从 1 的位置开始截取到 3-1 的位置
	copiedNums1 := nums[1:3]
	fmt.Println("slicedNums:", copiedNums1) // [2,33]

	// 从 1 的位置开始截取到最后
	copiedNums2 := nums[1:]
	fmt.Println("slicedNums:", copiedNums2) // [2,33,4,55]

	// 从 0 的位置开始截取到最后
	copiedNums3 := nums[:]
	fmt.Println("slicedNums:", copiedNums3) // [11,2,33,4,55]
}

追加元素

前面已经用到过了: 使用内置的 append 方法

go
package main

import "fmt"

func main() {
	nums := []int{1, 2, 3}

	// 直接追加元素列表
	nums = append(nums, 4, 5)
	fmt.Println(nums) // [1,2,3,4,5]

	// 追加另外一个切片中的所有元素(类似 JS 的扩展操作符)
	// 这个操作可以用来合并两个切片
	oddNums := []int{1, 3, 5, 7, 9}
	merged := append(nums, oddNums...)
	fmt.Println(merged) // [1,2,3,4,5,1,3,5,7,9]
}

删除元素

在go语言中, 没有类似 remove 之类的方法, 需要手动用 append 来模拟

go
package main

import "fmt"

func main() {
	// 删除第一个元素, 类似js的 array.shift()
	students := []string{"张三", "李四", "王五", "赵六", "钱七"}
	students = students[1:]
	fmt.Println("删除第一个元素后:", students)

	// 删除最后一个元素, 类似 js 的 array.pop()
	students = []string{"张三", "李四", "王五", "赵六", "钱七"}
	students = students[:len(students)-1]
	fmt.Println("删除最后一个元素后:", students)

	// 删除指定位置的元素, 类似 js 的 splice 方法
	students = []string{"张三", "李四", "王五", "赵六", "钱七"}
	students = append(students[:2], students[4:]...) // 删除2个
	fmt.Println("删除指定位置的元素后:", students)
}
go
package main

import "fmt"

// 移除切片的第一个元素并返回剩余部分
func sliceShift[T any](items []T) []T {
	if len(items) == 0 {
		return items
	}
	return items[1:]
}

// 移除切片的最后一个元素并返回剩余部分
func slicePop[T any](items []T) []T {
	if len(items) == 0 {
		return items
	}
	return items[:len(items)-1]
}

// 从切片中移除指定位置的元素
func sliceSplice[T any](items []T, start, deleteCount int) []T {
	// 边界检查
	if len(items) == 0 || start < 0 || start >= len(items) || deleteCount <= 0 {
		return items
	}

	// 确保不会超出切片边界
	if end := start + deleteCount; end > len(items) {
		deleteCount = len(items) - start
	}

	return append(items[:start], items[start+deleteCount:]...)
}

func main() {
	students = []string{"张三", "李四", "王五", "赵六", "钱七"}
	students = sliceShift(students)
	fmt.Println("删除第一个元素后:", students)

	students = []string{"张三", "李四", "王五", "赵六", "钱七"}
	students = slicePop(students)
	fmt.Println("删除最后一个元素后:", students)

	students = []string{"张三", "李四", "王五", "赵六", "钱七"}
	students = sliceSplice(students, 2, 2)
	fmt.Println("删除指定位置的元素后:", students)
}

复制元素

引用赋值

在其他语言中可能叫 浅拷贝

go
package main

import "fmt"

func main() {
	students := []string{"张三", "李四", "王五", "赵六"}
	studentList := students

	// 引用赋值: 两个变量是同一个内存地址
	fmt.Printf("students   : %p\n", students)    // 0xc0000a8000
	fmt.Printf("studentList: %p\n", studentList) // 0xc0000a8000

	// 修改其中一个变量的元素, 另外一个变量也会跟着修改
	studentList[0] = "张3"
	fmt.Printf("students   : %v\n", students)    // [张3, 李四, 王五, 赵六]
	fmt.Printf("studentList: %v\n", studentList) // [张3, 李四, 王五, 赵六]
}

完全复制

在其他语言中可能叫 深拷贝

go
package main

import "fmt"

func main() {
	students := []string{"张三", "李四", "王五", "赵六"}
	studentList := make([]string, len(students))

	// 这两个变量肯定是不同的内存地址
	fmt.Printf("students   : %p\n", students)    // 0xc000092040
	fmt.Printf("studentList: %p\n", studentList) // 0xc000092080

	// count := copy(dst, src): 这个内置方法可以复制一个切片中的元素到另外一个切片
	// dst  : 复制后放到哪个切片变量中
	// src  : 被复制的切片变量
	// count: 复制元素的个数
	count := copy(studentList, students)
	fmt.Println("count:", count)

	fmt.Println("===== 复制后 =====")
	fmt.Printf("students   : %v \n", students)    // [张三,李四,王五,赵六]
	fmt.Printf("studentList: %v \n", studentList) // [张三,李四,王五,赵六]

	// 由于是完全复制, 所以: 两个变量不会互相影响
	students[0] = "张3"
	fmt.Println("===== 修改后 =====")
	fmt.Printf("students   : %v \n", students)    // [张3,李四,王五,赵六]
	fmt.Printf("studentList: %v \n", studentList) // [张三,李四,王五,赵六]
}

copy 函数的注意点

copy 方法不会自动的扩充容量

go
package main

import "fmt"

func main() {
	students := []string{"张三", "李四", "王五", "赵六"}
	studentList := make([]string, len(students)-1)

	// count := copy(dst, src): 这个内置方法可以复制一个切片中的元素到另外一个切片
	// dst  : 复制后放到哪个切片变量中
	// src  : 被复制的切片变量
	// count: 复制元素的个数
	// dst 有多少容量, 就从 src 中复制多少个, 不会报错
	count := copy(studentList, students)
	fmt.Println("count:", count) // 3

	fmt.Println("===== 复制后 =====")
	fmt.Printf("students   : %v \n", students)    // [张三,李四,王五,赵六]
	fmt.Printf("studentList: %v \n", studentList) // [张三,李四,王五]
}

切片常用方法封装

go
import (
	"errors"
)

type SliceWrapper[T any] struct {
	items []T
}

func (sw *SliceWrapper[T]) Size() int {
	return len(sw.items)
}

func (sw *SliceWrapper[T]) IsEmpty() bool {
	return len(sw.items) == 0
}

func (sw *SliceWrapper[T]) GetData() []T {
	return sw.items
}

func (sw *SliceWrapper[T]) Get(index int) T {
	return sw.items[index]
}

func (sw *SliceWrapper[T]) Set(index int, value T) bool {
	sw.items[index] = value
	return true
}

func (sw *SliceWrapper[T]) First(item T) T {
	return sw.items[0]
}

func (sw *SliceWrapper[T]) Last(item T) T {
	return sw.items[sw.Size()-1]
}

func (sw *SliceWrapper[T]) PushFront(item T) {
	sw.items = append([]T{item}, sw.items...)
}

func (sw *SliceWrapper[T]) PushBack(item T) {
	sw.items = append(sw.items, item)
}

func (sw *SliceWrapper[T]) Slice(startIndex int, endIndex int) []T {
	return sw.items[startIndex:endIndex]
}

func (sw *SliceWrapper[T]) Insert(index int, item T) {
	size := sw.Size()
	leftItems := sw.Slice(0, index)
	rightItems := sw.Slice(index, size)
	newItems := make([]T, size)

	// left
	i := 0
	for ; i < len(leftItems); i++ {
		newItems[i] = leftItems[i]
	}

	// current
	newItems[i] = item
	i++

	// right
	for ; i < len(rightItems); i++ {
		newItems[i] = rightItems[i]
	}

	sw.items = newItems
}

func (sw *SliceWrapper[T]) Remove(index int) T {
	element := sw.items[index]
	sw.items = append(sw.items[:index], sw.items[index+1:]...)
	return element
}

func (sw *SliceWrapper[T]) PopFirst() T {
	return sw.Remove(0)
}

func (sw *SliceWrapper[T]) PopLast() T {
	lastIndex := sw.Size() - 1
	return sw.Remove(lastIndex)
}

func (sw *SliceWrapper[T]) ForEach(handler func(v T, i int, s []T)) {
	for i, v := range sw.items {
		handler(v, i, sw.items)
	}
}

func (sw *SliceWrapper[T]) Map(mapper func(v T, i int, s []T) T) []T {
	result := make([]T, sw.Size())
	for i, v := range sw.items {
		result[i] = mapper(v, i, sw.items)
	}
	return result
}

func (sw *SliceWrapper[T]) Filter(filter func(v T, i int, s []T) bool) []T {
	result := make([]T, 0)
	for i, v := range sw.items {
		if filter(v, i, sw.items) {
			result = append(result, v)
		}
	}
	return result
}

func (sw *SliceWrapper[T]) Reduce(reducer func(accumulator T, v T, i int, s []T) T, initialValue T) T {
	accumulator := initialValue
	for i, v := range sw.items {
		accumulator = reducer(accumulator, v, i, sw.items)
	}
	return accumulator
}

func (sw *SliceWrapper[T]) Some(handler func(v T, i int, s []T) bool) bool {
	for i, v := range sw.items {
		if handler(v, i, sw.items) {
			return true
		}
	}
	return false
}

func (sw *SliceWrapper[T]) Every(handler func(v T, i int, s []T) bool) bool {
	for i, v := range sw.items {
		if !handler(v, i, sw.items) {
			return false
		}
	}
	return true
}

func (sw *SliceWrapper[T]) Find(handler func(v T, i int, s []T) bool) (T, error) {
	for i, v := range sw.items {
		if handler(v, i, sw.items) {
			return v, nil
		}
	}

	var zero T
	return zero, errors.New("not found")
}

Released under the MIT License.