跳转至

第一个 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:

  Hello Jitendra

图说:从文件到输出的一整条链是——编译器找到 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. 可带走的

  1. 最小骨架 = package main + import + func main,三样缺一不可; 入口包名必须是 main、入口函数必须是 main;
  2. Go 区分大小写,包名、函数名的大小写写错就是另一个名字;
  3. fmt 是格式化输入输出的包,用里面的函数要加 fmt. 前缀;
  4. %s 是字符串占位洞,Printf("Hello %s", name) 把 name 塞进洞里;
  5. := 声明变量并让编译器猜类型,初值是文本就是字符串类型;
  6. Go 只有 25 个关键字,不许拿来起名;后面各章会陆续用掉它们;
  7. 首字母大写 = 导出的(对外可见),小写 = 包内私有——没有 public/private 关键字;
  8. _ 是空白标识符,接得住任何值但读不出来,用来丢弃不想要的东西;
  9. goto 存在但别用;fallthrough 是 switch 专用的「继续掉下一个 case」;
  10. 执行链:编译器找到 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」)

  1. 出处:「Beginning with Go」第 55–82 段(text/10-fm-learning-go-programming.txt:55,搜「package main」)。示例程序与输出均照原书。 ↩↩

  2. 出处:「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」)。包名/函数名不合标准时编译器报的两种错。 ↩↩

  3. 出处:「Beginning with Go」第 88 段(text/10-fm-learning-go-programming.txt:88,搜「double-quotes」)。原文:fmt 要用双引号写成字符串,并成为该包所有函数的前缀。 ↩↩

  4. 出处:「Beginning with Go」第 91 段(text/10-fm-learning-go-programming.txt:91,搜「ignored by the compiler」)。 ↩

  5. 出处:「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」)。 ↩↩

  6. 出处:「Beginning with Go」第 117 段(text/10-fm-learning-go-programming.txt:117,搜「identify the type」)。原文:静态类型语言的变量运行时不能换类型;:= 由编译器按值识别类型。 ↩

  7. 出处:「Beginning with Go」第 129 段(text/10-fm-learning-go-programming.txt:129,搜「25 keywords」)。 ↩↩

  8. 出处:「Beginning with Go」第 465、489–495 段(text/10-fm-learning-go-programming.txt:489,搜「should not start with digits」)。合法/非法标识符的对照例子。 ↩↩

  9. 出处:「Beginning with Go」第 180、420–426 段(text/10-fm-learning-go-programming.txt:420,搜「gotolabel」)。 ↩

  10. 出处:「Beginning with Go」第 198、390–396 段(text/10-fm-learning-go-programming.txt:390,搜「fallthrough」)。原文:把控制转给下一个 case,「不管条件如何」。 ↩

  11. 出处:「Beginning with Go」第 204、405 段(text/10-fm-learning-go-programming.txt:405,搜「use of go keyword」)。 ↩

  12. 出处:「Beginning with Go」第 465 段(text/10-fm-learning-go-programming.txt:465,搜「fusion of Unicode」)。标识符定义、大小写敏感、空白标识符、导出/非导出标识符均在本段。 ↩↩↩

  13. 出处:「Beginning with Go」第 120 段(text/10-fm-learning-go-programming.txt:120,搜「capital letter」)。原文:所有导出的名字都应大写开头——Printf 的 P 即因此对外可用。 ↩