Learngo 变量声明热身练习全解:11 个入门练习吃透 Go 变量声明、零值与作用域
Learngo 变量声明热身练习全解:11 个入门练习吃透 Go 变量声明、零值与作用域
本指南基于 learngo 仓库中文翻译版 声明并打印变量练习清单,完整解析其中的 11 个热身练习(int / bool / float64 / string 声明、非法命名、全内置类型遍历、多重声明、未使用变量、包变量与声明顺序)。每个练习都配有题目桩代码(main.go)与参考答案(solution/main.go),读者按顺序完成后即可牢固掌握 Go 变量声明的语法骨架、零值机制、命名规则与作用域差异,为后续学习短声明、赋值与类型转换打下基础。
练习总览:11 个题目按什么顺序学
这份练习清单的主题是"声明并打印变量",设计意图非常明确:热身,熟悉 var 声明语法,获得肌肉记忆。11 个练习难度递进,覆盖的知识点如下:
| # | 练习目录 | 核心知识点 | 期望输出 |
|---|---|---|---|
| 1 | 01-int | 声明并打印 int |
0 |
| 2 | 02-bool | 声明并打印 bool |
false |
| 3 | 03-float64 | 声明并打印 float64 |
0 |
| 4 | 04-string | 声明并打印 string |
"" |
| 5 | 05-undeclarables | 非法标识符与编译报错 | 编译失败 |
| 6 | 06-with-bits | 遍历全部内置类型 | 0 0 0 0 0 0 0 (0+0i) (0+0i) false 0 0 + "" |
| 7 | 07-multiple | 分组多重声明 var (...) |
false 0 |
| 8 | 08-multiple-2 | 平行声明 + 同时初始化 | "" "" |
| 9 | 09-unused | 未使用变量错误与空白标识符 | 无输出(可编译) |
| 10 | 10-package-variable | 包作用域变量 | 无输出(可编译) |
| 11 | 11-wrong-doer | 先使用后声明的编译错误 | 编译失败 |
每个练习目录下都有两个文件:带 // 练习: 注释说明的题目桩代码,以及可直接运行的参考答案。仓库根目录的 go.mod 表明这是一个 Go module,因此任选一个练习目录,执行 go run .(或直接 go run main.go)即可看到输出;想对照答案则运行 go run ./solution。
练习 01–04:四种基础类型的声明与打印
前四个练习是同一模式的重复:用 var 变量名 类型 声明一个变量,然后打印它。目的是让初学者把"声明 → 使用"的流程变成条件反射。
01-int:声明 height
题目要求声明一个名为 height 的 int 变量并打印,桩代码只留了 // var ? ? 两个占位符:
func main() {
// var ? ?
// ?
}
参考答案 solution/main.go:
package main
import (
"fmt"
)
func main() {
var height int
fmt.Println(height)
}
期望输出是 0——不是某个随机数,而是 int 的零值。
02-bool:声明 isOn
题目要求声明 bool 型变量 isOn。参考答案 solution/main.go:
var isOn bool
fmt.Println(isOn)
输出 false,即 bool 的零值。
03-float64:声明 brightness
参考答案 solution/main.go:
var brightness float64
fmt.Println(brightness)
输出 0。float64 是 Go 中的双精度浮点类型,未初始化时为零值 0。
04-string:声明 s,并学会 %T 与 %q
这一题开始引入格式化输出,为后续 07-printf 章节做铺垫。题目注释特意说明:"%T 打印变量的类型,%q 打印一个空字符串"。参考答案 solution/main.go:
package main
import "fmt"
func main() {
var s string
fmt.Printf("s (%T): %q\n", s, s)
}
期望输出为 s (string): ""。这里 %T 告诉我们 s 的类型是 string,%q 则用引号包裹的方式打印字符串,让"空字符串"可见——如果用 fmt.Println 打印空字符串,屏幕上只会出现一片空白,这正是 %q 的价值所在。
零值:为什么没初始化也能安全打印
前四个练习的期望输出(0、false、"")并非巧合,而是 Go 语言"零值(zero value)"机制的体现:任何变量在声明后、赋值前,都自动拥有一个与类型对应的零值。这一点在配套示例 03-零值/main.go 中做了直接演示:
var speed int // 数字类型
var heat float64 // 数字类型
var off bool
var brand string
fmt.Println(speed)
fmt.Println(heat)
fmt.Println(off)
fmt.Printf("%q\n", brand)
各类型零值一览(也可对照练习题 04-zero-values):
| 类型 | 零值 |
|---|---|
所有数字类型(int/float64/complex64 等) |
0(复数显示为 (0+0i)) |
bool |
false |
string |
"" |
| 指针 / 接口 / slice / map / channel / 函数 | nil |
配套问题清单明确回答了几个易错点:"哪种类型的零值是 0?——所有的数字类型";"哪种类型的零值是 false?——bool";"哪种类型的零值是 ""?——string";"哪种类型的零值是 nil?——pointer"。
练习 05:不可声明的变量——一次看清命名规则
这个练习反其道而行之:题目列出 6 个"变量名",要求逐一声明并观察编译报错。桩代码 main.go 中的目标是:
// var 3speed int
// var !speed int
// var spe?ed int
// var var int
// var func int
// var package int
这些声明全部非法,参考答案 solution/main.go 只是把它们保留为注释,因为一旦取消注释程序就无法编译。原因分两类:
- 标识符语法非法:
3speed以数字开头,!speed含特殊字符!,spe?ed含?。Go 标识符只能由 Unicode 字母、数字和下划线组成,且不能以数字开头。 - 关键字冲突:
var、func、package是 Go 的预声明关键字,不能用作变量名。
配套示例 01-声明语法/02-命名规则/main.go 展示了合法与"边缘合法"的命名:
var speed int // 正确
var SpeeD int // 正确(区分大小写,但风格上不推荐)
var _speed int // 下划线开头允许但不推荐
var 速度 int // Unicode 字母是允许的
自测题 02-declaration 中同样问到了这一点:four 是合法变量名,而 int(关键字)、2computers(数字开头)、one?there(含 ?)都不是。
练习 06:用 bit 声明——遍历全部内置类型
前四题只涉及 4 种类型,这一题要求一口气声明 13 种:int、int8、int16、int32、int64、float32、float64、complex64、complex128、bool、string、rune、byte。参考答案 solution/main.go:
package main
import "fmt"
func main() {
// 整数类型
var i int
var i8 int8
var i16 int16
var i32 int32
var i64 int64
// 浮点类型
var f32 float32
var f64 float64
// 复杂类型
var c64 complex64
var c128 complex128
// 布尔型
var b bool
// 字符串类型
var s string
var r rune // 同样是数字类型
var by byte // 同样是数字类型
fmt.Println(
i, i8, i16, i32, i64,
f32, f64,
c64, c128,
b, r, by,
)
// 你也可以使用 Println 做到
fmt.Printf("%q\n", s)
}
期望输出两行:0 0 0 0 0 0 0 (0+0i) (0+0i) false 0 0 与 ""。值得注意的细节:
- 整数有 5 种不同位宽(
int的位宽取决于平台,其余固定为 8/16/32/64 位),浮点有 2 种精度,复数有 2 种精度——它们全部共享零值0; rune(Unicode 码点)和byte(uint8的别名)本质上是数字类型,所以打印出0;- 字符串单独用
%q打印,因为空字符串在普通输出中不可见。
这套"全类型 + 零值"的组合拳,正是为 09-go-type-system 章节埋下的伏笔。
练习 07 与 08:多重声明的两种写法
当变量变多时,逐行 var 会显得啰嗦,Go 提供了两种批量声明方式。
07-multiple:分组声明 var (...)
题目要求用"多重变量声明语句"声明 active bool 和 delta int 并打印。参考答案 solution/main.go:
package main
import "fmt"
func main() {
var (
active bool
delta int
)
fmt.Println(active, delta)
}
期望输出 false 0。这种 var ( ... ) 分组写法在声明多个变量、且类型各不相同时最清晰。配套示例 05-多重声明/01-多重声明/main.go 将其扩展到了四种类型:
var (
speed int
heat float64
off bool
brand string
)
08-multiple-2:平行声明并初始化
题目要求在同一行声明两个字符串变量,且"只使用一次类型",然后打印。参考答案 solution/main.go:
var firstName, lastName string = "", ""
fmt.Printf("%q %q\n", firstName, lastName)
这里 var a, b string 是"平行声明"(parallel declaration):多个同名类型变量写在一行,类型只出现一次。加上 = "", "" 则同时完成了初始化。期望输出 "" ""。
配套示例 05-多重声明/02-平行声明/main.go 特别指出,var speed, velocity int 完全等价于分组写法:
// 这等同于
// var (
// speed int
// velocity int
// )
// 或者:
// var speed int
// var velocity int
var speed, velocity int
三种写法编译结果一致,选择依据是可读性:类型多样用分组,类型相同用平行声明。
练习 09:未使用的变量与空白标识符
Go 编译器有一条其他语言没有的严格规则:在块作用域(函数内)声明的变量必须被使用,否则程序无法编译。练习 09 要求声明一个名为 isLiquid 的 bool 变量,但不要打印它——直接用空白标识符丢弃。参考答案 solution/main.go:
package main
func main() {
var isLiquid bool
_ = isLiquid
}
_(下划线)就是空白标识符(blank identifier),它像一个"垃圾桶":把 isLiquid 赋值给它,编译器就认为变量被"使用"过了,不再报 declared but not used 错误。配套示例 04-未使用的变量和空白标识符/02-空白标识符/main.go 中的注释点明了意图:"让我们把变量赋值给空白标识符,这样,Go 编译器就不会发脾气了"。
对应的自测题 03-unused-variables 精确总结了规则边界:
- 块作用域(函数内)声明但未使用 → 程序无法编译;
- 包作用域声明但未使用 → 正常运行(这正是练习 10 的主题);
- 防止未使用变量错误的方法 → 使用空白标识符。
练习 10:包变量——作用域的第一次体验
题目要求把变量声明在"包作用域",即移到 func main() 之外、package main 之下。参考答案 solution/main.go:
package main
var isLiquid bool
func main() {
}
与练习 09 形成鲜明对比:这里的 isLiquid 完全没有被使用,但程序可以正常编译运行(没有任何输出)。因为"未使用变量"规则只约束块作用域内的局部变量,包级变量不受此限制。这也是初学者第一次直观感受"作用域(scope)"的存在——变量声明的位置决定了它的可见范围与生命周期。更深一层的作用域知识(块作用域、嵌套作用域、包作用域)会在 03-packages-and-scopes 章节系统讲解。
练习 11:做错事的人——先使用后声明
最后一个练习故意"做错事":先 fmt.Println 打印变量,再声明它。桩代码 main.go 中保留了注释掉的错误写法:
func main() {
// 去掉下面代码的注释来查看报错
// fmt.Println(age)
// var age int
}
一旦取消注释,编译器会立即报错:变量 age 在使用时尚未声明。配套示例 01-声明语法/03-声明顺序/main.go 用同样的模式验证了这一点。自测题 02-declaration 给出了明确的结论:"在使用一个变量之前,你必须先声明它"。
顺带一提,Go 的"先声明后使用"规则有个例外——函数声明可以在使用之后出现,但变量的规则是严格的。这一练习的价值在于:亲手触发一次编译错误,记住它的报错信息,以后写代码时就能凭直觉避开。
自测:四组选择题巩固知识点
仓库为每个主题都准备了选择题,建议完成练习后自测。以下四组与"声明"主题直接相关:
变量是什么(01-what)
- 变量"住"在内存中;
- 使用变量名是为了以后能够访问它;
- 改变变量值需要通过变量名;
- 声明之后不能改变变量类型——Go 是静态类型语言。
声明语句与标识符(02-declaration)
- 正确的声明语句是
var width int; - 使用变量前必须先声明;
- Go 是强类型语言;
- 合法变量名是
four。
未使用变量(03-unused-variables)
- 块作用域未使用变量 → 程序无法编译;
- 包作用域未使用变量 → 正常工作;
- 用空白标识符规避错误。
零值(04-zero-values)
- 数字类型零值是
0; bool零值是false;string零值是"";- 指针零值是
nil。
延伸学习:声明之后的下一步
完成这 11 个练习后,可以在仓库中继续按以下路线深入(均在 translation/chinese/06-变量/02-声明 与 06-变量 目录下):
- 理论巩固:重读 01-声明语法(语法、命名规则、声明顺序)、02-声明示例、06-例子 中的完整示例;
- 同章进阶:03-short-declaration(
:=短声明)、04-assignment(赋值与交换)、05-type-conversion(类型转换),随后可挑战 06-project-greeter 项目; - 前置铺垫:如果对作用域概念陌生,可先回到 03-packages-and-scopes 的"作用域"小节;练习 10 的包变量正是那里讲解内容的最小演示;
- 运行体验:本仓库每个目录都是独立可编译的小程序,任选练习目录执行
go run .,即可观察题目输出或复现编译错误——"先犯错、再修正"正是这套练习的设计初衷。
变量声明是 Go 旅程的第一块基石:语法只有 var 名字 类型 一条主线,但围绕它的零值、命名规则、多重声明、空白标识符与作用域,共同构成了后续所有章节(短声明、赋值、类型系统、函数参数与返回值)的地基。把这份清单的 11 个练习逐一亲手跑完,你会发现自己已经能毫无障碍地读懂任何 Go 程序的开头几行代码了。