当前位置: 首页 > 工具软件 > Bible Reader > 使用案例 >

Go语言圣经 - 第10章 包和工具 - 10.1 - 10.7

龙兴贤
2023-12-01

第10章 包和工具

现在随便一个小程序可能就包含10000个函数,但是我们不可能一个个去构建,大部分还是来自于他人,这些函数通过类似包和模块的方式被重用

go语言的包超过100个,可以在终端中使用go list std |wc -l去查看,开源包可以通过http://godoc.org来检索

go带了一个工具包里面有各种简化工作区和包管理的小工具

10.1 包简介

包能够模块化的让我们理解和更新代码,同时包能够实现封装特性,不至于让代码变得乱,也便于我们在全球分发。Go语言的包编译起来很快,因为它有三个语言特性:1.导入包需在文件开头显式声明,这样编译器就无需读取和分析整个源文件来判断包的依赖关系,2.禁止包的环状依赖,包的关系是有向无环图,每个包可以被独立编译,而且可能是并发编译3.编译后包的目标文件不仅仅是记录包本身的导出信息,目标文件同时还记录了包的依赖关系

10.2 导入路径

包一般是通过import自动导入,非标准库的包要写好互联网的路径地址,例如下面Go团队维护的HTML解析器和一个流行的第三方维护的MySQL驱动

import (
    "fmt"
    "math/rand"
    "encoding/json"

    "golang.org/x/net/html"

    "github.com/go-sql-driver/mysql"
)

10.3 包声明

每个Go源文件开头都必须有包声明语句,它就是被其他包导入时的标识符

通过包名访问包成员

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	fmt.Println(rand.Int())
}

通常包的导入路径就是包名,但是可以重名,但他们的路径可能不同

关于把包名作为导入路径的最后一段也有三个例外,1.main包本身路径无关紧要,2.测试包,3.在导入路径后增加版本号信息,例如“gopkg.in/yaml.v2”

10.4 导入声明

可以使用import一个个导入,也可以一起导入,还可以分割分组导入

import (
    "fmt"
    "html/template"
    "os"

    "golang.org/x/net/html"
    "golang.org/x/net/ipv4"
)

如果两个包同名,导入的时候要重命名,这种命名只会在当前文件中起作用

import (
    "crypto/rand"
    mrand "math/rand" // alternative name mrand avoids conflict
)

重命名有三个好处,一个时区分重名包,二是化繁为简,把一些长包名复杂包名变短,三是可避免和包里面的重名变量混淆

循环导入编译器会提示错误

10.5 包的匿名导入

如果我们导入了包却没有使用会导致编译错误,但是呢有时候我么们想利用导入包产生的副作用:它会计算包级别的初始化表达式和执行导入包的init初始化函数,这是我们需要抑制unused import错误,我们可以使用 _ 来重命名导入的包。 _ 为空白标识符,不能被访问

import _ "image/png"

这个被称为包的匿名导入,它通常是用来实现一个编译时机制, 然后通过在main主程序入口选择性的导入附加的包。我们先来看看它的特性以及它是如何工作的

package main

import (
	"fmt"
	"image"
	"image/jpeg"
	"io"
	"os"
)

func main() {
	if err := toJPEG(os.Stdin, os.Stdout); err != nil {
		fmt.Fprintf(os.Stderr, "jpeg:%v\n", err)
		os.Exit(1)
	}
}

func toJPEG(in io.Reader, out io.Reader) error {
	img, kind, err := image.Decode(in)
	if err != {
		return err
	}
	fmt.Fprintln(os.Stderr,"Input format =",kind)
	return jpeg.Encode(out,img,&jpeg.Options{Quality: 95})
}

如果我们给它一个合适的输入,它就能成功转换为输出

$ go build gopl.io/ch3/mandelbrot
$ go build gopl.io/ch10/jpeg
$ ./mandelbrot | ./jpeg >mandelbrot.jpg
Input format = png

如果没有匿名导入,它可以编译,但是无法正确输出

$ go build gopl.io/ch10/jpeg
$ ./mandelbrot | ./jpeg >mandelbrot.jpg
jpeg: image: unknown format

下面是代码的工作机制

package png // image/png

func Decode(r io.Reader) (image.Image, error)
func DecodeConfig(r io.Reader) (image.Config, error)

func init() {
    const pngHeader = "\x89PNG\r\n\x1a\n"
    image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}

最终的效果是,主程序只需要匿名导入特定图像驱动包就可以用image.Decode解码对应格式的图像了

数据库包database/sql也是采用了类似的技术,让用户可以根据需要选择导入数据库驱动,如:

import (
    "database/sql"
    _ "github.com/lib/pq"              // enable support for Postgres
    _ "github.com/go-sql-driver/mysql" // enable support for MySQL
)

db, err = sql.Open("postgres", dbname) // OK
db, err = sql.Open("mysql", dbname)    // OK
db, err = sql.Open("sqlite3", dbname)  // returns error: unknown driver "sqlite3"

10.6 包和命名

当创建一个包,一般要用短小的命名;要尽量不使用局部变量的名称;一般是单数形式,但是有其他考虑也可以复数

当设计包名时需要考虑包名和成员是如何配合的,如下面的例子:

bytes.Equal flag.Int hettp.Get json.Marshal

我们再来看下strings命名模式,strings提供了有关于字符串的各项操作

package strings

func Index(needle, haystack string) int

type Replacer struct{ /* ... */ }
func NewReplacer(oldnew ...string) *Replacer

type Reader struct{ /* ... */ }
func NewReader(s string) *Reader

strings.Index \ strings.Replacer等都是操作

其它的包,可能只描述了单一数据类型,例如html/template和math/rand等,只暴露一个主要的数据结构与它相关的方法,还有一个以New命名的函数用于创建实例

package rand // "math/rand"

type Rand struct{ /* ... */ }
func New(source Source) *Rand

还可能导致一些名字的重复,例如template.Template或rand.Rand,这也就是为什么这些种类的包名往往特别短的原因之一

在另一个极端,还有像net/http包那样含有非常多的名字和种类不多的数据类型,因为他们要执行一个复杂的任务,尽管有近20种类型和更多的函数,但是包中最重要的成员名字确是简单明了的:Get,Post,Handle,Error,Client,Server

 类似资料: