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 }🔬 深入原理:
int和uint在 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!只有当 type 和 value 都是 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。