01.01 程序、源代码与一次运行
本章示例给出学习时的预期结果;请结合自己的环境逐步核对。
本章面向第一次接触 Go 的学习者。先把“源码保存在哪里、命令在哪里执行、输出怎样产生”讲清楚,再认识最小程序中的语法。使用虚构 Arena 任务平台作为练习,不需要数据库、容器或第三方框架。
一、从一句话认识程序
从一句话到程序结果
目标很简单:让屏幕显示“欢迎来到 Go”。
这件事看似只是输出一句文字,背后却包含一条完整路径:人写下指令,计算机理解指令,再把结果显示出来。
程序是让计算机完成某项任务的一组明确指令。例如,“在屏幕上显示欢迎语”就是一个任务;告诉计算机应显示什么、何时显示的文字,就是程序的内容。
用人能阅读和修改的文字写出的程序,叫作源代码。Go 源代码通常写在以 .go 结尾的文件中。本章把文件命名为 main.go,便于整理。文件名只是约定;入口由后文的 main 包与 main 函数决定。
package main
import "fmt"
func main() {
fmt.Println("欢迎来到 Go")
}现在不必逐字理解它。只需先建立直觉:这段源代码表达的意图是“显示一行欢迎语”。
源代码本身不是屏幕结果。它只是保存在文件中的文字。计算机需要经过两个动作:
- 编译:把 Go 源代码翻译成计算机能够执行的形式。
- 运行:执行翻译后的程序,并产生可观察的结果。
在正确保存和运行的条件下,预期终端显示:
欢迎来到 Go
因此可以把第一次 Go 编程理解为一条流水线:
想法 → 源代码 → 编译 → 运行 → 屏幕结果
后续学习的重点,就是逐渐掌握这条路径中每一步该在哪里做、该输入什么,以及出错时应从哪一步检查。
二、文件、保存与当前目录
文件与目录:给代码安排位置
要让计算机运行程序,代码不能只停留在编辑器的临时页面里;它必须先被保存为磁盘上的一个文件。
- 文件:保存具体内容的单位,例如一篇文档、一张图片,或一份 Go 代码。
- 目录:用于整理文件和其他目录的“文件夹”。
- 文件后缀:文件名最后的部分,用来说明文件类型。Go 源代码通常以
.go结尾。
可以先为练习建立这样的目录结构:
go-practice/
└── arena-hello/
└── main.go这里,go-practice 是总练习目录,arena-hello 是本次“显示一句话”练习所在的目录,main.go 则是一份具体的 Go 源代码文件。main.go 是本章约定的文件名,入口并不由文件名决定;.go 帮助 Go 工具识别源码文件;文件内容本身仍是文本。
在编辑器中写下代码后,应使用“保存”操作,将它写入 arena-hello/main.go。只有保存成功,终端才能在对应目录中找到这个文件,并让 Go 工具读取、编译和运行它。
注意区分两个地方:
- 编辑器中的
main.go:写入 Go 源代码,例如package main和fmt.Println(...)。 - 终端:输入操作命令,例如进入目录、运行程序的命令。
终端命令不是 Go 代码,不能写进 main.go;Go 代码也不能直接当作终端命令输入。前者负责告诉操作系统“做什么”,后者负责告诉 Go 程序“运行时应做什么”。
当前目录与相对路径
终端并不会自动“看见”电脑中的所有文件。它在每一刻都有一个当前目录:也就是终端此刻所在的文件夹位置。可以把它理解为你站在一栋楼里的某个房间;当你说“拿桌上的书”时,别人必须先知道你在哪个房间。
例如,练习目录可以这样组织:
go-practice/
└── arena-hello/
└── main.go如果终端当前位于 go-practice/arena-hello,那么输入 go run main.go 时,main.go 是一个相对路径:它没有写出完整位置,而是表示“从当前目录开始,寻找名为 main.go 的文件”。
此时可以把命令理解为:
go run 当前目录中的 main.go但如果终端仍位于 go-practice,直接输入 go run main.go 就会失败,因为这里没有 main.go;文件实际在子目录 arena-hello 中。你可以先进入该目录,再运行:
cd arena-hello
go run main.go其中,cd arena-hello 是终端命令,作用是把当前目录切换到 arena-hello;go run main.go 才是请求 Go 编译并运行源代码的命令。
也可以在 go-practice 中使用相对路径:
go run arena-hello/main.go这里的 arena-hello/main.go 表示“从当前目录进入 arena-hello,再找到 main.go”。
因此,运行成功通常依赖两个条件:第一,编辑器已经将代码保存为 main.go;第二,终端所在目录正确,或命令中写出了正确的相对路径。编辑器负责写入和保存源代码,终端负责输入命令;不要把 go run main.go 写进 Go 文件,也不要把 package main 写进终端。
三、先确认 Go 工具可用
先确认工具:从官方下载到版本检查
Go 程序需要由 Go 工具链编译、运行和管理。请从官方入口下载适合自己操作系统的安装包:https://go.dev/dl/。不要机械照抄某个“最新版本”号;下载页会随时间更新,应选择与你的 Windows、macOS 或 Linux 环境匹配的正式版本,并按官方安装说明完成安装。
安装的边界很明确:此时你安装的是 go 命令及其编译器、标准库和项目管理工具,而不是某个具体项目。项目文件、练习目录和第三方依赖将在后续步骤中单独建立。
安装完成后,重新打开一个终端窗口,输入:
go version
这条命令不用于升级 Go,也不用于创建项目;它只做一件关键的事:确认当前终端能否在系统路径中找到 Go 工具链,并显示已安装的版本与平台信息。
如果安装正常,你会看到包含 go version、版本号和操作系统/架构的信息。课程示例以 Go 1.24 或更高版本 为预期范围;具体输出会因你的系统和安装版本而不同,无须与他人的终端逐字一致。
若终端提示找不到 go 命令,通常意味着安装未完成,或安装后的环境变量尚未生效。先关闭并重新打开终端;仍无法解决时,回到官方下载页的对应系统安装说明检查。
四、建立同一个练习项目并运行
下面的正式练习统一使用目录 arena-hello 和模块路径 example.com/arena-hello。前面的目录树用于说明相对位置;选定自己的个人练习位置后,在其中创建这个目录。
创建 Arena 练习目录
先在你自己容易找到的位置创建练习空间,例如“文档”“桌面”或专门的代码目录。不要在 Go 安装目录、系统目录或临时目录中练习;这些位置可能涉及权限问题,也不利于后续整理项目。
打开终端后,先确认当前所在位置。不同操作系统的终端提示符样式不同,但都表示“接下来命令将在哪个目录执行”。然后依次输入:
mkdir arena-hello
这条命令创建一个名为 arena-hello 的新目录。mkdir 是“创建目录”的命令;如果命令成功,通常不会输出提示,而是直接回到下一行等待输入。此时你仍在原来的位置,只是那里多了一个 arena-hello 文件夹。
接着输入:
cd arena-hello
cd 表示“切换目录”。执行成功后,终端的当前位置就进入了刚创建的 arena-hello。后续创建的源文件、模块配置和编译结果,都应属于这个目录,而不是它的上一级目录。
可以使用:
pwd
查看当前完整路径;Windows 命令提示符 cmd 中可使用不带参数的 cd 查看当前位置;PowerShell 使用 pwd。你应能看到路径末尾包含 arena-hello。这一步看似简单,却很重要:命令总是作用于“当前目录”,在错误位置执行初始化命令,往往会让项目文件散落到不该出现的地方。
最小模块为何存在
按本章的项目方式组织代码时,需要说明相关包所属的模块、模块的路径,以及依赖如何记录。这个身份说明由模块提供。
在练习目录中执行:
go mod init example.com/arena-hello
这条命令可以拆开理解:
go mod init:初始化一个新的 Go 模块。example.com/arena-hello:模块路径,即项目对外使用的身份名称。example.com是专门用于示例的域名形式;初学练习不需要真的拥有该网站。arena-hello表示这个模块对应当前的练习项目。
命令成功后,当前目录会生成 go.mod 文件。它通常包含类似信息:
module example.com/arena-hello
以及当前工具链采用的 Go 语言版本声明。这里的 module 行尤其重要:以后如果你在同一模块内创建包,或从其他代码导入本项目的包,Go 都以这个模块路径为基础判断其位置和身份。
模块路径不等于本地磁盘路径。你的目录可以位于任意个人位置,例如桌面、文档目录或专门的代码目录;而 example.com/arena-hello 是 Go 项目层面的逻辑名称。二者可以不同,但应保持模块路径稳定。
即使当前项目只有一个 main.go,初始化模块仍然是良好起点。它让 go run、依赖管理和后续包导入都有明确边界;当项目使用第三方包时,Go 还会据此维护依赖信息,例如生成 go.sum。
先把程序还原成可阅读的整体
在编辑器中新建并保存文件 main.go,写入下面这段完整源码:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}打开终端,先进入 main.go 所在的目录,再执行:
go run main.go
预期终端显示:
Hello, World!
这里有两份不同的内容:main.go 是你保存到磁盘上的源代码;终端中的 go run main.go 是让 Go 工具读取这份源码、编译并立刻启动程序的命令。程序运行后,fmt.Println("Hello, World!") 产生输出,文字才出现在终端中。
因此,“写代码”并不等于“已经运行”:必须先保存源码,再在正确目录执行命令。若终端提示找不到 main.go,通常应先检查当前所在目录;若输出与预期不同,则回到文件核对每一行是否已保存。
go run .、go run main.go 与 go build .
在含有 go.mod 与 main.go 的 arena-hello 目录,可以运行:
go run .点号表示当前目录的包。Go 检查并编译它,再运行生成的程序;生成的可执行内容由工具在临时位置管理,并不是跳过编译直接解释源码。
go run main.go 让工具按明确给出的 Go 文件进行处理。本章只有一个文件,两种写法都能演示相同欢迎输出;以后包中有多个源码文件时,文件列表方式不会自动补入所有未指定文件,应按项目边界选择命令。
go build . 用于编译当前包,不会接着执行它。对于本章的 main 包,通常会生成一个可执行文件;实际名称、后缀和运行方式与系统有关。先用 go run 完成本章练习即可。
模块初始化只需做一次。若 go.mod 已存在,先检查自己是否在正确目录,不要把重复初始化当作每次运行前必须执行的步骤。
先确认文件是否保存
在编辑器保存 main.go 后,查看当前练习目录,应该能找到 go.mod 和 main.go。macOS/Linux 可用 ls,PowerShell 可用 Get-ChildItem,Windows 命令提示符 cmd 可用 dir。
main.go 也可以使用另一个普通的 Go 源码文件名,例如 welcome.go;程序入口由 main 包和 main 函数决定。当前阶段保持统一名称,有助于减少路径错误。
五、逐行理解最小程序
package、import 与 fmt:代码从哪里来
先看程序开头的两部分:
package main
import "fmt"package 是“包”声明,用来给代码归类。可以把包理解为一组彼此相关的代码放在同一个名字下,便于组织、复用和查找。package main 表示:当前这份代码属于名为 main 的包。
这里的 main 不只是普通名称。对于要直接启动的 Go 程序,Go 规定它必须属于 main 包,并且后面还要有一个名为 main 的函数。满足这两个条件后,Go 才知道:“这是一个可以从这里开始运行的程序。”如果写成 package hello,代码仍可作为其他程序使用的包,但不能直接作为完整程序启动。
空行没有特殊计算意义,主要用于把不同职责的代码分开:上面声明“我属于哪个包”,下面声明“我要使用哪些现成工具”。
import 的意思是导入:请求使用其他包已经写好的代码。我们不必自己从零实现屏幕输出功能,因为 Go 标准库已经提供了大量可靠的工具。
"fmt" 是被导入包的路径。fmt 来自 Go 标准库,名称可联想为“格式化”;它提供格式化输入、输出等能力。后面写到的 fmt.Println,就是在使用这个包提供的输出功能。
双引号 " " 界定字符串字面量。本例只使用普通文本,不涉及转义写法。这里的 "fmt" 不是变量,也不是要在屏幕上打印的内容,而是告诉 Go:请找到并导入名为 fmt 的标准库包。
func main:程序从这一组指令开始
func main() {
fmt.Println("你好,Go!")
}函数可以理解为:为了完成一项工作而组织在一起的一组指令。例如,这里的工作是“向终端输出一句文字”。Go 用 func 表示“下面要定义一个函数”,后面的 main 是这个函数的名字。
func main 有特殊意义:在本例这种可直接启动的 Go 程序中,运行程序时,Go 会先完成程序初始化,再调用 main 包的 main 函数。本例需要关注的是函数体里的语句按顺序执行;初始化细节留待后续章节。普通函数如何接收数据、返回结果,以及代码怎样拆分为多个文件,会在后续学习;现在只需记住:main 是这次运行的起点。
逐个看符号:
func:声明一个函数。main:函数名;作为程序入口时必须写成这个名字。():圆括号表示函数的参数位置。这里括号为空,表示入口 main 函数不声明参数。{:函数体开始,后面写这项工作需要执行的指令。fmt.Println("你好,Go!"):函数体中的一条指令。}:函数体结束;程序执行到这里,main也就结束了。
fmt.Println 前面通常有缩进。缩进不会改变 Go 程序的执行含义,但它清楚地表明这行属于 main 的花括号内部。写完后可用 gofmt 或编辑器的格式化功能统一格式;运行程序本身不会自动改写源文件格式。
fmt.Println:把原样文本送到终端
程序中的这一行:
fmt.Println("你好,Go!")
会把一段文字显示到终端。它可以从左到右拆开理解:
fmt:前面通过import "fmt"请求使用的标准库包。它提供了格式化输入、输出等现成能力。.:点号表示“使用这个包里的某项功能”。因此,fmt.Println不是两个无关的单词,而是“调用fmt包中的Println”。Println:名称可理解为“打印一行”。它负责把内容输出到终端,并在末尾自动补上换行;所以下一次输出会从新的一行开始。(与):圆括号包住调用时交给Println的内容。这里的内容也可称为参数。"与":双引号界定字符串,本例中的普通文字构成要输出的文本。程序不会把其中的汉字当作命令,而是按文字输出。"你好,Go!":这就是实际送往终端的欢迎语。
例如改成:
fmt.Println("欢迎来到 Go 世界!")
再次运行后,预期终端显示:
欢迎来到 Go 世界!
注意,双引号不能漏掉;写成 fmt.Println(欢迎来到 Go 世界!) 时,Go 会把其中内容当作代码来理解,通常会报错。还可以连续调用:
fmt.Println("第一行")fmt.Println("第二行")
终端将分两行显示,因为每次 Println 都会自动换行。
六、根据现象定位错误
从现象回溯原因:三类错误与排查
第一次运行 Go 程序失败时,不要急着重写代码。先看现象,再判断错误属于哪一类。
运行环境错误:终端找不到文件、提示目录不对,或运行的仍是旧结果。常见原因是没有保存文件、终端没有进入
.go文件所在目录,或者命令写错。先保存,再用pwd(或查看当前路径)确认位置,用ls确认目录中确实有目标文件;随后执行go run 文件名.go。文件名可以不是main.go,但必须以.go结尾。编译错误:Go 在运行前检查源码,发现语法不符合规则就停止。例如漏掉文本末尾的双引号:
fmt.Println("你好)
又如漏写右圆括号)、右花括号}。编译器通常会报告文件名、行号和大致原因。优先打开对应行;若报错指向行看似正确,却找不到问题,再检查它的前一行是否遗漏了引号、括号或逗号。逻辑错误:程序能运行,却输出了不符合预期的内容。例如本想打印“你好”,实际写成了
fmt.Println("你号")。这时语法没有问题,要逐字比较源代码、期望输出与实际输出。
排查顺序应当由近到远:保存文件 → 确认目录和文件名 → 阅读第一条编译报错 → 检查引号、圆括号、花括号与 package main → 最后核对输出内容。一次只修复一个问题,然后重新运行;后续报错可能只是前一个错误引发的连锁反应。
包名问题与语法问题分别判断
package hello 本身可以是合法的包声明,但它不满足本章要直接运行 main 包的目标。遇到“不是 main 包”一类提示时,应检查运行目标和包名,不要据此得出其他包名都是语法错误的结论。
入口 main 函数不声明参数,也不声明返回值。命令行输入以及其他函数的参数会在后续课程介绍。
七、从预测到独立完成
练习前:用同一套流程检查作品
四道练习都遵循同一条工作链:修改源码 → 保存文件 → 在终端运行 → 对照输出 → 按验收条件检查。不要只看编辑器中的文字;程序是否正确,要以实际运行结果为准。
在编辑器中改代码
打开.go文件,只修改题目要求的位置。fmt.Println("...")中引号里的内容是要显示的文本;花括号{}、圆括号()和双引号"则是代码结构的一部分。保存文件
修改后执行保存操作。未保存时,终端运行的可能仍是旧版本文件,导致“明明改了,输出却没变”。确认终端当前目录正确
本章使用go run .时,应在练习包目录执行;使用明确文件路径时也可以从其他目录运行。可先查看当前目录内容,确认能看到目标.go文件,再运行:go run 文件名.go例如文件叫
main.go,则输入go run main.go。区分两个输入位置
- 编辑器:写和修改 Go 源代码。
- 终端:输入
go run main.go等命令,查看输出或错误信息。
不要把
go run main.go写进 Go 文件,也不要把fmt.Println(...)当作终端命令直接输入。按验收条件逐项核对
检查输出的文字、大小写、标点、行数和顺序;若出现错误,先读终端指出的文件位置,再回到对应代码附近修复。每次修复后都要重新保存并运行。
第一级:预测每一行输出
先不要运行程序。请只阅读源代码,预测终端会依次显示什么,并把结果逐行写下来。
package main
import "fmt"
func main() {
fmt.Println("你好,Go!")
fmt.Println("第一段程序正在运行。")
}请回答:程序运行后,屏幕上会出现哪两行文字?
提示:
fmt.Println(...)每执行一次,就会打印一次内容。- 双引号
"..."中的文字会按原样显示;双引号本身不会被打印出来。 Println末尾会自动换行,因此两个调用对应两行输出。- 不要把
package main、import "fmt"或func main()当作输出内容;它们是程序结构,不会直接显示在终端中。
参考输出为:
你好,Go!
第一段程序正在运行。为什么是这样?第一条 fmt.Println 读取到字符串 你好,Go!,打印后换到下一行;第二条 fmt.Println 再打印 第一段程序正在运行。。代码中的缩进、空行和双引号都不是输出文字的一部分。
验收条件:
- 输出恰好有两行;
- 第一行是
你好,Go!; - 第二行是
第一段程序正在运行。; - 没有双引号、额外提示文字、代码关键字或多余空行。
第二级:独立改写欢迎语
现在不要只修改引号中的几个字,而是把原来的一条欢迎打印语句,改写为两次独立的 fmt.Println 调用:
- 第一行显示欢迎语,例如:
欢迎来到 Go 编程! - 第二行显示开始提示,例如:
让我们开始吧!
先在编辑器中打开 main.go,找到 main 函数中的欢迎语句。删除原来那一条打印语句,再自行写出两条打印语句。完成后按保存,然后在终端执行:
go run .
最小提示
- 每次调用
fmt.Println会输出一行,并在末尾自动换行。 - 两条语句要分别写在两行;每条语句都需要完整的圆括号和双引号。
fmt.Println中的P必须大写。- 保存文件后再运行,否则终端执行的可能仍是旧版本代码。
参考思路
main 函数中应连续出现两次“调用 fmt.Println 打印静态文本”的结构。第一条的双引号内放欢迎语,第二条的双引号内放开始提示;不需要变量、循环或额外导入。
运行结果的结构应当是:
欢迎来到 Go 编程!
让我们开始吧!验收条件
go run .能成功执行,没有编译错误。- 终端恰好显示两行文本。
- 第一行是欢迎语,第二行是开始提示。
- 两行均由各自的一次
fmt.Println调用产生,而不是在同一对引号中手动插入换行。
第三级:制造并修复语法错误
这一题的目标不是“把程序弄坏”,而是学会区分三类问题:
- 编译错误:代码不符合 Go 语法,程序尚未开始运行。
- 运行环境错误:例如命令输入错误、目录不对、Go 未安装。
- 逻辑错误:程序能运行,但输出结果不符合预期。
先保留本章的完整程序骨架,下面仅展示需要修改的那一行:
fmt.Println("欢迎来到 Go")
然后只做一次改动,保存并运行。
删除结尾的一个双引号,使其类似于:
fmt.Println("欢迎来到 Go)先预测:终端不会打印欢迎语,而会报告与字符串或引号有关的编译错误。因为字符串必须由成对的双引号包围;少了右引号后,编译器无法判断文本在哪里结束。
恢复双引号后,再删除 main 函数体最后的右花括号
}。先预测:终端会报告与代码块结尾、文件结尾或缺少
}有关的编译错误。左花括号表示代码块开始,右花括号必须与之配对。
阅读报错时,重点查看两部分:错误类别或描述,以及 文件名:行号:列号。报错位置有时在真正遗漏字符的后一行;例如少写 } 时,编译器常常直到文件结束才发现代码块没有结束。
修复步骤固定为:根据报错定位附近代码 → 补回删除的字符 → 保存文件 → 再次运行。不要只改不保存,否则终端执行的仍是旧文件。
验收条件:
- 能预测删除双引号和删除右花括号都会导致编译错误;
- 能说出前者破坏字符串边界,后者破坏代码块配对;
- 能依据终端行号找到附近代码,补回字符并成功重新运行。
第四级:迁移到 Arena 欢迎信息
现在假设你正在为虚构的 Arena 任务与积分平台编写启动欢迎页。请独立创建并运行一个 Go 程序:不读取输入、不使用变量、不进行计算,只用静态文本输出四行信息。
内容清单:
- 欢迎信息:
欢迎来到 Arena! - 任务名称:
当前任务:完成第一课 - 起始积分:
起始积分:100 - 提交提示:
完成后请提交你的代码。
排版提示:
- 每项信息单独占一行,因此需要连续调用四次
fmt.Println。 - 程序仍需具备完整骨架:
package main、导入fmt、func main()。 - 中文标点、数字和感叹号都属于引号中的文本;不要遗漏双引号。
fmt.Println中的文本可以直接写中文,无须额外转换。
可先自行完成,再对照下面的参考结构理解:
main 包表示这是可执行程序;import "fmt" 让程序可以使用格式化输出工具;func main() 是运行入口。每条 fmt.Println("...") 都会打印引号内的静态文本,并在末尾自动换行。因此,四次调用会得到四行输出,调用顺序就是显示顺序。
逐项验收:
八、回顾与下一步
合上正文,尝试解释这条链路:在编辑器写源码,保存为正确位置的文件,在终端选择当前目录并调用 Go 工具,经过检查与编译,运行程序后观察输出。
你应能区分程序、源码、文件、目录、终端、工具链、包、模块和入口函数,并用本章例子说明它们的职责。暂时不需要解释结构体、接口、并发或数据库。
下一步:01.02 类型与数据表示。把欢迎语里的固定文字与积分逐步变成由程序保存和处理的数据。