Skip to content

《Learning Go 第二版》入门实战系列 02:语言基石——预声明类型、变量与常量声明全解

约 3682 字大约 12 分钟

《Learning Go 第二版》系列Go语言

2026-04-02

欢迎回到《Learning Go 第二版》系统学习之旅。在上一篇中,我们搭建了坚实的开发环境。现在,是时候深入 Go 语言的核心,探究其内置的类型系统与声明方式了。Go 的设计哲学强调“明确表达意图”,这在类型处理和变量声明上体现得淋漓尽致。本篇将带你系统学习 Go 的所有预声明类型、变量与常量的声明方法,以及背后的重要概念与最佳实践。学完本篇,你将能清晰、惯用地运用 Go 的基础类型来编写程序,为后续学习复合类型和更高级的特性打下坚实基础。

【本篇核心收获】

  • 透彻理解 Go 的核心概念:零值(Zero Value)、字面量(Literal)及其无类型特性、显式类型转换规则。
  • 系统掌握所有 12+ 种预声明类型:包括布尔类型、整数家族(int8-int64uint8-uint64, 及其特殊类型 intuintbyterune)、浮点类型(float32float64)、复数类型(complex64complex128)以及 string 类型。
  • 精通变量声明的多种方式:深刻理解 var 与短声明 := 的区别、适用场景及避坑指南。
  • 掌握常量的声明与使用:理解类型化与非类型化常量的区别,明确常量的能力与限制。
  • 遵循 Go 的命名规范与代码风格:了解变量/常量命名规则,理解未使用变量的约束,编写地道的 Go 代码。

1. 核心概念先行:零值、字面量与类型

在深入具体类型之前,必须理解几个贯穿 Go 语言设计的基础概念。

1.1 零值:清晰且安全的默认值

Go 会为所有已声明但未显式赋值的变量自动分配一个默认值,称为零值。这消除了未初始化变量的不确定性,使代码更安全、意图更清晰。每种类型都有其特定的零值,我们将在介绍各类型时具体说明。

1.2 字面量:代码中的直接值

字面量是直接在代码中写明的值,例如 423.14'a'"hello"。Go 支持多种字面量:

  • 整数字面量:十进制(10)、二进制(0b1010)、八进制(0o12012)、十六进制(0xA)。可使用下划线 _ 增强可读性(如 1_000_000)。
  • 浮点数字面量:包含小数点(3.14),可用科学计数法(6.03e23)。
  • Rune 字面量:用单引号括起的单个字符,支持多种格式('a''\n''\u0061')。
  • 字符串字面量
    • 解释型字符串:双引号 " 括起,支持转义字符(如 \n\")。
    • 原始字符串:反引号 ` 括起,内容完全原样输出,无需转义。
  • 虚数字面量(用于复数):浮点数后跟 i(如 2.5i)。

关键特性:字面量是无类型的。这意味着在明确赋予其类型之前,字面量可以灵活地用于多种兼容类型的上下文中(例如,字面量 10 可以赋值给 intfloat64 等变量)。这增强了代码的灵活性。

1.3 显式类型转换:杜绝隐式转换的混乱

Go 为了追求代码的明确性,禁止在不同类型变量间进行自动类型转换(隐式转换)。当你需要将一种类型的值用作另一种类型时,必须使用显式类型转换,格式为 T(v),其中 T 是目标类型,v 是值。

var x int = 10
var y float64 = 30.2
var sum1 float64 = float64(x) + y // 必须将 x 转换为 float64
var sum2 int = x + int(y)         // 必须将 y 转换为 int

这条规则同样适用于不同大小的整数类型之间(如 int32int64)。Go 也没有“真值”概念,不能将非布尔类型(如整数 0 或空字符串)当作布尔值使用,必须显式使用比较运算符(如 x == 0)。

2. 预声明类型详解

Go 内置了丰富而实用的类型,下面我们分类详解。

2.1 布尔类型 (bool)

  • 取值truefalse

  • 零值false

  • 声明示例

    var flag bool // 未赋值,默认为 false
    var isAwesome = true

2.2 数字类型

Go 提供了多种数字类型以满足不同需求。

2.2.1 整数类型

Go 提供了 8 种不同大小的有符号和无符号整数类型。

类型大小取值范围
int81 字节-128 到 127
int162 字节-32,768 到 32,767
int324 字节-2,147,483,648 到 2,147,483,647
int648 字节-9.22e18 到 9.22e18
uint81 字节0 到 255
uint162 字节0 到 65,535
uint324 字节0 到 4,294,967,295
uint648 字节0 到 1.84e19

所有整数类型的零值均为 0

特殊的整数类型别名

  • byteuint8 的别名,强调该值是一个字节数据。
  • intuint:平台相关类型。在 32 位系统上为 32 位,在 64 位系统上通常为 64 位。整数字面量的默认类型是 int
  • runeint32 的别名,专门表示一个 Unicode 码点(字符)。
  • uintptr:用于存储指针值的无符号整数(高级用法)。

整数类型选择指南

  1. 处理特定格式:当处理二进制文件、网络协议等对数据大小有严格要求时,使用对应大小的类型(如 uint32)。
  2. 通用逻辑:在其他绝大多数情况下,直接使用 int。它的平台相关性通常不会造成问题,并且是性能最佳、最惯用的选择。
  3. 泛型场景:如果需要编写处理任意整数类型的函数,使用泛型。

整数运算符:支持 +, -, *, /, %(取模),以及位运算符 &, |, ^, &^, <<, >>。所有算术和位运算符都可与 = 结合形成赋值运算符(如 +=, &=)。

2.2.2 浮点类型 (float32, float64)

  • 零值0.0
  • 选择建议优先使用 float64。浮点数字面量的默认类型是 float64,其精度(约15-16位小数)远高于 float32(约6-7位),能有效减少累积误差。除非有明确的内存限制或兼容性要求,否则不使用 float32
  • 重要警告
    • 浮点数是不精确的!它们存储的是近似值。切勿用浮点数表示货币等必须精确的值
    • 不要直接用 ==!= 比较浮点数!由于不精确性,两个数学上相等的浮点数在计算机中可能不相等。应比较两者差值的绝对值是否小于一个极小的公差(ε)。
  • 特殊值:除以 0.0 会得到 +Inf(正无穷)或 -Inf0.0/0.0 得到 NaN(非数字)。

图1:浮点数的精度限制示意图

图1:浮点数无法精确表示所有十进制小数

2.2.3 复数类型 (complex64, complex128)

  • 定义complex64 实部和虚部为 float32complex128 实部和虚部为 float64
  • 声明:使用 complex(real, imag) 函数,例如 var c = complex(2.5, 3.1)
  • 零值0 + 0i
  • 操作:支持常规算术运算,可用 real(c)imag(c) 获取实部和虚部。比较时同样需注意浮点精度问题。
  • 定位:Go 内置复数支持源于语言设计者的兴趣,但在需要复杂数值计算(如科学计算、机器学习)的领域并非主流,通常使用专门的库(如 Gonum)或其他语言。

小结:数字类型是 Go 的算力基础。记住核心原则:通用整数用 int,通用浮点用 float64,避免浮点相等比较,货币等精确值不用浮点。

2.3 字符串 (string) 与 Rune (rune)

  • string:表示不可变的 Unicode 字符串序列。零值为空字符串 ""。支持比较(==, !=, >, <等)和拼接(+)。

  • runeint32 的别名,专门用于表示单个 Unicode 字符。应使用 rune 而非 int32 来处理字符,以明确代码意图

    var myChar rune = 'J' // 良好 - 类型名明确表示用途
    // var myChar int32 = 'B' // 不佳 - 合法但令人困惑
  • 关系:字符串由一系列字节(byte/uint8)组成,这些字节是 UTF-8 编码的 Unicode 字符(rune)。下一章将深入探讨字符串的内部表示。

3. 变量声明:var:= 的艺术

Go 提供了多种声明变量的方式,每种都传达不同的意图。

3.1 使用 var 声明

var 是基础的变量声明关键字,可用于任何作用域。

  • 标准形式var 变量名 类型 = 值

  • 类型推断:可省略类型,由初始值推断。var x = 10(x 为 int)。

  • 零值初始化:可省略值,变量初始化为零值。var x int(x 为 0)。

  • 批量声明

    var x, y int = 10, 20
    var a, b = 10, "hello"
    var (
        c int
        d = 20
        e, f string
    )

3.2 使用短声明 :=

短声明运算符 := 是函数内部的“语法糖”,它能自动推断类型并完成声明和初始化。

  • 基本形式变量名 := 值,等价于 var 变量名 = 值

  • 多重赋值x, y := 10, "hello"

  • 关键特性:只要 := 左侧至少有一个新变量,就可以对已存在的变量重新赋值。

    x := 10
    x, y := 30, "hello" // 合法:x 被重新赋值,y 是新变量
  • 限制只能在函数内部使用

3.3 如何选择:var vs :=

遵循“明确意图”的原则:

  • 在函数内部,首选 :=。这是最简洁、最惯用的方式。
  • 在以下场景使用 var
    1. 初始化为零值时var count intcount := 0 更明确地表达了“使用零值”的意图。
    2. 当变量类型与初始值的默认类型不符时var code byte = 20code := byte(20) 更清晰。
    3. 在包级别(函数外)声明变量时应尽量避免声明包级变量,因其不利于跟踪状态变化)。
  • 避免变量遮蔽:在作用域重叠时,小心使用 := 可能导致意外地声明新变量而非赋值给外层变量。若需明确,可使用 var 声明新变量后再用 = 赋值。

包级变量警告:应尽量避免声明包级变量,因为它们使程序的数据流变得难以追踪,可能引发难以察觉的错误。

图2:包级变量使数据流分析复杂化

图2:避免使用包级变量

4. 常量声明:用 const 定义不变值

常量用于定义编译时即可确定的、程序运行期间不可变的值。

4.1 常量的声明与限制

使用 const 关键字声明,可以出现在包级或函数内。

const Pi = 3.14159
const (
    StatusOK = 200
    Prefix   = "ERR_"
)

常量的值必须是编译时可确定的,例如:字面量、其他常量、内置函数(lencap 等,应用于常量或字面量)、由上述元素组成的常量表达式。

const x = 10
const y = x * 2 // 合法
// var z = 5
// const w = z * 2 // 非法!z 是变量,值在运行时确定

Go 的常量本质上是命名的字面量。没有机制可以将数组、切片、映射或结构体字段声明为不可变。

4.2 类型化常量 vs 非类型化常量

  • 非类型化常量:声明时未指定类型,如 const x = 10。它具有默认类型(如整数的默认类型是 int),但可以灵活地赋值给任何兼容类型的变量,提供了最大的灵活性。

    const x = 10
    var i int = x     // 合法
    var f float64 = x // 合法
  • 类型化常量:声明时指定了类型,如 const x int = 10。它只能直接赋值给相同类型的变量,用于强制类型安全。

    const x int = 10
    var i int = x        // 合法
    // var f float64 = x // 非法!类型不匹配

通常,应优先使用非类型化常量以增加灵活性,除非有强制类型约束的需求(如用于枚举)。

5. 代码风格与规范

5.1 未使用的变量

Go 编译器要求每个局部变量必须被读取。声明局部变量而不使用会导致编译错误。这是一项旨在保持代码简洁的独特规则。

func main() {
    x := 10 // 如果后续不再使用 x,编译将报错
    fmt.Println("hello")
}

注意:编译器只检查变量是否被读取过,不检查赋值是否被读取。对包级变量没有此限制,这是另一个避免使用包级变量的理由。

5.2 命名规范

  • 标识符规则:以字母或下划线开头,后可跟字母、数字、下划线。Unicode字母也被允许,但强烈不建议使用非ASCII字符命名,以免造成混淆。
  • 命名风格:Go 使用驼峰式命名indexCounter),而非蛇形命名(index_counter)。
  • 变量名长度作用域越小,名字应越短。在短小的循环或代码块中,使用 ijkv 是惯用的。
  • 常量命名:不像某些语言用全大写,Go 常量的命名规则与变量相同,其可见性由首字母大小写控制(后续包章节详述)。
  • 包级命名:由于作用域大,应使用更具描述性的名字(如 DefaultServerPort)。

遵循这些规范能让你的代码更地道,更易于团队协作和维护。

6. 动手练习

  1. 类型转换实践:声明一个整数 i 值为 26,将其赋值给一个浮点数变量 f。打印两者。
  2. 常量灵活性:声明一个可同时赋值给整数和浮点数的非类型化常量,然后分别赋值给两种类型的变量并打印。
  3. 类型极值:声明 byteint32uint64 类型的变量,并赋值为各自类型的最大值。尝试对其加1,观察并思考结果。

【本篇核心知识点速记】

  • 零值与字面量:变量默认有零值;字面量(数字、字符、字符串)是无类型的,使用灵活。
  • 类型系统核心
    • 禁止隐式转换:必须用 T(v) 进行显式类型转换。
    • 无真值:必须用比较表达式得到布尔值。
  • 类型选择
    • 通用整数用 int,通用浮点用 float64
    • float 不精确,忌用 == 比较,忌表示货币。
    • 字符用 rune,字节数据用 byte
  • 变量声明
    • 函数内优先用短声明 :=
    • 零值初始化、类型明确指定、避免遮蔽时用 var
    • 尽量避免包级变量
  • 常量
    • const 声明,值必须编译时确定。
    • 优先用非类型化常量以增加灵活性。
  • 代码风格
    • 局部变量必须被使用。
    • 使用驼峰命名,短作用域用短名。
    • 追求意图清晰,而非极简。