数组 array
与其他动态类型的数组不一样的是:
- 数组一开始就必须确定长度且不能更改
- 一个数组只能存储一种类型的数据
- 不能使用变量指定数组长度
定义数组
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 位置
- 这个截取的语法与 python 的一模一样
- 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 函数创建了一个切片, 那么什么是切片呢?
什么是切片?
可以简单的理解为: 一种动态长度的特殊数组
定义切片
- 使用定义数组的语法(不指定长度)来定义切片
- 使用定义数组的语法(不指定长度)并且直接赋值来定义切片
- 使用 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")
}