Skip to content

variadic.go

文件信息

  • 📄 原文件:03_variadic.go
  • 🔤 语言:go

完整代码

go
package main

import (
	"fmt"
	"strings"
)

// ============================================================
//                      可变参数函数
// ============================================================
// 可变参数允许函数接受任意数量的参数
// 语法: func 函数名(参数名 ...类型)
// 在函数内部,可变参数是一个切片

func main03() {
	fmt.Println("\n==================== 03_variadic ====================")
	fmt.Println("=== 可变参数基础 ===")

	// ----------------------------------------------------------
	// 基本调用
	// ----------------------------------------------------------
	// 可以传递任意数量的参数
	fmt.Println("sum() =", sum())           // 0 个参数
	fmt.Println("sum(1) =", sum(1))         // 1 个参数
	fmt.Println("sum(1, 2) =", sum(1, 2))   // 2 个参数
	fmt.Println("sum(1, 2, 3, 4, 5) =", sum(1, 2, 3, 4, 5))

	// ----------------------------------------------------------
	// 传递切片(展开)
	// ----------------------------------------------------------
	fmt.Println("\n=== 切片展开 ===")

	nums := []int{10, 20, 30, 40}
	// 【重要】使用 ... 展开切片
	fmt.Println("sum(nums...) =", sum(nums...))

	// 【注意】不能直接传切片
	// sum(nums)  // 错误!类型不匹配

	// 部分展开
	part := []int{100, 200}
	fmt.Println("sum(1, 2, part...) 不允许") // Go 不支持这种混合

	// 但可以先构建切片再展开
	all := append([]int{1, 2}, part...)
	fmt.Println("构建后展开:", sum(all...))

	// ----------------------------------------------------------
	// 可变参数 + 固定参数
	// ----------------------------------------------------------
	fmt.Println("\n=== 可变参数 + 固定参数 ===")

	// 【规则】可变参数必须是最后一个参数
	greetAll("你好", "张三", "李四", "王五")
	greetAll("Hello", "Alice", "Bob")

	// ----------------------------------------------------------
	// 空接口可变参数
	// ----------------------------------------------------------
	fmt.Println("\n=== 任意类型可变参数 ===")

	// 使用 ...any(等价于 ...interface{})接受任意类型
	printAll("字符串", 42, 3.14, true, []int{1, 2, 3})

	// 【提示】fmt.Println 就是这样实现的
	// func Println(a ...any) (n int, err error)

	// ----------------------------------------------------------
	// 可变参数的内部实现
	// ----------------------------------------------------------
	fmt.Println("\n=== 可变参数内部原理 ===")

	// 可变参数在函数内部是切片
	inspectVariadic(1, 2, 3)
	inspectVariadic() // 空切片,不是 nil

	// ----------------------------------------------------------
	// 实际应用:字符串连接
	// ----------------------------------------------------------
	fmt.Println("\n=== 实际应用 ===")

	result := join("-", "2024", "01", "15")
	fmt.Println("日期:", result)

	result = join("/", "usr", "local", "bin")
	fmt.Println("路径:", result)

	// ----------------------------------------------------------
	// 实际应用:配置选项模式
	// ----------------------------------------------------------
	fmt.Println("\n=== 选项模式 ===")

	// 使用可变参数实现可选配置
	s1 := NewServer("localhost", 8080)
	fmt.Printf("服务器1: %+v\n", s1)

	s2 := NewServer("0.0.0.0", 80,
		WithTimeout(30),
		WithMaxConns(1000),
	)
	fmt.Printf("服务器2: %+v\n", s2)

	// ----------------------------------------------------------
	// 可变参数传递
	// ----------------------------------------------------------
	fmt.Println("\n=== 可变参数传递 ===")

	wrapper(1, 2, 3, 4, 5)
}

// ============================================================
//                      可变参数函数定义
// ============================================================

// ----------------------------------------------------------
// 基本可变参数
// ----------------------------------------------------------
// 语法: func 函数名(参数名 ...类型)
// 【内部】nums 是 []int 类型的切片
func sum(nums ...int) int {
	total := 0
	for _, n := range nums {
		total += n
	}
	return total
}

// ----------------------------------------------------------
// 固定参数 + 可变参数
// ----------------------------------------------------------
// 【规则】可变参数必须放在最后
// 【规则】只能有一个可变参数
func greetAll(greeting string, names ...string) {
	for _, name := range names {
		fmt.Println(greeting + ",", name)
	}
}

// ----------------------------------------------------------
// 任意类型可变参数
// ----------------------------------------------------------
// any 是 interface{} 的别名(Go 1.18+)
// 可以接受任意类型的参数
func printAll(args ...any) {
	for i, arg := range args {
		fmt.Printf("  参数%d: %v (类型: %T)\n", i, arg, arg)
	}
}

// ----------------------------------------------------------
// 查看可变参数内部
// ----------------------------------------------------------
func inspectVariadic(nums ...int) {
	fmt.Printf("  类型: %T\n", nums)
	fmt.Printf("  长度: %d\n", len(nums))
	fmt.Printf("  容量: %d\n", cap(nums))
	fmt.Printf("  nil?: %t\n", nums == nil)
	fmt.Printf("  值: %v\n", nums)
}

// ----------------------------------------------------------
// 字符串连接
// ----------------------------------------------------------
func join(sep string, parts ...string) string {
	return strings.Join(parts, sep)
}

// ----------------------------------------------------------
// 选项模式(Functional Options Pattern)
// ----------------------------------------------------------
// 【用途】灵活配置,避免长参数列表
// 【优点】
// - 向后兼容(添加新选项不影响旧代码)
// - 可读性好
// - 提供默认值

// 服务器配置
type Server struct {
	Host     string
	Port     int
	Timeout  int
	MaxConns int
}

// 选项函数类型
type ServerOption func(*Server)

// 选项函数
func WithTimeout(t int) ServerOption {
	return func(s *Server) {
		s.Timeout = t
	}
}

func WithMaxConns(n int) ServerOption {
	return func(s *Server) {
		s.MaxConns = n
	}
}

// 构造函数
func NewServer(host string, port int, opts ...ServerOption) *Server {
	// 1. 设置默认值
	s := &Server{
		Host:     host,
		Port:     port,
		Timeout:  10,      // 默认超时
		MaxConns: 100,     // 默认最大连接数
	}

	// 2. 应用选项
	for _, opt := range opts {
		opt(s)
	}

	return s
}

// ----------------------------------------------------------
// 可变参数传递给另一个可变参数函数
// ----------------------------------------------------------
// 【重要】传递时需要使用 ... 展开
func wrapper(nums ...int) {
	fmt.Println("wrapper 收到:", nums)
	// 传递给另一个可变参数函数
	fmt.Println("调用 sum:", sum(nums...))
}

// ============================================================
//                      重要注意事项
// ============================================================
//
// 1. 【可变参数位置】必须是最后一个参数
//    func foo(a int, b ...int) {}  // 正确
//    func foo(a ...int, b int) {}  // 错误
//
// 2. 【可变参数数量】只能有一个
//    func foo(a ...int, b ...string) {}  // 错误
//
// 3. 【切片传递】必须使用 ... 展开
//    nums := []int{1, 2, 3}
//    sum(nums...)  // 正确
//    sum(nums)     // 错误
//
// 4. 【空调用】可变参数可以不传
//    sum()  // 合法,nums 是空切片 []int{}
//
// 5. 【nil vs 空切片】
//    不传参数时,可变参数是空切片,不是 nil
//    len == 0, cap == 0, != nil
//
// 6. 【性能】每次调用都会创建新切片
//    高频调用时需注意性能
//
// 7. 【类型安全】
//    使用 ...any 会失去类型安全
//    尽量使用具体类型

💬 讨论

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

基于 MIT 许可发布