3·基础入门入门

函数与多返回值

funcreturnmultiple-return

第3课 函数与多返回值

学习目标

  1. 掌握Go语言中函数的定义与基本调用
  2. 理解并熟练运用函数的多返回值特性
  3. 学会使用命名返回值简化代码
  4. 了解可变参数函数的使用方法
  5. 掌握函数作为值的基本用法

核心概念

1. 函数的基本定义

在Go中,函数使用func关键字定义,就像一个“加工厂”,接受输入参数,经过处理后返回结果。

// 函数定义格式
func 函数名(参数列表) 返回类型 {
    // 函数体
    return 返回值
}

2. 多返回值 - Go的独特特性

Go函数可以返回多个值,这是Go语言的一大特色。特别适合同时返回结果和错误信息。

3. 命名返回值

可以在函数定义时给返回值命名,这样函数体内可以直接使用这些变量,最后使用return关键字时可以不带任何变量(称为“裸返回”)。

4. 可变参数函数

使用...语法定义可以接受任意数量参数的函数。

代码示例

示例1:基础函数与多返回值

package main

import "fmt"

// 计算圆的面积和周长 - 返回两个值
func circleProperties(radius float64) (float64, float64) {
    area := 3.14159 * radius * radius      // 面积 = π * r²
    circumference := 2 * 3.14159 * radius  // 周长 = 2 * π * r
    return area, circumference
}

// 带错误检查的除法函数
func safeDivide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("除数不能为零")
    }
    return a / b, nil
}

func main() {
    // 调用多返回值函数
    area, circum := circleProperties(5.0)
    fmt.Printf("半径为5的圆:面积=%.2f, 周长=%.2f\n", area, circum)
    
    // 使用空白标识符忽略不需要的返回值
    areaOnly, _ := circleProperties(3.0)
    fmt.Printf("半径为3的圆面积: %.2f\n", areaOnly)
    
    // 使用带错误检查的函数
    result, err := safeDivide(10, 0)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Println("结果:", result)
    }
    
    result, err = safeDivide(10, 2)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Printf("10 / 2 = %.1f\n", result)
    }
}

示例2:命名返回值与可变参数

package main

import "fmt"

// 命名返回值的函数
func rectangleProperties(length, width float64) (area, perimeter float64) {
    area = length * width          // 直接使用命名返回值变量
    perimeter = 2 * (length + width)
    return  // 裸返回,自动返回上面计算的值
}

// 可变参数函数 - 计算任意个数数字的平均值
func average(numbers ...float64) float64 {
    if len(numbers) == 0 {
        return 0
    }
    
    total := 0.0
    for _, num := range numbers {
        total += num
    }
    return total / float64(len(numbers))
}

// 带命名返回值和错误检查的函数
func parseAge(ageStr string) (age int, err error) {
    // 这里简化处理,实际应使用strconv包解析
    if ageStr == "" {
        return 0, fmt.Errorf("年龄字符串不能为空")
    }
    return 25, nil  // 示例返回固定值
}

func main() {
    // 使用命名返回值函数
    area, perimeter := rectangleProperties(5, 3)
    fmt.Printf("长方形(5x3): 面积=%.1f, 周长=%.1f\n", area, perimeter)
    
    // 使用可变参数函数
    fmt.Printf("平均值: %.2f\n", average(1, 2, 3, 4, 5))
    fmt.Printf("平均值: %.2f\n", average(10, 20))
    fmt.Printf("平均值: %.2f\n", average())  // 无参数
    
    // 将切片传递给可变参数函数
    scores := []float64{85, 92, 78, 90, 88}
    fmt.Printf("平均分: %.1f\n", average(scores...))  // 使用...展开切片
}

示例3:函数作为值

package main

import (
    "fmt"
    "strings"
)

// 函数作为参数
func applyToStrings(strs []string, operation func(string) string) []string {
    result := make([]string, len(strs))
    for i, s := range strs {
        result[i] = operation(s)
    }
    return result
}

func main() {
    // 定义匿名函数
    toUpper := func(s string) string {
        return strings.ToUpper(s)
    }
    
    toLower := func(s string) string {
        return strings.ToLower(s)
    }
    
    names := []string{"Alice", "Bob", "Charlie", "David"}
    
    // 使用函数作为参数
    upperNames := applyToStrings(names, toUpper)
    fmt.Println("大写:", upperNames)
    
    lowerNames := applyToStrings(names, toLower)
    fmt.Println("小写:", lowerNames)
    
    // 直接传递匿名函数
    prefixNames := applyToStrings(names, func(s string) string {
        return "Mr. " + s
    })
    fmt.Println("添加前缀:", prefixNames)
}

实践练习

练习1:基础函数练习

要求:编写一个函数calculateRectangle,接受长方形的长和宽作为参数,返回面积和周长。在main函数中调用该函数并打印结果。

预期输出

长方形(长=8, 宽=5): 面积=40, 周长=26

练习2:错误处理练习

要求:编写函数calculateSquareRoot,接受一个float64参数,返回它的平方根和错误。如果输入为负数,返回错误信息"不能计算负数的平方根"。在main函数中分别测试正数、负数和零的情况。

预期输出

9的平方根: 3.00
-4的平方根: 错误: 不能计算负数的平方根
0的平方根: 0.00

练习3:可变参数练习

要求:编写函数findMinMax,接受任意数量的整数,返回其中的最小值和最大值。使用可变参数实现。在main函数中测试不同数量的参数。

预期输出

findMinMax(3, 7, 1, 9, 4) -> 最小值: 1, 最大值: 9
findMinMax(10, 20) -> 最小值: 10, 最大值: 20
findMinMax(5) -> 最小值: 5, 最大值: 5

常见错误

1. 忽略多返回值

// 错误:忽略了第二个返回值
result, _ := someFunction()
// 正确:如果确实不需要,使用空白标识符_忽略

2. 混淆命名返回值和裸返回

// 错误示例
func badExample() (result int) {
    result = 10
    return 20  // 这会返回20,而不是命名返回值result的10
}

// 正确示例
func goodExample() (result int) {
    result = 10
    return  // 返回result的值10
}

3. 可变参数类型错误

// 错误:混合不同类型的参数
func wrongVariadic(args ...interface{}) {
    // 这样会失去类型安全性
}

// 正确:明确参数类型
func rightVariadic(nums ...int) {
    // 类型安全
}

4. 函数作为值时的常见误解

// 错误:试图直接调用函数值的函数
func add(a, b int) int { return a + b }
var operation func(int, int) int = add
// 错误: operation.Method() - 函数没有方法

// 正确:直接调用
result := operation(1, 2)

小结

  1. 函数定义:使用func关键字,可以定义参数和返回值类型
  2. 多返回值:Go函数可以返回多个值,特别适合同时返回结果和错误
  3. 命名返回值:在函数定义时命名返回值,可以简化代码并提高可读性
  4. 可变参数:使用...语法定义接受任意数量参数的函数
  5. 函数作为值:Go中函数是一等公民,可以赋值给变量、作为参数传递

关键要点

  • 多返回值是Go处理错误的惯用方式
  • 命名返回值可以减少重复的return语句
  • 可变参数提供了处理不确定数量参数的灵活性
  • 函数作为值使得代码更加模块化和可复用

通过本课学习,你应该能够编写基本的Go函数,使用多返回值处理正常结果和错误情况,并理解Go函数的高级特性。这些知识将为后续学习并发编程打下坚实基础。

练习编辑器

go
Loading...

继续学习

完成本课后,建议继续学习下一课「if/else 与循环」 以巩固所学知识。