Skip to content

returns.go

文件信息

  • 📄 原文件:02_returns.go
  • 🔤 语言:go

完整代码

go
package main

import (
	"errors"
	"fmt"
	"strconv"
)

// ============================================================
//                      多返回值与命名返回值
// ============================================================
// Go 函数可以返回多个值,这是 Go 的重要特性
// 最常见的用法是返回 (结果, 错误)

func main02() {
	fmt.Println("\n==================== 02_returns ====================")
	fmt.Println("=== 多返回值 ===")

	// ----------------------------------------------------------
	// 基本多返回值
	// ----------------------------------------------------------
	q, r := divide(17, 5)
	fmt.Printf("17 / 5 = %d%d\n", q, r)

	// ----------------------------------------------------------
	// 忽略部分返回值
	// ----------------------------------------------------------
	// 使用 _ 忽略不需要的返回值
	quotient, _ := divide(20, 3)
	fmt.Println("20 / 3 的商:", quotient)

	_, remainder := divide(20, 3)
	fmt.Println("20 / 3 的余数:", remainder)

	// ----------------------------------------------------------
	// 返回值 + 错误处理(Go 的惯用模式)
	// ----------------------------------------------------------
	fmt.Println("\n=== 错误处理模式 ===")

	// 【重要】Go 的错误处理惯例:
	// - 错误作为最后一个返回值
	// - 调用后立即检查错误
	// - 错误为 nil 表示成功

	result, err := safeDivide(10, 2)
	if err != nil {
		fmt.Println("错误:", err)
	} else {
		fmt.Println("10 / 2 =", result)
	}

	result, err = safeDivide(10, 0)
	if err != nil {
		fmt.Println("错误:", err)
	} else {
		fmt.Println("结果:", result)
	}

	// 【技巧】if 带初始化的错误检查(更紧凑)
	if res, err := safeDivide(100, 4); err != nil {
		fmt.Println("错误:", err)
	} else {
		fmt.Println("100 / 4 =", res)
	}

	// ----------------------------------------------------------
	// 命名返回值
	// ----------------------------------------------------------
	fmt.Println("\n=== 命名返回值 ===")

	w, h, area := getRectInfo(5, 3)
	fmt.Printf("矩形: 宽=%d, 高=%d, 面积=%d\n", w, h, area)

	// 使用裸返回的函数
	min, max := getMinMax(3, 7, 1, 9, 4)
	fmt.Printf("最小值=%d, 最大值=%d\n", min, max)

	// ----------------------------------------------------------
	// 命名返回值与 defer
	// ----------------------------------------------------------
	fmt.Println("\n=== 命名返回值与 defer ===")

	fmt.Println("doubleAndLog(5) =", doubleAndLog(5))

	// ----------------------------------------------------------
	// 实际应用示例
	// ----------------------------------------------------------
	fmt.Println("\n=== 实际应用示例 ===")

	// 解析用户输入
	if age, err := parseAge("25"); err != nil {
		fmt.Println("解析失败:", err)
	} else {
		fmt.Println("年龄:", age)
	}

	if age, err := parseAge("abc"); err != nil {
		fmt.Println("解析失败:", err)
	} else {
		fmt.Println("年龄:", age)
	}

	if age, err := parseAge("-5"); err != nil {
		fmt.Println("解析失败:", err)
	} else {
		fmt.Println("年龄:", age)
	}

	// ----------------------------------------------------------
	// 多返回值解构
	// ----------------------------------------------------------
	fmt.Println("\n=== 多返回值解构 ===")

	// 可以在一行中处理多个返回值
	s1, s2 := swap(1, 2)
	fmt.Printf("swap(1, 2) = %d, %d\n", s1, s2)

	// 链式赋值
	a, b := 1, 2
	a, b = swap(a, b)
	fmt.Printf("交换后: a=%d, b=%d\n", a, b)
}

// ============================================================
//                      多返回值函数定义
// ============================================================

// ----------------------------------------------------------
// 基本多返回值
// ----------------------------------------------------------
// 语法: func 函数名(参数) (类型1, 类型2, ...) { return 值1, 值2, ... }
// 【注意】多返回值的类型要用括号包裹
func divide(dividend, divisor int) (int, int) {
	quotient := dividend / divisor
	remainder := dividend % divisor
	return quotient, remainder
}

// ----------------------------------------------------------
// 带错误返回
// ----------------------------------------------------------
// 【惯例】错误作为最后一个返回值
// 【惯例】成功时 error 为 nil,失败时返回错误信息
func safeDivide(a, b int) (int, error) {
	if b == 0 {
		// 返回零值和错误
		return 0, errors.New("除数不能为零")
	}
	return a / b, nil
}

// ----------------------------------------------------------
// 命名返回值
// ----------------------------------------------------------
// 语法: func 函数名(参数) (名1 类型1, 名2 类型2) { ... }
//
// 【特点】
// 1. 命名返回值会被初始化为零值
// 2. 可以像普通变量一样使用
// 3. 可以使用"裸返回"(naked return)
//
// 【优点】
// - 自动初始化,减少变量声明
// - 提供返回值的文档说明
// - 某些情况下代码更简洁
//
// 【缺点】
// - 裸返回可能降低代码可读性
// - 容易引起变量遮蔽问题
func getRectInfo(width, height int) (w int, h int, area int) {
	// 命名返回值已自动初始化为 0
	w = width
	h = height
	area = width * height
	return // 裸返回,返回 w, h, area 的当前值
}

// ----------------------------------------------------------
// 命名返回值 + 裸返回
// ----------------------------------------------------------
// 【警告】裸返回在长函数中可能降低可读性
// 【建议】短函数可用裸返回,长函数建议显式返回
func getMinMax(nums ...int) (min, max int) {
	if len(nums) == 0 {
		return // 返回零值 0, 0
	}

	min = nums[0]
	max = nums[0]

	for _, n := range nums[1:] {
		if n < min {
			min = n
		}
		if n > max {
			max = n
		}
	}

	return // 裸返回
}

// ----------------------------------------------------------
// 命名返回值 + defer
// ----------------------------------------------------------
// 【重要】defer 可以修改命名返回值!
// 这是因为 defer 在 return 语句之后执行,但在函数返回之前
func doubleAndLog(n int) (result int) {
	defer func() {
		fmt.Printf("  [defer] 原值=%d, 返回值=%d\n", n, result)
		// 可以在这里修改 result
		// result = result + 1  // 这会改变最终返回值
	}()

	result = n * 2
	return // 返回 result 的值
}

// ----------------------------------------------------------
// 实际应用:解析并验证
// ----------------------------------------------------------
// 【模式】解析 + 验证 + 错误返回
func parseAge(s string) (int, error) {
	// 1. 解析
	age, err := strconv.Atoi(s)
	if err != nil {
		return 0, fmt.Errorf("无法解析 '%s' 为数字: %w", s, err)
	}

	// 2. 验证
	if age < 0 {
		return 0, errors.New("年龄不能为负数")
	}
	if age > 150 {
		return 0, errors.New("年龄不能超过150")
	}

	// 3. 返回结果
	return age, nil
}

// ----------------------------------------------------------
// 交换值
// ----------------------------------------------------------
// 【技巧】Go 的多返回值让交换变得简单
func swap(a, b int) (int, int) {
	return b, a
}

// ============================================================
//                      重要注意事项
// ============================================================
//
// 1. 【错误处理惯例】
//    result, err := someFunc()
//    if err != nil {
//        // 处理错误
//        return err  // 或其他错误处理
//    }
//    // 使用 result
//
// 2. 【不要忽略错误】
//    result, _ := someFunc()  // 危险!除非你确定不会出错
//
// 3. 【命名返回值陷阱】
//    func foo() (result int) {
//        result := 10  // 错误!这创建了新的局部变量,遮蔽了命名返回值
//        result = 10   // 正确!这赋值给命名返回值
//        return
//    }
//
// 4. 【何时使用命名返回值】
//    - 函数有多个返回值,需要文档说明
//    - 需要在 defer 中修改返回值
//    - 短函数,裸返回不影响可读性
//
// 5. 【何时避免裸返回】
//    - 长函数(超过几行)
//    - 有多个 return 点
//    - 返回值逻辑复杂

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布