目录 [−]
本章介绍Go语言的其它部分,算是Go语言规范的深入学习的收尾工作。
内建函数
前面几章我们已经认识了几个内建的函数,这里我们将所有的内建函数罗列一下。
如果你查看它们的定义的话,你可以访问builtin/builtin.go,但这些内建的函数只有声明,没有方法体,它们不是标准的Go类型,你不能把它们作为函数值进行参数传递,只能出现在调用表达式中。事实上这些内建类型并不真的在builtin包中,只是为了生成文档的需要。
你可以在任意的包中调用这些内建函数,不必引入特定的包如"builtin"。
1、close: 关闭channel
2、len(s):得到字符串、数组、数值指针、slice、map、chan的长度
3、cap(s):得到数组、数组指针的长度,得到slice、channel的容量
4、new(T): 生成类型T的零值指针,注意它返回的是指针 *T
5、make: 生成slice、map、channel对象
调用 类型 T 结果 make(T, n) slice slice of type T with length n and capacity n make(T, n, m) slice slice of type T with length n and capacity m make(T) map map of type T make(T, n) map map of type T,初始可以容纳 n 元素的空间 make(T) channel 不带缓存的channel of type T,比如我们在声明信号channel的时候 make(T, n) channel 带缓存的 channel of type T, 缓存大小为 n
6、append(s S, x ...T) S: 增加0到n个元素到slice中,返回新的slice
7、copy(dst, src []T) int: 复制源src slice的元素到目标dst slice中,返回复制的元素的数量n, n是src和dst长度的最小值。字符串也可以作为src,但是T的类型必须是byte
8、delete(m,k): 删除map中的一个映射, m为nil或者m[k]不存在也不会panic,而是一个空操作
9、complex、real、imag: 复数操作
10、panic、recover: 报告panic和处理panic,后面讲
11、print、println: 尽量不用这两个函数,因为保证将来它们还会留在Go语言中,使用fmt.Print、fmt.Println
包
Go的代码文件中都会有包(package)的定义,在import声明的前面。
同一个文件夹下的所有的文件都要使用同一个包名(当然,你如果单独编译每一个文件的话,可以不遵守,但是要编译整个项目,必须遵守)。
但是测试文件可以叫另外的包名,比如正常代码的包名为"package abc",测试代码和示例代码的包名为"package abc_test". Go标准库中混用了这两种风格。
main
包是一个特殊的包,必须声明一个main函数,main函数无参数,无返回值,它用来创建可执行程序。
包名不一定和文件夹的名字保持一致,经常我们的项目的名称很长,不太适合做包名,所以包名可以用一个简短的名称,但是如果可能,尽量用一样的名字,这样在下载库的时候就能直到包的名字了。
import
import用来引入所需要的类型,允许你访问另外的包中的导出类型。
以下四种形式都是可以接受的mport:
Import declaration Local name of Sin import "lib/math" math.Sin import m "lib/math" m.Sin import . "lib/math" Sin import _ "lib/math"
为了避免同名的包名冲突,你可以为导入的包名起一个名字,比如上例中的"m"。
你也可以使用.
,这样无需包名标识符,可以直接使用这个包下的导出类型。
最后一个情况是使用空标识符,主要是想利用包的初始化,而不是使用它的导出类型。
import不能导入包自己,不管是直接的还是间接的(循环引用)。你也不能直接导入一个包而不使用它的导出类型,所幸一些工具可以自动帮我们修正import的错误,或者自动帮我们导入,比如goimports。
import可以导入相对路径,如"import \"../foo/bar\"",但是强烈你不要这么做,这不是常用的导入风格。
import可以用小括号括起来导入多个包。
包的初始化
包变量的初始化顺序和它们声明的顺序一致,但是也得考虑它们的依赖。
循环依赖初始化也不可以:
|
|
包变量的初始化之后可以调用一个init函数实现其它的初始化过程,你可以显示地定义这个函数,它可以出现在同一个包下的多个文件中,执行的顺序由编译器决定。
如果包导入了其它包,则导入的包会先初始化。如果多个包都导入同一个包,则导入的包只被初始化一次。
error、panic和recover
Go预定义了error
类型,虽然它的首字母不是大写的,但是确可以在任何包下使用:
|
|
而errors包定义了生成简单Error的方法errors.New(text String) error
。如果你想自定义Error类型,你可以实现error接口。
运行时Error,比如数组索引越界会触发一个运行时的panic,它等价调用panic函数,panic的值是一个runtime.Error:
|
|
而panic的处理是在一个defer函数中执行的:
|
|
注意defer的执行顺序,前面已经讲过,和它们声明的顺序相反,所以输出结果为:
|
|
最后一句没有机会继续执行,因为recover执行完后函数就终止了。
这带来一个问题,如果函数有返回值,recover后函数的返回值是什么?
|
|
输出结果是:
|
|
可以函数的返回是返回类型的零值。
当然说零值也不完全正确,如果函数有命名的返回参数,并且命名的返回参数在panic之前赋值了的话,返回的结果还是最后的赋值结果,下面的代码中函数的返回结果为50:
|
|
panic如果没有处理,会传递给它的调用者,这就是panic的bubble。当然如果可以预见panic,最好的处理方式就是在本函数内进行处理,因为你不能控制外部调用者的行为,而且外部调用者不一定知道会有panic发生:
|
|
上面的代码输出:
|
|
如果连main都没有 recover,则程序异常退出。
还有一个不太引人注意的地方就是如果在recover中产生panic会怎么样?还是看例子:
|
|
函数f2产生的panic会被函数f1处理, f1在recover的过程中产生一个新的panic,这个panic会把它的调用者main捕获。
所以recover产生的panic会往上传递。