数组-固定长度的集合
长度是类型的一部分:
[5]int是一个与[3]int完全不同的类型。你不能将一个长度为5的数组赋值给一个长度为3的数组变量。值类型:当你在函数间传递数组时,Go会复制整个数组。这与Python的
list(传递的是引用)有本质区别。这意味着在函数内修改数组不会影响函数外的原始数组。
package main
import "fmt"
func main() {
// 1. 声明并初始化一个数组
var a [4]int // 声明一个包含4个整数的数组,元素自动初始化为0
fmt.Println(a) // 输出: [0 0 0 0]
// 2. 使用字面量初始化
b := [4]int{1, 2, 3, 4}
fmt.Println(b) // 输出: [1 2 3 4]
// 3. 让编译器计算长度
c := [...]string{"Go", "Python", "Java"}
fmt.Println(c) // 输出: [Go Python Java]
fmt.Printf("c的类型是: %T, 长度是: %d\n", c, len(c)) // 输出: c的类型是: [3]string, 长度是: 3
// 4. 访问和修改元素
b[0] = 10
fmt.Println(b) // 输出: [10 2 3 4]
// 5. 数组是值类型(演示)
modifyArray(a)
fmt.Println("main函数中的a:", a) // 输出: main函数中的a: [0 0 0 0] (未被改变)
}
func modifyArray(arr [4]int) {
arr[0] = 99
fmt.Println("modifyArray函数中的arr:", arr) // 输出: modifyArray函数中的arr: [99 0 0 0]
}
数组在Go中不常用,因为其固定长度和复制成本太高。它更多是作为切片的底层基础而存在。理解数组的工作原理,是理解切片的关键。
切片-动态、灵活的视图
切片是Go中最常用的数据结构。它的设计非常巧妙,既提供了动态数组的便利性,又通过共享底层数组实现了高效的内存操作。记住,append 可能会返回一个新的切片,所以总是要用 s = append(s, ...) 的形式。
核心特点:
引用类型:切片本身只是一个包含了三个信息的数据结构:指向底层数组的指针、长度 和 容量。当传递切片时,实际上是在复制这个小小的“头部信息”,所以多个切片可以共享同一个底层数组,修改一个会影响其他。
动态长度:你可以使用
append函数轻松地为切片添加元素,它会自动处理底层数组的扩容。长度 vs. 容量:这是理解切片的关键!
长度:切片当前包含的元素个数。用
len(s)获取。容量:从切片的起始元素到底层数组的最后一个元素的总个数。用
cap(s)获取。append操作在容量不足时会触发扩容,创建一个新的、更大的底层数组。
package main
import "fmt"
func main() {
// 1. 从数组创建切片
arr := [5]int{10, 20, 30, 40, 50}
var s []int = arr[1:4] // 创建一个从arr[1]到arr[3]的切片
fmt.Println(s) // 输出: [20 30 40]
fmt.Printf("len=%d, cap=%d\n", len(s), cap(s)) // 输出: len=3, cap=4 (容量是到arr末尾)
// 2. 直接声明和初始化切片
s1 := []int{1, 2, 3} // 注意没有 [...]
fmt.Println(s1) // 输出: [1 2 3]
// 3. 使用 make 创建切片 (指定长度和容量)
// make([]T, length, capacity)
s2 := make([]int, 3, 5) // 创建一个长度为3,容量为5的int切片,元素初始化为0
fmt.Println(s2) // 输出: [0 0 0]
fmt.Printf("len=%d, cap=%d\n", len(s2), cap(s2)) // 输出: len=3, cap=5
// 4. append 操作
s2 = append(s2, 100) // 添加一个元素
fmt.Println(s2) // 输出: [0 0 0 100]
fmt.Printf("len=%d, cap=%d\n", len(s2), cap(s2)) // 输出: len=4, cap=5
s2 = append(s2, 200, 300) // 添加多个元素,容量会不足
fmt.Println(s2) // 输出: [0 0 0 100 200 300]
fmt.Printf("len=%d, cap=%d\n", len(s2), cap(s2)) // 输出: len=6, cap=10 (容量自动翻倍扩容)
}
Map - 键值对的无序集合
Map是Go中进行查找和关联操作的首选数据结构。它的使用非常直观,但要时刻记住访问不存在的键会返回零值,所以在需要区分“零值”和“不存在”时,一定要用 value, ok := myMap[key] 这种地道的写法。
核心特点:
引用类型:和切片一样,传递的是引用。
无序:Map中的元素没有固定的顺序。(注意:在Go 1.0之后,每次运行程序,map的迭代顺序都是随机的,这是为了防止程序依赖特定的顺序)。
键的类型:map的键必须是可比较的类型(例如,数字、字符串、指针、数组),不能是切片、map或函数。
安全的访问方式:访问一个不存在的键时,会返回该值类型的“零值”(例如,
int返回0,string返回"")。Go提供了一个特殊的“逗号ok”语法来判断一个键是否真正存在
package main
import "fmt"
func main() {
// 1. 声明和初始化
var m map[string]int // 声明一个map,此时是nil
fmt.Println(m == nil) // 输出: true
// 使用 make 创建一个可用的map
m = make(map[string]int)
m["apple"] = 5
m["banana"] = 3
fmt.Println(m) // 输出: map[apple:5 banana:3]
// 2. 使用字面量初始化
ageMap := map[string]int{
"Alice": 25,
"Bob": 30,
}
fmt.Println(ageMap)
// 3. 访问元素
fmt.Println("Alice's age:", ageMap["Alice"]) // 输出: Alice's age: 25
// 4. “逗号ok”判断键是否存在
// 如果键不存在,value会得到int的零值0,ok为false
value, ok := ageMap["Charlie"]
if ok {
fmt.Println("Charlie's age is", value)
} else {
fmt.Println("Charlie not found") // 输出: Charlie not found
}
// 5. 删除元素
delete(ageMap, "Bob")
fmt.Println(ageMap) // 输出: map[Alice:25]
}