【问题标题】:Relationship between a package statement and the directory of a .go filepackage语句与.go文件目录的关系
【发布时间】:2017-09-20 15:33:02
【问题描述】:

查看这个实验。

~/go/src$ tree -F
.
├── 1-foodir/
│   └── 2-foofile.go
└── demo.go

1 directory, 2 files

~/go/src$ cat demo.go 
package main

import (
    "fmt"
    "1-foodir"
)

func main() {
    fmt.Println(foopkg.FooFunc())
}

~/go/src$ cat 1-foodir/2-foofile.go 
package foopkg

func FooFunc() string {
    return "FooFunc"
}

~/go/src$ GOPATH=~/go go run demo.go 
FooFunc

我认为我们总是导入一个包名。但是上面的例子 显示我们实际上导入了一个包目录名("1-foodir") 但是在该包中调用导出的名称时,我们使用 Go 文件中声明的包名 (foopkg.FooFunc)。

这对于像我这样来自 Java 和 Python 世界的初学者来说是很困惑的, 其中目录名称本身是用于限定的包名称 包中定义的模块/类。

为什么我们使用import语句的方式和 引用 Go 中包中定义的名称?你能解释一下规则吗 Go 背后的这些事情?

【问题讨论】:

    标签: go import package


    【解决方案1】:

    如果你说的是真的,那么你的函数调用实际上是 1-foodir.FooFunc() 而不是 foopkg.FooFunc()。相反,go 在 2-foofile.go 中看到包名称并将其导入为 foopkg,因为在 go 中,包的名称正是 .go 文件顶部的 package 之后出现的名称,前提是它是有效的标识符。

    该目录的唯一用途是收集一组共享相同包名的文件。 the spec

    中重申了这一点

    一组文件共享相同的 PackageName 形成一个包的实现。一个实现可能要求一个包的所有源文件位于同一个目录中。

    在 go 中,目录与包名称匹配是惯例,但不一定是这种情况,而且通常不是第 3 方包。标准库确实很好地遵守了这一约定。

    现在目录 do 发挥作用的是导入路径。您可以在单个二进制文件中有 2 个名为“foo”的包,只要它们具有 不同的 导入路径,即

    /some/path/1/foo/some/path/2/foo

    我们可以变得非常时髦,并将导入别名为我们想要的任何东西,例如我可以做的

    import (
        bar "/some/path/1/foo"
        baz "/some/path/2/foo"
    )
    

    再次强调,这不是因为包名必须是唯一的,而是包导入路径必须是唯一的。

    从这句话中可以了解到的另一点是——在一个目录中,你不能有两个包名。 go 编译器会抛出一个错误,指出它是cannot load package,它是found packages foo (foo.go) and bar (bar.go)

    更多信息请参见https://golang.org/doc/code.html#PackageNames

    【讨论】:

    【解决方案2】:

    大致原因:

    1. 包有一个由包子句设置的“名称”,即源代码开头的package thepackagename

    2. 通过几乎不透明的字符串导入包:导入声明中的导入路径。

    第一个是名称,第二个是如何找到该名称。第一个是给程序员的,第二个是给编译器/工具链的。 很方便(对于编译器和程序员)声明

    请导入"/some/hierarchical/location"中找到的包

    然后在像robot这样的语句中用它的简单名称来引用那个包

    robot.MoveTo(3,7)
    

    注意使用这个包就像

    /some/hierarchical/location.MoveTo(3.7)
    

    不会是合法代码,既不可读也不清晰也不方便。 但是对于编译器/工具链来说,如果导入路径具有结构并允许表达任意包位置,即不仅是文件系统中的位置,而且例如在档案内或远程机器上,或或或。

    在这一切中还需要注意:Go 编译器和go 工具。 Go 编译器和 go 工具是不同的东西,与 Go 编译器和语言规范所要求的相比,Go 工具对代码、工作区和包的布局方式施加了更多限制。 (例如,Go 编译器允许将不同目录中的文件编译到一个包中而不会出现任何问题。)

    go 工具要求包的所有(我知道有特殊情况)源文件都驻留在一个文件系统目录中,并且常识要求这个目录应该“命名为包”。

    【讨论】:

    • 加一个关于我在回答中错过的 go 工具的信息。
    【解决方案3】:

    首先,包子句和导入路径是不同的东西。

    package clause 声明PackageName

    PackageClause  = "package" PackageName .
    PackageName    = identifier .
    

    包子句的目的是对文件进行分组:

    一组文件共享相同的 PackageName 形成一个包的实现。

    按照约定ImportPath(见下文)的路径基名(目录名)与PackageName 相同。出于方便的目的,建议您无需考虑要使用的PackageName 是什么。

    但是,它们可以不同。

    基本名称只影响ImportPath,检查spec for import declartions

    ImportDecl       = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .
    ImportSpec       = [ "." | PackageName ] ImportPath .
    ImportPath       = string_lit .
    

    如果省略PackageName,则默认为导入包的包子句中指定的标识符。

    例如,如果您有一个目录foo,但您在其中声明了一个源文件中的package bar,那么当您使用import <prefix>/foo 时,您将使用bar 作为前缀来引用从中导出的任何符号包。

    darehas' answer 提出了一个很好的观点:你不能在同一个基本名称下声明多个包。但是,根据package clause,您可以将相同的包分布在不同的基本名称上:

    实现可能要求包的所有源文件位于同一目录中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-08
      • 1970-01-01
      • 2014-07-14
      相关资源
      最近更新 更多