第一个 Go 程序:解剖到每一行¶
这一章把上一章跑通的 hello world 拆开,一行一行讲——每行做什么、 不这么写会报什么错。讲完你会得到两样能一直用的东西: 一是「一个 Go 程序的最小骨架」长什么样;二是 Go 的命名规矩, 特别是「大写字母开头 = 别的包也能用」这条,后面每一章都会撞上它。
1. 主线例子:六行程序从头走到尾¶
原书用的例子是这个程序(为讲结构,我们照原书抄来逐行走)1:
package main
import (
"fmt"
)
/* 这是多行注释 */
// 这是单行注释
func main() {
name := "Jitendra"
fmt.Printf("Hello %s\n", name)
}
现在一步一步走,看编译器和运行时各做了什么:
第 1 行 package main —— 告诉编译器:这个文件属于名叫 main 的包。
每个可运行的 Go 程序都必须有一个包叫 main,它是程序的入口包2。
Go 区分大小写,所以包名写成 Package 或 Main 都不行——名字得小写。
如果入口包不叫 main,编译器会直接报错,程序根本不生成2。
第 2–4 行 import ("fmt") —— 引入别人写好的代码包。
fmt 是管格式化输入输出的标准包,scan、printf 这些打印/读取函数都住在里面。
被引入的包要用双引号包起来,因为 import 语句里写的是字符串3。
引入之后,fmt 就成了这个包里所有函数的前缀:想用它家的打印函数,
名字必须写成 fmt.Printf,不能光写 Printf3。
第 6–7 行注释 —— /* ... */ 包住多行,// 到行尾,两种写法。
注释是写给人看的,编译器直接跳过4。
第 9 行 func main() —— 声明一个叫 main 的函数。
func 是 Go 的关键字,表示「接下来是一个函数」。
main 包的 main 函数是整个程序唯一的入口:执行从它的第一行开始,
它 return(返回)的时候整个程序就退出。别的函数都可以自己起名字,
但只有 main 是特殊的,而且必须有——少了它,链接器会报「找不到入口」5。
第 10 行 name := "Jitendra" —— 声明一个变量。
:= 是 Go 的「声明并赋值」记号:左边是新变量的名字,右边是初值。
Go 是静态类型语言,但这个类型不需要你手写——编译器看右边的值是文本串,
就自动把 name 定成字符串类型6。这叫类型推断。变量和类型下一章细讲。
第 11 行 fmt.Printf("Hello %s\n", name) —— 终于干正事了。
Printf 是 fmt 包的格式化打印函数;%s 是个「占位洞」,
意思是在这里塞进一个字符串,塞进来的就是后面第二个参数 name;
\n 是换行符。运行后终端输出1:
图说:从文件到输出的一整条链是——编译器找到 main 包 → 编译成一个临时可执行文件 → 操作系统运行它 → 从 main 函数第一行顺序执行到 return → 进程退出5。
2. 25 个关键字:先认脸,不求记¶
书里把 Go 的全部关键字列了出来——一共 25 个7。
关键字是语言预留的词,你不能拿它们当变量名、函数名(拿 default 当变量名会直接编译错8)。
现在不用背。这 25 个词会在后面各章一个个正式登场,这里先混个脸熟, 按「第一次大概在哪章用」分组:
| 组 | 关键字 | 什么时候正式学 |
|---|---|---|
| 程序的骨架 | package、import、func | 本章 |
| 起名字、定类型 | var、const、type、struct、interface、map、chan | 03、10、12、13、14、14、15 章 |
| 条件分支 | if、else、switch、case、default、fallthrough | 05 章 |
| 循环与跳转 | for、range、break、continue、goto、return | 05 章 |
| 并发与收尾 | go、select、defer | 15、16、06 章 |
三个现在就想多说一句的:
goto:跳到程序里某个带标签的地方继续执行。 书里演示了它,但整个软件行业对它有共识——能用别的写法就别用它, 因为读代码的人很难跟上跳转。知道它存在即可9;fallthrough:switch 里的专用词,作用是「这个 case 执行完, 不按规矩停下,接着掉进下一个 case 继续执行」。 它很反直觉(别的语言里没有对应物),第 5 章细讲10;go:开一个 goroutine 的关键字,一个词就把并发启动了——第 15 章的主角11。
3. 标识符:起名字的规矩,和那条「大写」铁律¶
标识符(identifier)就是你给东西起的名字——变量、函数、常量、类型、包名、标签,都靠它认12。
合法的标识符由 Unicode(给全世界字符统一编号的国际标准)字母、数字、下划线组成, 第一个字符必须是字母或下划线。 书里给的正反例子照抄过来8:
合法 不合法
_ = 12 12 = "hello" // 不能以数字开头
Version = 4.0 count$ = 23 // 不能有 $ 这类特殊符号
name = "Ansh" default = 5.0 // 不能占用关键字
_12 = "hello"
接下来是全书最重要的一条命名规矩,单独成段:
首字母大写的标识符叫「导出的」(exported),别的包能用;首字母小写的叫「非导出的」, 只有本包内部能用。 书里强调:导出的标识符应该声明在包块里,因为它们要对外可见12。
这条规矩的直接影响,你在本章已经见过了:fmt.Printf 的 P 是大写的——
正因为大写,你 import 了 fmt 之后才能用它13。如果哪天你自己写包,
想让别人用你的函数,函数名就必须大写开头;不想给外面用,就小写。
没有 public/private 关键字,一个字母的大小写就把可见性说完了。
这是 Go 极简风格的典型样本。
4. 空白标识符 _:一个「只进不出」的垃圾桶¶
25 个关键字之外的另一个特殊名字是 _(下划线),它叫空白标识符(blank identifier)。
它的作用:该有个变量名的地方,你故意不要这个名字,用它占位。 它什么值都能接收,但接收之后谁都读不出来——像一封信投进没有回信地址的信箱12。
什么时候真用得上?书里此刻只给了定义,真正的使用场景在第 6 章(函数多返回值、
只要其中一个时)和第 8 章(引入一个包只为执行它的初始化、却不用它的名字)。
先记住形状:_ = 某值 的意思是「这个值我要扔掉」。
判断(我们的,不是书里的): 空白标识符是 Go「不允许有没用的东西」这条洁癖的产物—— 编译器不许存在声明了却没用的变量,于是语言给你一个官方的「扔」法。 它在别处强迫你显式处理每一个值,同时又给你一条体面的逃生通道。 如果错,会错在: 有些人把它当成可以随便用的垃圾桶, 把本该处理的错误也
_掉——那是被社区明确反对的用法,逃掉的错误会变成暗坑。
5. 作者的判断与证据¶
这一章几乎全是语言事实(规矩就是规矩,编译器 enforcing),没有推测。 唯一带态度的是两处:
| 书里的说法 | 性质 |
|---|---|
| 「goto 可以用来转移控制」并给了示例 | 中性陈述;书里没提行业对 goto 的保留态度,这是我们补的 |
| 「所有导出的名字都应大写开头」 | 语言规则,事实 |
6. 边界与局限¶
- 书里讲 Visual Studio Code 的 gopls 语言服务器安装提示,是编辑器操作的截图步骤, 会随 IDE 版本变化,此处不展开;
- 25 个关键字的清单以原书为准——今天 Go 的关键字仍是 25 个,这一条没过时7;
- 「包的导入」本章只讲了用双引号引入标准库,包之间怎么组织、怎么写自己的包, 要到第 8 章才讲;本章看到 import 只会用就行。
7. 可带走的¶
- 最小骨架 = package main + import + func main,三样缺一不可; 入口包名必须是 main、入口函数必须是 main;
- Go 区分大小写,包名、函数名的大小写写错就是另一个名字;
- fmt 是格式化输入输出的包,用里面的函数要加
fmt.前缀; %s是字符串占位洞,Printf("Hello %s", name)把 name 塞进洞里;:=声明变量并让编译器猜类型,初值是文本就是字符串类型;- Go 只有 25 个关键字,不许拿来起名;后面各章会陆续用掉它们;
- 首字母大写 = 导出的(对外可见),小写 = 包内私有——没有 public/private 关键字;
_是空白标识符,接得住任何值但读不出来,用来丢弃不想要的东西;- goto 存在但别用;fallthrough 是 switch 专用的「继续掉下一个 case」;
- 执行链:编译器找到 main 包 → 临时可执行文件 → 从 main 函数第一行跑到最后。
8. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 程序的基本组成 | Beginning with Go | text/10-fm-learning-go-programming.txt:43(搜「basic structure」) |
| 示例程序全文 | Beginning with Go | text/10-fm-learning-go-programming.txt:55(搜「package main」) |
| package main 与大小写敏感 | Beginning with Go | text/10-fm-learning-go-programming.txt:85(搜「case-sensitive」) |
| fmt 包、双引号、前缀 | Beginning with Go | text/10-fm-learning-go-programming.txt:88(搜「double-quotes」) |
| 两种注释 | Beginning with Go | text/10-fm-learning-go-programming.txt:67(搜「multiline comment」) |
| func main 是入口 | Beginning with Go | text/10-fm-learning-go-programming.txt:94(搜「Execution begins」) |
| 编译器从 main 包出发 | Beginning with Go | text/10-fm-learning-go-programming.txt:123(搜「temporary executable」) |
:= 与类型推断 |
Beginning with Go | text/10-fm-learning-go-programming.txt:117(搜「identify the type」) |
| 导出名字大写 | Beginning with Go | text/10-fm-learning-go-programming.txt:120(搜「capital letter」) |
| 25 个关键字 | Beginning with Go | text/10-fm-learning-go-programming.txt:129(搜「25 keywords」) |
| goto 演示 | Beginning with Go | text/10-fm-learning-go-programming.txt:420(搜「gotolabel」) |
| fallthrough | Beginning with Go | text/10-fm-learning-go-programming.txt:390(搜「fallthrough」) |
| go 关键字开 goroutine | Beginning with Go | text/10-fm-learning-go-programming.txt:405(搜「use of go keyword」) |
| 标识符定义与命名规则 | Beginning with Go | text/10-fm-learning-go-programming.txt:465(搜「fusion of Unicode」) |
| 合法/非法标识符例子 | Beginning with Go | text/10-fm-learning-go-programming.txt:474(搜「blank identifier」) |
-
出处:「Beginning with Go」第 55–82 段(
text/10-fm-learning-go-programming.txt:55,搜「package main」)。示例程序与输出均照原书。 ↩↩ -
出处:「Beginning with Go」第 85、97、104 段(
text/10-fm-learning-go-programming.txt:85,搜「case-sensitive」;:101,搜「Non-main package error」;:108,搜「Undeclared function error」)。包名/函数名不合标准时编译器报的两种错。 ↩↩ -
出处:「Beginning with Go」第 88 段(
text/10-fm-learning-go-programming.txt:88,搜「double-quotes」)。原文:fmt 要用双引号写成字符串,并成为该包所有函数的前缀。 ↩↩ -
出处:「Beginning with Go」第 91 段(
text/10-fm-learning-go-programming.txt:91,搜「ignored by the compiler」)。 ↩ -
出处:「Beginning with Go」第 94、123 段(
text/10-fm-learning-go-programming.txt:94,搜「Execution begins」;text/10-fm-learning-go-programming.txt:123,搜「temporary executable」)。 ↩↩ -
出处:「Beginning with Go」第 117 段(
text/10-fm-learning-go-programming.txt:117,搜「identify the type」)。原文:静态类型语言的变量运行时不能换类型;:=由编译器按值识别类型。 ↩ -
出处:「Beginning with Go」第 129 段(
text/10-fm-learning-go-programming.txt:129,搜「25 keywords」)。 ↩↩ -
出处:「Beginning with Go」第 465、489–495 段(
text/10-fm-learning-go-programming.txt:489,搜「should not start with digits」)。合法/非法标识符的对照例子。 ↩↩ -
出处:「Beginning with Go」第 180、420–426 段(
text/10-fm-learning-go-programming.txt:420,搜「gotolabel」)。 ↩ -
出处:「Beginning with Go」第 198、390–396 段(
text/10-fm-learning-go-programming.txt:390,搜「fallthrough」)。原文:把控制转给下一个 case,「不管条件如何」。 ↩ -
出处:「Beginning with Go」第 204、405 段(
text/10-fm-learning-go-programming.txt:405,搜「use of go keyword」)。 ↩ -
出处:「Beginning with Go」第 465 段(
text/10-fm-learning-go-programming.txt:465,搜「fusion of Unicode」)。标识符定义、大小写敏感、空白标识符、导出/非导出标识符均在本段。 ↩↩↩ -
出处:「Beginning with Go」第 120 段(
text/10-fm-learning-go-programming.txt:120,搜「capital letter」)。原文:所有导出的名字都应大写开头——Printf 的 P 即因此对外可用。 ↩