Go 函数
函数定义
基本语法
package main
import "fmt"
// 基本函数定义
func add(a int, b int) int {
return a + b
}
// 参数类型相同可以简写
func subtract(a, b int) int {
return a - b
}
// 多个返回值
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为 0")
}
return a / b, nil
}
// 命名返回值
func multiply(a, b int) (result int) {
result = a * b // 直接使用命名返回值
return // 自动返回 result
}
// 多个命名返回值
func calculate(a, b int) (sum, product int) {
sum = a + b
product = a * b
return // 自动返回 sum 和 product
}
func main() {
// 调用函数
result := add(3, 5)
fmt.Println(result) // 8
// 多个返回值
quotient, err := divide(10, 2)
if err != nil {
fmt.Printf("错误: %v\n", err)
} else {
fmt.Printf("结果: %d\n", quotient) // 5
}
// 忽略返回值
_, err2 := divide(10, 0)
if err2 != nil {
fmt.Printf("错误: %v\n", err2)
}
// 命名返回值
sum, product := calculate(3, 4)
fmt.Printf("和: %d, 积: %d\n", sum, product) // 和: 7, 积: 12
}可变参数
package main
import "fmt"
// 可变参数函数
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
// 可变参数必须在最后
func printInfo(name string, numbers ...int) {
fmt.Printf("姓名: %s, 数字: %v\n", name, numbers)
}
// 展开切片作为可变参数
func main() {
// 调用可变参数函数
result1 := sum(1, 2, 3, 4, 5)
fmt.Println(result1) // 15
result2 := sum(10, 20)
fmt.Println(result2) // 30
result3 := sum() // 可以传入 0 个参数
fmt.Println(result3) // 0
// 展开切片
numbers := []int{1, 2, 3, 4, 5}
result4 := sum(numbers...) // 使用 ... 展开切片
fmt.Println(result4) // 15
// 混合参数
printInfo("Alice", 1, 2, 3)
}函数作为值
package main
import "fmt"
// 函数可以作为值传递
func add(a, b int) int {
return a + b
}
func multiply(a, b int) int {
return a * b
}
// 函数类型
type Operation func(int, int) int
// 函数作为参数
func calculate(a, b int, op Operation) int {
return op(a, b)
}
// 函数作为返回值
func getOperation(opType string) Operation {
switch opType {
case "add":
return add
case "multiply":
return multiply
default:
return func(a, b int) int { return 0 } // 匿名函数
}
}
func main() {
// 函数作为值
var op Operation = add
result := op(3, 5)
fmt.Println(result) // 8
// 函数作为参数
result1 := calculate(3, 5, add)
result2 := calculate(3, 5, multiply)
fmt.Printf("add: %d, multiply: %d\n", result1, result2)
// 函数作为返回值
addOp := getOperation("add")
result3 := addOp(10, 20)
fmt.Println(result3) // 30
}匿名函数和闭包
package main
import "fmt"
func main() {
// 匿名函数
func() {
fmt.Println("匿名函数")
}() // 立即执行
// 匿名函数赋值给变量
greet := func(name string) {
fmt.Printf("Hello, %s!\n", name)
}
greet("Alice")
// 闭包:函数可以访问外部变量
counter := 0
increment := func() {
counter++ // 访问外部变量
fmt.Printf("计数: %d\n", counter)
}
increment() // 计数: 1
increment() // 计数: 2
increment() // 计数: 3
// 闭包示例:生成器
generator := createGenerator()
fmt.Println(generator()) // 1
fmt.Println(generator()) // 2
fmt.Println(generator()) // 3
// 闭包陷阱:循环变量
var funcs []func()
for i := 0; i < 3; i++ {
// 错误写法:所有函数都引用同一个 i
// funcs = append(funcs, func() {
// fmt.Println(i) // 都会打印 3
// })
// 正确写法:创建局部变量
i := i // 创建新的局部变量
funcs = append(funcs, func() {
fmt.Println(i) // 打印 0, 1, 2
})
}
for _, f := range funcs {
f()
}
}
// 生成器函数
func createGenerator() func() int {
count := 0
return func() int {
count++
return count
}
}defer 和函数
package main
import "fmt"
func main() {
// defer 在函数返回前执行
deferExample()
// defer 可以修改命名返回值
result := deferReturn()
fmt.Println(result) // 1(不是 0)
// defer 与 panic/recover
deferPanic()
}
func deferExample() {
fmt.Println("开始")
defer fmt.Println("延迟 1")
defer fmt.Println("延迟 2")
fmt.Println("结束")
// 输出:
// 开始
// 结束
// 延迟 2
// 延迟 1
}
func deferReturn() (result int) {
defer func() {
result++ // 修改返回值
}()
return 0 // 实际返回 1
}
func deferPanic() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("捕获到 panic: %v\n", r)
}
}()
panic("发生错误")
fmt.Println("这行不会执行")
}方法(Method)
package main
import "fmt"
// 定义类型
type Rectangle struct {
Width float64
Height float64
}
// 值接收者方法
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 指针接收者方法(可以修改接收者)
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
// 值接收者 vs 指针接收者
type Point struct {
X, Y int
}
// 值接收者:不会修改原始值
func (p Point) MoveByValue(dx, dy int) {
p.X += dx
p.Y += dy
}
// 指针接收者:会修改原始值
func (p *Point) MoveByPointer(dx, dy int) {
p.X += dx
p.Y += dy
}
func main() {
// 使用值接收者方法
rect := Rectangle{Width: 10, Height: 5}
area := rect.Area()
fmt.Printf("面积: %.2f\n", area) // 50.00
// 使用指针接收者方法
rect.Scale(2)
fmt.Printf("缩放后: %.2f x %.2f\n", rect.Width, rect.Height) // 20.00 x 10.00
// 值接收者 vs 指针接收者
point := Point{X: 1, Y: 2}
point.MoveByValue(10, 10)
fmt.Printf("值接收者: %+v\n", point) // {X:1 Y:2}(未改变)
point.MoveByPointer(10, 10)
fmt.Printf("指针接收者: %+v\n", point) // {X:11 Y:12}(已改变)
}接口和函数
package main
import "fmt"
// 接口定义
type Writer interface {
Write([]byte) (int, error)
}
// 函数类型实现接口
type WriterFunc func([]byte) (int, error)
func (f WriterFunc) Write(p []byte) (int, error) {
return f(p)
}
// 使用示例
func main() {
// 普通函数
writeFunc := func(data []byte) (int, error) {
fmt.Println(string(data))
return len(data), nil
}
// 转换为接口
var writer Writer = WriterFunc(writeFunc)
writer.Write([]byte("Hello, World!"))
}函数最佳实践
package main
import (
"fmt"
"io"
"os"
)
// 1. 错误处理:总是返回错误
func readFile(filename string) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err // 返回错误
}
defer file.Close()
return io.ReadAll(file)
}
// 2. 函数应该简短,职责单一
// 不好的例子:一个函数做太多事情
func badFunction() {
// 读取文件
// 处理数据
// 写入数据库
// 发送邮件
// ...
}
// 好的例子:拆分成多个函数
func goodFunction() {
data := readData()
processed := processData(data)
saveData(processed)
sendNotification()
}
func readData() []byte {
// 读取数据
return nil
}
func processData(data []byte) []byte {
// 处理数据
return nil
}
func saveData(data []byte) {
// 保存数据
}
func sendNotification() {
// 发送通知
}
// 3. 使用命名返回值提高可读性
func divide(a, b int) (quotient int, remainder int, err error) {
if b == 0 {
err = fmt.Errorf("除数不能为 0")
return
}
quotient = a / b
remainder = a % b
return
}
// 4. 使用 defer 进行资源清理
func processFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close() // 确保文件被关闭
// 处理文件
return nil
}
// 5. 函数文档注释
// CalculateSum 计算两个整数的和
// 参数:
// a: 第一个整数
// b: 第二个整数
// 返回:
// 两个整数的和
func CalculateSum(a, b int) int {
return a + b
}总结
Go 函数的特点:
- 多返回值:可以返回多个值,常用于错误处理
- 命名返回值:提高代码可读性
- 可变参数:使用
...语法 - 函数作为值:函数是一等公民
- 闭包:函数可以访问外部变量
- 方法:可以为类型定义方法
- defer:延迟执行,用于资源清理
掌握这些函数特性后,就可以编写灵活、可复用的代码了!