函数是Go语言的核心构建块,贯穿了从简单脚本到大型应用的所有开发场景。本文将系统梳理Go函数的定义规范、参数传递、作用域、高级特性(如递归、defer、闭包)等内容,结合代码示例帮助开发者全面掌握Go函数的使用技巧。
Go函数的定义遵循严格的语法规则,支持多返回值、参数简写等特性,确保代码的可读性和简洁性。
Go函数的核心结构由func关键字、函数名、参数列表、返回值列表和函数体组成,基本格式如下:
// 通用格式
func 函数名(参数列表) 返回值列表 {
// 函数体
}a, b int而非a int, b int)。int);(int, string));(sum int, avg float64)),此时函数内可直接return,无需显式指定返回值(按命名顺序返回)。// 普通多返回值:计算和与差
func calc(a, b int) (int, int) {
return a + b, a - b
}
// 命名返回值:计算乘积与商(避免返回值顺序混淆)
func multiplyAndDivide(a, b int) (product int, quotient int) {
product = a * b
if b != 0 {
quotient = a / b
}
return // 直接返回命名变量,无需显式传值
}Go中通过下划线_表示匿名变量,用于忽略函数的某个返回值(避免“未使用变量”编译错误)。
func main() {
sum, _ := calc(10, 5) // 忽略“差”的返回值
fmt.Println("和:", sum) // 输出:和:15
}当函数参数类型确定但数量不确定时,可使用可变参数,语法为参数名 ...类型(如nums ...int)。
len()获取参数个数,通过下标访问具体值;getSum(1,2,3)),或通过切片...传递已有的切片(如nums := []int{1,2,3}; getSum(nums...))。package main
import "fmt"
func main() {
// 直接传入多个值
total1 := getSum(10, 20, 30)
fmt.Println("总和1:", total1) // 输出:总和1:60
// 传递切片(需加...)
nums := []int{40, 50, 60}
total2 := getSum(nums...)
fmt.Println("总和2:", total2) // 输出:总和2:150
}
// 可变参数求和:nums...int 表示接收任意个int类型参数
func getSum(nums ...int) int {
sum := 0
fmt.Printf("参数个数:%d\n", len(nums)) // 输出参数总数
for i, v := range nums { // 遍历可变参数(切片)
fmt.Printf("参数%d:%d\n", i, v)
sum += v
}
return sum
}Go中变量的作用域分为局部变量和全局变量,遵循“就近原则”和“小作用域可访问大作用域变量”的规则。
if、for循环)中;package main
import "fmt"
func main() {
x := 10 // 函数内局部变量
if x > 5 {
x := 20 // 代码块内局部变量(与外层x同名)
fmt.Println("if块内x:", x) // 输出:20(就近原则)
}
fmt.Println("main函数内x:", x) // 输出:10(外层x未被修改)
}package main
import "fmt"
// 全局变量:包内所有函数可访问
var globalCount int = 0
func main() {
increment()
increment()
fmt.Println("全局计数:", globalCount) // 输出:2
}
func increment() {
globalCount++ // 访问并修改全局变量
}递归是解决“分治问题”(如阶乘、斐波那契数列)的常用方式,需满足两个核心条件:
stack overflow);package main
import "fmt"
func main() {
result := factorial(5)
fmt.Println("5的阶乘:", result) // 输出:120
}
// 递归函数:计算n!
func factorial(n int) int {
// 终止条件:n=1时返回1(1! = 1)
if n == 1 {
return 1
}
// 递推关系:n! = n * (n-1)!
return n * factorial(n-1)
}⚠️ 注意:递归深度过大会导致栈溢出(如
factorial(10000)),此时需改用迭代或尾递归优化(Go暂不支持尾递归优化)。
defer用于延迟函数或语句的执行,直到当前函数即将返回时才执行,常用于“资源清理”(如关闭文件、释放锁),避免资源泄漏。
defer按“后进先出(LIFO)”顺序执行(类似栈);defer语句中的函数参数在定义时就已计算,而非执行时。package main
import "fmt"
func main() {
fmt.Println("1")
defer fmt.Println("2") // 第1个defer
defer fmt.Println("3") // 第2个defer(后定义,先执行)
fmt.Println("4")
// 执行顺序:1 → 4 → 3 → 2
}package main
import "fmt"
func main() {
n := 10
// defer定义时,参数n的值已确定为10(即使后续n修改,也不影响)
defer fmt.Println("defer内n:", n)
n = 20
fmt.Println("main内n:", n) // 输出:main内n:20
// 最终输出:main内n:20 → defer内n:10
}package main
import (
"fmt"
"os"
)
func main() {
// 打开文件
file, err := os.Open("test.txt")
if err != nil {
fmt.Println("文件打开失败:", err)
return
}
// 延迟关闭文件:确保函数返回前执行,避免资源泄漏
defer file.Close()
// 后续文件读写操作...
fmt.Println("文件打开成功")
}在Go中,函数本身是一种“复合类型”,可赋值给变量、作为参数传递或作为返回值,这是函数式编程的基础。
函数类型由“参数类型”和“返回值类型”共同决定,格式为:
// 示例:接收两个int、返回一个int的函数类型
func(int, int) intpackage main
import "fmt"
func main() {
// 1. 将函数赋值给变量(函数名不加(),表示函数本身,而非调用)
var addFunc func(int, int) int = add
// 2. 通过变量调用函数
result := addFunc(3, 5)
fmt.Println("3+5 =", result) // 输出:8
// 简化写法:类型推导
subFunc := sub
fmt.Println("10-4 =", subFunc(10, 4)) // 输出:6
}
// 加法函数
func add(a, b int) int {
return a + b
}
// 减法函数
func sub(a, b int) int {
return a - b
}回调函数常用于“自定义逻辑注入”(如排序、过滤),使函数更灵活。
package main
import "fmt"
func main() {
// 1. 传递已定义的函数(加法、减法)
sum := calculate(10, 5, add)
diff := calculate(10, 5, sub)
fmt.Println("10+5 =", sum) // 输出:15
fmt.Println("10-5 =", diff) // 输出:5
// 2. 传递匿名函数(乘法、除法)
product := calculate(10, 5, func(a, b int) int {
return a * b
})
quotient := calculate(10, 5, func(a, b int) int {
if b == 0 {
fmt.Println("除数不能为0")
return 0
}
return a / b
})
fmt.Println("10*5 =", product) // 输出:50
fmt.Println("10/5 =", quotient) // 输出:2
}
// 高阶函数:接收两个int和一个回调函数,返回计算结果
func calculate(a, b int, op func(int, int) int) int {
return op(a, b) // 调用回调函数
}
// 加法回调函数
func add(a, b int) int {
return a + b
}
// 减法回调函数
func sub(a, b int) int {
return a - b
}闭包是Go中最强大的特性之一,核心定义:
package main
import "fmt"
func main() {
// 1. 创建第一个计数器(闭包1)
counter1 := newCounter()
fmt.Println(counter1()) // 输出:1
fmt.Println(counter1()) // 输出:2
// 2. 创建第二个计数器(闭包2,与counter1独立)
counter2 := newCounter()
fmt.Println(counter2()) // 输出:1(不受counter1影响)
fmt.Println(counter1()) // 输出:3(counter1继续自增)
}
// 外层函数:返回一个计数器函数(内层函数)
func newCounter() func() int {
// 外层函数的局部变量:仅闭包内可访问,避免全局污染
count := 0
// 内层函数:引用外层变量count
return func() int {
count++
return count
}
}⚠️ 注意:闭包会持有外层变量的引用,若不及时释放,可能导致内存泄漏(如长期存活的闭包引用大对象)。
Go中参数传递只有值传递(按值拷贝),但根据变量类型的不同,表现出类似“引用传递”的效果,核心取决于变量是“值类型”还是“引用类型”。
值类型包括:int、string、bool、float64、array等。
传递时会拷贝变量的副本,函数内修改副本不会影响原变量。
package main
import "fmt"
func main() {
arr := [3]int{1, 2, 3}
fmt.Println("修改前:", arr) // 输出:[1 2 3]
modifyArray(arr)
fmt.Println("修改后:", arr) // 输出:[1 2 3](原数组未变)
}
// 函数内修改的是数组副本
func modifyArray(a [3]int) {
a[0] = 100
fmt.Println("函数内:", a) // 输出:[100 2 3]
}引用类型包括:slice、map、chan等。
传递时拷贝的是“变量的地址”(而非数据本身),函数内通过地址修改数据,会影响原变量。
package main
import "fmt"
func main() {
slice := []int{1, 2, 3}
fmt.Println("修改前:", slice) // 输出:[1 2 3]
modifySlice(slice)
fmt.Println("修改后:", slice) // 输出:[100 2 3](原切片被修改)
}
// 函数内通过地址修改原切片数据
func modifySlice(s []int) {
s[0] = 100
fmt.Println("函数内:", s) // 输出:[100 2 3]
}Go函数的特性丰富且灵活,从基础的定义语法到高级的闭包、函数式编程,覆盖了开发中的各种场景。关键要点总结如下: