一、包(package)
包是 Go 代码组织的最小单元,用来拆分功能、避免命名冲突。
- 包声明
文件第一行package xxx,声明当前文件属于哪个包。
package main:可执行程序,会生成二进制,必须有func main()入口函数。- 其他包(如
package util):库包,只提供功能,不能直接运行。
go
package util
func Hello() string {
return "hello go"
}
- 导出规则(大小写控制访问权限)
- 大写开头:对外导出,其他包可以访问(函数、结构体、字段、常量)。
- 小写开头:仅本包内可见,外部包无法访问。
- 导入包 import
go
import (
"fmt"
"math/rand"
"yourProject/util" // 导入自己写的包
)
别名导入:
go
import u "yourProject/util"
匿名导入:只执行包 init 函数,不直接使用包内变量 import _ "github.com/xxx"
- init() 函数
- 每个包可以有多个
init(),无参数无返回。 - 执行时机:包被导入时自动执行,早于
main()。 - 执行顺序:依赖包 init → 当前包 init → main。
二、模块(module)
Go Module 是 Go 的依赖管理方案,go.mod 是模块描述文件,Go1.11+ 官方标准。
- 初始化模块
bash
go mod init github.com/demo/myproj
生成 go.mod 文件:
module github.com/demo/myproj
go 1.22
module:模块路径,也是 import 时的根路径。go:声明最低兼容 Go 版本。
-
go.sum
依赖校验文件,记录第三方包哈希,保证依赖版本安全。不要手动修改。
-
常用命令
bash
go mod tidy # 自动整理依赖,增加缺失、删除未使用
go get xxx@v1.2.3 # 指定版本拉取依赖
go list -m all # 查看全部依赖
-
模块下导入自己包
项目结构:myproj/
├─ go.mod
├─ main.go
└─ calc/
└─ calc.go
go.mod 模块路径:github.com/demo/myproj
main.go 导入子包:
go
import "github.com/demo/myproj/calc"
区分包与模块:模块是项目整体,包是模块内部的代码分组;一个模块可以包含多个包。
三、接口 interface
Go 的接口是鸭子类型:不需要显式写 implements,只要类型拥有接口全部方法,就自动实现该接口。
- 接口定义
go
type Animal interface {
Speak() string
}
接口只定义方法签名,不写实现逻辑,没有函数体。
- 类型隐式实现接口
只要结构体拥有接口里全部方法,就自动实现接口,不需要任何关键字。
go
type Dog struct{}
func (d Dog) Speak() string {
return "汪汪"
}
type Cat struct{}
func (c Cat) Speak() string {
return "喵喵"
}
Dog、Cat 都实现了 Animal 接口。
- 接口变量
接口变量可以存储任意实现该接口的类型实例。
go
var a Animal
a = Dog{}
fmt.Println(a.Speak())
a = Cat{}
fmt.Println(a.Speak())
- 空接口
interface{}
没有任何方法,所有类型都默认实现空接口,可以接收任意数据。
go
func printAny(v interface{}) {
fmt.Println(v)
}
Go1.18 之后推荐 any,等价于 interface{}。
- 类型断言
从接口变量取出底层真实类型:
go
a := Animal(Dog{})
if dogObj, ok := a.(Dog); ok {
fmt.Println(dogObj)
}
- 类型判断 switch
go
switch v := a.(type) {
case Dog:
fmt.Println("是狗", v)
case Cat:
fmt.Println("是猫", v)
default:
fmt.Println("未知类型")
}
- 接口嵌套
接口可以组合其他接口,类似继承效果:
go
type Runner interface {
Run()
}
type Eater interface {
Eat()
}
type Animal2 interface {
Runner
Eater
}
四、Go 的多态
Go 没有类继承,依靠接口实现多态。 多态:同一个接口,不同底层类型,调用同一个方法表现不同行为。
完整示例:
go
package main
import "fmt"
type Animal interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string { return "汪汪" }
type Cat struct{}
func (c Cat) Speak() string { return "喵喵" }
// 接收接口类型,兼容所有实现类
func MakeSound(a Animal) {
fmt.Println(a.Speak())
}
func main() {
MakeSound(Dog{})
MakeSound(Cat{})
}
输出:
汪汪
喵喵
关键点总结:
- 同一个接口
Animal,传入 Dog / Cat,调用同一个Speak(),行为不一样,就是多态。 - Go 多态是基于接口+鸭子类型,不是类继承。
- 接口变量保存:动态类型 + 动态值。编译只校验接口方法,运行时才确定真实类型。
五、注意
- 接口接收值还是指针:
- 如果方法是值接收者:值、指针都能赋值给接口。
- 如果方法是指针接收者:只有指针实例可以赋值给接口,值实例不会实现接口。
go
func (d *Dog) Speak() string { return "汪汪" }
var a Animal
// a = Dog{} // 报错,值类型没有实现接口
a = &Dog{} // 正确
- nil 接口坑:接口变量不等于 nil。接口内部动态类型不为 nil,就算值是 nil,接口本身不是 nil。
- 包名建议小写,简洁;模块路径一般用仓库地址。
- 接口尽量小,小接口设计原则,比如标准库
io.Reader只有一个 Read 方法。