Go36-3-代码包

发布时间:2019-06-23 08:43:10编辑:auto阅读(1598)

    把代码拆分到多个文件

    如果代码太复杂,就可以把代码写在多个源码文件里。或者是其他代码包中(这个后面讲)

    代码示例

    下面是程序的主体,但是其中调用了square()函数,这个函数并没有声明:

    // Go36/article03/example01/demo.go
    package main
    
    import (
        "flag"
        "fmt"
    )
    
    var x int
    
    func init() {
        flag.IntVar(&x, "x", 0, "计算平方")
    }
    
    func main() {
        flag.Parse()
        res := square(x)
        fmt.Println(x, "的平方:", res)
    }

    上面用到的square()函数被声明在了另一个文件中:

    // Go36/article03/example01/calc.go
    package main
    
    func square(x int) int {
        return x * x
    }

    这里只声明了一个函数,可以在这个写更多关于计算的函数,这样在同个包里都可以方便的调用这些函数使用。
    上面的2个文件都要在同一个目录下,并且需要被声明为属于同一个包。

    执行代码

    因为示例中都声明为main包,并且包里也有一个main函数。所以存在一个命令源码文件,这样就可以直接运行起来:

    PS H:\Go\src\Go36\article03\example01> go run demo.go calc.go -x 3
    3 的平方: 9
    PS H:\Go\src\Go36\article03\example01>

    上面注意要把所有的文件都写在命令里。
    还可以先构建代码包,在执行:

    PS H:\Go\src> go build Go36/article03/example01
    PS H:\Go\src> .\example01.exe -x 4
    4 的平方: 16
    PS H:\Go\src>

    把代码拆分到多个包

    先修改calc.go的路径,并且做一些修改:

    // Go36/article03/example02/lib/calc.go
    package lib2
    
    func Square(x int) int {
        return x * x
    }

    这里创建了一个子目录,把文件放到了这个子目录中,这样使得它同命令源码文件不在同一个目录下了。
    并行代码也做了一些修改:

    1. 包名变成了lib2,这里故意和目录不是同一个名字
    2. 函数名的首字母变成了大写

    包名和目录名不同

    现在要使用上面的包。导入包的路径应该是目录的路径名称:

    import (
        "Go36/article03/example02/lib"
    )

    如果要构建或者安装这个代码包,使用的命令应该是下面这样,还是用目录名称:

    go install Go36/article03/example02/lib

    并且命令成功后,pkg子目录产生的归档文件也是目录名称:

    pkg\windows_amd64\Go36\article03\example02\lib.a

    但是最后调用的时候需要使用包名称,命令源码文件的代码如下:

    // Go36/article03/example02/demo.go
    package main
    
    import (
        "flag"
        "fmt"
        "Go36/article03/example02/lib"
    )
    
    var x int
    
    func init() {
        flag.IntVar(&x, "x", 0, "计算平方")
    }
    
    func main() {
        flag.Parse()
        res := lib2.Square(x)
        fmt.Println(x, "的平方:", res)
    }

    上面调用程序时使用的lib2.称为限定符。
    结论:导入路径使用的是文件所在目录的路径。而调用程序时使用的限定符要与它声明的包的名称一致。
    为了不在使用代码包是产生困惑,应该让声明的包的名称与其父目录的目录名称一致。

    访问权限

    在这里把函数名称的首字母改为大写的原因是,名称的首字母为大写的程序实体才可以被当前包外的代码引用,否则它就只能被当前包内的其他代码引用。
    这涉及了Go语言中对于程序实体访问权限的规则。通过名称的首字母的大小写,就把访问权限分为了包级私有和公开这两种。对于包级私有,只有在包内部可以访问。由于我们需要在main包里调用lib包的函数,只能访问到公开的部分,所以需要把函数的首字母大写。

    模块级私有
    上面的访问权限都以包的级别进行划分的。在Go 1.5及后续版本中,可以通过创建internal代码包让一些程序实体仅仅能被当前模块中的其他代码引用。这是第三种访问权限:模块级私有。
    具体规则是,internal代码包中声明的公开程序实体仅能被该代码包的直接父包及其子包中的代码引用。当然,引用前需要先导入这个internal包。对于其他代码包,导入该internal包都是非法的,无法通过编译。
    这里的名称必须是internal,示例如下:

    // 父级目录 Go36/article03/example03/demo.go
    package main
    
    import (
        "flag"
        "Go36/article03/example03/lib"
        //"Go36/article03/example03/lib/internal" // 此行无法通过编译。
    )
    
    var x int
    
    func init() {
        flag.IntVar(&x, "x", 0, "计算平方")
    }
    
    func main() {
        flag.Parse()
        lib.Cale(x)
        //res := internal.Square(x)
        //lib.Cale(res)
    }
    
    // 子级目录 Go36/article03/example03/lib/demo_lib.go
    package lib
    
    import (
        "fmt"
        "Go36/article03/example03/lib/internal"
    )
    
    func Cale(x int) {
        res := internal.Square(x)
        fmt.Println(x, "的平方:", res)
    }
    
    // 孙子目录 Go36/article03/example03/lib/internal/internal.go
    package internal
    
    func Square(x int) int {
        return x * x
    }

    模块级私有的internal包,仅能被直接父包及其子包中的代码引用。上面如果要在父级里调用孙级目录的internal包,就是非法的:

    PS H:\Go\src\Go36\article03\example03> go run demo.go -x 7
    demo.go:6:2: use of internal package not allowed
    PS H:\Go\src\Go36\article03\example03>

关键字