Go中的可选参数?

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

437783 次浏览

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

方法调度被简化,如果它 不需要进行类型匹配,因为 很好。有其他语言的经验 告诉我们,有各种各样的 具有相同名称的方法,但 不同的签名偶尔 有用,但它也可能是 在实践中混乱而脆弱。 仅按名称匹配并要求 类型的一致性是一个主要 简化Go类型中的决策 系统。

不--都不是。根据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)是您可以将参数结构与不同的参数类型一起使用。

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

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


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

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

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

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

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

游乐场

为了简洁起见,您可以为选项的类型命名(游乐场):

type OptionFoobar func(*Foobar) error

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

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

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

这项技术由Rob Pike创造,戴夫·切尼也证明了这一点。

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

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

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

我有点晚了,但如果你喜欢流畅的界面,你可以为这样的链式调用设计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答案中的功能选项习语,具有相同的好处,但有一些缺点:

  1. 如果发生错误,它不会立即中止,因此,如果您期望您的构造函数经常报告错误,效率会略低。
  2. 您必须花一行时间声明一个err变量并将其归零。

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

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

您可以使用map传递任意命名参数。如果参数具有非统一类型,您将不得不使用“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类型(例如空字符串)很方便。不必定义自己的类型很好,但如果你需要自定义数据类型,你可以始终遵循相同的模式。我认为函数定义的可选性很明显,并且很少额外的代码或努力。

举个例子:

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