Go進(jìn)階之長(zhǎng)參數(shù)函數(shù)的實(shí)現(xiàn)
在Go中.變長(zhǎng)參數(shù)函數(shù)使用的最多的就是fmt包 log包中的幾個(gè)導(dǎo)出函數(shù).
源碼位置: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.變長(zhǎng)參數(shù)函數(shù):
變長(zhǎng)參數(shù)就是指調(diào)用時(shí)可以接受零個(gè) 一個(gè)或多個(gè)實(shí)際參數(shù)的函數(shù).示例如下:

可以看到無(wú)論傳入零個(gè) 兩個(gè)還是多個(gè)實(shí)際參數(shù).都傳給了Print()方法的形式參數(shù)a(參考上面源碼).形參a的類型是...any.這種接受"...T"類型形式參數(shù)的函數(shù)就被稱為變長(zhǎng)參數(shù)函數(shù).一個(gè)變長(zhǎng)參數(shù)函數(shù)只能有一個(gè)"...T"類型形式參數(shù).并且該形式參數(shù)應(yīng)該為函數(shù)參數(shù)列表中的最后一個(gè)形式參數(shù).否則Go編譯器會(huì)有錯(cuò)誤提示.


變長(zhǎng)參數(shù)函數(shù)的"...T"類型形式參數(shù)在函數(shù)體內(nèi)呈現(xiàn)為[]T類型的變量.可以將其理解為一個(gè)Go的語(yǔ)法糖.示例如下:
func sum(arg ...int) int {
var total int
//arg的類型為[]int
for _, v := range arg {
total += v
}
return total
}在函數(shù)外部."...T"類型形式參數(shù)可以匹配和接受的實(shí)參類型有兩種.
1).多個(gè)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
}我們只能選擇上述兩種實(shí)參類型的一種.要么是多個(gè)T類型變量.要么是t...(t為[]T類型變量).如果兩種混用.會(huì)得到類似下面的編譯錯(cuò)誤.

使用變長(zhǎng)參數(shù)函數(shù)最容易出現(xiàn)的問(wèn)題是實(shí)參與形參不匹配.示例如下:
func main() {
s := []string{"a", "b", "c"}
dump(s...)
}
func dump(args ...interface{}) {
for _, v := range args {
fmt.Println(v)
}
}
編譯器給出了類型不匹配的錯(cuò)誤.雖然string類型可以直接賦值給interface{}類型變量.但是[]string類型變量并不能直接賦值給[]interface{}類型變量.修改示例如下:
func main() {
s := []interface{}{"a", "b", "c"}
dump(s...)
}
func dump(args ...interface{}) {
for _, v := range args {
fmt.Println(v)
}
}不過(guò)有個(gè)例外.就是Go的內(nèi)置函數(shù)append函數(shù).它支持通過(guò)下面的方式將字符串附加到一個(gè)字節(jié)后面.示例如下:
func main() {
b := []byte{}
b = append(b, "hello"...)
fmt.Println(b)
}string類型本身不滿足類型要求(append本需要[]byte...).這算是Go的一個(gè)編譯優(yōu)化.編譯器自動(dòng)將string隱式轉(zhuǎn)換為了[]byte.
func main() {
b := []byte{}
b = append(b, "hello"...)
fmt.Println(b)
fooTest(b)
}
func fooTest(b ...byte) {
fmt.Println(b)
}
2.模擬函數(shù)重載:
Go語(yǔ)言不允許在同一個(gè)作用域下定義名字相同但函數(shù)原型不同的函數(shù).如果
定義這樣的函數(shù).Go編譯器會(huì)提示下面代碼的錯(cuò)誤信息.
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, " ")
}
要修復(fù)上面的錯(cuò)誤.只能修改函數(shù)命名.但是在其他語(yǔ)言中.比如java.就支持這種名字相同.參數(shù)類型不同的重載函數(shù).但Go語(yǔ)言并不支持函數(shù)重載.Go語(yǔ)言官方給出不支持的理由是:其他語(yǔ)言的經(jīng)驗(yàn)告訴我們.使用具有相同名稱但是函數(shù)簽名不同的多種方法有時(shí)會(huì)很有用.但在實(shí)踐中也可能會(huì)造成混淆和脆弱性.在Go的類型系統(tǒng)中.僅按名稱進(jìn)行匹配要求類型一致是一個(gè)主要的簡(jiǎn)化決策.變長(zhǎng)參數(shù)解決.示例如下:
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.模擬實(shí)現(xiàn)函數(shù)的可選參數(shù)和默認(rèn)參數(shù):
如果參數(shù)在傳入時(shí)有隱式要求的固定順序(調(diào)用者保證).還可以利用變長(zhǎng)參數(shù)函數(shù)模擬實(shí)現(xiàn)可選參數(shù)和默認(rèn)參數(shù).示例如下:
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.實(shí)現(xiàn)功能選項(xiàng):
1).通過(guò)參數(shù)暴露配置選項(xiàng):
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"))
}上面設(shè)計(jì)的唯一優(yōu)點(diǎn)就是快速實(shí)現(xiàn).不足之處很多.最致命的是接口無(wú)法擴(kuò)展.
2)結(jié)構(gòu)體封裝配置項(xiàng):
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"))
}優(yōu)點(diǎn):
1).后續(xù)添加配置項(xiàng)選項(xiàng).Options結(jié)構(gòu)體可以隨著時(shí)間變遷而增長(zhǎng).但FinishedHouse創(chuàng)建函數(shù)本身的Api簽名不變.
2).允許調(diào)用者使用nil來(lái)表示他們希望使用默認(rèn)值來(lái)創(chuàng)建.
3).可以更好地記錄文檔.
缺點(diǎn):
1).每次都要為Options中所有字段賦值.
2).如果Options中的值在調(diào)用后變化了怎么辦.
3).使用功能選項(xiàng):
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() {
//默認(rèn).
fmt.Printf("%+v\n", NewFinishedHouse())
fmt.Printf("%+v\n", NewFinishedHouse(WithStyle(1), WithFloorMaterial("title")))
}到此這篇關(guān)于Go進(jìn)階之長(zhǎng)參數(shù)函數(shù)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Go 長(zhǎng)參數(shù)函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
多階段構(gòu)建優(yōu)化Go?程序Docker鏡像
這篇文章主要為大家介紹了多階段構(gòu)建優(yōu)化Go?程序Docker鏡像,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
淺析Go使用定時(shí)器時(shí)如何避免潛在的內(nèi)存泄漏陷阱
這篇文章來(lái)和大家一起探討一下Go?中如何高效使用?timer,特別是與select?一起使用時(shí),如何防止?jié)撛诘膬?nèi)存泄漏問(wèn)題,感興趣的可以了解下2024-01-01
golang程序打包成腳本部署到Linux系統(tǒng)方式
Golang程序通過(guò)本地編譯(設(shè)置GOOS為linux生成無(wú)后綴二進(jìn)制文件),上傳至Linux服務(wù)器后賦權(quán)執(zhí)行,使用nohup命令實(shí)現(xiàn)后臺(tái)運(yùn)行,完成自動(dòng)化部署2025-07-07
go語(yǔ)言解決并發(fā)問(wèn)題小結(jié)
并發(fā)是GO最基本的功能了,但是在傳統(tǒng)的PHP中是比較困難的,如果不借助其它一些擴(kuò)展的話,是做不到并發(fā)的,這篇文章主要介紹了go語(yǔ)言如何解決并發(fā)問(wèn)題,需要的朋友可以參考下2024-05-05
Go語(yǔ)言中利用http發(fā)起Get和Post請(qǐng)求的方法示例
這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言中利用http發(fā)起Get和Post請(qǐng)求的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11
GO語(yǔ)言開(kāi)發(fā)終端命令行小工具改進(jìn)更新
這篇文章主要為大家介紹了GO語(yǔ)言開(kāi)發(fā)終端命令行小工具的改進(jìn)更新,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
Go語(yǔ)言使用Swiss Table實(shí)現(xiàn)更快的map
wiss Table 是一種高效的哈希表實(shí)現(xiàn),最初由 Google 在 C++ 中引入,后來(lái)也被其他語(yǔ)言(如 Rust)采用,下面我們看看如何使用 Swiss Table 的思想來(lái)實(shí)現(xiàn)一個(gè)更快的 Go map2025-03-03
golang的Pseudo-versions使用問(wèn)題解析
這篇文章主要為大家介紹有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪了golang的Pseudo-versions使用問(wèn)題解析,2023-07-07

