Skip to content

内置函数

本文档详细介绍Go语言的内置函数,包括内存管理、容器操作、并发控制等核心功能函数的使用方法和最佳实践。

📋 目录

内置函数概述

Go语言提供了一系列内置函数,这些函数是预定义的,无需导入任何包即可使用。内置函数具有以下特点:

  • 预定义:无需导入,直接可用
  • 类型参数:接受类型作为参数,而不是表达式
  • 编译时处理:大部分在编译时进行优化
  • 不能赋值:不能作为函数值使用,只能调用

内置函数列表

类别函数功能
内存管理new, make内存分配
容器操作len, cap, append, copy, delete长度、容量、追加、复制、删除
并发控制close关闭通道
类型操作complex, real, imag复数操作
错误处理panic, recover异常处理
输出调试print, println简单输出

内存管理函数

new函数

new(T) 分配类型T的零值内存,并返回指向该内存的指针:

go
func newExample() {
    // 分配int类型的零值内存
    p := new(int)
    fmt.Printf("p的类型: %T, p的值: %p, *p的值: %d\n", p, p, *p)
    
    // 分配结构体内存
    type Person struct {
        Name string
        Age  int
    }
    person := new(Person)
    fmt.Printf("person: %+v\n", *person)  // {Name: Age:0}
    
    // 等价于
    var person2 Person
    personPtr := &person2
    fmt.Printf("personPtr: %+v\n", *personPtr)
}

make函数

make(T, args...) 用于创建slice、map、channel,返回类型T(不是指针):

go
func makeExample() {
    // 创建slice
    slice1 := make([]int, 5)        // 长度5,容量5
    slice2 := make([]int, 5, 10)    // 长度5,容量10
    fmt.Printf("slice1: %v, len: %d, cap: %d\n", slice1, len(slice1), cap(slice1))
    
    // 创建map
    m := make(map[string]int)
    m["key1"] = 100
    fmt.Printf("map: %v\n", m)
    
    // 创建channel
    ch := make(chan int, 3)  // 缓冲通道,容量3
    ch <- 1
    ch <- 2
    fmt.Printf("channel length: %d\n", len(ch))
}

new vs make 对比

函数用途返回值适用类型
new(T)分配零值内存*T (指针)所有类型
make(T)创建并初始化T (值)slice, map, channel

容器操作函数

len和cap函数

len() 返回长度,cap() 返回容量:

go
func lenCapExample() {
    // 字符串
    str := "Hello, 世界"
    fmt.Printf("字符串长度(字节): %d\n", len(str))  // 13 (UTF-8编码)
    
    // 数组
    arr := [5]int{1, 2, 3, 4, 5}
    fmt.Printf("数组长度: %d, 容量: %d\n", len(arr), cap(arr))  // 5, 5
    
    // 切片
    slice := make([]int, 3, 10)
    fmt.Printf("切片长度: %d, 容量: %d\n", len(slice), cap(slice))  // 3, 10
    
    // 映射
    m := map[string]int{"a": 1, "b": 2}
    fmt.Printf("映射长度: %d\n", len(m))  // 2
    
    // 通道
    ch := make(chan int, 5)
    ch <- 1
    ch <- 2
    fmt.Printf("通道中元素数量: %d, 容量: %d\n", len(ch), cap(ch))  // 2, 5
}

len和cap的适用类型:

类型len()cap()
string字节长度-
[n]T, *[n]T数组长度(n)数组长度(n)
[]T切片长度切片容量
map[K]T键值对数量-
chan T缓冲区中元素数缓冲区容量

重要关系:

go
0 <= len(s) <= cap(s)  // 对于slice和channel

并发控制函数

close函数

close(ch) 用于关闭通道,表示不再向通道发送数据:

go
func closeExample() {
    ch := make(chan int, 3)
    
    // 发送数据
    go func() {
        for i := 1; i <= 5; i++ {
            ch <- i
            fmt.Printf("发送: %d\n", i)
        }
        close(ch)  // 关闭通道
        fmt.Println("通道已关闭")
    }()
    
    // 接收数据
    for {
        value, ok := <-ch
        if !ok {
            fmt.Println("通道已关闭,退出接收")
            break
        }
        fmt.Printf("接收: %d\n", value)
    }
    
    // 或者使用range自动处理关闭
    ch2 := make(chan int, 3)
    go func() {
        for i := 1; i <= 3; i++ {
            ch2 <- i
        }
        close(ch2)
    }()
    
    for value := range ch2 {
        fmt.Printf("Range接收: %d\n", value)
    }
}

close函数注意事项:

  • 只能关闭发送端或双向通道
  • 关闭已关闭的通道会panic
  • 向已关闭的通道发送数据会panic
  • 从已关闭的通道接收数据会立即返回零值
  • 关闭nil通道会panic
go
0 <= len(s) <= cap(s)

nil 切片,map,或者 channel 的长度都为 0。nil 切片,管道的容积都为 0。

表达式 len(x)s 是字符串常量时也为常量。如果 s 为数组或者指向数组的指针并且表达式 s 不包含 channel 接收器或者函数调用那么 len(s)cap(s) 也是常量;在这个情况下 s 时不能求值的。其他情况下 lencap 不是常量并且 s 是可以求值的。

go
const (
	c1 = imag(2i)                    // imag(2i) = 2.0 is a constant
	c2 = len([10]float64{2})         // [10]float64{2} contains no function calls
	c3 = len([10]float64{c1})        // [10]float64{c1} contains no function calls
	c4 = len([10]float64{imag(2i)})  // imag(2i) is a constant and no function call is issued
	c5 = len([10]float64{imag(z)})   // invalid: imag(z) is a (non-constant) function call
)
var z complex128
内存分配

内置函数 new 接收一个类型 T,它会在运行时给变量分配内存,并且返回一个指向类型 T*T 类型指针。变量的初始化在初始化值章节中介绍。

go
new(T)

例如:

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

给 S 类型的变量分配空间,并初始化它(a=0b=0.0),并且返回一个 *S 类型值保存变量所在的位置。

创建切片,map 和 管道

内置函数 make 以一个类型作为参数,它必须是一个切片,map 或者管道类型,它返回一个 T 类型的值,而不是(*T)类型,它会按初始化值章节描述的方式进行初始化。

go
Call             Type T     Result

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 with initial space for approximately n elements

make(T)          channel    unbuffered channel of type T
make(T, n)       channel    buffered channel of type T, buffer size n

n 和 m 必须是整数类型或者无类型常量。一个常量参数不能为负数并且该值在 int 类型的范围内;如果它是无类型常量,会被转换成 int 类型。如果 n 和 m 都是常量,那么 n 必须大于 m。如果 n 是负数或者大于 m 会引发运行时 panic。

go
s := make([]int, 10, 100)       // slice with len(s) == 10, cap(s) == 100
s := make([]int, 1e3)           // slice with len(s) == cap(s) == 1000
s := make([]int, 1<<63)         // illegal: len(s) is not representable by a value of type int
s := make([]int, 10, 0)         // illegal: len(s) > cap(s)
c := make(chan int, 10)         // channel with a buffer size of 10
m := make(map[string]int, 100)  // map with initial space for approximately 100 elements

使用 make 来指定大小初始化 map 类型将会创建一个预留 n 个元素空间的 map 类型。更详细的行为依赖于具体实现。

追加或者拷贝切片

内置函数 appendcopy 可以进行切片的通用操作。对于这两个函数,一个是拷贝内存,一个是引用内存。

可变参数的函数 append 可以向切片 s 中追加一个或多个 x 值,并返回这个切片。传进 ...T 的值会根据参数传值。作为特例,append 在 s 为 []byte 切片时,可以使用字符串后面跟 ... 作为参数。

如果 s 的容积容纳不下这些元素,那么 append 会分配一个新的足够大的数组。否则会使用原来的底层数组。

go
s0 := []int{0, 0}
s1 := append(s0, 2)                // append a single element     s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)          // append multiple elements    s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)            // append a slice              s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...)   // append overlapping slice    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"...)            // append string contents      b == []byte{'b', 'a', 'r' }

copy 函数从 src 拷贝原属到 dst 并且返回拷贝元素的个数。参数中所有的元素类型必须是 T 类型或者能转换成 T 的类型。拷贝元素的数量是 len(src)len(dst) 中的较小值。作为特例,copy 可以从 string 类型拷贝元素到 []byte 类型。这会把字符串中的元素拷贝到字节切片中。

go
copy(dst, src []T) int
copy(dst []byte, src string) int

例:

go
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")
删除 map 中的元素

内置函数 delete 移除 map 类型 m 中的键值 k。k 的类型必须是能够转换成 m 键类型的类型。

go
delete(m, k)  // remove element m[k] from map m

如果 map 类型 m 是 nil 或者 m[k] 不存在,那么 delete 函数不做任何事情。

操作复数

有三个函数可以组装或者分解复数。内置函数 complex 会构造一个复数,realimag 会分解出复数的实部和虚部。

go
complex(realPart, imaginaryPart floatT) complexT
real(complexT) floatT
imag(complexT) floatT

参数的类型和返回值类型是对应的。对于 complex,两个参数必须是相同的浮点类型,并返回由相同浮点数组成的复数类型。complex64float32 对应的类型,complex128float64 对应的参数类型。如果参数是一个无类型常量,它会转换成另一个参数的类型。如果两个参数都是无类型常量,他们必须实数或者虚数部分为零,并且它会返回一个无类型的复数常量。

realimag 函数和 complex 正好相反的,所以对于一个值复数类型 Z 的值 z,z==Z(complex(real(z),imag(z)))

如果这么操作都是常量,那么返回的值也是常量。

go
var a = complex(2, -2)             // complex128
const b = complex(1.0, -1.4)       // untyped complex constant 1 - 1.4i
x := float32(math.Cos(math.Pi/2))  // float32
var c64 = complex(5, -x)           // complex64
var s uint = complex(1, 0)         // untyped complex constant 1 + 0i can be converted to uint
_ = complex(1, 2<<s)               // illegal: 2 assumes floating-point type, cannot shift
var rl = real(c64)                 // float32
var im = imag(a)                   // float64
const c = imag(b)                  // untyped constant -1.4
_ = imag(3 << s)                   // illegal: 3 assumes complex type, cannot shift
处理 panic

两个内置函数 panicrecover,可以抛出和处理运行时 panic 和程序的错误条件。

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

当执行 F 函数时,显式的调用 panic或者运行时 panic 都会中断 F 的执行。但是 F 中的延迟函数还会执行。接下来调用 F 函数处的延迟函数也会执行,一直到顶级的延迟函数。鉴于这点,程序关闭并且错误条件可以抛出。包括 panic 中的值。这个顺序叫做 panicking

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

recover 函数允许程序从一个 panicking 中恢复执行。假设函数 G 延迟执行函数 D ,在 D 中调用 recover 这时如果在 G 执行时发生 panic 会在 D 中恢复。当函数执行到 D,recover 的返回值会返回 panic 对应的错误,并且终止 panicking 。在这个情况下 G 函数和 panic 之间的代码不会执行。任何在 D 中 G 之前的延迟函数会返回到调用者。

在下面两种情况下 recover 会返回 nil:

  • panic 的参数为 nil

  • 携程里没有发生 panic

  • recover 不是在延迟函数中执行

本例中的 protect 函数会在 g 发生 panic 的时候恢复执行。

go
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()
}
初始化

这个实现提供了多个内置函数来帮助进行初始化。这些函数用来输出信息但是不确定会一直存在于语言中,他们都没有返回值。

go
Function   Behavior

print      prints all arguments; formatting of arguments is implementation-specific
println    like print but prints spaces between arguments and a newline at the end

实现限制:printprintln 不接受除了布尔值,数字,字符串以外的其他类型。

程序的初始化和执行

零值

当为变量分配内存空间时,不管是声明还是调用 new 或者使用字面值和 make 初始化,只要创建了一个新值变量都会有一个默认值。这样的元素和值会使用它类型的零值:false 是布尔值的零值,0 为数值类型零值,"" 为字符串零值,nil 为指针,函数,接口,切片,频道,字典。初始化会递归完成,所以结构体里的数组中的元素也都会有它自己的零值。

下面两个声明时相等的:

go
var i int
var i int = 0

请看下面的声明:

go
type T struct { i int; f float64; next *T }
t := new(T)
t.i == 0
t.f == 0.0
t.next == nil

这和下面的声明时同等效果的:

go
var t T
包的初始化

保级变量会按声明的顺序进行初始化,如果依赖其他变量,则会在其他变量之后进行初始化。

更确切的说,如果包级变量还没初始化并且没有初始化表达式或者表达式中不包含对其他未初始化变量的依赖,那么会认为它正在等待初始化。初始化过程会从最早声明的变量开始向下一个包级变量重复,直到没有需要初始化的变量。

如果在初始化过程完成后还有未初始化的变量,那么这些变量可能是循环初始化了,这事程序不是合法的。

在多个文件中变量的声明顺序会依据编译时文件出现的顺序:声明在第一个文件中的变量优先于第二个文件中声明的变量,依此类推。

对依赖关系的分析不会根据变量的具体值,它只分析在源码中是否引用了其他变量。例如,如果变量 x 的初始化表达式引用了变量 y 那么 x 就依赖于 y:

  • 引用一个变量或者函数中用到了一个变量

  • 引用了一个方法值 m 或者方法表达式 t.m (这里的静态类型 t 不是借口类型,并且方法 mt 方法集中的方法)。t.m 的返回值不会在此时影响。

  • 变量,函数,或者方法 x 依赖变量 y

依赖分析会在每个包中执行;他只考虑当前包中的析变量,函数,和方法。

例如,给定声明:

go
var (
	a = c + b
	b = f()
	c = f()
	d = 3
)

func f() int {
	d++
	return d
}

初始化顺序为 d,b,c,a。

变量可以在包中声明的初始化函数 init 中进行初始化,它没有参数和返回值。

go
func init() {}

可以为每个包定义多个该函数,甚至在一个文件中也可以。并且不会声明该该标识符。因此 init 函数不能在程序中调用。

还未导入的包会先初始化包级的变量然后按照 init 函数在源码中的顺序调用,它可能在包的多个文件中。如果需要导入一个包,它会在初始化自己之前先初始化这个需要导入的包。如果导入一个包多次,那这个包只会初始化一次。导入的包不能存在循环引用。

包的初始化——变量初始化和对 init 函数的调用会按顺序发生在同一个 goroutine 中。 init 函数可能会启动其他 goroutine。不过一般 init 函数都是按序进行初始化的:它只在上一步已经执行完成时才会调用下一个步骤。

确保初始化行为是可以复现的,构建系统鼓励在同一个包中包含多个文件这些文件在编译器中会以字母排序。

程序执行

一个完整的程序由一个 main 包导入所有需要的包。main 包必须以 main 作为包名并且声明一个没有参数和返回值的 main 函数。

go
func main() {}

程序先初始化 main 包然后调用 main 函数。当 main 函数返回时,程序就会退出。它不会等待其他 goroutines 完成。

错误

预定义的错误类型为:

go
type error interface {
	Error() string
}

它是表示错误信息的常规接口,nil 代表没有发生错误。例如,在文件中读取数据可以定义为:

go
func Read(f *File, b []byte) (n int, err error)

运行时恐慌

运行时错误(例如数组的越界访问)会造成运行时恐慌,它和以 runtime.Error 接口实现调用内置的 panic 函数一样。runtime.Error 满足预定义的 error 接口。不同的错误值代表不同的运行时错误条件。

go
package runtime

type Error interface {
	error
	// and perhaps other methods
}

系统相关

unsafe 包

unsafe 是编译器已知的内置包,可以通过导入路径 unsafe 访问包内容,提供 unsafe 包目的是支持底层编程(包括操作非 Go 类型的数据结构)。使用 unsafe 包必须自己保证类型安全而且它有可能破坏程序的移植性。unsafe 包提供了以下接口:

go
package unsafe

type ArbitraryType int  // 任意一个 Go 类型;它不是一个具体的类型。
type Pointer *ArbitraryType

func Alignof(variable ArbitraryType) uintptr
func Offsetof(selector ArbitraryType) uintptr
func Sizeof(variable ArbitraryType) uintptr

Pointer 是一个指针类型,但是不能解引用 Pointer 的值。所有底层类型 uintptr 的指针和值都能转换成 Pointer 类型,反之亦然。Pointeruintptr 之间的转换效果由具体实现定义。

go
var f float64
bits = *(*uint64)(unsafe.Pointer(&f))

type ptr unsafe.Pointer
bits = *(*uint64)(ptr(&f))

var p ptr = nil

假设变量 v 由 var v = x 定义。Alignof 以表达式 x 作为参数并返回 x 的对齐字节数。Sizeof 以表达式 x 作为参数并返回 x 的大小。

函数 Offsetof 以选择器 s.f( s 或者 *s 结构体中的 f 字段)作为参数,返回字段相对结构体首地址的位置。如果 f 是一个嵌入字段,那 f 必须可以直接访问(不能通过指针进行间接访问)。对于结构体 s 的 f 字段:

go
uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))

计算机的体系结构要求对齐内存地址(对于一个变量的地址有多种因素影响对齐)。Alignof 函数获取一个人和类型的表达式并返回变量对齐的字节数。对于变量 x:

go
uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0

编译时 uintptr 类型常量表达式会调用 AlignofOffsetof,和 Sizeof

确定的大小和对齐字节数

对于数字类型,确定有以下尺寸:

go
type                                 size in bytes

byte, uint8, int8                     1
uint16, int16                         2
uint32, int32, float32                4
uint64, int64, float64, complex64     8
complex128                           16

Go 中规定的最小对齐特性:

  1. 对于任意变量类型 x:unsafe.Alignof(x) 至少为 1。

  2. 对于结构体类型:unsafe.Alignof(x) 是所有内部字段 unsafe.Alignof(x.f) 的最大值,并且至少为 1。

  3. 对于数组类型:unsafe.Alignof(x) 和数组元素类型的 alignment 相同。

结构体(数组)在内部没有字段(元素)的时候大小为 0。两个所占空间大小为 0 的不同变量可能在内存中拥有相同地址。

基于 MIT 许可发布