Go 基本类型
Go 的布尔、数值、字符串与复数等预声明类型:位宽含义、byte/rune 别名、string 不可变,以及禁止隐式数值类型转换的纪律。
[!info] 关联笔记
Go 基本类型
这个概念为什么会出现
程序最终要在内存里表示真假、数字、文本。若类型系统过松,隐式转换会制造静默截断与精度坑;若过严又无别名,字节与 Unicode 码点的意图会含糊。
Go 给出一组预声明的基本类型,并坚持:
- 静态、显式
- 不同数值类型之间通常不能隐式混用
string不可变byte/rune作为语义别名服务字节与 Unicode
所有复合结构(数组、slice、map、struct、接口)都建立在这层地基上。
[!abstract] 一句话理解 Go 基本类型是语言预声明的布尔、数值、字符串与复数类型;
byte/rune分别是uint8/int32的别名,string为不可变字节序列,数值之间需显式转换。
最小可运行示例
先把示例放进业务场景,再看代码:
场景:健康打卡服务上报用户资料
体态/健康 App 的一次打卡上报包含:
- 是否完成今日任务(
bool) - 年龄(
int)、体温(float64)、昵称(string) - 协议头里的一字节标记(
byte)与展示用中文姓氏首字(rune) - 设备侧
int32序列号与服务侧int64用户 ID 做一次合并展示
同时昵称里有中文,要分清 UTF-8 字节长度 和 码点个数。
package main
import "fmt"
// reportCheckIn 模拟“提交一次健康打卡”。
//
// 业务意图:把一次打卡里的基础字段用预声明类型表达清楚。
// 教学点:
// - bool / int / float64 / string 是最常见业务字段;
// - byte 表达“原始字节”,rune 表达“一个 Unicode 码点”;
// - 不同宽度整数不能隐式相加,必须显式转换。
func reportCheckIn() {
// 业务字段:完成标记、年龄、体温、昵称。
var done bool = true
var age int = 18
var tempC float64 = 36.6
var nickname string = "gopher"
// 协议魔数:单字节 'A' 表示 v1 协议。
var protocolTag byte = 'A'
// 展示姓氏首字:用 rune 存码点,而不是当成“一个 char 字节”。
var familyInitial rune = '中'
fmt.Println(done, age, tempC, nickname, protocolTag, familyInitial)
// %T 看运行时类型名;byte/rune 是别名,格式化细节以实现为准。
fmt.Printf("byte=%T rune=%T\n", protocolTag, familyInitial)
// 设备序列(int32)与用户 ID(int64)合并成日志里的 trace 号。
var deviceSeq int32 = 1
var userID int64 = 2
// sum := deviceSeq + userID // 编译错误:类型不匹配
// 教学点:Go 不做 C 式整数隐式提升,必须写 int64(deviceSeq)。
trace := int64(deviceSeq) + userID
fmt.Println("trace:", trace)
// 昵称含中文:len 统计的是 UTF-8 字节数,不是“字符数”。
displayName := "Go语言"
fmt.Println("bytes:", len(displayName), "runes:", len([]rune(displayName)))
}
func main() {
reportCheckIn()
}
建议运行:
go run .
期望输出:
true 18 36.6 gopher 65 20013
byte=uint8 rune=int32
trace: 3
bytes: 8 runes: 4
结合场景再看三个关注点
-
数值类型不混加
设备int32与用户int64要先显式转换,避免静默截断。 -
len(string)是字节长度
"Go语言"业务上 4 个字,UTF-8 下是 8 字节;按“字”计数要走[]rune或utf8.RuneCountInString。 -
byte/rune是语义别名
协议头用byte,中文首字用rune,底层分别是uint8/int32,但意图不同。
核心概念与准确模型
预声明类型一览
| 类别 | 类型 |
|---|---|
| 布尔 | bool |
| 字符串 | string |
| 有符号整数 | int int8 int16 int32 int64 |
| 无符号整数 | uint uint8 uint16 uint32 uint64 uintptr |
| 浮点 | float32 float64 |
| 复数 | complex64 complex128 |
| 别名 | byte(=uint8)、rune(=int32) |
参考:Spec — Boolean types、Numeric types、String types。
整数位宽与 int/uint
- 带数字后缀的类型:位宽固定(如
int32总是 32 位) int、uint:至少 32 位,在常见平台为与机器字同宽(32 或 64),属实现相关但有下限uintptr:足以存放指针位型的无符号整数,用于底层指针运算场景,不是普通计数器首选
选择直觉:
- 业务计数、切片下标、多数 API:优先
int - 协议/文件/哈希固定布局:用固定宽度(
uint32等) - 尺寸与位运算要警惕有符号/无符号混用
浮点与复数
- 默认浮点字面量常推导为
float64(在变量上下文中) float32省内存但精度更弱;科学计算默认float64complex64/complex128对应 float 分量宽度
浮点比较应容忍误差;不要对二进制浮点做天真的 == 金额运算(金额常用整数分)。
byte 与 rune
| 别名 | 底层 | 典型语义 |
|---|---|---|
byte | uint8 | 原始字节、I/O 缓冲、ASCII |
rune | int32 | Unicode 码点(code point) |
命名直觉:rune 像“一个字符符号”,强调完整码点,而不是 UTF-8 的可变长字节序列。
var c1 byte = 'A' // 65
var c2 rune = '你' // 码点
字符字面量 '…' 的类型取决于上下文;单独出现时常为 rune。
string:不可变字节序列
规范要点:
string值是不可变的 8 位字节序列- 不保证内容一定是合法 UTF-8(可容纳任意字节)
len(s)返回字节数s[i]是第 i 个字节(byte),不是第 i 个“字符”- 遍历码点用
for i, r := range s(r为rune)
修改字符串需构造新值(如 strings 包、[]byte 转换后再转回)。更深操作见 字符串。
无隐式数值转换
var a int = 1
var b int64 = 2
// var c int64 = a + b // 非法
var c int64 = int64(a) + b
即使底层都是“整数”,命名类型不同即不同。常量有更灵活的无类型常量规则(见变量/常量笔记与规范 Constants 章),但变量之间保持严格。
可赋值性与转换全景见 类型转换与可赋值性。
零值(预告)
基本类型都有零值:false、0、"" 等。系统展开见 变量、常量与零值。
设计动机
- 可预测的数据布局
固定宽度类型服务二进制协议与跨平台序列化。 - 拒绝静默数值强迫症转换
逼迫作者面对溢出与精度选择。 - 文本的双视角
字节管道用byte;人类字符用rune/UTF-8 约定。 - 字符串不可变
利于共享底层数据与并发只读安全(实现可优化)。
边界情况与反直觉行为
1. int 宽度不写死在可移植假设里
写磁盘格式时不要直接 binary.Write 一个 int 而不固定宽度。
2. len(string) ≠ 字符数
len("中") // 3(UTF-8 三字节),不是 1
3. 字符串可含非法 UTF-8
range 遇到坏序列会给出 0xFFFD 替换符等行为(见规范与 strings 文档)。
4. 截断是显式转换的责任
uint8(257) 会截断;编译器允许显式转换,不保证数值可逆。
5. byte/rune 与新定义类型不同
type B byte 是新类型,不是别名;byte 与 uint8 可互赋,因是别名。
常见误区
[!warning] 常见误区:假设 int 永远 64 位 错误:按 64 位写死序列化。
正确:协议用int32/int64等固定宽度。
[!warning] 常见误区:用 len 统计“字数” 错误:
len(name)当用户可见字符数。
正确:按产品定义用utf8.RuneCountInString或图形簇库。
[!warning] 常见误区:隐式混加不同整数类型 错误:从 C/JS 习惯期待自动提升。
正确:显式T(v)转换并考虑溢出。
[!warning] 常见误区:把 rune 当成“完整字形” 错误:一个用户感知字符 = 一个 rune。
正确:rune 是码点;表情与部分语言需多码点/图形簇。
[!warning] 常见误区:就地改 string 字节 错误:把 string 当可变缓冲。
正确:[]byte/strings.Builder再生成 string。
与相邻概念对比
| 概念 | 差异 |
|---|---|
| 数组/slice | 复合;元素可以是基本类型 |
命名类型 type Age int | 新类型,需显式转换来回 |
| 常量 | 无类型常量可更灵活表示 |
| 指针 | 地址,不是数值替代品;uintptr 例外场景底层 |
工程实践
- API 表面优先
int/string/bool/float64
除非互操作要求固定宽度。 - I/O 与加密用
[]byte
语义在字节层,不用[]rune冒充。 - 文本边界
校验 UTF-8 用utf8.ValidString;截断字符串按 rune 或按字素谨慎处理。 - 显式转换写清意图
int64(n)旁可注释为何安全(已检查范围)。 - 打印调试
%T看类型,%q看字符串,%d/%x看整数。 - 测试
覆盖非 ASCII、空串、最大边界与转换截断。
func clampU8(v int) (uint8, error) {
if v < 0 || v > 255 {
return 0, fmt.Errorf("out of range: %d", v)
}
return uint8(v), nil
}
可验证实验
实验 1:禁止隐式转换
尝试 int32 + int64,阅读编译器错误后加转换。
实验 2:string 长度
对 "Go语言" 打印 len、utf8.RuneCountInString、range 迭代次数。
实验 3:byte/rune 赋值
将同一字面量赋给 byte 与 rune,打印数值;尝试把非 ASCII 赋给 byte 观察。
实验 4:截断
var x uint8 = uint8(256) 或 257,打印结果。
实验 5:不可变
对 string 取 []byte 修改后再转 string,确认原 string 不变(注意:[]byte(s) 通常复制;勿依赖实现偷懒)。
本节总结
- 本质:预声明标量类型 + 字符串,构成数据表示地基。
- 关键规则:固定宽度 vs
int;byte/rune别名;string 不可变且按字节计量;无隐式数值转换。 - 最易错:len 当字符数、乱混整数类型、误用 int 做二进制布局。
- 下一步:变量与零值、字符串、转换。
自测题
概念题
byte与rune分别是什么类型的别名?语义差异?- 为什么
len("中文")不是 2? - 为什么 Go 拒绝
int与int64直接相加?
代码推理题
var a byte = 255
var b int = 1
// fmt.Println(a + b)
fmt.Println(int(a) + b)
第一处若取消注释会如何?第二处结果是什么?
工程思考题
网络协议字段是 32 位无符号长度。Go 结构体字段应选 int 还是 uint32?解析后与 len(slice) 比较时要注意什么?
参考答案
展开
byte→uint8(字节);rune→int32(Unicode 码点)。len计 UTF-8 字节;中文常用 3 字节/字。- 不同类型变量不能隐式转换,防止静默宽度/符号问题。
代码题:取消注释通常编译失败(类型不匹配);int(a)+b为 256。
工程题:字段用uint32固定布局;与int的len比较前显式转换,并检查是否超出平台int范围。
延伸阅读与资料来源
| 资料 | 类型 | 支撑 |
|---|---|---|
| Spec — Types | 规范 | 类型总览 |
| Spec — Numeric types | 规范 | 位宽与别名 |
| Spec — String types | 规范 | 不可变字节序列 |
| Spec — Conversions | 规范 | 显式转换 |
| Tour — Basic types | 教程 | 入门列表 |
| Tour — Type conversions | 教程 | 无隐式转换体验 |
| Package utf8 | 标准库 | 码点与有效性 |
笔记元信息
- 建议文件名:
go-basic-types.md - 所属阶段:语言基础起点
- 学习顺序:程序结构之后立刻学习
- 建议下一篇:变量、常量与零值
- 本篇状态:已深化(结构完整;字符串深入外置到 go-strings)