内建函数都是预声明的,一般情况下可以像普通函数一样使用。它们没有标准的Go类型,因此它们只能出现在调用表达式中,而不能用作函数值。这些函数均可以在builtin包下找到。包builtin只是提供这些预声明函数的文档,并不是真的存在该包中。

一 关闭函数(close)

func close(c chan<- Type)

在之前并发-信道的迭代和关闭一节中已经见过close函数的用法。对于信道c,内置函数close(c)表示将不再在该信道上发送值。如果c是仅接收的信道,则会出错。发送或关闭已关闭的通道会导致运行时恐慌。关闭nil信道也会导致运行时恐慌。在调用close之后,且先前所有已发送的值都接收完毕后,继续接收操作将返回信道类型的零值而不会阻塞。多值接收操作(逗号ok用法)会返回接收值以及信道是否关闭的指示。

close函数仅用于关闭信道,且只能由发送方关闭。

二 长度(len)和容量(cap)函数

func len(v Type) int
func cap(v Type) int

内建函数len和cap接受各种类型的参数并返回int类型的结果。该实现保证结果始终是int。

对于不同的类型,返回的结果如下:

函数      参数类型         结果
len(s)    字符串           字符串字节的长度
          数组([n]T)     数组长度为n,即数组s中元素的个数
          指针数组(*[n]T)数组长度为n,即数组*s中元素的个数,即使s == nil
          切片([]T)      切片长度,即切片s中元素的个数。如果s == nil,则len(s) == 0
          映射(map[K]T)  映射长度,即映射中元素的个数(或已定义键的个数。)如果s == nil,则len(s) == 0
          信道(chan T)   信道缓冲区中在排队的元素个数。如果s == nil,则len(s) == 0

cap(s)    [n]T, *[n]T      数组容量为n,即数组s中元素的个数,与len(s)相同
          []T              切片容量,即重新切片前所能达到的最大长度。如果s == nil,则cap(s) == 0
          chan T           信道缓冲区大小。以元素为单位。如果s == nil,则cap(s) == 0

切片的容量是在底层数组中分配空间的元素数。在任何时候,以下关系成立:

0 <= len(s) <= cap(s)

空的(nil)切片,映射或信道的长度为0。空的(nil)切片或信道的的容量为0。

如果s是字符串常量,则表达式len(s)是常量。如果s的类型是数组或指向数组的指针且表达式s不包含信道接收或(非常量)函数调用,则表达式len(s)cap(s)是常量;在这种情况下,不对s进行求值。否则,lencap的调用不是常量,而是对s进行求值 。

三 分配函数

Go有两种分配原语,即内置函数newmake,它们处理不同的事情并适用于不同的类型,这可能令人困惑,但规则很简单。

3.1 new函数

func new(Type) *Type

内置函数new接受一个类型T(Type)的参数(注意不是值),在运行时为该类型的变量分配内存,并返回指向它的类型*T的零值。与其他语言中的同名函数不同,它不会初始化内存,只会将其置零。也就是说,new(T)为类型为T的变量分配零值,并返回其地址,也就是类型为*T的值。用Go的术语说,它返回一个指针,该指针指向新分配的,类型为T的零值。

例如

type S struct { a int; b float64 }
new(S)

S类型的变量分配内存,初始化(a = 0,b = 0.0),并返回包含该位置地址的类型*S的值。

3.2 构造函数和复合字面量

有时零值还不够好,这时就需要一个初始化构造函数,如来自os包中的这段代码所示:

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    f := new(File)
    f.fd = fd
    f.name = name
    f.dirinfo = nil
    f.nepipe = 0
    return f
}

这里显得代码过于冗长。我们可通过复合字面量来简化它,该表达式在每次求值时都会创建新的实例。

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    f := File{fd, name, nil, 0}
    return &f
}

请注意,返回一个局部变量的地址完全没有问题,这点与C不同。该局部变量对应的数据在函数返回后依然有效。实际上,每当获取一个复合字面的地址时,都将为一个新的实例分配内存, 因此我们可以将上面的最后两行代码合并:

return &File{fd, name, nil, 0}

复合字面量的字段必须按顺序全部列出。但如果以字段:值对的形式明确地标出元素,初始化字段时就可以按任何顺序出现,未给出的字段值将赋予零值。 因此,我们可以用如下形式:

return &File{fd: fd, name: name}

少数情况下,若复合字面不包括任何字段,它将创建该类型的零值。表达式 new(File)&File{}是等价的。

更多关于字面量的初始化细节,可以参考类型体系一文的初始化章节。

3.3 make函数

func make(t Type, size ...IntegerType) Type

不同于new函数,内置函数make只用于初始化切片,映射,和信道,且创建的变量内存已经初始化,而不是零值。可选地后跟特定于类型的表达式列表。它返回类型T的值(而不是指针*T)。对于不同的类型,返回的结果如下:

函数调用        参数类型    结果
make(T, n)       slice      类型为T的切片,长度为n,容量为n
make(T, n, m)    slice      类型为T的切片,长度为n,容量为m

make(T)          map        类型为T的映射
make(T, n)       map        类型为T的映射,具有大约n个元素的初始空间

make(T)          channel    类型为T的无缓冲区信道
make(T, n)       channel    类型为T的带缓冲区信道,缓冲区大小为n

每个大小参数nm必须是整数类型或无类型常量。常量参数必须是非负的,并且可以通过int类型的值表示;如果它是一个无类型常量,则给出int类型。如果提供nm并且它们是常数,那么n必须不大于m。如果在运行时n为负或大于m,则会发生运行时恐慌。

s := make([]int, 10, 100)       // 长度为10,容量为100的切片
s := make([]int, 1e3)           // 长度和容量均为1000的切片
s := make([]int, 1<<63)         // 非法:长度不能表示为int值
s := make([]int, 10, 0)         // 非法:长度大于容量
c := make(chan int, 10)         // 缓冲区为10的信道
m := make(map[string]int, 100)  // 初始空间能容纳大约100个元素的映射
使用映射类型和大小n调用make将创建一个包含初始空间的映射,以容纳n个元素。精度的行为取决于实现。

3.4 make的特殊性

上文已经提到,make仅用于创建切片(slice)、映射(map)和信道(channel)。原因在于,这三种类型代表了底层引用的数据结构必须初始化后才能使用。比如切片,是一个具有三项内容的描述符,包含一个指向数据(在数组中)的指针、长度、容量。在这三项初始化前,切片的值总是为nil。对于切片、映射、信道来说,make初始化了其内部数据结构并准备好要使用的值。例如:

make([]int, 10, 100)

分配了一个100个int的数组,然后创建了一个长度为10,容量为100的切片,并指向数组的前10个元素(创建切片时,容量属性可以省略)。作为对比,new([]int)返回一个新分配的零值切片,即一个指向nil的指针。

这些例子说明了newmake之间的区别:

var p *[]int = new([]int)       // 分配切片结构 *p == nil; 几乎没什么用处
var v  []int = make([]int, 100) // 切片v现在引用了一个长度为100的新底层数组

// 不必要的复杂:
var p *[]int = new([]int)
*p = make([]int, 100, 100)

// 习惯用法:
v := make([]int, 100)

记住,make仅适用于切片,映射和通道,并且不会返回指针。要获取显式指针,使用new分配或明确获取变量的地址。

四 追加(append)和复制(copy)切片

内建函数appendcopy用于切片的常规操作。对于这两个函数,结果与参数引用的内存是否重叠无关。

4.1 append函数

func append(slice []Type, elems ...Type) []Type

可变参数函数append将零个或多个值elems追加到[]Type类型的slice,其中[]Type表示切片类型,并返回结果切片,同样是一个[]Type类型的切片。值elems被传递给类型为...Type的参数,Type是切片类型([]Type)的元素类型,并且应用相应的参数传递规则。一个特殊情况是,append也可以接受第一个参数为[]byte,第二个参数为字符串后跟...类型,这种形式将追加字符串的字节。

结果需要返回的原因是,底层数组可能会发生变化。
由于该原因,所以通常习惯上使用变量自身来引用新的切片结果:

slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)

如果slice的容量不足以容纳追加的值,则append将分配一个新的、足够大的底层数组,该新的数组既能满足现有切片的元素又可以容纳追加的值。否则,append将重用底层数组。

s0 := []int{0, 0}
s1 := append(s0, 2)                // 追加单个元素     s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)          // 追加多个元素     s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)            // 追加一个切片     s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...)   // 追加重叠的切片   s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

var t []interface{}
t = append(t, 42, 3.1415, "foo")   //                t == []interface{}{42, 3.1415, "foo"}

var b []byte
b = append(b, "bar"...)            // 追加字符串内容   b == []byte{'b', 'a', 'r' }
上述示例最后一行,如果没有...,就无法编译,因为类型错误:
cannot use "bar" (type string) as type byte in append

4.2 copy函数

func copy(dst, src []Type) int

函数copy将切片元素从源src复制到目标dest,并返回复制的元素个数。两个参数必须具有相同的元素类型T,并且必须可以分配给类型为[]T的切片。复制的元素数是len(src)len(desc)中的最小值。作为一种特殊情况,copy还接受一个字符串参数,该参数可分配给[]byte类型,其源参数为字符串类型。这种形式将字符串中的字节复制到字节切片中。

copy(dst, src []T) int
copy(dst []byte, src string) int
源切片和目标切片可能存在重叠

示例

var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int, 6)
var b = make([]byte, 5)
n1 := copy(s, a[0:])            // n1 == 6, s == []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:])            // n2 == 4, s == []int{2, 3, 4, 5, 4, 5}
n3 := copy(b, "Hello, World!")  // n3 == 5, b == []byte("Hello")

五 delete函数

delete函数用于从映射中删除元素。

func delete(m map[Type]Type1, key Type)

内置函数delete从映射m中删除键为k的元素。k的类型必须可分配给m的键类型。
示例:

delete(m, k)  // 从映射m中移除元素 m[k]

如果映射mnil或元素m[k]不存在,则delete什么都不做。

六 复数相关的函数

有三个函数用于组装和分解复数。内置函数complex从浮点数实部和虚部构造复数值,而realimag则用于提取复数值的实部和虚部。

func complex(r, i FloatType) ComplexType // r - 实部; i - 虚部
func imag(c ComplexType) FloatType
func real(c ComplexType) FloatType

参数的类型和返回值相对应。对于complex,两个参数必须是相同的浮点类型,要么都是float32,要么都是float64,返回类型是具有相应浮点数组成的复数类型:
float32参数对应complex64float64参数对应complex128

如果其中一个参数求值结果为无类型常量,则首先将其隐式转换为另一个参数的类型。如果两个参数求值结果均为无类型常量,则它们必须是非复数或其虚部必须为零,并且函数的返回值是无类型复数常量。

对于realimag,参数必须是复数类型,返回类型是相应的浮点类型:complex64参数对应float32complex128参数对应float64。如果参数求值结果为无类型常量,则它必须是数字,并且函数的返回值是无类型浮点常量。

realimag函数一起形成复数的逆运算,因此对于复数类型Z的值zz == Z(complex(real(z), imag(z)))

如果这些函数的操作数都是常量,则返回值是常量。

var a = complex(2, -2)             // complex128
const b = complex(1.0, -1.4)       // 无类型复数常量 1 - 1.4i
x := float32(math.Cos(math.Pi/2))  // float32
var c64 = complex(5, -x)           // complex64
var s uint = complex(1, 0)         // 无类型复数常量 1 + 0i 可以转化为 uint
_ = complex(1, 2<<s)               // 非法: 2 假定为浮点类型,不能移位
var rl = real(c64)                 // float32
var im = imag(a)                   // float64
const c = imag(b)                  // 无类型常量 -1.4
_ = imag(3 << s)                   // 非法: 3 假定为复数类型,不能移位

七 处理panic

两个内建函数panicrecover,用于协助报告和处理运行时恐慌和程序定义的错误条件。

func panic(v interface{})
func recover() interface{}

可以参考之前的文章错误处理和运行时恐慌(Panic)了解panicrecover详细用法。

在执行函数F时,显式调用panic或运行时panic会终止F的执行。任何由F推迟的函数将继续正常执行。接下来,运行由F的调用者推迟的任何函数,依此类推,直到执行到该goroutine中的顶级函数所推迟的函数。此时,程序终止并报告错误条件,包括传递给panic的参数值。此终止序列称为恐慌。

panic(42)
panic("unreachable")
panic(Error("cannot parse"))

recover函数允许程序管理出现panicgoroutine的行为。假设函数G推迟了函数DD调用了recover,并且在与G运行的同一个goroutine中的另一个函数发生了panic。当推迟函数的运行到达D时,D调用recover的值即当初传递给panic的值。如果D正常返回,没有产生新的panic,那么panic序列将会停止。这种情况下,Gpanic函数之间的调用状态将被丢弃,并恢复正常执行。然后运行由GD之前推迟的任何函数,并且G的执行以返回其调用者而终止。

如果满足以下任何条件,则recover的返回值为nil

  • panic的参数为nil;
  • goroutine没有panic;
  • 推迟函数未直接调用recover。

以下示例中的protect函数调用函数参数g并保护调用者免受g引发的运行时恐慌。

func protect(g func()) {
    defer func() {
        log.Println("done")  // Println executes normally even if there is a panic
        if x := recover(); x != nil {
            log.Printf("run time panic: %v", x)
        }
    }()
    log.Println("start")
    g()
}

八 自举相关函数

当前实现提供了几个在自举期间有用的内置函数。记录这些函数是为了完整性,但不保证以后会保留在该语言中。他们不返回结果。

func print(args ...Type)
func println(args ...Type)

行为说明如下:

函数      行为

print      打印所有参数至标准错误;参数的格式是特定于实现的
println    同print,但在参数之间打印空格,并且在最后打印一个换行符

示例:

package main

func main() {
    print("hello", "world\n") // helloworld
    println("hello", "world") // hello world
}

实现限制:printprintln不需要接受任意参数类型,但必须支持打印布尔,数字和字符串类型。

参考:
https://golang.org/ref/spec#Built-in_functions
https://golang.org/pkg/builtin
https://golang.org/doc/effective_go.html#data