一段让人困惑的代码
var v float32
fmt.Println(v) // 0
fmt.Printf("%T\n", v) // float32
fmt.Println(v == 0) // true
fmt.Printf("%T\n", 0) // int
前三行很正常,第四行就离谱了——0 我又没给它定类型,怎么 %T 告诉我它是 int?
再看几个更微妙的:
var v float32 = 3 // ✅ 没问题,3 不是 int 吗?
fl := 42
fl = 42.0 // ✅ int 变量居然能接受 42.0?
fl = 42.5 // ❌ 报错了
同样是无类型字面量,为什么有的能赋、有的不能?Go 不是号称没有隐式类型转换吗?
其实字面量根本没有类型
带着这些疑问翻了 Go 规范,发现 Go 里有一种东西叫 untyped constant(无类型常量)。
简单来说:字面量和无类型 const,在被使用的那一刻之前,压根就没有类型。
3 // 无类型整数字面量
3.14 // 无类型浮点字面量
"hello" // 无类型字符串字面量
true // 无类型布尔字面量
const x = 3 // 无类型常量,没指定类型
const y int = 3 // 有类型常量,绑定了 int
注意区分:变量不存在无类型这回事。 var v = 3 声明的那一刻,编译器就推断出 v 是 int 了。
为什么需要这个机制?
如果字面量一出生就有类型,代码会极其啰嗦:
// 假设 3 默认是 int,以下全部编译报错
var x float32 = 3 // ❌ 得写 float32(3)
var y float64 = 3 // ❌ 得写 float64(3)
const z int64 = 3 // ❌ 得写 int64(3)
// 数学运算也炸
result := 3 + 1.5 // ❌ int + float64 不行
有了 untyped constant,字面量就是一团"纯值",到哪都能自动适配,省掉了大量显式转型。本质上是语言设计上的一个便利机制。
"没有隐式转换"到底是什么意思?
Go 禁止的是一个已有类型的值偷偷变成另一个类型:
var x int = 3
var y float32 = x // ❌ x 已经是 int 了,不能隐式转成 float32
但字面量 3 从来没拥有过类型,所以 var y float32 = 3 不是"从 int 转成 float32"——它从头到尾就是 float32,只不过在赋值的那一刻才被确定下来。
类型确定 ≠ 隐式转换。 前者是从未有到有,后者是从 A 到 B。
42.0 能赋给 int,42.5 不行?
这个最容易踩坑:
fl := 42
fl = 42.0 // ✅ 没问题
fl = 42.5 // ❌ constant 42.5 truncated to integer
42.0 和 42.5 都是无类型浮点常量,都不是 float64。Go 判断能否赋值的标准是:这个值能不能被目标类型表示。
42.0的值是42,int能装下 → 通过42.5有小数部分,int装不下 → 报错
关键不在于它长得像整数还是浮点,在于值本身是否在目标类型的范围内。
var bo bool = 0 为什么报错?
刚理解了无类型常量,很容易觉得 0 能赋给任何类型:
var bo bool = 0 // ❌ 编译报错
但 0 虽然没有具体类型(不是 int 也不是 int64),它有一个种类(kind)——无类型整数常量。
无类型常量的种类是锁死的:
| 无类型常量 | 种类 | 可以赋给 |
|---|---|---|
0 | 整数 | int、float64、int64 等数值类型 |
3.14 | 浮点 | float32、float64 等浮点类型 |
true | 布尔 | bool |
'a' | 字符 | int32、rune |
所以:
var bo bool = true // ✅ 无类型布尔 → bool,种类一致
var n int = 0 // ✅ 无类型整数 → int,种类一致
var f float64 = 0 // ✅ 无类型整数 → 浮点,数值种类之间可以隐式转换
var bo bool = 0 // ❌ 整数种类 → 布尔种类,跨种类,不行
"没有类型"不是万能通行证,种类边界是不能跨的。
回到开头的两个问题
v == 0 为什么是 true?
0 是无类型常量,参与比较时不需要先变成某个具体类型,Go 直接拿它的值和 v 比。效果等同于 float32(0),结果自然是 true。
fmt.Printf("%T\n", 0) 为什么是 int?
0 作为参数传给 ...any,此时必须给它一个具体类型。Go 规范定了无类型常量的默认类型:
| 字面量 | 默认类型 |
|---|---|
整数 0 | int |
浮点 3.14 | float64 |
字符串 "hi" | string |
布尔 true | bool |
所以 0 被确定为 int,%T 打印出来就是 int。
补充:所有类型的零值
既然聊到了类型,顺便记一下 Go 所有类型的零值(未赋值时的默认值):
| 类型 | 零值 |
|---|---|
int, int8~int64 | 0 |
uint, uint8~uint64, uintptr | 0 |
float32, float64 | 0 |
complex64, complex128 | (0+0i) |
bool | false |
string | "" |
pointer, slice, map, channel, func, interface | nil |
array | 每个元素都是对应类型的零值 |
struct | 每个字段都是对应类型的零值 |
总结
Go 没有隐式类型转换,但字面量天生无类型,它们在使用的那一刻才由上下文决定最终类型。这不是"偷偷转换",而是"从未拥有,直到需要"。
但"没有类型"不等于"什么类型都行"——种类(整数/浮点/布尔/字符串)之间不能跨。 赋值的时候,值的种类必须和目标类型一致。