函数定义与调用
函数定义语法
函数定义使用 func
关键字,基本结构为:
go
func 函数名(参数列表) (返回值列表) {
// 函数体
}
其中:
- 参数列表由逗号分隔的参数变量及其类型组成
- 返回值列表可以是单个返回值或多个返回值(用括号括起来)
- 函数体是实现功能的代码块
函数示例类型
无参数无返回值函数:
go
func greet() {
fmt.Println("Hello, World!")
}
// 调用:greet()
带参数函数:
go
func add(a int, b int) int {
return a + b
}
// 调用:sum := add(3, 5)
多返回值函数:
go
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// 调用:
// result, err := divide(10.0, 2.0)
// if err != nil {
// log.Fatal(err)
// }
函数调用与作用域
函数调用通过函数名称加括号完成,如:
go
result := add(3, 5)
作用域规则:
- 函数内部定义的变量(包括参数)只在函数内部可见
- 函数可以访问外部包级变量(全局变量)
- 参数和返回值也有自己的作用域
- 函数内部可以定义与外部同名的变量,此时会"遮蔽"外部变量
go
var x = 10 // 包级变量
func example() {
x := 20 // 遮蔽外部x
fmt.Println(x) // 输出20
}
函数参数与返回值深入
值传递与引用传递
值传递:默认方式,传递参数的副本
go
func modifyValue(x int) {
x = x + 10 // 不影响原始值
fmt.Println("函数内x:", x) // 输出15
}
// 调用:
// a := 5
// modifyValue(a)
// fmt.Println("原始a:", a) // 输出5
引用传递:通过指针传递引用
go
func modifyPointer(x *int) {
*x = *x + 10 // 修改原始值
fmt.Println("函数内*x:", *x) // 输出15
}
// 调用:
// b := 5
// modifyPointer(&b)
// fmt.Println("原始b:", b) // 输出15
可变参数
使用 ...
语法接受不定数量参数:
go
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
// 调用方式:
// result := sum(1, 2, 3) // 传递多个参数
// nums := []int{1, 2, 3}
// result := sum(nums...) // 传递切片
命名返回值
可以给返回值命名,在函数体内直接使用:
go
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // 自动返回x,y
}
// 调用:
// a, b := split(100)
注意事项:
- 命名返回值会被初始化为零值
- 可能导致代码可读性下降,需谨慎使用
- 适用于返回值含义明确且简短的情况
高阶函数特性
函数作为参数
实现回调模式:
go
func process(data string, callback func(string)) {
// 预处理...
processed := strings.ToUpper(data)
callback(processed)
}
// 使用:
process("hello", func(s string) {
fmt.Println("处理结果:", s) // 输出: 处理结果: HELLO
})
匿名函数与闭包
匿名函数:
go
func() {
fmt.Println("立即执行函数")
}() // 立即调用
// 赋值给变量
greet := func(name string) {
fmt.Println("Hello,", name)
}
greet("Alice") // 输出: Hello, Alice
闭包示例(状态保持):
go
func counter() func() int {
i := 0
return func() int {
i++
return i
}
}
// 使用:
c := counter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
fmt.Println(c()) // 3
d := counter() // 新的计数器实例
fmt.Println(d()) // 1
defer 关键字
延迟执行机制:
go
func readFile() error {
file, err := os.Open("file.txt")
if err != nil {
return err
}
defer file.Close() // 确保函数退出时关闭文件
// 处理文件内容...
return nil
}
陷阱与注意事项:
-
defer 语句在函数返回前执行,但参数在 defer 声明时求值
gofunc example() { x := 5 defer fmt.Println("x =", x) // 输出5,因为x的值在defer时已经确定 x = 10 }
-
多个 defer 按后进先出顺序执行
gofunc example() { defer fmt.Println("first") defer fmt.Println("second") defer fmt.Println("third") // 输出顺序: third, second, first }
-
defer 可能影响性能关键路径,在性能敏感代码中应避免过多使用
错误处理与恢复
错误处理惯例
Go 惯用返回 error 类型表示错误:
go
func doSomething() error {
if err := check(); err != nil {
return fmt.Errorf("检查失败: %w", err) // 使用%w包装错误
}
return nil
}
// 调用:
if err := doSomething(); err != nil {
log.Printf("操作失败: %v", err)
}
自定义错误
定义错误类型:
go
type MyError struct {
Code int
Message string
}
func (e *MyError) Error() string {
return fmt.Sprintf("错误%d: %s", e.Code, e.Message)
}
// 使用:
func validate(input string) error {
if len(input) < 5 {
return &MyError{Code: 400, Message: "输入太短"}
}
return nil
}
错误包装:
go
func processFile(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("读取文件失败: %w", err)
}
// 处理数据...
return nil
}
panic 和 recover
panic 用于不可恢复错误:
go
func mustPositive(n int) {
if n <= 0 {
panic("必须为正数")
}
}
recover 捕获 panic:
go
func safeCall() {
defer func() {
if r := recover(); r != nil {
log.Println("捕获到panic:", r)
// 可以进行恢复操作或清理工作
}
}()
mayPanic()
}
func mayPanic() {
panic("意外错误")
}
最佳实践:
- 避免在库函数中使用 panic
- 只在程序无法继续执行时使用 panic
- 确保资源在 panic 后仍能正确释放
- 对于可预期的错误情况,应使用 error 而不是 panic
性能优化与技巧
函数内联
内联条件:
- 函数体简单(通常不超过40条指令)
- 没有复杂的控制流
- 非接口方法
可手动禁止内联:
go
//go:noinline
func smallButNoInline() {
// 简单但禁止内联的函数
}
内存分配优化
减少内存分配技巧:
重用缓冲区:
go
var buf bytes.Buffer
for i := 0; i < 100; i++ {
buf.Reset()
buf.WriteString("iteration ")
buf.WriteString(strconv.Itoa(i))
fmt.Println(buf.String())
}
预分配切片:
go
func process(items []Item) []Result {
results := make([]Result, 0, len(items)) // 预分配容量
for _, item := range items {
results = append(results, processItem(item))
}
return results
}
基准测试
使用 testing 包进行性能测试:
go
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
add(3, 5)
}
}
// 运行: go test -bench=.
闭包性能
避免闭包滥用:
go
// 不推荐:每次循环创建闭包
for i := 0; i < n; i++ {
go func() {
fmt.Println(i) // 可能捕获到相同的i
}()
}
// 推荐:显式传递参数
for i := 0; i < n; i++ {
go func(x int) {
fmt.Println(x)
}(i)
}
实际应用案例
HTTP 路由处理
go
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "欢迎访问首页")
})
http.HandleFunc("/user", userHandler)
http.ListenAndServe(":8080", nil)
}
func userHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
handleGetUser(w, r)
case "POST":
handlePostUser(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
并发安全调用
使用 sync.Once 确保只执行一次:
go
var (
instance *Singleton
once sync.Once
)
func GetInstance() *Singleton {
once.Do(func() {
instance = &Singleton{
initialized: time.Now(),
}
})
return instance
}
标准库模式
sort.Slice 示例:
go
people := []struct {
Name string
Age int
}{
{"Alice", 25},
{"Bob", 30},
{"Charlie", 20},
}
// 按年龄排序
sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age
})
fmt.Println(people) // [{Charlie 20} {Alice 25} {Bob 30}]
常见问题与陷阱
循环中的值捕获
典型问题:
go
for _, val := range values {
go func() {
fmt.Println(val) // 所有goroutine可能打印相同的值
}()
}
解决方案:
go
for _, val := range values {
go func(v interface{}) {
fmt.Println(v) // 正确捕获当前值
}(val)
}
可变参数与切片
区别:
go
func f(slice []int) {} // 接受切片
func g(nums ...int) {} // 接受可变参数
slice := []int{1,2,3}
f(slice) // 直接传递
g(slice...) // 需要展开
defer 陷阱
常见问题:
go
func f() (x int) {
defer func() { x++ }()
return 5 // 实际返回6
}
资源释放时机:
go
func read() error {
r, err := acquireResource()
if err != nil {
return err
}
defer r.Release() // 正确释放
// 处理逻辑...
if err := process(r); err != nil {
return err // Release()仍会被调用
}
return nil
}
零值返回与 nil 判断
注意事项:
go
func returnsError() error {
var err *MyError // nil
return err // 返回非nil的error接口值
}
if err := returnsError(); err != nil {
// 会进入此分支,因为接口值包含nil指针
}
正确做法:
go
func returnsError() error {
var err *MyError
if err == nil {
return nil // 显式返回nil
}
return err
}