news 2026/5/12 19:02:12

Go进阶之长参数函数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Go进阶之长参数函数

在Go中.变长参数函数使用的最多的就是fmt包 log包中的几个导出函数.

源码位置:src/fmt/print.go

// Println formats using the default formats for its operands and writes to standard output. // Spaces are always added between operands and a newline is appended. // It returns the number of bytes written and any write error encountered. func Println(a ...any) (n int, err error) { return Fprintln(os.Stdout, a...) } // Printf formats according to a format specifier and writes to standard output. // It returns the number of bytes written and any write error encountered. func Printf(format string, a ...any) (n int, err error) { return Fprintf(os.Stdout, format, a...) }

源码位置:src/log/log.go

// Printf calls l.Output to print to the logger. // Arguments are handled in the manner of [fmt.Printf]. func (l *Logger) Printf(format string, v ...any) { l.output(0, 2, func(b []byte) []byte { return fmt.Appendf(b, format, v...) }) } // Println calls l.Output to print to the logger. // Arguments are handled in the manner of [fmt.Println]. func (l *Logger) Println(v ...any) { l.output(0, 2, func(b []byte) []byte { return fmt.Appendln(b, v...) }) }

1.变长参数函数:

变长参数就是指调用时可以接受零个 一个或多个实际参数的函数.示例如

下:

可以看到无论传入零个 两个还是多个实际参数.都传给了Print()方法的形

式参数a(参考上面源码).形参a的类型是...any.这种接受"...T"类型形式参

数的函数就被称为变长参数函数.

一个变长参数函数只能有一个"...T"类型形式参数.并且该形式参数应该为

函数参数列表中的最后一个形式参数.否则Go编译器会有错误提示.

变长参数函数的"...T"类型形式参数在函数体内呈现为[]T类型的变量.可以

将其理解为一个Go的语法糖.示例如下:

func sum(arg ...int) int { var total int //arg的类型为[]int for _, v := range arg { total += v } return total }

在函数外部."...T"类型形式参数可以匹配和接受的实参类型有两种.

1).多个T类型变量.

2).t...(t为[]T类型变量).示例如下:

func main() { a, b, c := 1, 2, 3 fmt.Println(sum(a, b, c)) nums := []int{1, 2, 3} fmt.Println(sum(nums...)) } func sum(arg ...int) int { var total int //arg的类型为[]int for _, v := range arg { total += v } return total }

我们只能选择上述两种实参类型的一种.要么是多个T类型变量.要么是t...(t

为[]T类型变量).如果两种混用.会得到类似下面的编译错误.

使用变长参数函数最容易出现的问题是实参与形参不匹配.示例如下:

func main() { s := []string{"a", "b", "c"} dump(s...) } func dump(args ...interface{}) { for _, v := range args { fmt.Println(v) } }

编译器给出了类型不匹配的错误.虽然string类型可以直接赋值给interf

ace{}类型变量.但是[]string类型变量并不能直接赋值给[]interface{}类

型变量.修改示例如下:

func main() { s := []interface{}{"a", "b", "c"} dump(s...) } func dump(args ...interface{}) { for _, v := range args { fmt.Println(v) } }

不过有个例外.就是Go的内置函数append函数.它支持通过下面的方式将

字符串附加到一个字节后面.示例如下:

func main() { b := []byte{} b = append(b, "hello"...) fmt.Println(b) }

string类型本身不满足类型要求(append本需要[]byte...).这算是Go的一

个编译优化.编译器自动将string隐式转换为了[]byte.

func main() { b := []byte{} b = append(b, "hello"...) fmt.Println(b) fooTest(b) } func fooTest(b ...byte) { fmt.Println(b) }

2.模拟函数重载:

Go语言不允许在同一个作用域下定义名字相同但函数原型不同的函数.如果

定义这样的函数.Go编译器会提示下面代码的错误信息.

func concat(a, b int) string { return fmt.Sprintf("%d %d", a, b) } func concat(x, y string) string { return fmt.Sprintf("%s %s", x, y) } func concat(s []string)string { return strings.Join(s, " ") }

要修复上面的错误.只能修改函数命名.但是在其他语言中.比如java.就支持

这种名字相同.参数类型不同的重载函数.但Go语言并不支持函数重载.Go语

言官方给出不支持的理由是:

其他语言的经验告诉我们.使用具有相同名称但是函数签名不同的多种方法

有时会很有用.但在实践中也可能会造成混淆和脆弱性.在Go的类型系统中.

仅按名称进行匹配要求类型一致是一个主要的简化决策.

变长参数解决.示例如下:

func main() { fmt.Println(concat("-", 1, 2)) fmt.Println(concat("-", "hello", "gopher")) } func concat(sep string, args ...interface{}) string { var result string for i, v := range args { if i != 0 { result += sep } switch v.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: result += fmt.Sprintf("%v", v) case string: result += fmt.Sprintf("%s", v) case []int: ints := v.([]int) for i, v := range ints { if i != 0 { result += sep } result += fmt.Sprintf("%v", v) } case []string: strings := v.([]string) result += fmt.Sprintf("%v", strings) default: fmt.Println("不支持此类型") return "" } } return result }

3.模拟实现函数的可选参数和默认参数:

如果参数在传入时有隐式要求的固定顺序(调用者保证).还可以利用变长参

数函数模拟实现可选参数和默认参数.示例如下:

type record struct { name string gender string age int city string country string } func enroll(args ...interface{}) (*record, error) { if len(args) > 5 || len(args) < 3 { return nil, fmt.Errorf("the number of arguments passed to wrong") } r := &record{ city: "ShanXi", country: "TaiYuan", } for i, v := range args { switch i { case 0: name, ok := v.(string) if !ok { return nil, fmt.Errorf("the first argument to enroll must be a string") } r.name = name case 1: gender, ok := v.(string) if !ok { return nil, fmt.Errorf("the second argument to enroll must be a string") } r.gender = gender case 2: age, ok := v.(int) if !ok { return nil, fmt.Errorf("the third argument to enroll must be an uint16") } r.age = age case 3: city, ok := v.(string) if !ok { return nil, fmt.Errorf("the third argument to enroll must be a string") } r.city = city case 4: country, ok := v.(string) if !ok { return nil, fmt.Errorf("the fourth argument to enroll must be a string") } r.country = country default: return nil, fmt.Errorf("unknown argument %d", i) } } return r, nil } func main() { r, _ := enroll("小明", "male", 26) fmt.Printf("%+v\n", *r) r1, _ := enroll("小明", "male", 26, "linfen") fmt.Printf("%+v\n", *r1) }

4.实现功能选项:

1).通过参数暴露配置选项:
type FinishedHouse struct { style int centralAirConditioning bool floorMaterial string wallMaterial string } func NewFinishedHouse(style int, centralAirConditioning bool, floorMaterial, wallMaterial string) *FinishedHouse { h := &FinishedHouse{ style: style, centralAirConditioning: centralAirConditioning, floorMaterial: floorMaterial, wallMaterial: wallMaterial, } return h } func main() { fmt.Printf("%+v\n", NewFinishedHouse(0, true, "wood", "paper")) }

上面设计的唯一优点就是快速实现.不足之处很多.最致命的是接口无法扩

展.

2)结构体封装配置项:
type FinishedHouse struct { style int centralAirConditioning bool floorMaterial string wallMaterial string } type Options struct { style int centralAirConditioning bool floorMaterial string wallMaterial string } func NewFinishedHouse(options *Options) *FinishedHouse { var style = 0 var centralAirConditioning = true var floorMaterial = "wood" var wallMaterial = "paper" if options != nil { style = options.style centralAirConditioning = options.centralAirConditioning floorMaterial = options.floorMaterial wallMaterial = options.wallMaterial } h := &FinishedHouse{ style: style, centralAirConditioning: centralAirConditioning, floorMaterial: floorMaterial, wallMaterial: wallMaterial, } return h } func main() { fmt.Printf("%+v\n", NewFinishedHouse(0, true, "wood", "paper")) }
优点:

1).后续添加配置项选项.Options结构体可以随着时间变迁而增长.但

FinishedHouse创建函数本身的Api签名不变.

2).允许调用者使用nil来表示他们希望使用默认值来创建.

3).可以更好地记录文档.

缺点:

1).每次都要为Options中所有字段赋值.

2).如果Options中的值在调用后变化了怎么办.

3).使用功能选项:
type FinishedHouse struct { style int centralAirConditioning bool floorMaterial string wallMaterial string } type Option func(h *FinishedHouse) func NewFinishedHouse(options ...Option) *FinishedHouse { h := &FinishedHouse{ style: 0, centralAirConditioning: true, floorMaterial: "wood", wallMaterial: "paper", } for _, option := range options { option(h) } return h } func WithStyle(style int) Option { return func(h *FinishedHouse) { h.style = style } } func WithCentralAirConditioning(centralAirConditioning bool) Option { return func(h *FinishedHouse) { h.centralAirConditioning = centralAirConditioning } } func WithFloorMaterial(floorMaterial string) Option { return func(h *FinishedHouse) { h.floorMaterial = floorMaterial } } func WithWallMaterial(wallMaterial string) Option { return func(h *FinishedHouse) { h.floorMaterial = wallMaterial } } func main() { //默认. fmt.Printf("%+v\n", NewFinishedHouse()) fmt.Printf("%+v\n", NewFinishedHouse(WithStyle(1), WithFloorMaterial("title"))) }

我也曾像个疯子一样.

如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/10 19:40:48

手把手教你用GPT-OSS-20B搭建本地对话系统,零基础避坑指南

手把手教你用GPT-OSS-20B搭建本地对话系统&#xff0c;零基础避坑指南 你是不是也试过&#xff1a; 在网页上点开一个AI对话框&#xff0c;输入“帮我写一封辞职信”&#xff0c;等三秒&#xff0c;弹出一段格式工整、语气得体、连“感谢公司培养”都写得恰到好处的文字——然…

作者头像 李华
网站建设 2026/5/10 16:48:21

Qwen3-Reranker-8B实操手册:自定义instruction提升垂直领域重排效果

Qwen3-Reranker-8B实操手册&#xff1a;自定义instruction提升垂直领域重排效果 1. 为什么你需要关注Qwen3-Reranker-8B 你有没有遇到过这样的问题&#xff1a;在搭建企业级搜索系统时&#xff0c;召回的文档相关性不错&#xff0c;但排序结果总差一口气&#xff1f;比如法律…

作者头像 李华
网站建设 2026/5/10 20:37:25

SiameseUIE教程:从云实例登录到5类测试全部通过的完整链路

SiameseUIE教程&#xff1a;从云实例登录到5类测试全部通过的完整链路 1. 为什么这个镜像特别适合受限云环境 你有没有遇到过这样的情况&#xff1a;在一台配置紧张的云实例上部署模型&#xff0c;系统盘只有40G&#xff0c;PyTorch版本被锁死不能动&#xff0c;重启后环境还…

作者头像 李华
网站建设 2026/5/12 13:50:57

当大模型遇见扫描件:GPT-4V在真实办公场景中的突围与妥协

当大模型遇见扫描件&#xff1a;GPT-4V在真实办公场景中的突围与妥协 1. 多模态大模型的技术革命与文档处理困境 2023年成为多模态大模型爆发的元年&#xff0c;GPT-4V的推出彻底改变了人机交互的范式。这款能同时处理文本和图像的AI系统&#xff0c;在理想测试环境下展现出的…

作者头像 李华
网站建设 2026/5/9 6:51:57

Fun-ASR-MLT-Nano-2512GPU算力优化:TensorRT加速尝试与FP16/INT8推理对比

Fun-ASR-MLT-Nano-2512GPU算力优化&#xff1a;TensorRT加速尝试与FP16/INT8推理对比 1. 为什么需要给Fun-ASR-MLT-Nano-2512做算力优化 Fun-ASR-MLT-Nano-2512语音识别模型&#xff0c;是113小贝在阿里通义实验室开源模型基础上二次开发构建的轻量化多语言语音识别方案。它不…

作者头像 李华
网站建设 2026/5/10 7:39:13

Nano-Banana在碳足迹计算中应用:拆解图驱动的材料分拣路径规划

Nano-Banana在碳足迹计算中应用&#xff1a;拆解图驱动的材料分拣路径规划 1. 为什么拆解图是碳足迹计算的第一把钥匙 你有没有想过&#xff0c;一台旧手机回收时&#xff0c;真正决定它环保价值的&#xff0c;不是它被扔进哪个垃圾桶&#xff0c;而是它被“看懂”了多少&…

作者头像 李华