函数是基于特定输入执行特定任务、并可返回执行结果的代码块(Go 中的方法本质上也是函数,只是额外绑定了接收者)。
Go 函数基础
在编程语言中,函数用于将大问题拆分为若干具有特定功能或职责的小任务,从而提升代码复用性与可维护性。
函数声明
函数的声明方式如下:
func 函数名(参数列表) (返回值列表) {
函数体
}
例如:
func min(a int, b int) (result int, err error) {
if a < b {
result = a
} else {
result = b
}
err = nil
return
}
-
func为关键字,表示函数声明开始。 -
min为函数名。在同一个包中函数名必须唯一,并遵守 Go 的导出规则:以大写字母开头的函数可被其他包导入使用;以小写字母开头的函数仅包内可见。 - 函数名后的括号内是参数列表。参数的声明方式与变量一致。Go 支持变长参数:在参数类型前加
...表示可接收数量不定的实参。 - 参数列表之后是返回值列表。多数情况下只需按顺序写返回值类型;示例中为返回值命名,这称为具名返回值。
- 大括号内是函数体,包含具体实现代码。函数声明也可以没有函数体,用于声明在 Go 之外实现的函数(常见于汇编实现,通过链接器完成绑定)。
函数声明在语义上可视为将一个函数字面值赋给同名变量,例如:
var min = func(a int, b int) (result int, err error) {
if a < b {
result = a
} else {
result = b
}
err = nil
return
}
这种写法符合 Go 语法规则,因此可以理解为:声明了一个函数类型的变量。func 关键字、参数列表与返回值列表共同构成函数类型;参数与返回值的组合也称为函数签名(function signature),在比较函数类型是否一致时起决定性作用。
在描述函数签名时,通常会省略参数名与具名返回值的名字;因此即使参数名或具名返回值名不同,只要参数与返回值的类型序列一致,它们仍属于同一函数类型。
每个函数声明定义的函数,是对应函数类型的一个实例。
使用变量形式时,func(){} 被称为函数字面值,也常被称为匿名函数。
函数参数
在函数声明阶段,参数列表中的参数称为形式参数(形参) ;函数调用时传入的参数称为实际参数(实参) 。调用时实参与形参按位置绑定,编译器会检查实参的类型与数量是否匹配,否则会产生编译错误。
Go 中函数参数的传递形式是值传递:实参会被复制后传入形参。对于整型、数组、结构体等类型,复制的是其完整数据,开销与数据大小成正比;对于字符串、切片、map、chan、func 等类型,复制的是其内部“描述符/引用”(例如切片头、字符串头、map 头等),因此复制开销通常是固定的。这种仅复制描述符而非底层数据的行为常被称为浅拷贝。需要注意:即便是“引用类型”,它们仍然是值传递,只是值本身包含了对底层数据的引用。
在参数传递中有两种常见的“转换/包装”行为:
- 形参为接口类型:实参会被赋值到接口值中(接口值由动态类型与动态值组成)。
- 形参为变长参数:调用点会把零个或多个实参打包为一个对应类型的切片传入(也可用
slice...将已有切片展开为变长实参)。
示例:
package main
import "fmt"
// 演示 Go 中的函数参数传递方式
func main() {
commonPass(1, 2)
interfacePass("%d\n", 1)
variadic(1, 2, 3, 4)
}
func commonPass(x, y int) int {
fmt.Println("x + y =", x+y)
return x + y
}
func interfacePass(format string, i interface{}) {
fmt.Printf(format, i)
}
func variadic(args ...int) {
fmt.Printf("%T\n", args)
fmt.Printf("args len: %d\n", len(args))
for _, v := range args {
fmt.Println(v)
}
}
输出:
x + y = 3
1
[]int
args len: 4
1
2
3
4
函数返回值
Go 支持多返回值,错误处理也常依赖多返回值实现。返回值常见三种形式:
func foo() // 无返回值
func foo() error // 单返回值
func foo() (int, string, error) // 多返回值
- 无返回值:省略返回值列表
- 单返回值:返回值列表可省略括号
- 两个及以上返回值:返回值列表必须用括号包围
示例:
func returnOneValue() int {
result := 1
return result
}
func returnTwoValue() (int, int) {
result1 := 1
result2 := 2
return result1, result2
}
func returnThreeValue() (int, int, int) {
result1 := 1
result2 := 2
result3 := 3
return result1, result2, result3
}
带名字的返回值称为具名返回值。具名返回值会在函数开始时被声明并初始化为零值,作用域覆盖整个函数体,因此在函数体内不需要(也不应)再次用 := 重新声明同名变量,否则会导致编译错误或变量遮蔽问题。若具名返回值能显著提升可读性(尤其是多返回值场景),则可以使用。
具名返回值的常见场景:
- 多返回值:将返回值含义直接体现在签名中,可在末尾直接
return返回当前变量值。- 需要在
defer中修改外层函数返回值时:具名返回值更直观。使用惯例:
- 通常将
error作为最后一个返回值- 返回值数量不宜过多:通常 1~2 个,尽量不超过 3 个
func returnValueWithName() (v1 int, v2 int, v3 int) {
v1 = 1
v2 = 2
v3 = 3
return
}
一等公民
如果一门语言允许某种元素像普通值一样被创建、赋值、作为参数传递、在函数中生成并作为返回值返回,那么该元素就是这门语言的“一等公民”。
特征 1:函数可以存储在变量中
package main
import "fmt"
var f1 = func(x int) int { return x + 1 }
func main() {
fmt.Printf("%T\n", f1) // func(int) int
fmt.Println(f1(5)) // 6
}
函数可以保存在变量中,并具有明确的函数类型。
特征 2:支持在函数内创建并通过返回值返回
package main
import "fmt"
func main() {
removeHandler := setupListener()
defer removeHandler()
}
func setupListener() func() {
fmt.Println("setup listener")
removeListener := func() {
fmt.Println("remove listener")
}
return removeListener
}
返回的函数可以访问 setupListener 中的局部变量,这类捕获外部变量的函数字面值称为闭包(closure) 。只要闭包仍可达,被捕获的变量就可能继续存活。
特征 3:函数可以作为参数传入函数
package main
import "fmt"
func funcAsParam(a int, b int, f func(int, int) int) int {
return f(a, b)
}
func main() {
fmt.Println(funcAsParam(1, 2, func(a int, b int) int { return a + b }))
}
特征 4:函数拥有自己的类型
每个函数声明定义的是对应函数类型的一个实例,也可以基于函数类型定义新的类型:
package main
import "fmt"
type MathOperation func(int, int) int
func applyOperation(x int, y int, op MathOperation) int {
return op(x, y)
}
func main() {
fmt.Println(applyOperation(1, 2, func(a, b int) int { return a * b }))
}
defer 简化函数实现
在 Go 中访问 IO、网络连接、锁、文件等资源时,使用完毕应及时释放。若同时使用多个资源,手动在多分支路径上关闭会增加复杂度并降低可读性,Go 提供 defer 机制用于统一收尾。
defer 机制
-
defer是一种延迟调用机制。 - 只能在函数或方法内部使用。
-
defer后必须是一次函数/方法调用(该调用的返回值若有,会在语义上被丢弃)。 -
defer 注册的调用会绑定到当前 goroutine,并在当前函数返回前按**后进先出(LIFO)**顺序执行。 - 无论函数是正常返回还是中途
return,已注册的defer 都会执行;若发生panic,在展开栈的过程中也会执行已注册的defer(除非进程直接终止等极端情况)。

package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
defer deferTest(i)
}
}
func deferTest(order int) {
fmt.Printf("Defer test %d completed\n", order)
}
输出:
Defer test 2 completed
Defer test 1 completed
Defer test 0 completed
使用 defer 的注意事项
明确哪些调用可以直接 defer
- 自定义函数/方法:可以直接
defer(其返回值会被丢弃)。 - 内置函数中,可直接作为
defer 调用的常见有:close、copy、delete、panic、print、println、recover、clear(Go 1.21+)。 - 不能直接
defer 的内置函数:append、cap、len、make、new、real、imag、complex、min、max等(它们不是“可调用的函数值”,而是编译器内建指令/特殊形式)。 - 对于不能直接
defer的内置能力,通常通过包裹一层匿名函数实现延迟执行:
defer func() {
_ = append(s, 1) // 示例:仅演示语法;实际要使用返回值
}()
注意表达式的求值时机
defer 语句在注册时就会对被调用函数值以及实参进行求值并保存;真正的调用发生在函数返回前。
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
defer func(order int) {
fmt.Printf("Defer %d executed\n", order)
}(i)
}
fmt.Println("Main function completed")
}
输出:
Main function completed
Defer 2 executed
Defer 1 executed
Defer 0 executed
以上例子中,匿名函数在注册 defer 时就把当次循环的 i 作为参数值保存下来;由于 defer 采用栈结构管理,因此按后进先出顺序执行。
性能损耗
早期版本的 Go 中 defer 有较明显开销;从 Go 1.14 起引入了更激进的优化(如 open-coded defer 等),在很多常见场景下开销已显著降低。但在极端高频、极致性能敏感的路径中,仍应结合基准测试决定是否使用 defer。