打包声明和.go文件目录之间的关系

Lon*_*ner 6 import package go

看到这个实验。

~/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
Run Code Online (Sandbox Code Playgroud)

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

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

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

Iza*_*ana 8

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

包条款声明PackageName

PackageClause  = "package" PackageName .
PackageName    = identifier .
Run Code Online (Sandbox Code Playgroud)

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

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

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

然而,它们可以不同。

基本名称仅影响导入声明的ImportPath检查规范:

ImportDecl       = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .
ImportSpec       = [ "." | PackageName ] ImportPath .
ImportPath       = string_lit .
Run Code Online (Sandbox Code Playgroud)

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

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

dareshas 的回答提出了一个很好的观点:您不能在同一基本名称下声明多个包。但是,根据package 子句,您可以将相同的包分布在不同的基本名称上:

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


Vol*_*ker 7

大致原因:

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

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

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

请导入在 "/some/hierarchical/location"

然后通过它的简单名称来引用该包,就像robot在语句中一样

robot.MoveTo(3,7)
Run Code Online (Sandbox Code Playgroud)

请注意,使用此包

/some/hierarchical/location.MoveTo(3.7)
Run Code Online (Sandbox Code Playgroud)

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

同样重要的是要注意:有 Go 编译器和go工具。Go 编译器和 Go 工具是不同的东西,与 Go 编译器和语言规范所要求的相比,Go 工具对您如何布置代码、工作空间和包施加了更多限制。(例如,Go 编译器允许将来自不同目录的文件编译到一个包中而不会出现任何问题。)

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


dar*_*has 6

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

该目录的唯一用途是收集一组共享相同软件包名称的文件。规范中重申了这一点

共享相同PackageName的一组文件构成了一个包的实现。一个实现可能要求包的所有源文件都驻留在同一目录中。

按照惯例,目录必须与软件包名称匹配,但这不是必须的,通常不是与第三方软件包一起使用。stdlib确实遵守了该约定。

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

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

而且我们可以得到非常时髦的效果,并将导入别名化为我们想要的任何内容,例如我可以

import (
    bar "/some/path/1/foo"
    baz "/some/path/2/foo"
)
Run Code Online (Sandbox Code Playgroud)

再次说明,这样做的原因不是软件包名称必须唯一,而是软件包导入路径必须唯一。

从该语句中收集的另一点见解是-在目录中,您不能有两个软件包名称。go编译器将抛出一个错误,指出它cannot load package及其所在found packages foo (foo.go) and bar (bar.go)

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

  • ` https://golang.org/doc/code.html` 下不再有 `#PackageNames` 。 (2认同)