Go 语言基础笔记:包、init、匿名函数、容器、结构体与错误处理
承接上一篇《Go 语言基础入门笔记:从编译运行到函数流程控制》,本文梳理项目开发高频核心知识点:包管理、init 初始化函数、匿名函数与闭包、数组 / 切片 /map 三大容器、结构体、Go 独有的极简错误处理,配套完整示例、踩坑总结。
一、包(Package)与 Go Module 模块化管理
1. 包基础规则
- 所有
.go文件首行必须声明package xxx,无包声明直接编译失败; - 同一文件夹下所有源码文件包名必须完全一致;
- 两种包区分:
package main:可执行程序入口,必须搭配func main(),能编译生成 exe;同一目录仅允许存在一个 main 函数;- 普通库包(
fmt/utils):无 main 函数,仅可被import导入,无法独立运行。
2. 访问权限:首字母大小写控制导出(Go 无 public/private)
核心规则:
- 首字母大写:导出标识符,跨包可访问(函数、变量、结构体、结构体字段通用);
- 首字母小写:私有标识符,仅当前包内文件可用,外部包导入后编译报错稀土掘金。 示例 utils 包:
package utils
// 公有函数,外部包可调用
func CalcSum(a,b int)int{
return a+b
}
// 私有函数,仅utils包内可用
func checkNum(n int)bool{
return n>0
}
// 公有结构体
type User struct {
Name string // 公有字段
age int // 私有字段,外部包无法读取修改
}
3. import 导入包三种拓展写法
// 1. 标准批量导入
import (
"fmt"
"项目模块名/utils"
)
// 2. 包别名(解决重名包冲突)
import u "项目模块名/utils"
// 3. 匿名导入 _:仅执行包内init函数,不使用包内任何API
import _ "github.com/go-sql-driver/mysql"
// 4. 点导入 .:省略包名直接调用(不推荐,可读性差)
import . "fmt"
4. Go Module(go.mod)现代项目标准
- 初始化命令:
go mod init 模块名,生成go.mod、go.sum; - 核心作用:脱离 GOPATH 路径限制、统一管理第三方依赖、定义模块内导包路径;
- 常用命令:
go mod tidy:自动补全缺失依赖、清理无用包;go mod vendor:本地缓存依赖,支持离线编译;
- 工程目录规范:
plaintext
myproj/ ├─ go.mod ├─ cmd/ # 所有可执行程序(api、工具脚本) ├─ internal/# 内部私有库,外部模块无法导入 ├─ pkg/utils # 通用公共工具包
二、init () 初始化函数
1. 基础定义
func init(){},无参数、无返回值,禁止手动调用,Go 运行时自动执行;每个.go源文件可定义 1 个 init 函数。
2. 全局固定执行顺序(重中之重)
- 导入依赖包:全局变量初始化 → 执行依赖包
init(); - 当前包:全局变量初始化 → 当前包
init(); - 最后执行
package main的main()函数。
import utils → utils全局变量 → utils.init()
→ main包全局变量 → main.init() → main()
3. 核心特性与场景
- 同目录多个文件存在 init:按文件名字母顺序执行,业务代码不建议依赖该顺序;
- 典型使用场景:数据库连接初始化、配置加载、驱动注册、全局预处理;
- init 与 main 对比:
表格
特性 init() main() 包限制 所有包均可定义 仅 package main数量 单个文件 1 个,一个包可多个 一个包仅允许 1 个 调用方式 自动执行,不可手动调用 程序唯一入口,自动启动 用途 前置初始化逻辑 业务主逻辑
三、匿名函数 & 闭包
1. 匿名函数基础
无函数名的函数,三种标准用法:
(1)自执行函数(定义后立刻运行)
func main(){
// 末尾()代表立即调用
res := func(a,b int)int{
return a*b
}(6,7)
fmt.Println(res)
}
(2)赋值给变量,重复调用
greet := func(name string){
fmt.Printf("你好:%s\n",name)
}
greet("张三")
greet("李四")
(3)作为回调 / 参数、配合 goroutine 异步执行
// 异步goroutine
go func(){
fmt.Println("异步任务执行")
}()
2. 闭包 Closure(匿名函数进阶)
定义
引用外层局部变量的匿名函数,捕获变量为引用传递,外层函数执行完毕后变量不会销毁(逃逸到堆内存)博客园。 计数器经典示例:
// 工厂函数,返回闭包
func counter() func()int{
count := 0 // 被闭包捕获的外部变量
return func()int{
count++
return count
}
}
func main(){
c1 := counter()
fmt.Println(c1()) // 1
fmt.Println(c1()) // 2
c2 := counter()
fmt.Println(c2()) // 1 独立计数,互不干扰
}
闭包核心特性
- 捕获外部变量引用,多个闭包共用同一变量时修改会同步生效;
- 外层函数销毁,捕获变量依然存活;
- 适用场景:迭代器、状态封装、回调函数、延迟计算。
四、三大容器:数组、切片、Map
(一)数组 Array:固定长度值类型
- 语法:
var arr [长度]类型,长度属于数组类型一部分,[3]int与[5]int是完全不同类型; - 本质:值类型,函数传参时完整拷贝副本,函数内修改无法影响原数组;如需修改原数组需传递数组指针腾讯云;
- 初始化方式:
var a [3]int // 零值 [0,0,0]
b := [3]int{1,2,3}
c := [...]int{10,20} // 自动推导长度
- 限制:长度固定,开发极少直接使用,业务全部用切片替代。
(二)切片 Slice:动态可变数组(开发最常用)
1. 底层结构
切片底层包含三部分:指针(底层数组地址)、len 有效长度、cap 容量;引用类型,传参修改会同步影响原切片。
2. 初始化三种方式
// 1. 声明nil切片(无底层数组,可直接append)
var s []int
// 2. 字面量初始化
s1 := []int{1,2,3}
// 3. make创建(指定长度+容量,分配底层数组)
s2 := make([]int, 2, 5) // len=2, cap=5
3. 核心操作
append追加元素:容量不足时自动扩容;nil 切片 append 安全,会自动分配底层数组CSDN博...;- 切片截取
s[low:high:max]:左闭右开,max 控制新切片容量; copy(dst, src):切片数据拷贝,避免底层数组共享修改冲突。
高频坑点
- 切片截取共享底层数组,修改子切片会影响原切片;
- 索引越界运行 panic,编译不会拦截;
- nil 切片与空切片
[]int{}不相等,可通过len(s)==0统一判空。
(三)Map:键值对哈希表
1. 基础规则
- 引用类型,仅 make 初始化后才能写入键值;直接
var m map[string]int为 nil map,写入会直接 panic腾讯云; - key 类型约束:必须支持
==比较(int/string/ 结构体等,切片、map、函数不可作为 key); - 初始化:
// 正确初始化
m := make(map[string]int, 10) // 预分配容量,优化性能
user := map[string]int{"张三":18, "李四":20}
2. 增删改查
// 新增/修改
m["age"] = 18
// 取值,双返回值判断key是否存在
val, ok := m["age"]
if ok{ fmt.Println(val) }
// 删除键
delete(m, "age")
// 遍历 for range
for k,v := range m {
fmt.Println(k,v)
}
经典踩坑
- nil map 直接赋值:
panic: assignment to entry in nil map; - 遍历过程中删除 key 行为未定义,推荐先收集待删除 key 再批量删除CSDN博...;
- map 无序,遍历顺序每次不固定。
五、结构体 Struct:自定义复合数据类型
1. 定义与初始化
// 定义结构体
type User struct {
Name string
Age int
}
func main(){
// 1. 按顺序赋值
u1 := User{"小明", 19}
// 2. 键值赋值(推荐,顺序无关)
u2 := User{Name:"小红", Age:20}
// 3. 零值初始化
var u3 User // Name:"", Age:0
// 4. 结构体指针(构造函数常用)
u4 := &User{Name:"小李"}
}
2. 访问权限规则
结构体、结构体字段同样遵循首字母大写导出:
- 结构体大写:其他包可创建该结构体;
- 字段大写:外部包可读可写;字段小写:仅当前包内访问。
3. 结构体标签 Tag(序列化必备)
字段后反引号定义标签,json、orm 库解析使用:
type User struct {
Name string `json:"username"`
Age int `json:"age" db:"user_age"`
}
4. 结构体比较
所有字段均可使用==比较时,结构体变量支持==/!=;包含切片、map 等不可比较字段时,只能用reflect.DeepEqual深度对比。
六、Go 错误处理(无 try-catch,双返回值极简模式)
Go 摒弃传统异常捕获,分为普通 error 错误(业务可恢复)与panic 崩溃(不可恢复致命错误)两套体系。
1. error 普通错误(标准处理方式)
- error 是内置接口,
errors.New("错误信息")快速创建错误对象; - 规范写法:函数多返回值,最后一位返回 error;成功返回 nil,失败返回错误实例。 基础示例:
import "errors"
// 除法函数,除数为0返回错误
func div(a,b int)(int, error){
if b == 0 {
return 0, errors.New("除数不能为0")
}
return a/b, nil
}
func main(){
res, err := div(10,0)
if err != nil {
fmt.Println("执行失败:", err)
return // 业务中断处理
}
fmt.Println("结果:", res)
}
2. panic & recover:运行时崩溃捕获
panic
主动抛出致命错误,终止当前函数、触发 defer 逆序执行,若无 recover 程序直接退出; 适用场景:参数严重非法、初始化失败等无法继续运行的场景。
recover
仅能在defer 匿名函数内调用,捕获 panic,恢复程序运行;
func main(){
defer func(){
// 捕获崩溃
if r := recover(); r != nil {
fmt.Println("捕获崩溃,程序恢复:", r)
}
}()
panic("数组越界致命错误")
fmt.Println("不会执行")
}
error vs panic 使用区分
- error:业务可预期错误(参数非法、查询无数据、IO 失败),优先使用;
- panic:程序设计缺陷、不可恢复异常,底层库使用,业务代码尽量少主动 panic。
七、核心知识点踩坑汇总
- 同目录
package main只能存在 1 个func main(),多 main 直接编译报错; :=短变量声明仅函数内可用,全局只能使用var;- 数组是值类型,切片、map、指针、error 为引用类型;nil 仅能赋值给引用 / 接口类型;
- nil 切片允许 append,nil map 写入直接 panic;
- init 函数执行顺序:依赖包 init → 当前包 init → main;
- 闭包捕获外部变量为引用,循环中使用闭包容易出现变量复用问题;
- Go 无隐式类型转换,数字与字符串互转必须使用
strconv标准库; - 结构体小写字段外部包无法访问,需提供大写方法封装读写;
- 错误处理优先判断
err != nil,不要忽略 error 返回值。
结语
本文覆盖 Go 项目开发中代码组织(包 /module)、初始化机制(init)、函数高阶特性(匿名函数 / 闭包)、四大复合类型(数组 / 切片 /map/ 结构体)、独有的错误处理体系,是从基础语法过渡到实战项目的核心桥梁。下一篇可拓展方法、接口、goroutine 并发等进阶内容。
更多推荐
所有评论(0)