Go学习笔记:复杂数据类型——数组、切片、Map、结构体与指针
本文首发于 栏轩·阁欢迎访问阅读原文获取更好的阅读体验。一、数组Array— 定长数组是固定长度的一旦声明就不能改变长度。// 声明var 变量名 [长度]类型varnums[3]intnums[0]10nums[1]20nums[2]30// 声明 初始化nums:[3]int{10,20,30}// 让 Go 自己数长度nums:[...]int{10,20,30}// 自动推断长度为 3// 数组长度是类型的一部分nums1:[3]int{1,2,3}nums2:[5]int{1,2,3,4,5}// nums1 nums2 ← ❌ 编译错误长度不同类型不同数组的局限长度固定不够灵活。实际开发中很少直接用数组更常用的是切片。二、切片Slice— 动态数组 ✅ 最常用切片是 Go最常用的数据结构类似于 Python 的 list 或 Java 的 ArrayList。2.1 创建切片// 方式1字面量创建names:[]string{Go,Python,Rust}// 方式2make 创建nums:make([]int,3)// [0, 0, 0]长度 3nums:make([]int,3,5)// 长度 3容量 5// 方式3从数组切取arr:[5]int{1,2,3,4,5}slice:arr[1:4]// [2, 3, 4] 左闭右开2.2 切片的增删改查// 追加元素nums:[]int{1,2,3}numsappend(nums,4)// [1, 2, 3, 4]numsappend(nums,5,6)// [1, 2, 3, 4, 5, 6]// 合并两个切片numsappend(nums,[]int{7,8}...)// ... 把切片展开// 获取长度和容量fmt.Println(len(nums))// 8实际元素个数fmt.Println(cap(nums))// 底层数组容量自动扩容// 遍历fori,v:rangenums{fmt.Printf(索引 %d %d\n,i,v)}2.3 长度 vs 容量nums:make([]int,3,5)// 长度 3容量 5// nums [0, 0, 0, _, _]// ↑ 长度 ↑ 容量底层数组总大小fmt.Println(len(nums))// 3fmt.Println(cap(nums))// 5numsappend(nums,1)// 还有容量不会重新分配numsappend(nums,2)// [0, 0, 0, 1, 2]numsappend(nums,3)// 容量不够了 → 自动扩容为 2 倍len表示能访问多少个元素cap表示底层数组能放多少个。追加元素时如果没超过 cap 就不会重新分配内存效率更高。三、Map — 键值对类似于其他语言的字典 / HashMap。// 创建 map// map[键类型]值类型scores:make(map[string]int)scores[张三]90scores[李四]85// 创建 初始化scores:map[string]int{张三:90,李四:85,王五:92,}// 取值安全方式带 ok 判断是否存在score,ok:scores[赵六]ifok{fmt.Println(分数:,score)}else{fmt.Println(此人不存在)}// 删除delete(scores,李四)// 遍历注意map 遍历顺序不固定forname,score:rangescores{fmt.Printf(%s: %d\n,name,score)}⚠️ 遍历 Map 时顺序不固定每次运行可能都不一样。如果需要固定顺序先把 key 取出来排序再遍历。四、结构体struct— 自定义类型4.1 定义和使用// 定义结构体typeUserstruct{NamestringAgeintEmailstring}// 使用u1:User{pc,25,pcexample.com}// 按字段顺序不推荐u2:User{Name:pc,Age:25,Email:pcexample.com}// 指定字段名推荐 ✅u3:User{Name:pc}// 只赋值部分字段其余为零值// 访问字段fmt.Println(u2.Name)// pcu2.Age26// 修改字段4.2 结构体方法Go 没有 class但可以给 struct 绑定方法typeUserstruct{NamestringAgeint}// 值接收者不修改原始数据func(u User)SayHello(){fmt.Printf(你好我是 %s今年 %d 岁\n,u.Name,u.Age)}// 指针接收者可以修改原始数据func(u*User)Birthday(){u.Age}// 使用u:User{Name:pc,Age:25}u.SayHello()// 你好我是 pc今年 25 岁u.Birthday()// Age 变成 26u.SayHello()// 你好我是 pc今年 26 岁什么时候用值接收者 vs 指针接收者只读不修改 → 值接收者需要修改字段 → 指针接收者结构体很大 → 指针接收者避免复制开销五、指针 — 简单理解Go 的指针没有 C 的指针运算比 C 安全得多。// 取地址 * 取值x:10p:x// p 是一个指向 int 的指针fmt.Println(p)// 0xc0000...内存地址fmt.Println(*p)// 10取指针指向的值*p20// 通过指针修改 xfmt.Println(x)// 20为什么需要指针// 没有指针值传递原来的值不变funcdouble(xint){xx*2}// 有指针可以修改原来的值funcdouble(x*int){*x*x*2}n:5double(n)fmt.Println(n)// 10 新手阶段掌握和*就够了。在 Go 里大部分时间用的是指针接收者(struct 方法)和切片(内部就是指针)不需要像 C 那样手动操作指针。六、完整示例typeStudentstruct{NamestringScorefloat64}func(s Student)Info()string{status:不及格ifs.Score60{status及格}returnfmt.Sprintf(%s: %.1f分 - %s,s.Name,s.Score,status)}funcmain(){// 用结构体 切片组织数据students:[]Student{{张三,85},{李四,42},{王五,93},}// 用 map 索引index:make(map[string]float64)for_,s:rangestudents{index[s.Name]s.Score}// 查询for_,name:range[]string{张三,赵六}{ifscore,ok:index[name];ok{fmt.Printf(%s 的分数: %.1f\n,name,score)}else{fmt.Printf(%s 不存在\n,name)}}// 遍历for_,s:rangestudents{fmt.Println(s.Info())}}七、小结类型特点常用程度数组[n]T定长用的少⭐⭐切片[]T动态长度最常用⭐⭐⭐⭐⭐Mapmap[K]V键值对快速查询⭐⭐⭐⭐结构体struct组织多个字段代替 class⭐⭐⭐⭐⭐指针*T引用传值注意 和 *⭐⭐⭐下一课预告错误处理与包管理——学会用好error和go mod。