Skip to content
Go
builtin — Go 预声明标识符

builtin — Go 预声明标识符

builtin 不是可导入的包,而是 Go 语言预声明的标识符集合。这些标识符在任何位置都可以直接使用,无需导入。


1. 预声明类型

// 数值类型
bool
string
int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr
byte    // = uint8
rune    // = int32
float32 float64
complex64 complex128

// 特殊类型
error   // interface { Error() string }

🔬 深入原理intuint 在 64 位系统上是 64 位,在 32 位系统上是 32 位。如果你需要精确的位宽(如文件格式、网络协议),必须使用 int32/int64 等确定宽度的类型。


2. 预声明函数

2.1 内存分配:new vs make

这是 Go 新手最容易混淆的两个函数。

特性 new(T) make(T, ...)
适用类型 任意类型 slice/map/chan
返回值 *T(指针) T(本身,非指针)
是否初始化 零值填充 初始化内部数据结构
使用场景 获取一个零值的指针 创建可用的 slice/map/chan
// new — 分配零值 + 返回指针
p := new(int)      // p 是 *int,指向的值是 0
*p = 42

// make — 初始化内部结构 + 返回值本身
s := make([]int, 5, 10)    // slice: len=5, cap=10
m := make(map[string]int)  // map: 可用的空 map
ch := make(chan int, 10)   // channel: 缓冲区 10

🚨 陷阱var m map[string]int 声明的是 nil map,可以读但不能写。写入 nil map 会 panic。必须 make(map[string]int) 才能写。而 nil slice 可以 append,这是不同的。

2.2 集合操作

// append — 向 slice 添加元素
s := []int{1, 2}
s = append(s, 3, 4)       // 可变参数
s = append(s, other...)... // 展开另一个 slice

// copy — 复制 slice 元素(返回复制的数量)
dst := make([]int, len(src))
n := copy(dst, src)        // n = min(len(dst), len(src))

// delete — 从 map 删除键
delete(myMap, key)          // 即使 key 不存在也不报错

// len — 返回长度
len(slice)    // slice 长度
len(map)      // map 键值对数量
len(channel)  // channel 缓冲区中的元素数

// cap — 返回容量
cap(slice)    // slice 底层数组的容量
cap(channel)  // channel 缓冲区大小

性能提示:如果知道 slice 的最终大小,用 make([]T, 0, capacity) 预分配容量,避免多次扩容分配。

2.3 异常处理:panic / recover

// panic — 立即终止当前 goroutine 的正常执行
func must(i int, err error) int {
    if err != nil {
        panic(err)
    }
    return i
}

// recover — 只能在 defer 中捕获 panic
func safeCall(f func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic recovered: %v", r)
        }
    }()
    f()
    return nil
}

🚨 陷阱

  • recover 只能在 defer 中直接调用的函数中使用。defer func() { recover() }() 有效;defer recover() 无效。
  • 一个 goroutine 的 panic 不会传播到其他 goroutine。
  • 不要用 panic 做常规错误处理。

2.4 Channel 操作:close

ch := make(chan int, 10)
ch <- 1
close(ch)

// 关闭后:
// - 可以继续接收剩余数据(直到缓冲排空)
// - 接收已关闭且排空的 channel 得到零值 + false
val, ok := <-ch  // ok == false 表示已关闭且排空

💡 最佳实践发送方负责关闭 channel,接收方不应关闭。向已关闭的 channel 发送会 panic。

2.5 类型转换

// 数值转换
i := int(3.14)     // 截断为 3(不是四舍五入)
f := float64(42)   // 显式转换

// 字符串与字节/字符的转换
s := string([]byte{'h', 'e', 'l', 'l', 'o'})  // []byte → string
b := []byte(s)                                 // string → []byte(复制!)
r := []rune(s)                                 // string → rune slice(正确处理 Unicode)

🔬 深入原理[]byte(s)string(b) 都会复制数据,不是零成本的。在某些极致的性能场景中(如 strings.Builder),标准库内部使用了 unsafe 转换来避免复制。


3. 预声明常量

true  false  // bool
iota         // 自增计数器(仅 const 块内)
nil          // 零值(指针、接口、map、slice、chan、func)

iota 详解

const (
    _ = iota        // 0(跳过)
    KB = 1 << (10 * iota)  // 1 << 10 = 1024
    MB                      // 1 << 20 = 1048576
    GB                      // 1 << 30
)

// iota 每行递增,重置在每个 const 块开始
const (
    A = iota  // 0
    B         // 1
    C         // 2
)

🔬 深入原理iota 只在 const 声明块内有效,且每遇到一个新行(包括空行)都会递增。多个 const 声明在同一行共享同一个 iota 值。

nil 的类型

nil 本身没有类型,它在使用时获得目标类型:

  • 指针:(*int)(nil)
  • 接口:(error)(nil)
  • slice:([]int)(nil)
  • map:(map[string]int)(nil)
  • chan:(chan int)(nil)
  • func:(func())(nil)

🚨 陷阱var w io.Writer = (*os.File)(nil) — 此时 w 不是 nil(接口值包含类型信息),w == nil 为 false。这是 Go 中最经典的坑之一。判断接口是否为 nil 要谨慎:接口的 nil 要求类型和值都是 nil

🔬 深入理解:接口的 nil 判断

接口在 Go 内部由两部分组成:动态类型 + 动态值

接口值 = (type, value)

真正的 nil 接口: (nil, nil)    →  w == nil 为 true
"有类型的 nil":  (*os.File, nil) →  w == nil 为 false!

只有当 typevalue 都是 nil 时,接口才等于 nil。

// 场景演示
var f *os.File = nil                    // f 是 nil 指针
var w io.Writer = f                     // w = (*os.File, nil),不是 nil 接口!
fmt.Println(w == nil)                   // false!这是一个惊天大坑

var w2 io.Writer = nil                  // w2 = (nil, nil),真正的 nil 接口
fmt.Println(w2 == nil)                  // true

// 🔬 如何正确判断?
// 方法 1:直接和 nil 比较 — 只对真正的 nil 接口有效
if w != nil {
    // 进入这里!即使底层值是 nil
}

// 方法 2:反射判断 — 可以检测"有类型的 nil"
func IsNilInterface(v interface{}) bool {
    if v == nil {
        return true  // (nil, nil),真正的 nil 接口
    }
    rv := reflect.ValueOf(v)
    switch rv.Kind() {
    case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan, reflect.Func, reflect.Interface:
        return rv.IsNil()  // 检查动态值是否为 nil
    }
    return false
}

// 💡 最佳实践:让函数返回具体的 nil,而不是赋给接口再返回
// ❌ 容易出错的写法
func getFile() *os.File {
    return nil  // 返回 nil *os.File
}
func GetWriter() io.Writer {
    return getFile()  // 返回的是 (*os.File)(nil),不是 nil 接口!
}

// ✅ 正确的写法
func GetWriter() io.Writer {
    f := getFile()
    if f == nil {
        return nil  // 显式返回 nil 接口
    }
    return f
}

💡 记忆口诀不要把 nil 指针赋给接口变量再返回。函数返回接口类型时,如果具体类型是 nil,应显式 return nil