【问题标题】:golang: implicit vs explicit func definitiongolang:隐式 vs 显式 func 定义
【发布时间】:2017-08-17 14:11:36
【问题描述】:

考虑这个包:

package A
var X="change me"
var Y=func(i int) int { return i*i) }
func Z(i int) int { return -i) }

两个显式变量 (X,Y) 可以在另一个包中更改,比如 main...

package main
import "A"
func main () {
    A.X="done"
    A.Y=func (i int) int { return i*i*i }
    print(A.X,A.Y(7))
    //... but A.Z apparently can't be changed.
    //A.Z=func (int i) int { return i*i*i } //main.go:8: cannot assign to A.Z
}

显然,定义 func 变量(如 Y)和显式 func(如 Z)之间存在差异。我用谷歌搜索了这个,但没有发现太多启蒙的方式。看起来好像 var SomeFunc=func (...) 确实定义了一个变量,但 func SomeFunc(...) 定义了一个常量。

PS:我在研究这个时发现的一个小东西,到目前为止我读过的围棋书籍中没有提到它。包导入前的点会导入名称,而不必对其进行限定:

package main
import . "A"
func main () {
    X="done"
    Y=func (i int) int { return i*i*i }
    print(X,Y(7))
}

【问题讨论】:

标签: go


【解决方案1】:

声明一个用函数值初始化的变量是有区别的:

var Y=func(i int) int { return i*i) }

并声明一个函数:

 func Z(i int) int { return -i) }

specification says this about declarations:

声明将非空白标识符绑定到常量、类型、变量、函数、标签或包。

specification also says:

函数声明将标识符(函数名)绑定到函数。

Y 的声明将变量绑定到名称。这个变量用函数值初始化。 Z 的声明将函数绑定到名称。

如果出现显式句点 (.) 而不是名称,则在该包的包块中声明的所有包的导出标识符都将在导入源文件的文件块中声明,并且必须在没有限定符的情况下访问。

【讨论】:

    【解决方案2】:

    func SomeFunc(),本质上创建标识符SomeFunc 到您定义的函数的强/恒定/不可变绑定。当你像这样创建一个变量时:

    var (
        SomeFunc = func(i int) int {
            return i * 2
        }
    )
    

    您创建了一个func(int) int 类型的全局变量。您可以稍后重新分配此变量。这是你不能用func SomeFunc 标识符真正做到的事情。简单来说,这是因为func SomeFunc()将函数直接绑定到了标识符上。 var SomeFunc 方法创建一个变量(在本例中为 func(int) int 类型),并且该变量使用您分配的函数进行初始化。与变量一样:可以重新分配。

    Example

    可以对函数做什么,就是使用一个作用域变量来隐藏它们。这可能会被大多数 linter 标记,但这是一种有时在测试中很有用的技术/技巧

    Example

    至于点导入:请不要这样做,除非有一个非常、非常、非常很好的理由。一个很好的理由是您编写一个添加到现有包的包,因此您不再导入现有包,而是导入您自己的包。将其视为扩展一个包。 99% 的时间。无论您做什么,都不要在导入 encoding/json 以将 json 序列化注释添加到结构时使用它来消除错误。在这些情况下,请使用下划线:

    package foo
    import (
        "encoding/json"
    )
    
    type Bar struct {
        Foobar string `json:"foobar"`
    }
    
    func New() *Bar {
        &Bar{"Default foobar"}
    }
    

    不知道 golang 1.8,但是这样的包可能会导致编译器错误(包 encoding/json 导入但未使用)。要消除该错误,您只需将导入更改为:

    import(
        _ "encoding/json"
    )
    

    点包、下划线和包别名都遵循相同的规则:尽可能少地使用它们。


    示例中使用的代码:

    package main
    
    import (
        "fmt"
    )
    
    var (
        SomeFunc = func(i int) int {
            return i * 2
        }
    )
    
    func main() {
        fmt.Println(SomeFunc(2)) // output 4
        reassign()
        fmt.Println(SomeFunc(2)) // output 8
        shadowReassign()
        fmt.Println(SomeFunc(2)) // output 2
    }
    
    // global function
    func reassign() {
        // assign new function to the global var. Function types MUST match
        SomeFunc = func(i int) int {
            return i * 4
        }
    }
    
    // assign function to local reassign variable
    func shadowReassign() {
        reassign := func() {
            // same as global reassign
            SomeFunc = func(i int) int {
                return i
            }
        }
        reassign()
    }
    

    【讨论】:

    • 只是一个想法。在 main 中添加另一个调用以重新分配可能很有用,然后打印以显示原始全局仍然有效并且没有被覆盖。只是为了完全清楚。
    • @elias:所以实际上你所说的是 func SomeFunc(...) 确实定义了一个常量,例如“const SomeFunc = func (...)”。 (这不是有效的 Go,我知道。)我还没有真正看到 Go 文档中为什么会出现这种情况的解释。至于点导入,我同意,尽管有有效的用例,但应该谨慎使用。我之所以提到它,是因为我以前从未见过它(过去几周我读过很多关于 Go 的文章)。
    • @RayfenWindspear:我不确定我是否理解你的评论。
    • @CharlesPerrin:将标识符直接绑定到静态函数的最重要原因之一非常简单:包。如果从包中导出函数,它实际上是只读的。对变量做同样的事情,你最终会得到一个可以被任何人更改的包(重新分配一个导出的变量)。至于之前的评论:我相信 RayfenWindspear 是指遮蔽全局变量
    猜你喜欢
    • 2018-06-17
    • 2011-10-29
    • 1970-01-01
    • 1970-01-01
    • 2010-10-29
    • 1970-01-01
    • 1970-01-01
    • 2011-08-17
    • 1970-01-01
    相关资源
    最近更新 更多