📝本文首发于 栏轩·阁
欢迎访问阅读原文,获取更好的阅读体验。
一、数组(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"}// 方式2:make 创建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}nums=append(nums,4)// [1, 2, 3, 4]nums=append(nums,5,6)// [1, 2, 3, 4, 5, 6]// 合并两个切片nums=append(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))// 5nums=append(nums,1)// 还有容量,不会重新分配nums=append(nums,2)// [0, 0, 0, 1, 2]nums=append(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,"pc@example.com"}// 按字段顺序(不推荐)u2:=User{Name:"pc",Age:25,Email:"pc@example.com"}// 指定字段名(推荐 ✅)u3:=User{Name:"pc"}// 只赋值部分字段,其余为零值// 访问字段fmt.Println(u2.Name)// "pc"u2.Age=26// 修改字段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(取指针指向的值)*p=20// 通过指针修改 xfmt.Println(x)// 20为什么需要指针?
// 没有指针:值传递,原来的值不变funcdouble(xint){x=x*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.Score>=60{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。