IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [原]Go语言核心技术(卷一)之1.4-包和文件

    abv123456789发表于 2016-03-15 17:39:02
    love 0

    一、Package

    Go语言中的包(Package)就像其它语言的库(Library)或模块(Module)一样,支持模块化,封装性,可重用性,单独编译等特点。包的源码是由数个.go文件组成,这些文件所在的目录名是import路径的最后一个词,例如github.com/sunface/corego包的所有文件都存储在$GOPATH/src/github.com/sunface/corego底下。


    每个包都有独立的命名空间。例如,在image包中的Decode和unicode/utf16中的Decode是完全不同的函数。如果要引用第三方库的函数,我们要使用package.Func的形式,例如image.Decode和utf16.Decode。


    包也允许我们自己控制包内变量、函数的可见性。在Go语言中,变量、函数等的导出只取决于一个因素:名字首字母的大小写。


    想象一下,如果我们的温度转换软件开始流行了,然后希望贡献给开源社区,应该怎么做?

    首先让我们创建一个包github.com/sunface/temconv,在1.3节的例子基础上做一些变化。这个包中包含了两个文件,演示了怎么样把数据声明和数据访问分开,在现实项目中,这个包实际只需要一个文件。

    temconv.go包含了类型声明,常量,类型的method:

    package tempconv
    
    import "fmt"
    
    type Celsius float64
    type Fahrenheit float64
    
    const (
        AbsoluteZeroC Celsius = -273.15
        FreezingC     Celsius = 0
        BoilingC      Celsius = 100
    )
    
    func (c Celsius) String() string    { return fmt.Sprintf("%g°C", c) }
    func (f Fahrenheit) String() string { return fmt.Sprintf("%g°F", f) }

    conv.go包含了转换函数:

    //温度转换
    package tempconv
    
    // 将Celsius温度转换为Fahrenheit
    func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
    
    // 将Fahrenheit温度转换为Celsius.
    func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }

    每个.go文件的第一行都是package tempconv的声明,表示该文件属于哪个包。当包被导入后,可以这样调用它的成员:tempconv.CToF等等。包级别的变量,例如类型、常量等,对同一个包内的所有文件都是可见的,就好像所有代码定义在同一个文件中一样。注意这里temconv.go导入了fmt.但是conv.go没有,因为它没有用到fmt的任何成员。

    上面代码中包级别的const变量,都是大写字母开头的,因此可以在temconv包的外部使用,tempconv.AbsoluteZeroC:

    fmt.Println(tempconv.CToF(tempconv.BoilingC)) // "212°F"


    我们会选择包内的某个.go文件进行包级别的注释,注释写在该文件的package声明前(见之前的conv.go)。一般来说,这里的注释是对文件进行概述的。一个包只有一个文件需要包级别的注释。这些注释一般会放在doc.go文件中,后续可以通过go doc tempconv来查看包注释。


    二、包导入(import)

    在Go程序中,每一个包都是通过一个唯一的字符串来标示的,被称为导入路径,这些包是在import声明中统一导入的。Go语言规范不会对导入的包名进行任何约定,这些是由Go的工具来完成解析的。当使用go的工具 (tool)时,一个导入路径代表了一个文件夹,该文件夹内包含了组成包的.go文件。

    在import声明中,每个包都有自己的导入包名,按照惯例,这个包名是导入路径的最后一个词,例如github.com/sunface/tempconv的导入包名是temconv:

    package main
    
    import (
        "fmt"
        "os"
        "strconv"
    
        "github.com/sunface/tempconv"
    )
    
    func main() {
        for _, arg := range os.Args[1:] {
            t, err := strconv.ParseFloat(arg, 64)
            if err != nil {
                fmt.Fprintf(os.Stderr, "cf: %v\n", err)
                os.Exit(1)
            }
            f := tempconv.Fahrenheit(t)
            c := tempconv.Celsius(t)
            fmt.Printf("%s = %s, %s = %s\n",
                f, tempconv.FToC(f), c, tempconv.CToF(c))
        }
    }
    因此可以直接调用tempconv.CToF。还可以使用别名机制避免包名冲突:
    import (
    	"github.com/sunface/tempconv"
    	temp "github.com/sunfei/tempconv"
    )
    调用github.com/sunfei/tempconv:temp.CToF;调用github.com/sunface/tempconv:tempconv.CToF。

    上面这个程序将单独的数字命令行参数转换成Celsius和Fahrenhit的值。

    $ go build github.com/sunface/corego/ch1.4/cf
    $ ./cf 32
    32°F = 0°C, 32°C = 89.6°F
    $ ./cf 212
    212°F = 100°C, 212°C = 413.6°F
    $ ./cf -40
    -40°F = -40°C, -40°C = -40°F

    如果导入一个包后不去使用,那么就会报编译错误,编译器的这个检查可以帮助消除不需要的包引用,虽然在debug期间可能会比较蛋疼,例如注释掉log.Print("hello")可能会消除程序对log包的引用,这个时候编译器就会报错。还好,我们可以使用golang.org/x/tools/cmd/goimports工具,它会自动插入和移除包引用,大多数ide都支持配置去使用goimports。



    三、包的初始化

    包的初始化时会按照声明的顺序初始化包级别的变量,除非变量间有依赖顺序:

    var a = b + c     // a 第三个初始化 3
    var b = f()       // b 第二个初始化,调用了f
    var c = 1         // c 第一个初始化
    
    func f() int { return c + 1 }
    如果某个包有多个.go文件,那这些文件会按照提交给编译器的顺序来初始化,在唤醒编译器前,go tool会通过文件名对.go文件进行排序。

    任何文件都可以包含任何数目的init函数:

    func init() { /* ... */ }
    这种init函数不能被调用也不能被引用,在每个文件内,init函数都会在程序刚启动的时候自动运行,文件内每个init函数会按照声明的顺序依次执行。



    每个包只会被初始化一次,首先是初始化依赖包,如果包p导入q,可以肯定的是:q在p初始化之前肯定会完全初始化。因为依赖包会先初始化,所以程序的初始化是自底向上的,main包肯定是最后一个初始化(包的组织类似一个树形结构,main是根节点)。这样在main函数开始时,所有的包都会初始化完毕!







沪ICP备19023445号-2号
友情链接