Learngo 变量声明热身练习全解:11 个入门练习吃透 Go 变量声明、零值与作用域

原创2026-10-02 10:29:311,767 阅读
文章标签:示例工程教程

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 程序的开头几行代码了。

登录后查看全文
learngo