第 2 章 · 变量、类型与常量
本章目标:掌握 Go 短变量声明(:=)、基本类型、零值、常量与 iota 枚举;理解 值类型与引用类型(slice/map/chan 预告);熟练使用 fmt 格式化与显式类型转换;在 toolkit-go 中定义订单状态常量与商品价格字段(分为单位);对照 java-dev ch02 基本类型与 python-dev ch02 动态类型。
学时建议:3~4 小时(含 1 小时跟练)
前置:完成 go-dev ch01,能运行 toolkit-go;建议已浏览 java-dev ch02 变量与类型。
2.1 场景说明:toolkit-go 需要强类型数据
toolkit-go(目录 ~/learn-go/toolkit-go,Windows 为 %USERPROFILE%\learn-go\toolkit-go)在 ch15 将解析 access 日志并统计慢请求。本章先在 internal/model/ 中定义与商城对齐的基础类型:订单状态用 常量枚举,商品价格用 int64 分 存储,避免浮点误差(与 django-web / fastapi-web / gin-web 的 slug + is_published 字段约定一致,价格语义对齐)。
| 字段 | Go 类型 | Java 对照 | Python 对照 |
|---|---|---|---|
| 商品 slug | string | String | str |
| 价格 | int64(分) | long / BigDecimal | int 或 Decimal |
| 是否上架 | bool | boolean | bool |
| 订单状态 | const + iota | enum | 字符串或 IntEnum |
learn-go/toolkit-go/
├── go.mod
├── cmd/toolkit/main.go
└── internal/
└── model/
├── order_status.go # 本章:状态常量
├── product_stub.go # 本章:商品桩
└── money.go # 本章:元分转换
2.2 变量声明方式
Go 提供三种常见声明方式,编译器在编译期确定类型(静态类型)。
// internal/model/vars_demo.go — 教学示例,可删除
package model
// 包级变量:须显式类型或使用 var 推断
var AppName string = "toolkit-go"
var DefaultTopN = 10
func DemoVars() {
// 短声明:仅函数内,类型由编译器推断
port := 8080
host := "127.0.0.1"
// 多变量
var x, y int = 1, 2
a, b := 3, 4
_ = port
_ = host
_ = x
_ = y
_ = a
_ = b
}
| 方式 | 语法 | 适用场景 |
|---|---|---|
var x T = v | 显式类型 | 包级变量、需零值初始化 |
var x = v | 类型推断 | 包级或函数内 |
x := v | 短声明 | 仅函数内,最常用 |
| Java | Go | Python |
|---|---|---|
int x = 10; | x := 10 或 var x int = 10 | x = 10 |
| 必须先声明类型 | 短声明可推断 | 无声明关键字 |
包级无 := | 包级无 := | 无此限制 |
注意:包级变量不能使用:=;未使用的局部变量会导致编译错误(Go 强制「声明即使用」)。用_丢弃不需要的值。
2.3 基本类型与零值
Go 每种类型都有明确的零值(zero value),变量声明后未赋值即为零值。
| 类型 | 零值 | toolkit-go 示例 |
|---|---|---|
bool | false | 日志行是否慢请求 |
int / int32 / int64 | 0 | 响应耗时毫秒、Price(分) |
float32 / float64 | 0.0 | 仅展示用,不存金额 |
string | "" | 商品 slug、日志路径 |
byte / rune | 0 | 字节 / Unicode 码点 |
*T | nil | 可选指针字段 |
error | nil | 函数错误返回值(接口类型) |
slice / map / chan | nil | ch04 详讲 |
// internal/model/product_stub.go
package model
import "fmt"
type ProductStub struct {
Slug string
Price int64 // 分:6800 表示 68.00 元
IsPublished bool
}
func NewProductStub(slug string, priceYuan float64) ProductStub {
cents := YuanToCents(priceYuan)
return ProductStub{Slug: slug, Price: cents, IsPublished: false}
}
func (p ProductStub) PriceYuan() string {
return fmt.Sprintf("%.2f", float64(p.Price)/100)
}
| Java(java-dev) | Go(go-dev) | Python |
|---|---|---|
| 成员变量有默认值 | 所有类型零值明确 | 无「未初始化」概念 |
long price = 6800L | price := int64(6800) | price = 6800 |
| 隐式窄化可能编译失败 | 必须显式转换 | 动态转换 |
2.4 整数、浮点与字符串
| 分类 | Go 类型 | 说明 |
|---|---|---|
| 有符号整数 | int8~int64、int | int 长度随平台(32/64 位) |
| 无符号整数 | uint8~uint64、uint | 位运算、哈希 |
| 浮点 | float32、float64 | 默认浮点字面量为 float64 |
| 复数 | complex64、complex128 | 科学计算,本课少用 |
| 字符串 | string | UTF-8 字节序列,不可变 |
| 字节 / 字符 | byte(=uint8)、rune(=int32) | 处理 ASCII / Unicode |
package model
import (
"fmt"
"unicode/utf8"
)
func DemoTypes() {
var stock int64 = 100
var rate float64 = 0.15
slug := "go-handbook"
ch := 'G' // rune
fmt.Println(stock, rate, slug, string(ch))
fmt.Println(utf8.RuneCountInString("Go语言")) // 4 个 rune
}
金额规范(全站统一):
| 错误做法 | 正确做法 |
|---|---|
float64 存储 68.99 元 | int64 存储 6899 分 |
| 浮点加减产生 0.1+0.2 问题 | 整数分运算精确 |
JSON 传 "price": 68.99 | 传 "price": 6899(分) |
2.5 常量与 iota 枚举
// internal/model/order_status.go
package model
// 订单状态 — 整数枚举(iota)
const (
OrderPending = iota // 0
OrderPaid // 1
OrderShipped // 2
OrderCancelled // 3
)
// 对外 API / JSON 友好字符串常量
const (
StatusPending = "pending"
StatusPaid = "paid"
StatusShipped = "shipped"
StatusCancelled = "cancelled"
)
// 应用版本 — 普通常量
const Version = "0.1.0"
// HTTP 状态码子集(iota 跳过示例)
const (
HTTPStatusOK = 200
HTTPStatusCreated = 201
HTTPStatusBadReq = 400
HTTPStatusNotFound = 404
HTTPStatusServer = 500
)
| 模式 | 说明 | Java 对照 |
|---|---|---|
iota 自增 | 同一 const 块内从 0 递增 | enum 序号 |
| 字符串常量 | 序列化 / API 边界 | enum.name() 或字符串字段 |
const 块 | 逻辑分组 | public static final 一组 |
iota 高级模式(选修):
const (
_ = iota // 跳过 0
KB = 1 << (10 * iota) // 1 << 10, 1 << 20 ...
MB
GB
)
Go 1.21+ 可用 type OrderStatus int 增强类型安全(类似 Java enum 包装),本课以 iota + 字符串常量为主。
2.6 类型转换(无隐式转换)
Go 无隐式数值转换,必须显式 cast。这与 Java 部分场景(如 int → long widening)和 Python 动态转换均不同。
// internal/model/money.go
package model
import "math"
func YuanToCents(yuan float64) int64 {
return int64(math.Round(yuan * 100))
}
func CentsToYuan(cents int64) float64 {
return float64(cents) / 100
}
func DemoConvert() {
var durationMs int = 1500
var seconds float64 = float64(durationMs) / 1000
cents := YuanToCents(68.99) // 6899
_ = seconds
_ = cents
}
| 错误写法 | 正确写法 | 说明 |
|---|---|---|
var f float64 = 10(int 赋 float) | var f float64 = float64(10) | 必须显式 |
int64(3.14) 直接截断 | 先 math.Round 再转 | 避免静默丢精度 |
用 float64 存分 | 用 int64 存分 | 业务规范 |
string(65) 得 Unicode | strconv.Itoa(65) 得 "65" | 类型转换语义不同 |