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 账号登录后即可参与讨论