620

Go 可以有可选参数吗?或者我可以只定义两个具有相同名称和不同数量参数的函数吗?

4

15 回答 15

549

Go 没有可选参数,也不支持方法重载

如果方法分派也不需要进行类型匹配,则它会被简化。使用其他语言的经验告诉我们,具有相同名称但不同签名的各种方法有时很有用,但在实践中也可能令人困惑和脆弱。仅按名称匹配并要求类型的一致性是 Go 类型系统中一个主要的简化决定。

于 2010-01-09T02:45:56.073 回答
290

实现可选参数之类的一个好方法是使用可变参数。该函数实际上接收您指定的任何类型的切片。

func foo(params ...int) {
    fmt.Println(len(params))
}

func main() {
    foo()
    foo(1)
    foo(1,2,3)
}
于 2013-11-06T13:16:14.667 回答
215

您可以使用包含参数的结构:

type Params struct {
  a, b, c int
}

func doIt(p Params) int {
  return p.a + p.b + p.c 
}

// you can call it without specifying all parameters
doIt(Params{a: 1, c: 9})
于 2012-11-28T11:22:14.907 回答
165

对于任意的、可能大量的可选参数,一个很好的习惯用法是使用功能选项

对于您的 type Foobar,首先只编写一个构造函数:

func NewFoobar(options ...func(*Foobar) error) (*Foobar, error){
  fb := &Foobar{}
  // ... (write initializations with default values)...
  for _, op := range options{
    err := op(fb)
    if err != nil {
      return nil, err
    }
  }
  return fb, nil
}

其中每个选项都是一个改变 Foobar 的函数。然后为您的用户提供方便的方式来使用或创建标准选项,例如:

func OptionReadonlyFlag(fb *Foobar) error {
  fb.mutable = false
  return nil
}

func OptionTemperature(t Celsius) func(*Foobar) error {
  return func(fb *Foobar) error {
    fb.temperature = t
    return nil
  }
}

操场

为简洁起见,您可以为选项的类型(Playground)命名:

type OptionFoobar func(*Foobar) error

如果您需要强制参数,请将它们作为构造函数的第一个参数添加到 variadic 之前options

功能选项习语的主要好处是:

  • 您的 API 可以随着时间的推移而增长,而不会破坏现有代码,因为在需要新选项时构造函数签名保持不变。
  • 它使默认用例变得最简单:根本没有参数!
  • 它提供了对复杂值初始化的精细控制。

这种技术是由Rob Pike创造的,也由Dave Cheney演示过。

于 2014-10-12T14:54:15.407 回答
24

Go 中既不支持可选参数也不支持函数重载。Go 确实支持可变数量的参数:将参数传递给 ... 参数

于 2010-01-09T03:47:43.697 回答
11

没有——也没有。根据Go for C++ 程序员文档,

Go 不支持函数重载,也不支持用户定义的运算符。

我找不到同样明确的声明,即不支持可选参数,但也不支持它们。

于 2010-01-09T02:46:01.157 回答
10

您可以使用映射传递任意命名参数。aType = map[key].(*foo.type)如果参数具有非统一类型,则必须使用“”声明类型。

type varArgs map[string]interface{}

func myFunc(args varArgs) {

    arg1 := "default"
    if val, ok := args["arg1"]; ok {
        arg1 = val.(string)
    }

    arg2 := 123
    if val, ok := args["arg2"]; ok {
        arg2 = val.(int)
    }

    fmt.Println(arg1, arg2)
}

func Test_test() {
    myFunc(varArgs{"arg1": "value", "arg2": 1234})
}
于 2019-08-01T05:14:37.427 回答
6

Go 不支持可选参数默认值函数重载,但您可以使用一些技巧来实现它们。

分享一个示例,您可以在一个函数中拥有不同数量和类型的参数。这是一个易于理解的简单代码,您需要添加错误处理和一些逻辑。

func student(StudentDetails ...interface{}) (name string, age int, area string) {
    age = 10 //Here Age and area are optional params set to default values
    area = "HillView Singapore"

    for index, val := range StudentDetails {
        switch index {
            case 0: //the first mandatory param
                name, _ = val.(string)
            case 1: // age is optional param
                age, _ = val.(int)
            case 2: //area is optional param
                area, _ = val.(string)
        }
    }
    return
}

func main() {
    fmt.Println(student("Aayansh"))
    fmt.Println(student("Aayansh", 11))
    fmt.Println(student("Aayansh", 15, "Bukit Gombak, Singapore"))
}
于 2021-09-08T04:15:17.800 回答
4

您可以将其很好地封装在类似于下面的函数中。

package main

import (
        "bufio"
        "fmt"
        "os"
)

func main() {
        fmt.Println(prompt())
}

func prompt(params ...string) string {
        prompt := ": "
        if len(params) > 0 {
                prompt = params[0]
        }
        reader := bufio.NewReader(os.Stdin)
        fmt.Print(prompt)
        text, _ := reader.ReadString('\n')
        return text
}

在这个例子中,提示符默认有一个冒号和前面的一个空格。. .

: 

. . . 但是,您可以通过向提示函数提供参数来覆盖它。

prompt("Input here -> ")

这将导致如下提示。

Input here ->
于 2016-11-17T00:27:59.303 回答
4

Go 语言不支持方法重载,但是你可以像可选参数一样使用可变参数,也可以使用 interface{} 作为参数,但这不是一个好的选择。

于 2018-02-07T06:44:24.950 回答
4

所以我觉得我参加这个聚会已经很晚了,但我正在寻找是否有比我已经做的更好的方法来做到这一点。这有点解决了你试图做的事情,同时也给出了可选参数的概念。

package main

import "fmt"

type FooOpts struct {
    // optional arguments
    Value string
}

func NewFoo(mandatory string) {
    NewFooWithOpts(mandatory, &FooOpts{})
}

func NewFooWithOpts(mandatory string, opts *FooOpts) {
    if (&opts) != nil {
        fmt.Println("Hello " + opts.Value)
    } else {
        fmt.Println("Hello")
    }
}

func main() {
    NewFoo("make it work please")

    NewFooWithOpts("Make it work please", &FooOpts{Value: " World"})
}

更新1:

添加了一个功能示例以显示功能与示例

于 2021-03-26T03:38:59.040 回答
2

我最终使用了 params 结构和可变参数 args 的组合。这样,我不必更改多个服务使用的现有接口,并且我的服务能够根据需要传递额外的参数。golang 操场中的示例代码:https: //play.golang.org/p/G668FA97Nu

于 2016-08-11T14:56:32.670 回答
2

我有点晚了,但如果你喜欢流畅的界面,你可以为这样的链式调用设计你的设置器:

type myType struct {
  s string
  a, b int
}

func New(s string, err *error) *myType {
  if s == "" {
    *err = errors.New(
      "Mandatory argument `s` must not be empty!")
  }
  return &myType{s: s}
}

func (this *myType) setA (a int, err *error) *myType {
  if *err == nil {
    if a == 42 {
      *err = errors.New("42 is not the answer!")
    } else {
      this.a = a
    }
  }
  return this
}

func (this *myType) setB (b int, _ *error) *myType {
  this.b = b
  return this
}

然后这样称呼它:

func main() {
  var err error = nil
  instance :=
    New("hello", &err).
    setA(1, &err).
    setB(2, &err)

  if err != nil {
    fmt.Println("Failed: ", err)
  } else {
    fmt.Println(instance)
  }
}

这类似于@Ripounet 答案中提出的功能选项习语,具有相同的好处,但也有一些缺点:

  1. 如果发生错误,它不会立即中止,因此,如果您希望构造函数经常报告错误,效率会稍低。
  2. 您将不得不花费一行来声明一个err变量并将其归零。

然而,有一个可能的小优势,这种类型的函数调用应该更容易让编译器内联,但我真的不是专家。

于 2017-08-20T00:20:31.503 回答
2

如果您不想使用指针,则可以使用指针并将它们留空:

func getPosts(limit *int) {
  if optParam != nil {
    // fetch posts with limit 
  } else {
    // fetch all posts
  }
}

func main() {
  // get Posts, limit by 2
  limit := 2
  getPosts(&limit)

  // get all posts
  getPosts(nil)
}
于 2021-07-22T14:17:13.177 回答
0

另一种可能性是使用带有字段的结构来指示其是否有效。sql 中的 null 类型(例如NullString)很方便。不必定义自己的类型很好,但如果您需要自定义数据类型,您始终可以遵循相同的模式。我认为从函数定义中可以清楚地看到可选性,并且几乎不需要额外的代码或工作。

举个例子:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}
于 2020-05-09T19:09:37.057 回答