Go 变量、类型与零值

AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。

Go 1.24

Go 是静态类型语言,但没有隐式类型转换:int 与 int64 是两个类型,必须显式转换。这条规则消灭了一整类 bug,也要求你一开始就选对类型。

var 与 :=

var count int          // 声明,赋零值 0
var name string = "ann"   // 声明并初始化
count = 1              // 赋值

age := 30              // 短变量声明,仅函数内可用

:= 是「声明 + 赋值」的简写,类型由右侧推断。三条规则:

  1. 只能在函数内部使用,包级别必须写 var / const。
  2. 左侧至少有一个变量是新声明的,否则编译错误。
  3. 右侧类型一旦推断出来就固定,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

常见错误与注意事项

  1. 混用 int 与 int64:编译报「mismatched types」,必须显式转换。
  2. 用 string(n) 转数字:得到的是码点字符,数字转字符串用 strconv.Itoa。
  3. 用 len(s) 当字符数:得到字节数,中文 1 字 3 字节,要字符数用 utf8.RuneCountInString。
  4. := 重复声明同名变量:编译错误,改用 =。
  5. 循环内用 + 拼字符串:频繁分配内存,改用 strings.Builder。
  6. 以为零值就是「未设置」:int 的 0 与「用户输入的 0」无法区分,需要额外标志位或指针。

小结

  • 函数内用 :=,包级别用 var / const;:= 左侧必须有新变量。
  • 所有变量都有零值,struct 零值通常可直接使用;nil map 不能写。
  • 跨平台与序列化用定宽类型;类型转换必须显式写。
  • 数字与字符串互转用 strconv;枚举用 const + iota。
  • 字符串按字节存储,字符处理用 range / []rune,拼接用 strings.Builder。

下一步