Go 基本类型

Go 的布尔、数值、字符串与复数等预声明类型:位宽含义、byte/rune 别名、string 不可变,以及禁止隐式数值类型转换的纪律。

#type / concept #status / growing #tech / dev #resource / go

[!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

结合场景再看三个关注点

  1. 数值类型不混加
    设备 int32 与用户 int64 要先显式转换,避免静默截断。

  2. len(string) 是字节长度
    "Go语言" 业务上 4 个字,UTF-8 下是 8 字节;按“字”计数要走 []runeutf8.RuneCountInString

  3. 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 typesNumeric typesString types

整数位宽与 int/uint

  • 带数字后缀的类型:位宽固定(如 int32 总是 32 位)
  • intuint:至少 32 位,在常见平台为与机器字同宽(32 或 64),属实现相关但有下限
  • uintptr:足以存放指针位型的无符号整数,用于底层指针运算场景,不是普通计数器首选

选择直觉:

  • 业务计数、切片下标、多数 API:优先 int
  • 协议/文件/哈希固定布局:用固定宽度(uint32 等)
  • 尺寸与位运算要警惕有符号/无符号混用

浮点与复数

  • 默认浮点字面量常推导为 float64(在变量上下文中)
  • float32 省内存但精度更弱;科学计算默认 float64
  • complex64/complex128 对应 float 分量宽度

浮点比较应容忍误差;不要对二进制浮点做天真的 == 金额运算(金额常用整数分)。

byterune

别名底层典型语义
byteuint8原始字节、I/O 缓冲、ASCII
runeint32Unicode 码点(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 srrune

修改字符串需构造新值(如 strings 包、[]byte 转换后再转回)。更深操作见 字符串

无隐式数值转换

var a int = 1
var b int64 = 2
// var c int64 = a + b // 非法
var c int64 = int64(a) + b

即使底层都是“整数”,命名类型不同即不同。常量有更灵活的无类型常量规则(见变量/常量笔记与规范 Constants 章),但变量之间保持严格。

可赋值性与转换全景见 类型转换与可赋值性

零值(预告)

基本类型都有零值:false0"" 等。系统展开见 变量、常量与零值

设计动机

  1. 可预测的数据布局
    固定宽度类型服务二进制协议与跨平台序列化。
  2. 拒绝静默数值强迫症转换
    逼迫作者面对溢出与精度选择。
  3. 文本的双视角
    字节管道用 byte;人类字符用 rune/UTF-8 约定。
  4. 字符串不可变
    利于共享底层数据与并发只读安全(实现可优化)。

边界情况与反直觉行为

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新类型,不是别名;byteuint8 可互赋,因是别名。

常见误区

[!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 例外场景底层

工程实践

  1. API 表面优先 int/string/bool/float64
    除非互操作要求固定宽度。
  2. I/O 与加密用 []byte
    语义在字节层,不用 []rune 冒充。
  3. 文本边界
    校验 UTF-8 用 utf8.ValidString;截断字符串按 rune 或按字素谨慎处理。
  4. 显式转换写清意图
    int64(n) 旁可注释为何安全(已检查范围)。
  5. 打印调试
    %T 看类型,%q 看字符串,%d/%x 看整数。
  6. 测试
    覆盖非 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语言" 打印 lenutf8.RuneCountInStringrange 迭代次数。

实验 3:byte/rune 赋值

将同一字面量赋给 byterune,打印数值;尝试把非 ASCII 赋给 byte 观察。

实验 4:截断

var x uint8 = uint8(256)257,打印结果。

实验 5:不可变

对 string 取 []byte 修改后再转 string,确认原 string 不变(注意:[]byte(s) 通常复制;勿依赖实现偷懒)。

本节总结

  • 本质:预声明标量类型 + 字符串,构成数据表示地基。
  • 关键规则:固定宽度 vs intbyte/rune 别名;string 不可变且按字节计量;无隐式数值转换。
  • 最易错:len 当字符数、乱混整数类型、误用 int 做二进制布局。
  • 下一步变量与零值字符串转换

自测题

概念题

  1. byterune 分别是什么类型的别名?语义差异?
  2. 为什么 len("中文") 不是 2?
  3. 为什么 Go 拒绝 intint64 直接相加?

代码推理题

var a byte = 255
var b int = 1
// fmt.Println(a + b)
fmt.Println(int(a) + b)

第一处若取消注释会如何?第二处结果是什么?

工程思考题

网络协议字段是 32 位无符号长度。Go 结构体字段应选 int 还是 uint32?解析后与 len(slice) 比较时要注意什么?

参考答案

展开
  1. byteuint8(字节);runeint32(Unicode 码点)。
  2. len 计 UTF-8 字节;中文常用 3 字节/字。
  3. 不同类型变量不能隐式转换,防止静默宽度/符号问题。
    代码题:取消注释通常编译失败(类型不匹配);int(a)+b 为 256。
    工程题:字段用 uint32 固定布局;与 intlen 比较前显式转换,并检查是否超出平台 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)
创建于 2026/6/20 更新于 2026/7/15