跳到主要内容

数据类型与零值

问题

Go 有哪些数据类型?零值机制是什么?类型转换有什么规则?

答案

基本类型

Go 有以下基本数据类型:

分类类型大小零值说明
布尔bool1 字节false
整数int8/int16/int32/int641/2/4/8 字节0有符号整数
uint8/uint16/uint32/uint641/2/4/8 字节0无符号整数
int/uint4 或 8 字节0平台相关(32/64位)
uintptr4 或 8 字节0存放指针的整数类型
浮点float32/float644/8 字节0.0IEEE 754
复数complex64/complex1288/16 字节0+0i
字符串string16 字节(header)""不可变字节序列
别名byte(= uint8)、rune(= int321/4 字节0byte 处理 ASCII,rune 处理 Unicode

复合类型

// 数组 —— 固定长度,值类型
var arr [3]int // [0, 0, 0]

// 结构体 —— 字段集合,值类型
type Point struct {
X, Y float64
}
var p Point // {0, 0}

引用类型

var sl []int             // nil(nil slice)
var m map[string]int // nil(nil map,不能写入!)
var ch chan int // nil
var fn func() // nil
var ptr *int // nil
var itf interface{} // nil
nil map 陷阱

nil map 可以读(返回零值),但不能写入——写入会 panic。必须先 make 初始化:

var m map[string]int
_ = m["key"] // OK,返回 0
// m["key"] = 1 // panic: assignment to entry in nil map
m = make(map[string]int)
m["key"] = 1 // OK

零值机制

Go 的零值(Zero Value)保证所有变量声明后都有确定的初始值,不存在"未初始化"的情况:

var b bool       // false
var i int // 0
var f float64 // 0.0
var s string // ""
var p *int // nil
var sl []int // nil
零值可用性

Go 标准库大量利用零值可用性设计。例如 sync.Mutex 零值就是未锁定的有效互斥锁,bytes.Buffer 零值就是空缓冲区,无需调用构造函数:

var mu sync.Mutex   // 直接使用,无需 NewMutex()
mu.Lock()
defer mu.Unlock()

var buf bytes.Buffer // 直接使用,无需 NewBuffer()
buf.WriteString("hello")

类型转换

Go 不支持隐式类型转换,必须显式转换:

var i int = 42
var f float64 = float64(i) // int → float64
var u uint = uint(f) // float64 → uint

// 字符串与字节切片互转
s := "hello"
b := []byte(s) // string → []byte(拷贝)
s2 := string(b) // []byte → string(拷贝)

// 字符串与 rune 切片互转(处理中文等多字节字符)
r := []rune("你好") // [20320, 22909]
s3 := string(r) // "你好"

// int → string(注意!不是数字转字符串)
s4 := string(65) // "A"(Unicode 码点 65)
s5 := strconv.Itoa(65) // "65"(数字转字符串用 strconv)
常见陷阱

string(intValue) 是把整数当 Unicode 码点转为字符,不是数字转字符串!数字转字符串要用 strconv.Itoa()fmt.Sprintf()

类型别名与类型定义

// 类型定义(创建新类型)
type UserID int64 // UserID 和 int64 是不同类型
var id UserID = 100
// var n int64 = id // 编译错误!需要显式转换

// 类型别名(只是别名,完全相同)
type Byte = uint8 // byte 的官方定义方式
type Rune = int32 // rune 的官方定义方式
var b byte = 1
var u uint8 = b // OK,byte 和 uint8 是同一类型
特性类型定义 type A B类型别名 type A = B
是否新类型✅ 新类型❌ 完全相同
能否互相赋值❌ 需要显式转换✅ 直接赋值
能否定义方法✅ 可以❌ 不能(除非在同一包内)
使用场景领域建模、增强语义渐进式重构、跨包兼容

常见面试问题

Q1: Go 中 int 和 int64 有什么区别?

答案

int 在 32 位系统上是 32 位,在 64 位系统上是 64 位,大小取决于平台。int64 在任何平台上都是 64 位。intint64不同类型,不能直接互相赋值,需要显式转换。

在实际开发中,通常使用 int 即可。只有在序列化协议、跨平台一致性等场景下才需要使用固定大小的 int64

Q2: Go 的零值有什么好处?

答案

  1. 消除未初始化变量:不会出现 Java 中的 NullPointerException 或 C 中的随机值
  2. 零值可用设计sync.Mutexbytes.Buffer 等类型的零值就是可用状态,无需构造函数
  3. 简化代码:减少 if xxx != nil 的判断(nil slice 可以 append,nil map 可以读)
  4. 安全性:指针零值为 nil,访问 nil 指针会触发明确的 panic,而不是访问随机内存

Q3: byterune 有什么区别?什么时候用哪个?

答案

  • byteuint8 的别名,表示一个字节(0-255),用于处理 ASCII 字符和原始字节数据
  • runeint32 的别名,表示一个 Unicode 码点,用于处理多字节字符(如中文、emoji)
s := "你好Go"
fmt.Println(len(s)) // 8(字节数:中文3字节×2 + Go 2字节)
fmt.Println(len([]rune(s))) // 4(字符数)

for i, b := range []byte(s) { // 遍历字节
fmt.Printf("%d: %x\n", i, b)
}
for i, r := range s { // range 字符串默认遍历 rune
fmt.Printf("%d: %c\n", i, r)
}

处理文本内容(尤其是多语言)用 rune,处理二进制数据或网络协议用 byte

Q4: 为什么 Go 不支持隐式类型转换?

答案

Go 的设计哲学是显式优于隐式。隐式类型转换虽然方便,但容易出现精度丢失、语义错误等隐蔽 Bug(如 C 语言中 intunsigned int 混合运算的经典问题)。Go 通过强制显式转换,让开发者明确知道发生了什么,减少意外行为。

Q5: newmake 的区别?

答案

维度new(T)make(T, args)
适用类型任意类型仅 slice、map、channel
返回值*T(指针)T(值)
做了什么分配内存,填零值分配 + 初始化内部数据结构
常见替代&T{} 更常用无替代
p := new(int)         // *int,指向 0
s := make([]int, 5) // []int,长度 5,已初始化
m := make(map[string]int) // map[string]int,已初始化可写入

实际开发中 new 很少使用,通常用 &T{} 替代。make 是 slice/map/channel 初始化的唯一方式。

Q6: Go 有枚举类型吗?如何实现枚举?

答案

Go 没有 enum 关键字,通过 const + iota 实现枚举:

type Weekday int

const (
Sunday Weekday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)

// 还可以实现 String() 方法用于打印
func (d Weekday) String() string {
names := [...]string{"Sunday", "Monday", "Tuesday",
"Wednesday", "Thursday", "Friday", "Saturday"}
if d < Sunday || d > Saturday {
return "Unknown"
}
return names[d]
}

相关链接