go函数定义必须显式声明参数和返回类型,不支持类型推导;参数为值传递,map/slice等修改底层数组可影响外部,但重赋值无效;变参须在末尾且类型一致;闭包捕获变量而非值。

Go 函数定义必须显式声明每个参数类型和返回类型
Go 不支持类型推导,func 声明里漏写任一参数或返回值的类型,编译直接报错。比如 func add(a, b int) int 合法,但 func add(a, b) int 或 func add(a int, b int)(缺返回类型)都不行。
常见错误是把多个同类型参数简写成 a, b int 误以为是“批量声明”,其实这是合法语法——但仅限于相邻且类型相同;一旦中间夹了不同类型的参数,就必须逐个写类型,比如 func process(name string, age int, active bool),不能缩成 name, age, active string, int, bool。
- 返回值若为单个,可省略括号:
func now() time.Time - 多个返回值必须用括号包裹:
func split(s string) (string, string) - 命名返回值(如
func count() (n int))会让函数体中直接用n = 42赋值,但容易掩盖逻辑,不建议在复杂函数中使用
Go 所有函数参数都是值传递,包括 slice、map、channel、interface
这点最容易误解:看到 func modify(m map[string]int) 就以为“传的是引用”,实际上传的是 map 类型的 header 值(包含指针字段),所以修改 m["k"] = v 会影响原 map;但若在函数内重新赋值 m = make(map[string]int),外部变量完全不受影响。
真正“不可变”的是 struct 和数组:传入 [3]int 或 Person{},函数内任何改动对外都不可见;想改就得传指针 *Person。
立即学习“go语言免费学习笔记(深入)”;
-
slice同理:能改底层数组元素,但不能通过s = append(s, x)改变原始 slice 的长度/容量(除非原 slice 是指针解引用而来) -
string是只读字节序列,传参后无法修改内容,也不需要指针 - 性能上,小 struct(如
type Point struct{ x, y float64 })传值开销小;大 struct 建议传*T避免拷贝
调用函数时,实参与形参数量、顺序、类型必须严格匹配
Go 没有默认参数、没有重载、不支持按名传参。少传、多传、类型不符,编译器立刻报错,比如 add(1, "2") 会提示 cannot use "2" (type string) as type int in argument to add。
唯一例外是变参函数(...T),但必须放在参数列表末尾,且调用时要么传零个,要么传同类型多个值,或一个切片加 ... 展开:
func sum(nums ...int) int {
s := 0
for _, n := range nums {
s += n
}
return s
}
// 正确调用:
sum(1, 2, 3)
sum([]int{1,2,3}...)
// 错误调用:
sum(1, []int{2,3}...) // 类型混用,编译失败
- 变参本质是语法糖,函数内接收到的是一个
[]T,不是“可选参数” - 如果已有切片想传给非变参函数(比如
fmt.Println),必须用...展开,否则类型不匹配 - 不要为了“兼容性”在已有函数末尾加
...interface{},这会让调用意图模糊,也掩盖真实接口需求
闭包捕获变量时,捕获的是变量本身,不是当时值
这是并发和循环中高频踩坑点。比如在 for 循环里启动 goroutine 并引用循环变量,所有 goroutine 最终看到的是循环结束后的最终值。
典型错误写法:
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i) // 全部打印 3
}()
}
- 修复方式之一:在循环内用局部变量绑定当前值,
val := i; go func() { fmt.Println(val) }() - 修复方式之二:把
i作为参数传入闭包,go func(v int) { fmt.Println(v) }(i) - 注意:闭包捕获的是栈/堆上的变量地址,只要变量生命周期没结束,闭包就能访问;但如果变量已释放(如函数返回后局部变量),再访问就是未定义行为
函数是一等公民,但闭包不是“快照”,它活在变量的生命周期里——这点比参数传递机制更隐蔽,也更常出问题。










