一、关键字
Go语言中类似if和switch的关键字有25个,不能用于自定义名字,只能在特定语法结构中使用。
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
二、基本数据类型
| 类型分类 | Go语言类型 | 默认值 | Java对应类型 | Java包装类 | 备注 |
|---|---|---|---|---|---|
| 布尔型 | bool |
false |
boolean |
Boolean |
|
| 整数类型 | int |
0 |
int |
Integer |
平台相关(32或64位) |
int8 |
0 |
byte |
Byte |
Go的int8范围同Java的byte(-128~127) |
|
int16 |
0 |
short |
Short |
||
int32 |
0 |
int |
Integer |
||
int64 |
0 |
long |
Long |
||
uint |
0 |
无直接对应 | 无 | Go特有无符号整数 | |
uint8 |
0 |
无符号需求时用int |
无 | Go的uint8常用作字节 |
|
uint16/uint32/uint64 |
0 |
无直接对应 | 无 | ||
byte |
0 |
byte |
Byte |
byte是uint8的别名 |
|
rune |
0 |
char |
Character |
rune是int32的别名,表示Unicode码点 |
|
| 浮点型 | float32 |
0 |
float |
Float |
|
float64 |
0 |
double |
Double |
||
| 复数类型 | complex64 |
(0+0i) |
无直接对应 | 无 | Go特有 |
complex128 |
(0+0i) |
无直接对应 | 无 | ||
| 字符串 | string |
""(空字符串) |
String |
String |
Java的String是类而非基本类型 |
| 指针 | 指针类型(如*int) |
nil |
引用类型 | 无 | Go指针更类似C/C++ |
| 其他引用类型 | slice |
nil |
ArrayList/数组 |
无 | |
map |
nil |
HashMap |
无 | ||
channel |
nil |
无直接对应 | 无 | Go特有 | |
function |
nil |
函数式接口 | 无 | ||
interface |
nil |
Object/接口 |
无 |
三、声明常量和变量
1、常量(const关键字)
Go
//声明一个常量
const a = 1
//声明多个常量
const (
a = 1
b = true
)
//声明可导出的常量
const (
D = 10
E = false
)
/*
常量的访问范围:
在 Go 中,常量的访问范围由其 首字母大小写 决定:
首字母大写:常量是 导出的(exported),可以被其他文件访问。
首字母小写:常量是 未导出的(unexported),只能在定义它的文件中访问。
*/
2、变量(格式:var 变量名字 类型 = 表达式)
Go
//声明一个变量
var s string
//声明单个变量并初始化
var a int = 1
var b bool = true
//声明多个变量并初始化
var a, b = 10, false
//简短变量声明
a, b := 10, false
//变量也可以调用函数并初始化
freq := rand.Float64() * 3.0
/*
1、在包级别声明的变量会在main入口函数执行前完成初始化,局部变量将在声明语句被执行到时完成初始化。
2、":="是一个变量声明语句,而"="是一个变量赋值操作。
3、简短变量声明语句也可以用函数的返回值来声明和初始化变量
4、假设一个变量在上面已经声明了,那么下面针对这个变量的操作就是"赋值"行为
*/
3、指针(&表示取地址,*表示取值)
Go
//示例1:&代表取地址,*代表取值
func main() {
x := 1
// 把x的地址赋值给p,相当于取x变量的内存地址。或者说"p指针保存了x变量的内存地址"
p := &x
fmt.Println(*p) // 那么*p就是x的值,输出"1"
*p = 2 // 通过指针修改x的值
fmt.Println(x) // 那么x的值变成"2"
}
//示例2:
func main() {
var x, y int
fmt.Println(&x == &x) // "true" 变量x地址相等
fmt.Println(&x == &y) // "false" 变量x,y地址不相等
fmt.Println(&x == nil) // "false" 变量x地址不为空
}
//示例3:在Go语言中,返回函数中局部变量的地址也是安全的。例如下面的代码,调用f函数时创建局部变量v,在局部变量地址被返回之后依然有效,因为指针p依然引用这个变量。
func f() *int {
v := 1
return &v
}
func main() {
fmt.Println(f() == f()) // "false" 这里调用了两次f函数,虽然都是变量v,但是地址不一样
}
//示例4:指针包含了一个变量的地址,如果将指针作为参数调用函数,就可以在函数中通过该指针来更新变量的值。
func incr(p *int) int { //这里只改变了参数的变量值,并不改变p指针
*p++ // 非常重要:只是增加p指向的变量的值,并不改变p指针!!!
return *p
}
func main() {
v := 1
incr(&v)
fmt.Println(v) // 所以这里第一次输出2
fmt.Println(incr(&v)) // 在此调用+1输出了3
}
/*
1、变量有时候被称为可寻址的值。即使变量由表达式临时生成,那么表达式也必须能接受&取地址操作。
2、任何类型的指针的零值都是nil。如果p指向某个有效变量,那么p != nil测试为真。指针之间也是可以进行相等测试的,只有当它们指向同一个变量或全部是nil时才相等。
*/
4、new函数(不是关键字)
new函数是创建变量的另一种方式,类似于一种语法糖,表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为*T。注意:由于new只是一个预定义的函数,它并不是一个关键字,因此我们可以将new名字重新定义为别的类型。
Go
func main() {
p := new(int) // 初始化变量p, 数据类型为int 类型,默认值为0
fmt.Println(*p) // "0"
*p = 2 // 设置 int 匿名变量p的值为 2
fmt.Println(*p) // "2"
}
5、变量的生命周期
包一级:在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。
局部变量:局部变量的生命周期则是动态的,每次从创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。函数的参数变量和返回值变量都是局部变量,它们在函数每次被调用的时候创建。
垃圾回收:从每个包级的变量和每个当前运行函数的每一个局部变量开始,通过指针或引用的访问路径遍历,是否可以找到该变量。如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在并不会影响程序后续的计算结果。(注意:和JVM的JIT逃逸分析类似,变量如果逃逸到函数之外,一般情况下会在栈上分配,也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间。未逃逸的就会在堆上分配。)
四、赋值
Go
//示例1:最简单的赋值语句是将要被赋值的变量放在=的左边,新值的表达式放在=的右边。
func main() { //自增和自减是语句,而不是表达式
var v int
v = 1
fmt.Println(v) // 输出 1
v++ // 等价方式 v = v + 1;v 变成 2
fmt.Println(v) // 输出 2
v-- // 等价方式 v = v - 1;v 变成 1
fmt.Println(v) // 输出 1
}
func main() { //直接交换x,y的值
x, y := 3, 4
fmt.Println("x:", x, "y:", y) // x: 3 y: 4
x, y = y, x
fmt.Println("x:", x, "y:", y) // x: 4 y: 3
}
//示例2:元组赋值
func main() { //直接交换a[0], a[1]的值
a := []int{1, 2, 3, 4, 5}
a[0], a[1] = a[1], a[0]
fmt.Println(a) // 输出 [2 1 3 4 5]
}
func gcd(x, y int) int {
for y != 0 {
x, y = y, x%y
}
return x
}
func main() {
fmt.Println("48 and 18 的最大公约数:", gcd(48, 18)) // 输出: 6
}
//示例3:多返回值必须用相同数量的变量接收,也就是左右变量数目一致,可以用下划线空白标识符_来丢弃不需要的值
func test() (int, int) {
return 3, 4
}
func main() {
a, b := test()
fmt.Println(a, b) // 输出: 3 4
}
//示例4:显式赋值
func main() {
medals := []string{"gold", "silver", "bronze"}
// Medal 1: gold
// Medal 2: silver
// Medal 3: bronze
for i, medal := range medals {
fmt.Printf("Medal %d: %s\n", i+1, medal)
}
}
func main() {
medals := [3]string{}
medals[0] = "gold"
medals[1] = "silver"
medals[2] = "bronze"
// Medal 1: gold
// Medal 2: silver
// Medal 3: bronze
for i, medal := range medals {
fmt.Printf("Medal %d: %s\n", i+1, medal)
}
}
五、复合数据类型
1、数组
数组的长度需要在编译阶段确定,一般是下面这种顺序初始化值
Go
//声明数组有多种方式
func main() {
//数组a
a := []int{1, 2, 3}
fmt.Println(a) // [1 2 3]
//数组b
b := [4]int{1, 2, 3, 4}
fmt.Println(b) // [1 2 3 4]
//数组c
c := [...]int{1, 2, 3, 4}
fmt.Println(c) // [1 2 3 4]
//数组d
d := make([]int, 4)
d[0] = 1
d[1] = 2
d[2] = 3
fmt.Println(d) // [1 2 3 0]
}
也可以指定一个索引和对应值列表的方式初始化
Go
type Currency int
const (
USD Currency = iota // 美元 跳过0
EUR // 欧元 应该为1
GBP // 英镑 应该为2
RMB // 人民币 应该为3
)
func main() {
symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"}
fmt.Println(RMB, symbol[RMB]) // 输出:"3 ¥"
}
初始化索引的顺序是无关紧要的,而且没用到的索引可以省略,和前面提到的规则一样,未指定初始值的元素将用零值初始化
Go
//定义了一个含有10个元素的数组r,最后一个元素被初始化为-1,其它元素都初始化为0
func main() {
r := [...]int{9: -1}
fmt.Println(r) // 输出: [0 0 0 0 0 0 0 0 0 -1]
}
当调用一个函数的时候,传递的是参数的副本,并不是原始对象,也就是说Go语言是值传递,并且Go语言传递大数组的效率比较低。所以在Go语言中,传递数组有以下三种方式:
Go
//1、使用指针
func modifyArrayByPointer(arr *[3]int) {
(*arr)[0] = 100 // 或 arr[0] = 100(语法糖)
}
func main() {
arr := [3]int{1, 2, 3}
modifyArrayByPointer(&arr)
fmt.Println(arr) // [100 2 3]
}
//2、使用指针数组
// 对于非常大的数组, 使用指针传递以避免拷贝开销。这里为了测试设置比较小
func processLargeArray(arr *[5]int) {
// 通过指针操作,避免拷贝
for i := range arr {
arr[i] = arr[i] * 2
}
}
func main() {
arr := [5]int{1, 2, 3}
processLargeArray(&arr)
fmt.Println(arr) // [2 4 6 0 0]
}
//3、使用切片slice
func modifySlice(s []int) {
if len(s) > 0 {
s[0] = 100 // 修改原始数据
}
}
func main() {
arr := [3]int{1, 2, 3}
slice := arr[:] // 创建指向数组的切片
modifySlice(slice)
fmt.Println(arr) // 输出 [100 2 3]
}
2、Slice(切片--引用类型)
切片是一个轻量级的动态数组抽象,它不存储数据本身,而是:
-
指向底层数组的指针(slice并没有指明序列的长度,而是会隐式地创建一个合适大小的数组,然后slice的指针指向底层的数组,返回的都是原始数组序列的子序列)
-
长度(当前元素个数)
-
容量(最大可容纳元素个数)
-
slice是间接引用,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素,唯一合法的比较操作是和nil比较
Go
func nonempty(strings []string) []string {
i := 0
for _, s := range strings {
if s != "" {
strings[i] = s
i++
}
}
return strings[:i]
}
func main() {
// 1. 从数组创建
arr := [5]int{1, 2, 3, 4, 5}
slice1 := arr[1:4]
fmt.Println(slice1) // [2, 3, 4]
// 2. 直接创建
slice2 := []int{1, 2, 3}
fmt.Println(slice2) // [1 2 3]
// 3. 使用make函数
slice3 := make([]int, 3, 5) // 长度3,容量5
fmt.Println(slice3) // [0 0 0]
// 4. 从切片创建(共享底层数组)
slice4 := slice1[1:3]
fmt.Println(slice4) // [3 4]
// 5. 不同方式创建空切片,并检查其长度和nil状态
var slice5 []int // len(slice5) == 0, slice5 == nil
slice5 = nil // len(slice5) == 0, slice5 == nil
slice5 = []int(nil) // len(slice5) == 0, slice5 == nil
slice5 = []int{} // len(slice5) == 0, slice5 != nil
fmt.Println(len(slice5)) // 切片长度为:0
// 6. slice之间不能比较,不能使用==操作符来判断两个slice是否含有全部相等元素
// if slice1 == slice2 { // 编译错误:切片不能比较
// fmt.Println("slice1 和 slice2 相等")
// }
// 7. 使用append函数追加元素
slice6 := []int{1, 2, 3}
slice6 = append(slice6, 4, 5)
fmt.Println(slice6) // [1 2 3 4 5]
// 8. 使用copy函数复制切片
src := []int{1, 2, 3} // 源切片
dest := make([]int, 2) // 目标切片,长度为2
n := copy(dest, src) // 复制元素
fmt.Println(n, dest) // 2 [1 2]
// 9. 使用nonempty函数过滤空字符串
data := []string{"one", "", "three"}
// fmt.Printf("%q\n", nonempty(data)) // 输出 ["one" "three"]
// fmt.Printf("%q\n", data) // 输出 ["one" "three" "three"]
data = nonempty(data) // 重新赋值以保留过滤结果
fmt.Printf("%q\n", data) // 输出 ["one" "three"]
// 10. 一个slice可以用append函数来模拟一个stack
var stack []int
stack = append(stack, 1) // 入栈
stack = append(stack, 2) // 入栈
fmt.Println(stack) // [1 2]
x := stack[len(stack)-1] // 取出栈顶元素
stack = stack[:len(stack)-1] // 出栈
fmt.Println(x) // 2
fmt.Println(stack) // [1]
// 11. 遍历slice
slice7 := []int{10, 20, 30}
for i, v := range slice7 {
fmt.Printf("索引 %d 的值是 %d\n", i, v)
// 索引 0 的值是 10
// 索引 1 的值是 20
// 索引 2 的值是 30
}
}
3、Map(并发不安全--引用类型)
-
Map是引用类型,零值为
nil -
键必须是可比较的类型,值可以是任意类型
-
Map不是并发安全的,需要使用sync.Mutex或sync.Map
-
遍历顺序是随机的
-
使用
delete()删除元素,删除不存在的键不会报错 -
**Go的Map底层是哈希表实现:
- 多个桶(bucket),每个桶存储8个键值对
- 使用链地址法解决哈希冲突
- 负载因子达到6.5时触发扩容
- 扩容时渐进式rehash**
Go
func main() {
// 声明方式1:使用make函数
var m1 map[string]int // 声明,此时m1为nil
m1 = make(map[string]int) // 初始化
m1["key1"] = 100
fmt.Println(m1["key1"]) // 输出: 100
// 声明方式2:字面量初始化
m2 := map[string]int{
"apple": 5,
"banana": 3,
"orange": 2,
}
fmt.Println(m2["banana"]) // 输出: 3
// 声明方式3:指定初始容量
m3 := make(map[string]int, 10) // 预分配容量,提升性能
m3["item1"] = 50
fmt.Println(m3["item1"]) // 输出: 50
// 空map可以读取,但不能写入
// m4["key"] = 1 // 运行时panic: assignment to entry in nil map
var m4 map[string]int
fmt.Println(m4 == nil) // true
fmt.Println(len(m4)) // 0
fmt.Println(m4["nonexistent"]) // 读取不存在的键,返回零值: 0
// m4是一个空指针,没有指向任何有效的hmap结构体。写入操作需要操作这个结构体的内部字段(buckets、count等),而nil指针无法安全访问这些字段。
m4["nonexistent"] = 1 // 运行时panic: assignment to entry in nil map
// 添加/修改
m5 := make(map[string]int)
m5["apple"] = 5
m5["banana"] = 3
m5["apple"] = 10 // 修改已有键的值
// 查询
value := m5["apple"]
fmt.Println(value) // 10
// 检查键是否存在
value, exists := m5["orange"]
if exists {
fmt.Println("orange:", value)
} else {
fmt.Println("orange不存在") // orange不存在
}
// 删除
delete(m5, "banana")
fmt.Println(m5) // map[apple:10]
delete(m5, "nonexistent") // 删除不存在的键不会报错
// 获取长度
fmt.Println("map长度:", len(m5)) // map长度: 1
// 遍历
m6 := map[string]string{
"red": "#FF0000",
"green": "#00FF00",
"blue": "#0000FF",
}
// 遍历所有键值对(顺序随机)
for key, value := range m6 {
fmt.Printf("%s: %s\n", key, value) // red: #FF0000 green: #00FF00 blue: #0000FF (顺序可能不同)
}
// 只遍历键
for key := range m6 {
fmt.Println("Key:", key) // red, green, blue
}
// 只遍历值
for _, value := range m6 {
fmt.Println("Value:", value) // #FF0000, #00FF00, #0000FF
}
}
Go
// 有效的键类型(可比较的类型):
// - 所有基本类型(除slice、map、function)
// - 数组(如果元素类型可比较)
// - 结构体(如果所有字段可比较)
// - 指针
// - 接口(如果动态值可比较)
// 无效的键类型(会导致编译错误):
// - slice
// - map
// - function
// - 包含上述类型的结构体
type Point struct {
X, Y int
}
func main() {
m := make(map[Point]string)
m[Point{1, 2}] = "位置1"
m[Point{3, 4}] = "位置2"
println(m[Point{1, 2}]) // 输出: 位置1
println(m[Point{3, 4}]) // 输出: 位置2
}
Go
// 并发写会出现安全性问题
func main() {
// 创建一个普通的 map
m := make(map[int]int)
// 两个 goroutine 并发写入同一个 map
go func() {
for i := 0; i < 1000; i++ {
m[i] = i
}
}()
go func() {
for i := 1000; i < 2000; i++ {
m[i] = i
}
}()
time.Sleep(time.Second)
// 可能触发:fatal error: concurrent map writes
}
// 方案1:使用sync.Mutex(推荐用于复杂场景)
// 结构体
type SafeMap struct {
sync.RWMutex
m map[string]int
}
// 构造函数:确保 map 被正确初始化
func NewSafeMap() *SafeMap {
return &SafeMap{
m: make(map[string]int),
}
}
// set函数:设置键值对
func (sm *SafeMap) Set(key string, value int) {
// 写锁保护
sm.Lock()
// 确保在函数退出时释放锁
defer sm.Unlock()
// 设置值
sm.m[key] = value
}
// get函数:获取键对应的值
func (sm *SafeMap) Get(key string) (int, bool) {
// 读锁保护
sm.RLock()
// 确保在函数退出时释放锁
defer sm.RUnlock()
// 获取值
value, exists := sm.m[key]
// 返回值和是否存在
return value, exists
}
// 示例使用
func main() {
// 创建 SafeMap 实例
sm := NewSafeMap()
// 设置和获取值
sm.Set("test", 42)
// 获取值
if val, ok := sm.Get("test"); ok {
println(val) // 输出 42
}
}
// 方案2:使用sync.Map(适用于读多写少场景)
func main() {
// 创建一个 sync.Map
var syncMap sync.Map
// 存储
syncMap.Store("name", "测试")
// 加载
value, ok := syncMap.Load("name")
fmt.Println(value, ok) // 输出: 测试 true
value, ok = syncMap.Load("age")
fmt.Println(value, ok) // 输出: <nil> false
// 加载或存储
actual, loaded := syncMap.LoadOrStore("name", "新值")
fmt.Println(actual, loaded) // 输出: 测试 true
actual, loaded = syncMap.LoadOrStore("age", 30)
fmt.Println(actual, loaded) // 输出: 30 false
// 更新
syncMap.Store("name", "更新后的值")
updatedValue, _ := syncMap.Load("name")
fmt.Println(updatedValue) // 输出: 更新后的值
// 删除
syncMap.Delete("name") // 删除键为 "name" 的项
// 遍历
syncMap.Range(func(key, value interface{}) bool {
fmt.Println("key:", key, "value:", value) // 输出: key: age value: 30
return true
})
}
4、结构体(深/浅拷贝)
- 访问/修改字段:使用点操作符
. - 通过指针访问:Go 自动解引用
- 如果所有字段都可比较(如基本类型、数组等,引用类型不可以),则结构体本身也可比较
- 结构体不支持传统继承,嵌入实现组合复用(也就是常说的组合大于继承)
Go
// User 声明一个可导出的结构体
type User struct {
Name string
Age int
Sex string
Address Address // 嵌套结构体
Address2 // 匿名成员
}
type Address struct {
City string
Country string
}
type Address2 struct {
City string
Country string
}
// DeepCopyJSON 深拷贝函数
func DeepCopyJSON(dst, src interface{}) error {
// 序列化
data, err := json.Marshal(src)
if err != nil {
return err
}
// 反序列化到新对象
err = json.Unmarshal(data, dst)
return err
}
func main() {
// 结构体实例
user := User{
Name: "张三",
Age: 18,
Sex: "男",
Address: Address{
City: "北京",
Country: "中国",
},
}
fmt.Println(user.Name) // 张三
fmt.Println(user.Age) // 18
fmt.Println(user.Sex) // 男
fmt.Println(user.Address.City)
// 通过指针访问结构体成员
user1 := &user
// 这里会修改原始成员变量:原因是默认浅拷贝,指针都指向同一个结构体
user1.Age = 19
fmt.Println(user1.Name) // 张三
fmt.Println(user.Age) // 19
fmt.Println(user1.Age) // 19
//1、手动深拷贝
user2 := User{
Name: user.Name,
Age: user.Age,
Sex: user.Sex,
Address: Address{
City: user.Address.City,
Country: user.Address.Country,
},
}
user2.Age = 20
fmt.Println(user2.Age) // 20
fmt.Println(user2.Address.City) // 北京
//2、使用结构体指针深拷贝
user3 := &user
user3.Age = 21
user3.Address.City = "上海"
fmt.Println(user3.Age) //
fmt.Println(user3.Address.City) // 上海
//3、序列化深拷贝
var user4 User
err := DeepCopyJSON(&user4, user)
if err != nil {
return
}
user4.Age = 22
fmt.Println(user4.Age) // 22
}
5、JSON
- Go 标准库
encoding/json提供对 JSON 的编码(marshal) 和 解码(unmarshal) 支持 - 解码时目标必须是指针,且JSON 中多余的字段会被自动忽略
- 对于 HTTP 响应或大文件,推荐使用
json.Decoder/json.Encoder - 对可选字段使用
,omitempty
Go
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Tags []string `json:"tags,omitempty"` // 为空时忽略,omitempty:当字段为零值(如 false, 0, nil, "", 空 slice)时,不输出该字段
CreatedAt time.Time `json:"created_at"`
SecretField string `json:"-"` // 不序列化
}
func main() {
product := Product{
ID: 1,
Name: "product1",
Price: 9.99,
Tags: []string{},
CreatedAt: time.Now(),
SecretField: "secret",
}
// 编码
marshal, _ := json.Marshal(product)
println(string(marshal)) // {"id":1,"name":"product1","price":9.99,"created_at":"2025-12-17T16:57:38.654206+08:00"}
}
六、函数
1、函数声明
- Go 没有默认参数、不支持函数重载
- 调用时必须提供所有参数,且顺序固定(不能按名传参)
- 命名返回值在复杂函数中可提高可读性,但在简单函数中可能冗余
- 裸返回(return)在函数较长时可能降低可读性,建议谨慎使用
Go
// 1、基本函数
func hypot(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
// 2、多返回值
func safeSqrt(x float64) (float64, error) {
if x < 0 {
return 0, errors.New("negative number")
}
return math.Sqrt(x), nil
}
// 3、命名返回值
func max(a, b int) (result int) {
if a > b {
result = a
} else {
result = b
}
return // 裸返回
}
// 4、忽略参数
func alwaysReturnFirst(_ int, y int) int {
return y
}
// 5、引用类型参数可被修改
func resetSlice(s []int) {
for i := range s {
s[i] = 0 // 修改原 slice 元素
}
}
//6、递归函数
func recursion(n int) int {
if n <= 1 {
return 1
}
return n * recursion(n-1)
}
func main() {
fmt.Println(hypot(3, 4)) // 输出: 5
if v, err := safeSqrt(-4); err != nil {
fmt.Println("Error:", err) // 输出: Error: negative number
} else {
fmt.Println(v)
}
fmt.Println(max(10, 20)) // 输出: 20
fmt.Println(alwaysReturnFirst(1, 99)) // 输出: 99
nums := []int{1, 2, 3}
resetSlice(nums)
fmt.Println(nums) // 输出: [0 0 0]
println(recursion(5)) // 输出:120
}
2、匿名函数
- 可在函数内部定义(闭包)
- 能捕获外部变量(形成闭包)
Go
/**
* 闭包
* 匿名函数
* 参数: int类型 n
* 返回: int类型 的匿名函数
*/
func makeAdder(n int) func(int) int {
// 返回一个匿名函数
return func(x int) int {
return x + n // 捕获 n
}
}
func main() {
adder := makeAdder(10)
fmt.Println(adder(5)) // 15
}
3、可变参数函数
(...T) 表示可以接受多个该类型的函数,实际接收为 []T 切片
Go
// 可变参数函数
func sum(nums ...int) int {
total := 0
// 遍历数组
for _, n := range nums {
total += n
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3, 4)) // 输出:10
nums := []int{1, 2, 3}
fmt.Println(sum(nums...)) // 输出:6
}
4、Deferred函数
-
defer将函数调用推迟到外层函数返回前执行 -
常用于资源清理(如关闭文件、解锁)
Go
func readFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
// 读取文件
_, err = f.Read(make([]byte, 1024))
if err != nil {
return err
}
defer f.Close() // 确保关闭
return nil
}
func main() {
if err := readFile("C:\\Pictures\\清乾隆 缂丝秋桃绶带图轴.jpg"); err != nil {
panic(err)
}
}
5、错误(error)
-
错误通过
error接口返回(非异常) -
调用者必须显式检查错误
-
避免使用 panic 处理常规错误
-
推荐使用
fmt.Errorf构造带上下文的错误信息。Gofunc sqrt(x float64) (float64, error) { if x < 0 { // 推荐使用 fmt.Errorf 构造带上下文的错误信息。 return 0, fmt.Errorf("sqrt of negative number: %g", x) } return math.Sqrt(x), nil }
6、Panic异常(不可恢复的严重错误)
Go的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等,这些运行时错误会引起panic异常。
-
panic终止当前函数并向上冒泡 -
应仅用于不可恢复的严重错误(如程序 bug)
Go
func mustOpen(name string) *os.File {
f, err := os.Open(name)
if err != nil {
panic(err) // 不推荐用于常规错误
}
return f
}
7、Recover捕获异常
-
recover只能在defer函数中调用 -
可捕获
panic并恢复程序执行
Go
// safeCall 安全地调用指定的函数,捕获可能发生的恐慌并将其转换为错误返回
// 参数:
// fn - 需要被安全调用的函数
// 返回值:
// err - 如果函数执行过程中发生恐慌则返回包装后的错误,否则返回nil
func safeCall(fn func()) (err error) {
// 使用defer和recover机制来捕获可能发生的恐慌
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic recovered: %v", r)
}
}()
fn()
return nil
}
func main() {
safeCall(func() {
panic("something went wrong") // 如果上面的函数没有defer和recover,就会报错:panic: something went wrong
})
}
七、方法和接口
1、接口
- 接口只包含方法签名,不包含实现
- 可以嵌入其他接口(组合)
- 只要一个类型实现了接口中的所有方法,就说明实现了该接口 --- 无需显式声明
implements
Go
/*
定义接口Speaker,该接口有一个方法Speak()
在JAVA中需要先Implements实现这个接口,然后才能实现具体的方法。
在GO中不需要Implements,直接实现接口方法即可
*/
type Speaker interface {
Speak() string
}
// 定义具体类型1
type Dog struct{}
// 实现接口1
func (d Dog) Speak() string { return "汪汪汪!" }
// 定义具体类型2
type Cat struct{}
// 实现接口2
func (c Cat) Speak() string { return "喵喵喵!" }
func main() {
var s Speaker
s = Dog{}
fmt.Println(s.Speak()) // 汪汪汪!
s = Cat{}
fmt.Println(s.Speak()) // 喵喵喵!
}
2、方法
接收者 (Receiver) :方法声明时,在关键字 func 和函数名之间包含一个参数,称为接收者。
-
值接收者 (Value Receiver):操作的是接收者的副本,不影响原对象。(只读操作,适用于小结构体)
-
指针接收者 (Pointer Receiver):操作的是原对象的引用,操作会影响原对象。(读写操作,适用于大型结构体(避免拷贝开销))
绑定范围 :方法可以绑定到任何同一包内定义的具名类型 (Named Type)上,包括结构体、数值、字符串、甚至函数类型。但不能绑定到基础类型(如 int)或其它包中的类型。
组合与封装:通过在结构体中嵌入(Embedding)另一个类型,可以"继承"该类型的方法。
Go
/*
定义接口Speaker,该接口有一个方法Speak()
在JAVA中需要先Implements实现这个接口,然后才能实现具体的方法。
在GO中不需要Implements,直接实现接口方法即可
*/
type Speaker interface {
Speak() string
}
// ----------------------------
// 1. 值接收者:操作副本,不影响原对象
// ----------------------------
type Dog struct {
Name string
count int // 用于记录 Speak 被调用次数(但不会真正更新原对象)
}
// 值接收者:d 是 Dog 的副本
func (d Dog) Speak() string {
d.count++ // 修改的是副本的 count
return fmt.Sprintf("%s(汪汪!已叫 %d 次)", d.Name, d.count)
}
// ----------------------------
// 2. 指针接收者:操作原对象,会修改它
// ----------------------------
type Bird struct {
Name string
count int // 用于记录 Speak 被调用次数
}
// 指针接收者:b 指向原对象
func (b *Bird) Speak() string {
b.count++ // 修改的是原对象的 count
return fmt.Sprintf("%s(叽叽!已叫 %d 次)", b.Name, b.count)
}
func main() {
// ====== 测试值接收者(Dog)======
fmt.Println("=== Dog(值接收者)===")
dog := Dog{Name: "旺财"}
fmt.Println("第一次调用:", dog.Speak()) // 第一次调用: 旺财(汪汪!已叫 1 次)
fmt.Println("第二次调用:", dog.Speak()) // 第二次调用: 旺财(汪汪!已叫 1 次)
fmt.Println("原对象 count 值:", dog.count) // 仍然是 0!
fmt.Println("=== Dog只修改了自己方法里面的count,原始值依旧是0 ===")
// ====== 测试指针接收者(Bird)======
fmt.Println("\n=== Bird(指针接收者)===")
bird := Bird{Name: "小翠"}
fmt.Println("第一次调用:", bird.Speak()) // 第一次调用: 小翠(叽叽!已叫 1 次)
fmt.Println("第二次调用:", bird.Speak()) // 第二次调用: 小翠(叽叽!已叫 2 次)
fmt.Println("原对象 count 值:", bird.count) // 已变为 2!
fmt.Println("=== Bird是指针接收者,修改了原始值变为2 ===")
// ====== 接口调用验证 ======
fmt.Println("\n=== 通过接口调用 ===")
var s Speaker
s = dog // 值类型赋值给接口
fmt.Println("接口调用 Dog:", s.Speak()) // 接口调用 Dog: 旺财(汪汪!已叫 1 次)
s = &bird // 注意:必须传指针才能满足接口(因为 Speak 在 *Bird 上)
fmt.Println("接口调用 Bird:", s.Speak()) // 接口调用 Bird: 小翠(叽叽!已叫 3 次)
fmt.Println("Bird 最终 count:", bird.count) // Bird 最终 count: 3
}
/**
=== Dog(值接收者)===
第一次调用: 旺财(汪汪!已叫 1 次)
第二次调用: 旺财(汪汪!已叫 1 次)
原对象 count 值: 0
=== Dog只修改了自己方法里面的count,原始值依旧是0 ===
=== Bird(指针接收者)===
第一次调用: 小翠(叽叽!已叫 1 次)
第二次调用: 小翠(叽叽!已叫 2 次)
原对象 count 值: 2
=== Bird是指针接收者,修改了原始值变为2 ===
=== 通过接口调用 ===
接口调用 Dog: 旺财(汪汪!已叫 1 次)
接口调用 Bird: 小翠(叽叽!已叫 3 次)
Bird 最终 count: 3
*/
八、Goroutines和Channels
1、Goroutine(协程)
- 使用 go 关键字来启动一个协程
- 轻量级:初始栈仅 2KB,可动态增长;成千上万个 goroutine 可同时运行。
- 非阻塞:启动后立即返回,不等待函数执行完成。
- 由 Go 运行时调度,运行在少量 OS 线程上(M:N 调度)。
- 不要通过共享内存来通信,而应通过通信来共享内存
Go
go f() // 启动新 goroutine 执行 f()
go func() { ... }() // 启动匿名函数
2、Channel(通道)
-
类型:
chan T,用于在 goroutine 之间传递类型为T的值。 -
同步机制:发送和接收操作默认是阻塞的,天然实现同步
-
无缓冲通道:发送和接收必须同时就绪(同步点)。
-
有缓冲通道:缓冲区满时发送阻塞,空时接收阻塞。
-
关闭后:不能再发送(panic),但可继续接收剩余值,直到返回零值 +
false。
Go
ch := make(chan int) // 无缓冲通道
ch := make(chan int, 3) // 有缓冲通道(容量 3)
3、select 语句(多路复用)
- 类似
switch,但用于 channel 操作。 - 可同时监听多个 channel 的发送/接收。
default分支实现非阻塞操作。
Go
// 该函数演示了Go语言中select语句的使用,用于在多个channel操作之间进行选择
func main() {
// 初始化变量和channel
x := 1
ch1 := make(chan int, 3)
ch2 := make(chan int, 3)
// 使用select语句在多个channel操作之间进行非阻塞选择
// select会随机选择一个可以执行的case,如果没有任何case可以执行则执行default分支
select {
case v := <-ch1:
fmt.Println("Received from ch1:", v)
case ch2 <- x:
fmt.Println("Sent to ch2")
default:
fmt.Println("No communication ready")
}
}
4、总结
1、常见并发模式:
- 生成器(Generator):goroutine 产生数据,通过 channel 输出。
- 工作池(Worker Pool):多个 worker 从任务队列(channel)消费。
- 扇入/扇出(Fan-in/Fan-out):合并多个输入 / 分发到多个处理者。
- 超时控制:结合
time.After实现
|---------------------------------------|-----------------------|----------------------------------|
| 主 goroutine 退出,所有 goroutine 被强制终止 | 程序不会等待后台 goroutine 完成 | 使用 sync.WaitGroup 或 channel 同步 |
| 向已关闭的 channel 发送 → panic | | 只由发送方关闭 channel |
| 重复关闭 channel → panic | | 确保只关闭一次 |
| 无缓冲 channel 需要配对操作 | 单独发送或接收会永久阻塞 | 确保有对应的接收/发送方 |
| 忘记读取 channel 导致 goroutine 泄漏 | | 使用带缓冲 channel 或确保消费 |
| 在循环中启动 goroutine 捕获循环变量 | 闭包捕获的是变量地址,值可能变化 | 显式传参或复制变量 |
2、示例 1:基础 goroutine + channel 同步
Go
// say函数用于打印指定字符串3次,每次间隔100毫秒,并通过done通道通知完成
// 参数s: 要打印的字符串
// 参数done: 用于通知任务完成的布尔类型通道
func say(s string, done chan bool) {
// 循环打印字符串3次
for i := 0; i < 3; i++ {
fmt.Println(s)
time.Sleep(100 * time.Millisecond)
}
// 向done通道发送true值,表示任务已完成
done <- true
}
// main 函数是程序的入口点,演示了 goroutine 的并发执行
// 通过 channel 实现主 goroutine 和子 goroutine 之间的同步
func main() {
// 创建一个布尔类型的 channel 用于 goroutine 间通信和同步
done := make(chan bool)
// 启动一个子 goroutine 执行 say 函数,传入 "world" 和 done channel
go say("world", done)
// 主 goroutine 直接执行 say 函数,传入 "hello" 和 nil
say("hello", nil)
// 阻塞等待子 goroutine 完成,从 done channel 接收值
<-done
}
3、示例 2:工作池(Worker Pool)
Go
// worker 是一个工作协程函数,用于处理作业队列中的任务
// id: 工作协程的唯一标识符
// jobs: 只读通道,用于接收待处理的作业任务
// results: 只写通道,用于发送处理结果
// wg: 等待组,用于协调所有工作协程的完成
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
// 循环处理作业队列中的任务,直到通道关闭
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(time.Second) // 模拟耗时
results <- job * 2
}
}
// 该函数启动多个worker goroutine来处理作业,并收集处理结果
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// 启动3个worker goroutine来处理作业
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// 发送任务
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// 等待所有 worker 结束
wg.Wait()
close(results)
// 收集结果
for res := range results {
fmt.Println("Result:", res)
}
}
4、示例 3:超时控制(select + time.After)
Go
// 该函数演示了带超时机制的goroutine通信
// 通过channel和select语句实现主goroutine与子goroutine之间的数据传递和超时控制
func main() {
// 创建一个无缓冲的字符串channel,用于goroutine间通信
ch := make(chan string)
// 启动一个匿名goroutine函数
go func() {
// 子goroutine休眠2秒模拟耗时操作
time.Sleep(2 * time.Second)
// 向channel发送结果数据
ch <- "result"
}()
// 使用select语句实现超时控制
// 监听多个channel操作,执行第一个准备好的操作
select {
// 从ch通道接收数据的情况
case res := <-ch:
fmt.Println("Got:", res)
// 超时情况:After函数返回的通道在指定时间后可读
case <-time.After(1 * time.Second):
fmt.Println("Timeout!")
}
} // 输出: Timeout!
5、示例4:实现简单聊天室
Go
package main
import (
"bufio"
"fmt"
"log"
"net"
"strings"
)
// 消息类型:包含内容和来源
type clientMessage struct {
text string
user string
}
// 全局广播通道
var (
entering = make(chan *client) // 用户进入
leaving = make(chan *client) // 用户离开
messages = make(chan clientMessage) // 消息广播
)
// 客户端结构
type client struct {
name string
conn net.Conn
message chan<- clientMessage // 发送消息到该客户端的通道(只写)
}
// 广播协程:管理在线用户列表并转发消息
func broadcaster() {
clients := make(map[*client]bool) // 当前在线用户
for {
select {
case msg := <-messages:
// 广播消息给所有客户端
for cli := range clients {
cli.message <- msg
}
case cli := <-entering:
// 新用户加入
clients[cli] = true
msg := fmt.Sprintf("当前在线用户 (%d): ", len(clients))
var names []string
for c := range clients {
names = append(names, c.name)
}
msg += strings.Join(names, ", ")
for cli := range clients {
cli.message <- clientMessage{text: msg, user: "[系统]"}
}
case cli := <-leaving:
// 用户离开
delete(clients, cli)
close(cli.message)
msg := fmt.Sprintf("[系统] %s 已离开。当前在线: %d 人", cli.name, len(clients))
for c := range clients {
c.message <- clientMessage{text: msg, user: "[系统]"}
}
}
}
}
// 客户端处理协程
func handleConn(conn net.Conn) {
defer conn.Close()
// 设置用户名
scanner := bufio.NewScanner(conn)
conn.Write([]byte("请输入你的昵称: "))
if !scanner.Scan() {
return
}
name := strings.TrimSpace(scanner.Text())
if name == "" {
name = "匿名用户"
}
// 创建客户端
clientChan := make(chan clientMessage) // 用于向该客户端发送消息
cli := &client{
name: name,
conn: conn,
message: clientChan,
}
// 通知 broadcaster 有新用户加入
entering <- cli
// 启动一个 goroutine 向客户端写消息
go func() {
for msg := range clientChan {
fmt.Fprintf(conn, "[%s] %s\n", msg.user, msg.text)
}
}()
// 读取用户输入并广播
messages <- clientMessage{text: "已加入聊天室", user: name}
input := bufio.NewScanner(conn)
for input.Scan() {
text := strings.TrimSpace(input.Text())
if text == "/quit" {
break
}
messages <- clientMessage{text: text, user: name}
}
// 用户退出
leaving <- cli
messages <- clientMessage{text: "已离开聊天室", user: name}
}
func main() {
listener, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
defer listener.Close()
fmt.Println("聊天服务器启动,监听端口 8080...")
go broadcaster()
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
go handleConn(conn) // 每个连接一个 goroutine
}
}
bash
#测试:
# 终端 1
telnet localhost 8080
# 输入昵称:Alice
# 终端 2
telnet localhost 8080
# 输入昵称:Bob
九、锁
1、竞争条件(Race Condition)
- 定义:多个 goroutine 并发读写同一变量,结果依赖于执行时序。
- 后果:程序行为不可预测(崩溃、数据错乱、静默错误)。
- 检测工具 :
go run -race(竞态检测器)
Go
var balance = 100
// Deposit 向账户中存入指定金额
// amount: 存入的金额
// wg: 用于同步的等待组,函数执行完毕后会调用Done方法
func Deposit(amount int, wg *sync.WaitGroup) {
defer wg.Done()
// 多个 goroutine 同时修改balance变量,存在竞态条件风险
balance += amount
}
// 该函数创建两个goroutine来执行存款操作,并等待它们完成
// 最后打印出最终的余额,但由于竞态条件,结果是不确定的
func main() {
// 创建WaitGroup用于等待所有goroutine完成
var wg sync.WaitGroup
// 设置需要等待的goroutine数量为2
wg.Add(2)
// 启动第一个goroutine,存入10元
go Deposit(10, &wg)
// 启动第二个goroutine,存入20元
go Deposit(20, &wg)
// 等待所有goroutine完成
wg.Wait()
// 打印最终余额,由于存在竞态条件,结果可能是110、120或130等不确定值
fmt.Println("Final balance:", balance) // 可能是 110、120、130?不确定!
}
2、sync.Mutex 互斥锁
- 最基本的同步原语:同一时间只允许一个 goroutine 访问临界区
- 方法:
Lock()/Unlock() - 必须配对使用,推荐用
defer
Go
// Deposit 向账户中存入指定金额
// 参数:
// amount: 要存入的金额
// wg: 用于同步的WaitGroup指针,函数执行完毕后会调用Done方法
func Deposit(amount int, wg *sync.WaitGroup) {
defer wg.Done()
// 加锁保护共享资源balance
mu.Lock()
defer mu.Unlock()
balance += amount
}
// 该函数创建两个goroutine来执行存款操作,并等待它们完成
// 最后打印出最终的余额,但由于竞态条件,结果是不确定的
func main() {
// 创建WaitGroup用于等待所有goroutine完成
var wg sync.WaitGroup
// 设置需要等待的goroutine数量为2
wg.Add(2)
// 启动第一个goroutine,存入10元
go Deposit(10, &wg)
// 启动第二个goroutine,存入20元
go Deposit(20, &wg)
// 等待所有goroutine完成
wg.Wait()
// 打印最终余额,现在使用了互斥锁,确保了结果是正确的
fmt.Println("Final balance:", balance) // Final balance: 130
}
3、sync.RWMutex 读写锁
- 适用于 读多写少 场景。
- 允许多个 reader 同时读,但 writer 必须独占。
- 方法:
RLock()/RUnlock()(读锁)Lock()/Unlock()(写锁)
Go
type SafeCache struct {
mu sync.RWMutex
cache map[string]string
}
// Get 从缓存中获取与给定键关联的值
// 该方法是并发安全的,因为它使用了读锁
//
// 参数:
// - key: 要在缓存中查找的字符串键
//
// 返回值:
// - string: 与键关联的值,如果未找到键则返回空字符串
func (c *SafeCache) Get(key string) string {
// 获取读锁以允许多个goroutine同时读取
c.mu.RLock()
defer c.mu.RUnlock()
return c.cache[key]
}
// Set 在缓存中设置键值对
// 该方法是线程安全的,使用互斥锁确保并发访问安全
//
// 参数:
// key: 用于存储值的字符串键
// value: 要存储在缓存中的字符串值
func (c *SafeCache) Set(key, value string) {
c.mu.Lock()
defer c.mu.Unlock()
c.cache[key] = value
}
4、内存同步(Memory Synchronization)
- 关键点:锁不仅保护临界区,还保证内存可见性
- 没有同步的情况下,一个 goroutine 的写可能对另一个不可见(CPU 缓存、编译器优化)
- sync 包的操作(如 mutex、channel)会建立 "happens-before" 关系,确保内存同步
- 不要用"volatile"或"原子读"来替代锁 ------ Go 没有 volatile,普通读写不保证可见性
5、sync.Once 惰性初始化
- 确保某段代码只执行一次,即使被多个 goroutine 调用
- 常用于单例、配置加载、连接池初始化
Go
var (
once sync.Once
dbConn *DB
)
func GetDB() *DB {
once.Do(func() {
dbConn = connectToDB() // 只会执行一次
})
return dbConn
}
6、竞争条件检测
-
使用
-race标志编译/运行 -
只能检测已发生的竞争,不能证明无竞争
-
性能开销大(2~20x),仅用于开发/测试
Go
go run -race main.go
go test -race ./...
go build -race
7、示例:并发的非阻塞缓存
结合 map + mutex + 函数闭包 实现带缓存的函数(memoization
Go
package main
import (
"fmt"
"sync"
"time"
)
type Memo struct {
f Func
cache map[string]*entry
mu sync.RWMutex
}
type Func func(key string) (interface{}, error)
type entry struct {
res result
ready chan struct{} // 用于等待结果就绪
}
type result struct {
value interface{}
err error
}
// New 创建一个新的 Memo 实例,使用给定的函数进行初始化
// 它会初始化缓存映射表,用于存储记忆化的结果
//
// 参数:
// f: 需要被记忆化的函数
//
// 返回值:
// 指向新创建的 Memo 实例的指针
func New(f Func) *Memo {
return &Memo{f: f, cache: make(map[string]*entry)}
}
// Get 是线程安全的
// Get 从缓存中获取指定键的值,如果键不存在则执行慢函数计算结果并缓存
// key: 要获取值的键
// value: 获取到的值
// err: 获取过程中可能发生的错误
func (memo *Memo) Get(key string) (value interface{}, err error) {
memo.mu.RLock()
e := memo.cache[key]
memo.mu.RUnlock()
if e != nil {
// 已存在:等待结果就绪(防止重复计算)
<-e.ready
return e.res.value, e.res.err
}
// 不存在:加写锁,双重检查
memo.mu.Lock()
e = memo.cache[key]
if e == nil {
// 第一次创建 entry
e = &entry{ready: make(chan struct{})}
memo.cache[key] = e
memo.mu.Unlock()
// 执行慢函数
e.res.value, e.res.err = memo.f(key)
close(e.ready) // 通知所有等待者
} else {
// 其他 goroutine 刚刚创建了 entry
memo.mu.Unlock()
<-e.ready
}
return e.res.value, e.res.err
}
// 参数:
// key - 用于生成结果的输入字符串
// 返回值:
// interface{} - 基于输入key生成的格式化结果字符串
// error - 错误信息,当前实现始终返回nil
func slowFunc(key string) (interface{}, error) {
// 模拟耗时操作,延迟1秒
time.Sleep(1 * time.Second)
return fmt.Sprintf("result for %s", key), nil
}
//Memoization模式的并发使用
// 该函数创建了一个带缓存的函数调用器,并通过多个goroutine并发调用
// 来展示缓存机制如何避免重复计算
func main() {
memo := New(slowFunc)
// 启动多个goroutine并发调用memo.Get方法
// 通过WaitGroup等待所有goroutine执行完成
// 所有goroutine都使用相同的key"test"来测试缓存效果
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(key string) {
defer wg.Done()
res, _ := memo.Get(key)
fmt.Println(res)
}("test")
}
wg.Wait()
}
十、反射
反射:在运行时检查变量的类型和值,并能动态调用方法、修改字段
Go 的反射基于两个核心类型:
reflect.Type:表示类型的元信息(如名称、字段、方法等)reflect.Value:表示值的可操作封装(可读、可写、可调用)
修改变量的值时必须传递指针(reflect.ValueOf(x).SetInt(20) → panic(不可寻址))
Go
func main() {
var x = 10
t := reflect.TypeOf(x) // Type: int
// ValueOf 返回的是副本,若想修改原变量,必须传指针
v := reflect.ValueOf(x) // Value: 10
fmt.Println(t.Name()) // 输出:"int"
fmt.Println(v.Int()) // 输出:10
// Elem() 解引用指针,使用指针修改变量值
v1 := reflect.ValueOf(&x).Elem() // Value: 10
if v1.CanSet() {
v1.SetInt(20)
}
// 简写:reflect.ValueOf(&x).Elem().SetInt(20)
fmt.Println(x) // 输出:20
}
使用反射遍历结构体字段(含标签)
Go
type User struct {
Name string `json:"name" validate:"required"`
Age int `json:"age"`
}
// 通过反射获取其字段名、值和JSON标签
func main() {
u := User{"Alice", 30}
v := reflect.ValueOf(u)
t := reflect.TypeOf(u)
// 遍历结构体的所有字段,输出字段名、值和JSON标签
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fieldType := t.Field(i)
fmt.Printf("字段名: %s\n", fieldType.Name)
fmt.Printf("值: %v\n", field.Interface())
fmt.Printf("JSON 标签: %s\n", fieldType.Tag.Get("json"))
}
}
使用反射动态调用方法
Go
type Greeter struct{}
func (g Greeter) SayHello(name string) string {
return "Hello, " + name
}
// 创建Greeter实例,通过反射获取并调用其SayHello方法,最后输出方法调用结果
func main() {
g := Greeter{}
v := reflect.ValueOf(g)
// 获取指定名称的方法
method := v.MethodByName("SayHello")
if !method.IsValid() {
panic("方法不存在")
}
// 准备方法调用的参数并执行方法调用
args := []reflect.Value{reflect.ValueOf("Go")}
results := method.Call(args)
// 输出方法返回的结果
fmt.Println(results[0].String()) // 输出:Hello, Go
}
创建新实例(类似 new(T))
Go
type User struct {
Name string `json:"name" validate:"required"`
Age int `json:"age"`
}
func main() {
var u User
t := reflect.TypeOf(u)
// 创建指针:相当于 new(User)
ptr := reflect.New(t)
elem := ptr.Elem() // 获取 *User 指向的 User 值
// 设置字段
elem.FieldByName("Name").SetString("Bob")
elem.FieldByName("Age").SetInt(25)
// 转回 interface{}
newUser := ptr.Interface().(*User)
fmt.Printf("%+v\n", *newUser) // {Name:Bob Age:25}
}