背景与动机
Go 的基本数据类型不只是“能装什么值”,更决定了表达式如何计算、默认值是什么、能否比较、以及不同类型之间是否允许直接混用。这个主题表面上像语法入门,实际是后面数组、切片、结构体、接口、并发乃至性能优化的基础。
学习这一节,核心要弄清 5 件事:
- 整型和浮点型各自的值域、精度和默认选型。
bool为什么保持极简,没有“数字即真”的隐式规则。string在 Go 里到底是“字符序列”还是“字节序列”。- Go 为什么要求大多数类型转换必须显式写出来。
- “转换”与“格式化/解析”不是一回事。
核心原理拆解
1. 整型:有符号、无符号、平台相关大小要分清
Go 的整型分为两类:
- 有符号整型:
int8、int16、int32、int64、int - 无符号整型:
uint8、uint16、uint32、uint64、uint、uintptr
还存在两个常见别名:
byte是uint8的别名rune是int32的别名,常用于表示 Unicode 码点
固定宽度类型的语义最稳定,例如:
int32永远是 32 位uint64永远是 64 位
而 int 和 uint 是平台相关大小:
- 32 位架构上通常是 32 位
- 64 位架构上通常是 64 位
Go 规范要求整数使用二进制补码语义理解。工程上最重要的结论不是“底层怎么存”,而是:
- 需要稳定数据宽度时,用
int32、uint64这类固定类型 - 只做普通计数、索引、长度运算时,优先用
int

2. 浮点型:遵循 IEEE 754,但不能拿来做“精确小数”
Go 的浮点型主要是:
float32float64
它们遵循 IEEE 754 浮点标准。默认情况下,浮点字面量如果没有额外约束,通常会落到 float64 语义中。
浮点型适合表达:
- 科学计算
- 比例、平均值、连续量
- 允许存在舍入误差的数值
不适合直接表达:
- 金额
- 高精度统计结果
- 需要严格十进制精确性的业务值
原因不是 Go 特有,而是二进制浮点无法精确表示很多十进制小数,例如 0.1、0.2。
典型现象:
0.1 + 0.2 != 0.3在很多语言里都可能成立。Go 这里没有“帮你修正直觉”,而是保持底层数值模型的真实语义。
3. 布尔型:只有 true 和 false,没有隐式 truthy/falsy
Go 的布尔型只有一个预声明类型:
bool它只表示两个值:
truefalse
这看起来简单,但设计上很重要。Go 不允许把整数、指针、字符串隐式当成布尔值使用。也就是说,下面这种写法非法:
if 1 { }if "hello" { }必须写成明确判断:
if n != 0 { }if s != "" { }这种限制带来的价值是:
- 条件表达式语义更清晰
- 减少“空值即假”这类隐式规则
- 编译器更容易在类型层面帮你发现错误
4. 字符串:本质是不可变的字节序列,不是“字符数组”
Go 里的 string 代表的是一段只读字节序列。这个定义非常关键。
它有几个核心性质:
- 可以为空,但长度永不为负
- 可以通过
len(s)获取字节长度 - 可以按索引访问某个字节
- 一旦创建,内容不可修改
例如:
s := "Go语言"fmt.Println(len(s))fmt.Println(s[0])这里:
len(s)返回的是字节数,不是“字符数”s[0]取到的是第一个字节,不是第一个 Unicode 字符
这也是很多初学者最容易误解的地方。Go 字符串常常保存 UTF-8 文本,但类型本身并不承诺“一个索引对应一个字符”。
如果你想按 Unicode 码点处理,更常见的方式是:
- 使用
for range遍历字符串 - 或先转换成
[]rune
字符串为什么不可变
不可变的好处是:
- 共享更安全
- 作为 map key 更稳定
- 不会因为局部修改破坏别处引用语义
代价是:
- 频繁拼接会产生新字符串
- 修改文本通常要借助
[]byte或[]rune

5. 类型转换:Go 强调显式转换,避免“看起来像对其实错了”
Go 不鼓励不同类型自动混算。即使两个类型底层位宽一样,也往往要显式转换。
例如:
var a int32 = 10var b int = 20fmt.Println(int(a) + b)这里不能直接 a + b,因为 int32 和 int 是不同类型。
这种设计的重点是:把可能丢精度、变符号、变语义的地方显式暴露出来。
数值类型转换的核心规则
- 整数转更小位宽整数:高位会被截断
- 有符号和无符号互转:按位截断/扩展后得到目标类型值,不会报“溢出异常”
- 浮点转整数:小数部分直接截断,朝零取整
- 整数或浮点转目标浮点型:会按目标精度舍入
例如:
fmt.Println(int(3.9)) // 3fmt.Println(uint8(300)) // 44fmt.Println(int8(130)) // -126这里最重要的不是背结果,而是记住:Go 的数值转换通常“总能生成一个目标类型值”,但这个值不一定符合你的业务预期。
字符串转换不是文本解析
这点必须单独讲清。
string(65)结果是 "A",因为这是把整数当 Unicode 码点转换成字符串,不是把数字格式化成十进制文本。
如果你想得到 "65",应该用:
strconv.Itoa(65)反过来,如果你有 "123" 想变成整数,也不是类型转换,而是解析:
strconv.Atoi("123")string、[]byte、[]rune 转换的语义不同
[]byte(s):拿到字符串的原始字节副本string(bs):把字节切片重新构造成字符串[]rune(s):按 UTF-8 解码后得到 Unicode 码点序列
这三者不是同一件事,尤其不能把“字节数”和“字符数”混为一谈。
最小可运行代码示例
package main
import ( "fmt" "strconv")
func main() { var age int = 18 var pi float64 = 3.14 var ok bool = true var text string = "Go语言"
fmt.Println(age, pi, ok, text)
var n int32 = 10 var m int = 20 fmt.Println(int(n) + m)
fmt.Println(len(text)) // 字节长度 fmt.Println([]rune(text)) // 按 rune 解码 fmt.Println(string(65)) // "A" fmt.Println(strconv.Itoa(65)) // "65"
fmt.Println(int(3.9)) // 3}这个例子覆盖了:
- 整型、浮点型、布尔型、字符串的声明
- 不同整型之间的显式转换
string的字节长度与rune视角- “类型转换”和“格式化为字符串”的区别
常见陷阱与错误示例
1. 误以为 int 永远是 64 位
错误理解:
var id int然后把它当成稳定的 64 位存储类型使用。int 只保证“与平台字长一致”,不保证跨平台固定 64 位。涉及协议、文件格式、数据库字段边界时,应用 int64、uint32 这类固定宽度类型。
2. 浮点数直接做精确相等比较
错误示例:
if 0.1+0.2 == 0.3 { // 期望进入}这类判断在浮点场景里不可靠。更稳妥的做法是比较误差是否在允许范围内。
3. 把字符串索引当成“取第几个字符”
错误示例:
s := "你"fmt.Println(s[0])这里拿到的是第一个字节,不是完整字符。处理 Unicode 文本时,应该用 for range 或 []rune。
4. 把 string(123) 当成 "123"
错误示例:
fmt.Println(string(123))这不是数字转文本,而是把 123 当成 Unicode 码点转换。数字文本化要用 strconv.Itoa、FormatInt;文本解析要用 strconv.Atoi、ParseInt。
5. 误以为类型转换会自动做安全检查
错误示例:
var x uint8 = uint8(300)这不会报错,但结果不是 300,而是截断后的值。Go 的显式转换让风险可见,但不替你做业务语义兜底。
性能影响或设计取舍
1. 默认用 int 是 Go 的工程取舍,不是“最省内存”
int 通常是最顺手的默认整型,因为它和很多内建函数、索引、长度运算天然配合。但它不是跨平台协议类型,也不是最省空间的类型。需要稳定布局时,应选固定宽度整型。
2. float64 常比 float32 更常见,因为默认精度更稳
float32 节省内存,但精度更低。大多数通用业务代码里,float64 更安全、更符合默认浮点字面量语义;只有在大规模数组、图形计算、明确内存敏感场景下,float32 才更有吸引力。
3. 字符串不可变,换来安全和共享,代价是修改要拐弯
字符串拼接、截取、转换都很方便,但如果你频繁编辑文本,直接堆叠字符串往往不划算。大量构造文本时,更适合用 strings.Builder 或先操作 []byte。
4. string 与 []byte、[]rune 转换通常有成本
这类转换常常意味着分配和拷贝,尤其在热路径里要谨慎。设计上这是 Go 在“语义清晰”和“低层零成本共享”之间的取舍:默认安全清楚,性能敏感时再主动优化。
5. 显式转换牺牲一点简洁性,换来类型边界清晰
Go 不允许很多隐式数值混算,看起来啰嗦一点,但它把精度变化、符号变化、宽度变化的风险直接暴露到代码表面,长期维护收益很高。
面试高频问题
1. int 和 int32 有什么区别
int32 是固定 32 位有符号整数;int 是平台相关大小,通常跟机器字长一致。两者即使在某个平台上位宽一样,也仍然是不同类型,不能直接混用。
2. 为什么 Go 的字符串说是字节序列,不是字符数组
因为 string 的语言定义就是不可变字节序列,len 返回字节数,索引得到的是字节。UTF-8 文本只是常见用法,不改变它的底层语义。
3. string(65) 和 strconv.Itoa(65) 有什么区别
string(65) 是把整数当 Unicode 码点转换成字符串,结果是 "A";strconv.Itoa(65) 是把整数格式化成十进制文本,结果是 "65"。
4. 浮点数为什么不适合表示金额
因为二进制浮点无法精确表示很多十进制小数,连续计算后容易产生舍入误差。金额这类值通常需要定点整数或专门的十进制方案。
5. Go 为什么要求很多类型转换必须显式写出来
因为不同类型即使底层表示接近,语义也可能不同。显式转换能把精度损失、符号变化、截断风险暴露出来,减少隐式错误。
6. []byte(s) 和 []rune(s) 的区别是什么
[]byte(s) 取的是字符串底层字节序列;[]rune(s) 是把字符串按 UTF-8 解码后得到 Unicode 码点序列。前者适合原始字节处理,后者适合按字符语义处理文本。
一句话总结
Go 的基本数据类型可以概括为:整型关注位宽和符号,浮点型关注精度与舍入,布尔型坚持显式真假,字符串本质上是不可变字节序列,而类型转换强调显式和语义清晰,绝不是“顺手改个外观”那么简单。