Go进阶之长参数函数的实现

 更新时间:2026年06月05日 09:50:33   作者:念何架构之路  
本文主要介绍了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类型可以直接赋值给interface{}类型变量.但是[]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")))

}

到此这篇关于Go进阶之长参数函数的实现的文章就介绍到这了,更多相关Go 长参数函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Golang交叉编译(跨平台编译)的使用

    Golang交叉编译(跨平台编译)的使用

    本文主要介绍了Golang交叉编译(跨平台编译)的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • 浅谈Go语言高并发处理思路

    浅谈Go语言高并发处理思路

    本文主要介绍了Go语言高并发处理思路,通过使用goroutine和channel,可以实现高效的异步处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-11-11
  • Go json omitempty如何实现可选属性

    Go json omitempty如何实现可选属性

    在Go语言中,使用`omitempty`可以帮助我们在进行JSON序列化和反序列化时,忽略结构体中的零值或空值,本文介绍了如何通过将字段类型改为指针类型,并在结构体的JSON标签中添加`omitempty`来实现这一功能,例如,将float32修改为*float32
    2024-09-09
  • Go使用Protocol Buffers在数据序列化的优势示例详解

    Go使用Protocol Buffers在数据序列化的优势示例详解

    这篇文章主要为大家介绍了Go使用Protocol Buffers在数据序列化的优势示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • Golang使用Docker进行集成测试的示例详解

    Golang使用Docker进行集成测试的示例详解

    集成测试需要解决外部依赖问题,如 MySQL、Redis、网络等依赖,本文就来聊聊 Go 程序如何使用 Docker 来解决集成测试中外部依赖问题吧
    2023-07-07
  • go并发实现素数筛的代码

    go并发实现素数筛的代码

    这篇文章主要介绍了go并发实现素数筛的代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • go-zero接入skywalking实现链路追踪的详细教程

    go-zero接入skywalking实现链路追踪的详细教程

    SkyWalking是一个开源的服务追踪系统,它专注于分布式系统的可观测性和服务性能分析,它的主要目标是帮助开发者理解和优化微服务架构中的服务间交互情况,本文介绍了go-zero接入skywalking链路追踪的详细教程,需要的朋友可以参考下
    2024-08-08
  • Go Gin框架路由相关bug分析

    Go Gin框架路由相关bug分析

    这篇文章主要为大家介绍了Go Gin框架路由相关bug分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • 基于Go语言实现简单的计算器

    基于Go语言实现简单的计算器

    这篇文章主要为大家详细介绍了如何基于Go语言实现简单的计算器,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-10-10
  • Windows下升级go版本过程详解

    Windows下升级go版本过程详解

    这篇文章主要为大家介绍了Windows下升级go版本过程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03

最新评论