Go 变量、类型与零值
AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。
Go 是静态类型语言,但没有隐式类型转换:int 与 int64 是两个类型,必须显式转换。这条规则消灭了一整类 bug,也要求你一开始就选对类型。
var 与 :=
var count int // 声明,赋零值 0
var name string = "ann" // 声明并初始化
count = 1 // 赋值
age := 30 // 短变量声明,仅函数内可用
:= 是「声明 + 赋值」的简写,类型由右侧推断。三条规则:
- 只能在函数内部使用,包级别必须写
var/const。 - 左侧至少有一个变量是新声明的,否则编译错误。
- 右侧类型一旦推断出来就固定,
age = "30"会编译失败。
x := 1
x := 2 // 编译错误:no new variables on left side of :=
x = 2 // 正确:赋值
零值:没有「未初始化」
Go 里每个变量都有确定的初始值,不存在「未定义」状态:
| 类型 | 零值 |
|---|---|
数值(int、float64 等) |
0 |
string |
""(空字符串) |
bool |
false |
| 指针、切片、map、channel、interface、函数 | nil |
| struct | 各字段按零值初始化 |
var s string
var n int
var p *int
var m map[string]int
fmt.Println(s == "", n, p == nil, m == nil) // true 0 true true
零值可用是 Go 的设计目标之一:结构体零值通常就能直接使用。
type Counter struct {
total int
}
var c Counter // 零值可用,无需构造函数
c.total++
例外要记住:nil map 可读不可写,nil 切片可以直接 append(见本系列后续章节)。
基本类型
var (
i int = 42
i8 int8 = 127 // -128 ~ 127
i64 int64 = 1 << 40
f float64 = 3.14
b bool = true
s string = "hello"
by byte = 'a' // byte = uint8 的别名
r rune = '你' // rune = int32 的别名,表示一个 Unicode 码点
)
int 的宽度依平台而定(64 位系统是 64 位),涉及序列化、文件格式、跨平台计算时用 int64 等定宽类型,避免不同架构结果不一致。
常量与 iota
const Pi = 3.14159
const AppName = "hello"
const (
StatusPending = iota // 0
StatusRunning // 1
StatusDone // 2
)
iota 在每个 const 块内从 0 开始逐行递增,常用于枚举。需要跳过值时用 _:
const (
_ = iota // 丢弃 0
KB = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20
GB // 1 << 30
)
常量是无类型的(untyped constant),可以按上下文自动适应类型:
const Max = 100
var a int = Max // 可以
var b int64 = Max // 也可以
类型转换必须显式
var i int = 42
var f float64 = float64(i)
var u uint = uint(i)
// 浮点转整数是截断,不是四舍五入
fmt.Println(int(3.9)) // 3
字符串与数字互转要用标准库,不是强制转换:
import "strconv"
s := strconv.Itoa(42) // "42"
n, err := strconv.Atoi("42") // 42, nil
f, _ := strconv.ParseFloat("3.14", 64)
注意 string(65) 不是 "65",而是把码点 65 转成字符 "A":
fmt.Println(string(rune(65))) // A
fmt.Println(strconv.Itoa(65)) // 65
字符串、byte 与 rune
Go 的 string 是只读的字节序列,默认按 UTF-8 存储:
s := "你好"
fmt.Println(len(s)) // 6,字节数
fmt.Println(utf8.RuneCountInString(s)) // 2,字符数
for i, r := range s {
fmt.Println(i, r) // 0 20320 / 3 22909(i 是字节下标,r 是 rune)
}
按字节遍历与按字符遍历的结果不同,处理中文、emoji 时务必用 range 或 []rune:
chars := []rune("你好")
fmt.Println(len(chars)) // 2
fmt.Println(string(chars[0])) // 你
字符串与 []byte 转换(会产生复制):
b := []byte("hello")
s := string(b)
拼接字符串:少量用 +,循环内累积用 strings.Builder(避免每次分配新字符串)。
var sb strings.Builder
for _, w := range []string{"a", "b", "c"} {
sb.WriteString(w)
}
fmt.Println(sb.String()) // abc
常见错误与注意事项
- 混用
int与int64:编译报「mismatched types」,必须显式转换。 - 用
string(n)转数字:得到的是码点字符,数字转字符串用strconv.Itoa。 - 用
len(s)当字符数:得到字节数,中文 1 字 3 字节,要字符数用utf8.RuneCountInString。 :=重复声明同名变量:编译错误,改用=。- 循环内用
+拼字符串:频繁分配内存,改用strings.Builder。 - 以为零值就是「未设置」:
int的 0 与「用户输入的 0」无法区分,需要额外标志位或指针。
小结
- 函数内用
:=,包级别用var/const;:=左侧必须有新变量。 - 所有变量都有零值,struct 零值通常可直接使用;nil map 不能写。
- 跨平台与序列化用定宽类型;类型转换必须显式写。
- 数字与字符串互转用
strconv;枚举用const+iota。 - 字符串按字节存储,字符处理用
range/[]rune,拼接用strings.Builder。