2023-06-01 07:00:00

Go?

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


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

方法调度被简化了 不需要做类型匹配 好。有使用其他语言的经验 告诉我们有各种各样的 方法,但名称相同 偶尔会有不同的签名 有用,但也可能有用 在实践中令人困惑和脆弱。 仅通过名称和要求进行匹配 类型的一致性是主要因素 简化围棋类型中的决策 系统。


不——都不是。根据Go for c++程序员文档,

Go不支持函数 重载,不支持用户 定义操作符。

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


Go中不支持可选参数和函数重载。Go确实支持可变数量的参数:参数


你可以使用一个包含形参的结构体:

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})

与省略号(params…SomeType)相比,其主要优点是可以将param结构体与不同的形参类型一起使用。


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

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

func main() {
    foo()
    foo(1)
    foo(1,2,3)
}

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

对于你的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

如果需要强制参数,将它们作为构造函数的第一个参数添加到可变参数选项之前。

Functional options成语的主要好处是:

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

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


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


你可以很好地将它封装在一个类似于下面的func中。

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函数提供参数来覆盖它。

prompt("Input here -> ")

这将导致如下提示。

Input here ->

我有点晚了,但如果你喜欢流畅的界面,你可能会为链式调用设计你的setter:

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回答中给出的函数选项习惯用法,具有相同的好处,但有一些缺点:

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

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


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


您可以通过映射传递任意命名参数。如果参数具有不一致的类型,则必须使用“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})
}

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

举个例子:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}

所以我觉得我来这个派对已经晚了,但我一直在寻找是否有比我现在做的更好的方法。这在某种程度上解决了你试图做的事情,同时也给出了一个可选参数的概念。

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:

添加了一个功能示例,以显示功能与示例的对比


你可以使用指针,如果你不想使用它们,让它们为nil:

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)
}

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"))
}