Go语言有超过100个的标准包(可以用go list std | wc -l命令查看标准包的具体数目),标准库为大多数的程序提供了必要的基础构件。在Go的社区,有很多成熟的包被设计、共享、重用和改进,目前互联网上已经发布了非常多的Go语言开源包,它们可以通过 http://godoc.org 检索。在本章,我们将演示如果使用已有的包和创建新的包。

包简介

任何包系统设计的目的都是为了简化大型程序的设计和维护工作,通过将一组相关的特性放进一个独立的单元以便于理解和更新,在每个单元更新的同时保持和程序中其它单元的相对独立性。这种模块化的特性允许每个包可以被其它的不同项目共享和重用,在项目范围内、甚至全球范围统一地分发和复用。

每个包一般都定义了一个不同的命名空间用于它内部的每个标识符的访问。每个命名空间关联到一个特定的包,让我们给类型、函数等选择简短明了的名字,这样可以在我们使用它们的时候减少和其它部分名字的冲突。

每个包还通过控制包内名字的可见性和是否导出来实现封装特性。通过限制包成员的可见性并隐藏包API的具体实现,将允许包的维护者在不影响外部包用户的前提下调整包的内部实现。通过限制包内变量的可见性,还可以强制用户通过某些特定函数来访问和更新内部变量,这样可以保证内部变量的一致性和并发时的互斥约束。

当我们修改了一个源文件,我们必须重新编译该源文件对应的包和所有依赖该包的其他包。即使是从头构建,Go语言编译器的编译速度也明显快于其它编译语言。Go语言的闪电般的编译速度主要得益于三个语言特性。第一点,所有导入的包必须在每个文件的开头显式声明,这样的话编译器就没有必要读取和分析整个源文件来判断包的依赖关系。第二点,禁止包的环状依赖,因为没有循环依赖,包的依赖关系形成一个有向无环图,每个包可以被独立编译,而且很可能是被并发编译。第三点,编译后包的目标文件不仅仅记录包本身的导出信息,目标文件同时还记录了包的依赖关系。因此,在编译一个包的时候,编译器只需要读取每个直接导入包的目标文件,而不需要遍历所有依赖的的文件(很多都是重复的间接依赖)。

导入路径

每个包是由一个全局唯一的字符串所标识的导入路径来定位的。

import ("fmt""math/rand""encoding/json""golang.org/x/net/html""github.com/go-sql-driver/mysql"
)

Go语言的规范并没有指明包的导入路径字符串的具体含义,导入路径的具体含义是由构建工具来解释的。

如果你计划分享或发布包,那么导入路径必须是全球唯一的。为了避免冲突,所有非标准库包的导入路径建议以所在组织的互联网域名为前缀;而且这样也有利于包的检索。例如,上面的import语句导入了Go团队维护的HTML解析器和一个流行的第三方维护的MySQL驱动。

包声明

在每个Go源文件的开头都必须有包声明语句。包声明语句的主要目的是确定当前包被其它包导入时默认的包名。

例如,math/rand包的每个源文件的开头都包含package rand包声明语句,所以当你导入这个包,你就可以用rand.Int、rand.Float64类似的方式访问包的成员。

通常来说,默认的包名就是包导入路径名的最后一段,因此即使两个包的导入路径不同,它们依然可能有一个相同的包名。例如,math/rand包和crypto/rand包的包名都是rand。稍后我们将看到如何同时导入两个有相同包名的包。

关于默认包名一般采用导入路径名的最后一段的约定也有三种例外情况:

第一个例外,包对应一个可执行程序,也就是main包,这时候main包本身的导入路径是无关紧要的。名字为main的包是给go build构建命令一个信息,这个包编译完之后必须调用连接器生成一个可执行程序。

第二个例外,包所在的目录中可能有一些文件名是以test.go为后缀的Go源文件,并且这些源文件声明的包名也是以_test为后缀名的。这种目录可以包含两种包:一种普通包,加一种则是测试的外部扩展包。所有以_test为后缀包名的测试外部扩展包都由go test命令独立编译,普通包和测试的外部扩展包是相互独立的。测试的外部扩展包一般用来避免测试代码中的循环导入依赖。

第三个例外,一些依赖版本号的管理工具会在导入路径后追加版本号信息,例如"gopkg.in/yaml.v2"。这种情况下包的名字并不包含版本号后缀,而是yaml。

导入包

可以在一个Go语言源文件包声明语句之后,其它非导入声明语句之前,包含零到多个导入包声明语句。每个导入声明可以单独指定一个导入路径,也可以通过圆括号同时导入多个导入路径。下面两个导入形式是等价的,但是第二种形式更为常见。

import "fmt"
import "os"import ("fmt""os"
)

导入的包之间可以通过添加空行来分组;通常将来自不同组织的包独自分组。包的导入顺序无关紧要,但是在每个分组中一般会根据字符串顺序排列。(gofmt和goimports工具都可以将不同分组导入的包独立排序。)

import ("fmt""html/template""os""golang.org/x/net/html""golang.org/x/net/ipv4"
)

如果我们想同时导入两个有着名字相同的包,例如math/rand包和crypto/rand包,那么导入声明必须至少为一个同名包指定一个新的包名以避免冲突。这叫做导入包的重命名。

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

导入包的重命名只影响当前的源文件。其它的源文件如果导入了相同的包,可以用导入包原本默认的名字或重命名为另一个完全不同的名字。

导入包重命名是一个有用的特性,它不仅仅只是为了解决名字冲突。如果导入的一个包名很笨重,特别是在一些自动生成的代码中,这时候用一个简短名称会更方便。选择用简短名称重命名导入包时候最好统一,以避免包名混乱。选择另一个包名称还可以帮助避免和本地普通变量名产生冲突。例如,如果文件中已经有了一个名为path的变量,那么我们可以将"path"标准包重命名为pathpkg。

每个导入声明语句都明确指定了当前包和被导入包之间的依赖关系。如果遇到包循环导入的情况,Go语言的构建工具将报告错误。

匿名包导入

如果只是导入一个包而并不使用导入的包将会导致一个编译错误。但是有时候我们只是想利用导入包而产生的副作用:它会计算包级变量的初始化表达式和执行导入包的init初始化函数。这时候我们需要抑制“unused import”编译错误,我们可以用下划线_来重命名导入的包。像往常一样,下划线_为空白标识符,并不能被访问。

import _ "image/png" // 注册 PNG 解码器

这个被称为包的匿名导入。

标准库提供了GIF、PNG和JPEG等格式图像的解码器,用户也可以提供自己的解码器,但是为了保持程序体积较小,很多解码器并没有被全部包含,除非是明确需要支持的格式。image.Decode函数在解码时会依次查询支持的格式列表。每个格式解码器包的入口指定了四件事情:格式的名称;一个用于描述这种图像格式类型的字符串,用于解码器检测识别;一个Decode函数用于完成解码图像工作;一个DecodeConfig函数用于解码图像的大小和颜色空间的信息。每个解码器包的入口通过调用image.RegisterFormat函数注册解码器,一般是在每个格式包的init初始化函数中调用,例如image/png包是这样注册的:

package png // image/pngfunc 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)
}

如果没有这一行匿名导入语句,程序依然可以编译和运行,但是它将不能正确识别和解码PNG格式的图像。

数据库包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"

包的命名

下面是一些关于Go语言软件包和包成员命名的约定。

当创建一个包,一般要简洁明了的包名,但也不能太简短导致难以理解。标准库中最常用的包有bufio、bytes、flag、fmt、http、io、json、os、sort、sync和time等包,它们的名字都简洁明了。

要尽量避免包名与经常用于局部变量的名字发生冲突,否则可能导致用户重命名导入包,例如前面看到的path包。

包名一般采用单数的形式。标准库的bytes、errors和strings使用了复数形式,这是为了避免和预定义的类型冲突,同样还有go/types是为了避免和type关键字冲突。

要避免包名有其它的含义。例如,2.5节中我们的温度转换包最初使用了temp包名,虽然并没有持续多久。但这是一个糟糕的尝试,因为temp几乎是临时变量的同义词。然后我们有一段时间使用了temperature作为包名,但是这个名字并没有表达包的真实用途。最后我们改成了和strconv标准包类似的tempconv包名,这个名字比之前的就好多了。

当设计一个包的时候,需要考虑包名和成员名两个部分如何很好地配合。成员名不必再包含包名,下面有一些例子:

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

我们可以看到一些常用的命名模式。strings包提供了和字符串相关的诸多操作:

package stringsfunc Index(needle, haystack string) inttype Replacer struct{ /* ... */ }
func NewReplacer(oldnew ...string) *Replacertype Reader struct{ /* ... */ }
func NewReader(s string) *Reader

字符串单词string本身并没有出现在每个成员名字中。因为用户会这样引用这些成员strings.Indexstrings.Replacer等。

《Go语言程序设计》 读书笔记 (八) 包相关推荐

  1. c语言程序设计读书笔记3000字,C语言程序设计读书笔记题目

    读书笔记注意事项: 1. 读书笔记要求至少有六个题目,在一类.二类.三类题目中各选两题,具体题目选择由学生自行选择. 2. 每个题目必须包含所选题目,以及具体题目的程序实现过程,要求每行语句后都需要有 ...

  2. C语言程序设计---读书笔记汇总(整理中)

    目录 一  写在前面 1.1 书籍信息 1.2 简单叙述 二   类型.运算符与表达式 2.1 变量名 2.2 数据类型及长度 2.3 常量 2.4 声明 2.5 算数运算符 2.6 关系运算符与逻辑 ...

  3. R语言实战读书笔记(八)回归

    简单线性:用一个量化验的解释变量预测一个量化的响应变量 多项式:用一个量化的解决变量预测一个量化的响应变量,模型的关系是n阶多项式 多元线性:用两个或多个量化的解释变量预测一个量化的响应变量 多变量: ...

  4. Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据

    Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据 Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据 7.1 程序数据的命名 PL/SQL要求在给数据结构命名的时候应 ...

  5. Go语言实战读书笔记

    2019独角兽企业重金招聘Python工程师标准>>> Go语言实战读书笔记 第二章 通道(channel).映射(map)和切片(slice)是引用类型.引用类型的对象需要使用ma ...

  6. Oracle PL/SQL 程序设计读书笔记 - 第14章 DML和事务管理

    Oracle PL/SQL 程序设计读书笔记 - 第14章 DML和事务管理 Oracle PL/SQL 程序设计读书笔记 - 第14章 DML和事务管理 ACID原则:即一个事务具有原子性.一致性. ...

  7. TCPIP详解Protocol 读书笔记(八) Traceroute程序

    TCP/IP详解:Protocol 读书笔记(八) Chapter8 Traceroute程序 文章目录 TCP/IP详解:Protocol 读书笔记(八) Chapter8 Traceroute程序 ...

  8. 在c语言程序中 对文件进行操作首先要,《C语言程序设计》试题八及答案

    版权声明:以上文章中所选用的图片及文字来源于网络以及用户投稿,由于未联系到知识产权人或未发现有关知识产权的登记,如有知识产权人并不愿意我们使用,如果有侵权请立即联系:55525090@qq.com,我 ...

  9. c语言用户自己建立数据类型,C语言程序设计学习笔记--用户建立数据类型

    C语言程序设计学习笔记--用户建立数据类型. 9.1定义和使用结构体变量 1.定义:C语言允许用户自己建立不同类型数据组合成的组合型数据类型就是结构体 2.形式: struct 结构体名 { 成员表列 ...

最新文章

  1. tcp/ip 协议栈Linux源码分析二 IPv4分片报文重组分析二
  2. GPT v.s. 中国象棋:写过文章解过题,要不再来下盘棋?
  3. TIMING_05 VIVADO环境下的时序约束 之 基本时钟周期约束
  4. ASP.NET Core 单元测试:如何 Mock HttpContext.Features.Get()
  5. [转载]如何将Putty生成的PrivateKey转换为SecureCRT所需的PublicKey
  6. 保利管道微服务1_.netcore 3.1高性能微服务架构:webapi规范
  7. mysql拒绝访问root用户_Linux部署MySql数据库(超简单)
  8. tf.map_fn 多输入
  9. Noi2016 循环之美
  10. vax关于js方面的一些设置
  11. 使用vlmcsd搭建KMS服务器激活环境
  12. (C语言)求字符串长度的四种方法
  13. 天气预报接口使用及示例
  14. html网页设计动态烟花效果
  15. hadoop hdfs记录踩到的坑
  16. App接入阿里云号码认证服务 一键登录 Java后端服务部分
  17. sysvol 域控制器 文件_[转载]重建域控制器上的SYSVOL和NETLOGON共享
  18. char和varchar区别
  19. python处理原始音频数据
  20. vue小例子-单位换算-父子组件通信

热门文章

  1. (转) Dockerfile 中的 COPY 与 ADD 命令
  2. Whats the difference between git reset --mixed, --soft, and --hard?
  3. 《javascript高级程序设计》读书笔记1
  4. JMeter学习笔记--使用HTTP信息头管理器
  5. MySQL(root用户)密码重置
  6. 携号转网可能只会叫好不叫座
  7. 推销自己的前端技术书籍
  8. X权限 gpasswd getent 命令详解
  9. android3D动画,绕y轴旋转
  10. Android 获取系统或SDCARD剩余空间信息(转)