Go语言核心之美 1.4-包和文件

erlib · · 820 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

一、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函数开始时,所有的包都会初始化完毕!




文章所有权:Golang隐修会 联系人:孙飞,CTO@188.com!


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:erlib

查看原文:Go语言核心之美 1.4-包和文件

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

820 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传