Go语言方法中的默认值

232

在Go语言中,有没有一种方法可以指定函数的默认值?我正在查找文档,但无法找到任何指定这种可能性的内容。

func SaySomething(i string = "Hello")(string){
...
}
4个回答

253

不过还有其他选项可以实现默认值。这方面有一些优秀的博客文章,但是这里提供了一些具体的例子。

选项1:调用者选择使用默认值

// Both parameters are optional, use empty string for default value
func Concat1(a string, b int) string {
  if a == "" {
    a = "default-a"
  }
  if b == 0 {
    b = 5
  }

  return fmt.Sprintf("%s%d", a, b)
}

选项2:在末尾的一个可选参数

// a is required, b is optional.
// Only the first value in b_optional will be used.
func Concat2(a string, b_optional ...int) string {
  b := 5
  if len(b_optional) > 0 {
    b = b_optional[0]
  }

  return fmt.Sprintf("%s%d", a, b)
}

选项3:一个配置结构体

// A declarative default value syntax
// Empty values will be replaced with defaults
type Parameters struct {
  A string `default:"default-a"` // this only works with strings
  B string // default is 5
}

func Concat3(prm Parameters) string {
  typ := reflect.TypeOf(prm)

  if prm.A == "" {
    f, _ := typ.FieldByName("A")
    prm.A = f.Tag.Get("default")
  }

  if prm.B == 0 {
    prm.B = 5
  }

  return fmt.Sprintf("%s%d", prm.A, prm.B)
}

选项4:完全的可变参数解析(类似JavaScript)

func Concat4(args ...interface{}) string {
  a := "default-a"
  b := 5

  for _, arg := range args {
    switch t := arg.(type) {
      case string:
        a = t
      case int:
        b = t
      default:
        panic("Unknown argument")
    }
  }

  return fmt.Sprintf("%s%d", a, b)
}

282
真是太痛苦了。我希望它像大多数其他现代语言一样是这样的:func Concat1(a string = 'foo', b int = 10) string {,这将使任何给定的示例几乎只需要一行代码就能完成。 - Rotareti
3
我们是否有写两个不同的函数并让调用者明确他们期望的选项? - ProgramCpp
10
不,Go语言不允许函数重载。 - Vüsal
4
默认值很棘手,当你有一个省略值的调用者时 - 你永远不知道他们想要的值是“实现时的默认值”还是“将来任意时刻的默认值” - 这使得代码意图不太明显。Go 让它有些痛苦可能并不像你想的那么糟糕。 - Greg
Go语言的另一个缺点是缺乏默认参数值和命名参数。 - Trevor Sullivan

176


19

不,没有办法指定默认值。我认为这是有意而为之的,以增强可读性,代价是写作人需要更多的时间(和希望思考)。

我认为拥有“默认值”的正确方法是编写一个新函数,该函数向更通用的函数提供该默认值。通过这样做,您的代码在显示意图方面变得更加清晰。例如:

func SaySomething(say string) {
    // All the complicated bits involved in saying something
}

func SayHello() {
    SaySomething("Hello")
}

我轻而易举地编写了一个函数来完成常见任务,并重复使用了通用函数。你可以在许多库中看到这一点,例如 fmt.Println 只是在原本 fmt.Print 的基础上添加了一个换行符。然而,在阅读他人代码时,通过调用的函数清楚地知道他们想要做什么。使用默认值时,如果不去参考函数的默认值,我将不知道应该发生什么。


1
我喜欢并且同意这个答案,但是我仍然希望他们至少有一种难以理解但是符合惯用语的方式来做到这一点。 - Sam Gomena
45
“只需添加另一个函数”这个论点可能导致需要处理多个排列组合的方法的爆炸性增加,需要承担对这些方法进行有意义、可发现和易记名称的负担,并增加了学习使用更多方法与较少方法的软件包的负担。#jmtcw - MikeSchinkel
2
嗯,作为一个曾经不得不在10-20年后解开大量源代码以查找错误以支持客户的人,我认为额外的函数名称可能会有一些好处。 - mukunda
2
“这种方法可能有一些好处”和“让我们设计语言,使得这种方法是唯一可能的(即使在不合理的情况下)”之间存在差距。 - bfontaine

2
默认函数参数通常为客户端代码提供一个非常常见的参数值,同时允许客户端代码覆盖它。通过将函数转换为结构体的方法并将默认参数转换为该结构体的字段,可以实现类似的结果。
让我们以基于您示例的函数为例(它无法编译):
func SaySomething(msg string = "Hello") string {
    return fmt.Sprintf("I say %q", msg)
}

首先,定义一个结构体来保存默认参数:
type SomethingSayer struct {
    msg string
}

其次,将函数 SaySomething 转化为结构体 SomethingSayer 上的方法。原来的函数参数 msg 被替换为 s.msg,即 SomethingSayer 的一个字段:
func (s SomethingSayer) SaySomething() string {
    return fmt.Sprintf("I say %q", s.msg)
}

您也可以为 SomethingSayer 结构定义一个构造函数。这将使用原始的默认参数设置字段 msg
func NewSaySomethingSayer() SomethingSayer {
    return SomethingSayer {
        msg: "Hello", // the default argument
    }
}

最后,定义一个方便的封装函数,它类似于原始函数,但没有默认参数的那个参数:
func SaySomething() string {
    return NewSaySomethingSayer().SaySomething()
}

这样,当默认设置适合时,客户端代码可以直接调用 SaySomething()。如果不是这种情况,它仍然可以构建自己的 SomethingSayer,然后调用 SaySomething() 方法:
fmt.Println(SaySomething())    // I say "Hello"

s := SomethingSayer {
    msg: "Bye",
}
fmt.Println(s.SaySomething())  // I say "Bye"

请注意,标准库在http.Gethttp.Headhttp.Post中也遵循了这种方法。也就是说,此函数允许您执行GET请求而无需显式构造http.Client:它使用全局可用的http.Client值(http.DefaultClient)并调用其上的Get方法。同样,客户端代码仍然可以通过构造自己的http.Client并在其上调用Get来覆盖默认参数(例如超时)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接