☰
第一个Go程序:从环境配置到编译运行的完整入门指南
2026/10/9 13:10:50 网站建设 项目流程

1. 先搞清楚这一节要带你走到哪

第一次接触 Go 的人,通常会遇到三种情况:要么是在项目里看到同事用 Go 写后端服务,性能好部署又方便;要么是刷招聘网站时发现 Go 工程师的岗位和薪资都在涨;要么是研究云原生、区块链、微服务这些热门方向时,发现生态里到处都有 Go 的身影。不管你是哪一种,第一章第五节“第一个 go 程序”要做的就一件事:把你从“听说过 Go”推到“亲手把 Go 跑起来”。

这一节的目标非常明确:在你自己的电脑上装好可用的 Go 开发环境,手写一份最简单的 Go 源码,然后用 Go 自带的工具链把它编译并运行。整个过程不涉及复杂语法、不涉及框架选型、不涉及并发模型,纯粹是打地基。它适合真正的零基础新手,也适合已经会 Java、Python、C++ 但想快速切入 Go 语法的开发者。

我见过太多人栽在第一步。代码写得一点问题没有,愣是卡在环境上:PATH 没配对、GOPATH 概念搞混、安装包版本选错,一晚上就这么没了。所以这一节不打算只给你一串命令复制粘贴,而是把环境、编码、运行、排错四个环节完整拆开,每一段都会解释背后的原因。你看完不仅能跑通,还能理解 Go 工具链到底在做什么。

2. 动手前先把 Go 环境配好

2.1 Windows 上最省事的 zip 包安装法

Windows 用户安装 Go 有两个主流途径:一个是官方提供的 MSI 安装包,双击一路 Next 就行;另一个是官方提供的 zip 压缩包,解压后手动配环境变量。两种方式各有优势,MSI 会自动帮你写注册表、配 PATH,对新手最友好;zip 包则更干净,解压到哪就是哪,卸载时直接删文件夹,而且很多 CI 脚本和自动化部署流程里也习惯用 zip 包方式。这一节就以 zip 包方式为例,因为它能让你更清楚地看到 Go 环境到底由哪几部分组成。

具体操作分三步。第一步,去 Go 官方下载页面选择 Windows 对应的 zip 包。这里有个常见坑:绝大多数现代电脑是 64 位系统,要下载 amd64 版本;只有极老的机器才可能用 386 版本,arm64 版本是给 Windows on ARM 设备用的,普通用户不需要。第二步,把 zip 包解压到你指定的安装位置,比如 C:\go。解压后你会看到 bin、src、pkg 等目录,bin 目录里就有 go.exe。第三步,把 C:\go\bin 加到系统 PATH 环境变量里,这一步决定了你在命令行敲 "go" 的时候,系统能不能找到它。

添加 PATH 的具体路径是:右键“此电脑”进入属性,选高级系统设置,点环境变量,在系统变量里找到 Path,点编辑,新建一条填 C:\go\bin。这里要留意,如果之前装过其他版本的 Go,PATH 里可能会残留 C:\Program Files\Go\bin 之类路径,最好清理掉,否则命令行里敲的到底是哪个 go,你自己都说不清楚。配好之后,一定要打开一个新的命令行窗口——旧窗口不会刷新环境变量——然后敲 go version,看到版本信息就说明安装成功了。

2.2 Ubuntu Server 上安装 Go 的两种思路

Linux 用户,尤其是 Ubuntu Server 上做服务端开发的同学,安装 Go 的方式和 Windows 不完全一样。最常见的手段是用 apt 安装:sudo apt update && sudo apt install golang-go,一条命令搞定。但我个人更推荐官方 tar.gz 包的安装方式,原因是 apt 仓库里的 Go 版本往往滞后,官方已经发布新版本了,apt 源里可能还停留在旧版本。版本滞后不是小问题,Go 的很多新特性,比如性能优化、工具链增强,都依赖较新版本。写第一个程序无所谓,后面写真实项目就会遇到“语法明明没错但编译器不认识”的尴尬。

tar.gz 包方式的操作分三步。第一步,下载对应架构的安装包,x86_64 的服务器选 amd64 版本。第二步,解压到 /usr/local:sudo tar -C /usr/local -xzf go1.22.x.linux-amd64.tar.gz,这样 Go 就安装到了 /usr/local/go 目录下。第三步,把 /usr/local/go/bin 加进 PATH,常见做法是编辑 ~/.profile 或 /etc/profile.d/go.sh,追加一行 export PATH=$PATH:/usr/local/go/bin。设置完成后执行 source ~/.profile 或者重新登录终端,再敲 go version 验证。

这里有个容易被忽视的细节:如果你之前用 apt 装过旧版 Go,卸载时不能只删 /usr/local/go,因为 apt 会把可执行文件放在 /usr/bin/go 里。先执行 sudo apt remove golang-go 清理干净,再装官方包,否则 which go 会同时搜到 /usr/bin/go 和 /usr/local/go/bin/go 两个结果,导致版本混乱。我排过不少环境问题,最后发现都是这类“残留版本”引起的。

2.3 环境算不算配好,用三个命令验证

环境配得对不对,不只看 go version 有没有输出。我一般会做三个检查,分别对应三个不同环节。第一,go version 能输出版本号,说明主程序可用,PATH 基本没问题。第二,随便 cd 到任意目录,敲 go env GOPATH,这个命令会打印当前 GOPATH 的值,Windows 上默认是 C:\Users\你的用户名\go,Linux 上是 /home/你的用户名/go。第三,临时建一个目录,写一个最简单的 go 文件,用 go run 跑通,这一步才是真正验证“工具链能不能完成一次完整编译”。

为什么要单独查 GOPATH?因为很多新手在早期资料里看到过 GOPATH 的用法,会手动去设一个 GOPATH 指向自己的代码目录,结果后面用 go mod 时又和 Modules 冲突,出现一堆莫名其妙的报错。实际上,从 Go 1.11 开始 Modules 就是官方推荐的依赖管理方式,从 Go 1.16 开始 Modules 默认启用,现在开发时你几乎不需要手动设置 GOPATH。它默认指向用户目录下的 go 文件夹,里面存的是依赖包缓存,不是项目源码。这个认知如果一开始就建立好,后面会少走很多弯路。

3. 从零写出第一份 Go 代码

3.1 项目目录应该怎么建

很多教程一上来就让你写 hello.go,却不说文件放哪。这看起来是小事,实际上关系到你对 Go 项目结构的第一印象。Go Modules 时代,项目结构已经非常自由:你可以随意新建一个文件夹,在里面初始化一个 module,然后正常写代码,不需要像早期 GOPATH 时代那样必须把项目放在 $GOPATH/src 下面。我建议你新建一个专门的学习目录,比如 D:\golearn(Windows)或者 ~/golearn(Linux),在里面再建一个 hello 子目录,专门放这个第一个程序。

为什么要单独建子目录?因为一个 go module 的根目录通常对应一个独立项目,这个 hello 项目虽然只包含一个文件,但保持“一个项目一个目录”的习惯,后面你写第二个、第三个程序时就不会弄混。在 hello 目录里打开终端,执行 go mod init hello,你会看到目录里多了一个 go.mod 文件。这个文件内容很简单,第一行是 module hello,表示这个模块的名字叫 hello,下面一般跟着一行 go 版本号。go.mod 是 Go Modules 的核心,记录了这个项目的依赖关系,虽然目前没有任何外部依赖,但提前建立这个文件能让 go run、go build 等命令正常工作。

有个细节值得提一下:go mod init 后面的名字不一定要和目录名一致,它只是模块标识,你可以叫它 hello、test、myfirstapp,甚至可以写成类似 github.com/yourname/hello 的完整路径。我写学习项目时喜欢用简短名字,写真实项目才用带路径的全名。这个习惯不影响编译,但能让 go.mod 看起来更规范。

3.2 逐行编写 hello.go

在 hello 目录下新建一个文件,命名为 hello.go,注意后缀必须是 .go。用任何文本编辑器都可以,新手我推荐直接用 VS Code,装上官方 Go 扩展后写代码会有语法高亮、自动补全和保存时自动格式化,这对建立良好的代码风格帮助很大。下面是我写过无数次的第一份 Go 程序:

package main import "fmt" func main() { fmt.Println("Hello, Go!") }

就这么五行,我们逐行来看。第一行 package main 声明这个文件属于 main 包。Go 程序由一个或多个包组成,包是代码组织的基本单位。这里的关键点是:只有 package main 的包才能被编译成可执行文件。如果你写的是 package foo,后面执行 go build,编译器不会生成可以直接运行的程序,只会生成一个可以被其他包引用的库。这个区别一定要在第一天就记住,因为它解释了为什么所有 Go 可执行程序的入口文件都长这样。

第二行 import "fmt" 是导入标准库里的 fmt 包。fmt 是 format 的缩写,这个包提供了格式化输入输出的能力,最常用的就是 Println 系列函数。你可以把 import 理解成“引入工具箱”,fmt 就是语言自带的一个常用工具箱。Go 对导入语句有一个强制要求:导入的包必须被使用,否则编译直接报错。这和其他很多语言不一样,它的目的是杜绝无用的依赖,防止代码库臃肿。我第一次在 Go 里写了个多余的 import,编译器直接拒绝通过,当时觉得烦,后来习惯了反而认为这个约束很合理。

第三行 func main() 声明了一个名为 main 的函数。在 Go 里,函数用 func 关键字声明,后面跟函数名、参数列表、返回值列表。main 函数是整个程序的入口,运行时从这一行开始执行。这里有个硬性规则:每个可执行的 main 包必须包含一个 main 函数,而且一个程序只能有一个入口。当然,如果你写的是被其他项目引用的库包,就不需要 main 函数,因为库不是直接运行的程序。

第四行和第五行是一对大括号,里面写着函数体,也就是 main 函数实际要执行的代码。fmt.Println("Hello, Go!") 做的事情很简单:向标准输出打印一行文本,打印完自动换行。这里的字符串用双引号包起来,Go 里单引号和双引号有严格区分,单引号表示单个字符,双引号表示字符串。这个细节和不少语言不一样,后续写复杂代码时容易踩坑,不过第一个程序还涉及不到。

3.3 三个必须理解的核心概念

写完了 hello.go,你的任务还没结束。如果只是复制粘贴然后运行成功,过三天你大概率什么都记不住。我建议你花五分钟把下面三个概念在脑子里过一遍,它们是你之后看懂一切 Go 代码的基础。

第一个是“包(package)”。前面说了,Go 源码文件的第一行永远是 package 声明。你可以把包理解成一个命名空间或者文件夹,它把相关代码组织在一起。标准库里包名通常和目录名一致,比如 fmt 包的源码就在 $GOROOT/src/fmt 目录下。你自己写项目时,一个包对应一个目录是约定俗成的规则,这个规则保证了代码可读性:看到 import "myapp/utils",你就知道这个文件要用到 myapp 项目里 utils 目录下的代码。

第二个是“导入(import)”。import 后面跟的是包的路径,不是包名。这句话很关键。比如你导入 fmt,编译器会去标准库目录下找 fmt 这个包;如果你导入的是自己写的子包,路径就是从 module 根目录开始的相对路径。理解了这一点,后面遇到 import 报错时,先检查路径对不对,而不是怀疑包本身的代码有错。

第三个是“入口函数(main)”。C 语言有 main,Java 有 main 方法,Go 也有 main 函数,它是程序启动时第一个执行的代码位置。Go 对 main 函数有一个额外要求:它不能有参数,也不能有返回值。这和 C、Python 都不一样,后者通常能通过命令行参数把外部数据传进来。Go 里获取命令行参数要用 os.Args,这是后话,但你现在知道标准 main 函数长什么样,就足够了。

4. 运行、编译与验证

4.1 go run 和 go build 到底差在哪

写完代码,接下来就是运行。Go 提供了两个高频命令:go run 和 go build。很多新手搞不清它们的区别,其实一句话就能概括:go run 是编译并立刻运行,跑完不留痕迹;go build 是编译成可执行文件,你自己去运行。

具体来说,在 hello 目录里执行 go run hello.go,Go 工具链会把源码编译成临时二进制文件,放在临时目录里,然后启动它。你会在终端看到 Hello, Go! 的输出,但当前目录里什么额外文件都没有生成。这个过程对学习尤其合适,因为你每改一次代码,重新 go run 一次,就能立刻看到结果,省去管理中间产物的麻烦。缺点也很明显:每次都要重新编译,大项目会拖慢启动速度,而且临时二进制不会留在你手上,没法分发给别人用。

go build 则完全相反。执行 go build 后,当前目录会出现一个可执行文件,文件名默认是模块名。拿 hello 项目来说,Windows 下你会看到 hello.exe,Linux 下是 hello。这个文件就是编译产物,可以双击运行,可以从命令行执行,也可以复制到别的机器上直接跑,前提是目标系统和当前系统兼容。go build 默认不带任何优化参数,产物体积可能偏大,但没关系,第一个程序我们关注的是流程跑通。

我用一张表把两者的区别列出来,方便你之后快速查阅:

对比项go rungo build
是否生成文件不生成,使用临时目录生成可执行文件
使用场景开发调试、快速验证发布部署、分发产物
命令格式go run hello.gogo build 或 go build hello.go
运行方式自动编译并执行手动执行生成的二进制
适合人群学习阶段、修改频率高项目成型、需要交付

4.2 完整走一遍编译流程

我把整个流程从头到尾走一遍,你照着操作就行。假设你已经在 Windows 上配好了环境,并且建好了 D:\golearn\hello 目录,里面目前为空。

第一步,打开命令行,cd 到 D:\golearn\hello。第二步,执行 go mod init hello,如果提示 go.mod already exists,说明你之前已经初始化过了,没关系。第三步,把上文那段 hello.go 代码保存到目录里,然后执行 go run hello.go。你会看到终端输出 Hello, Go!。到这一步,你的第一个 Go 程序已经成功运行了。

接下来试一下构建。执行 go build,然后执行 dir(Windows)或者 ls -l(Linux),你会发现目录里多了一个 hello.exe 或 hello 文件。再执行 .\hello.exe(Windows)或 ./hello(Linux),会看到相同输出。注意一个小差异:go run 后面跟的是源文件名 hello.go,而 go build 后面什么都不跟,它默认编译当前目录下的整个 module。当然,go build hello.go 这种写法也合法,它会生成以源文件名为基础的可执行文件,但更常见的做法是直接敲 go build。

这一步还可能遇到一个新手常见困惑:go build 执行后没有任何输出提示,直接静默结束了。这不是出错了,Go 的设计风格就是“没有消息就是好消息”,只有编译失败时才会打印错误信息。程序员的第一个直觉往往是“是不是少了点什么”,其实什么都没少,去看看目录里多出来的那个文件,就是最好的成功证明。

4.3 从 hello world 延伸出去

跑通第一个程序之后,我建议你做两件事,让这次学习的效果最大化。

第一件事,改动输出文本。把 "Hello, Go!" 改成你自己的名字,比如 fmt.Println("Hello, 张三!"),重新 go run。这看起来无聊,实际上是在训练你建立“改代码—重新编译—看结果”的反馈循环。这个循环是你后面写任何程序的核心工作方式,越早习惯越好。

第二件事,多打印一行。在 main 函数里再加一行 fmt.Println("I am learning Go."),注意新的一行要写在 main 函数的大括号里面,缩进用 tab 键或者 VS Code 默认配置都行。Go 官方推荐 tab 缩进,gofmt 工具会自动帮你规范化格式。保存后 go run,你会看到两行输出。这时候你就能直观感受到“函数体里的代码按顺序执行”这个最基本的执行模型。

我见过不少学员学完这一节后,急着去看并发、看 goroutine、看接口,结果基础概念没吃透,后面越学越吃力。第一个程序的意义不在于它能干什么,而在于它把你和工具链之间的桥梁搭起来了。后面无论你写多复杂的项目,归根结底都是 package、import、func 这三个基本要素的组合。

5. 新手必踩的坑:排错实录

5.1 命令行里找不到 go

这是出现频率最高的问题,症状是敲 go version 或 go run 时,终端提示“go 不是内部或外部命令”(Windows)或者 command not found(Linux)。原因只有一个:系统在 PATH 环境变量里找不到 go 这个可执行文件。

Windows 下的排查步骤:先确认安装目录里的 bin 下确实有 go.exe;再打开环境变量编辑器,确认 Path 里有 C:\go\bin 这一类条目;最后确认敲命令的那个命令行窗口是新开的。我想强调一下“新开窗口”这件事:Windows 的环境变量是在进程启动时读取的,已经开着的窗口不会自动刷新,所以改完 PATH 后必须把命令行窗口全部关掉再重开。这个操作我至少帮别人排查过十几次,每次都发现是没重开窗口。

Linux 下的排查步骤:先 which go 看系统能不能找到你期望的那个 go,如果输出 /usr/local/go/bin/go,说明 PATH 没问题;如果没有输出,检查 /etc/profile 或 ~/.profile 里的 export 语句是否写对了。尤其是 export PATH=$PATH:/usr/local/go/bin 这句,$PATH 不能丢,丢了会把原有路径全部冲掉,然后你会发现 ls、vi 这些基本命令都找不到了。这个坑我踩过一次,教训深刻,所以每次配置 PATH 都先看一眼原来那行是什么样子。

5.2 go run 报语法错误或 undefined

如果你抄了代码,却出现 "expected ';', found 'EOF'" 或者 "undefined: fmt" 之类的提示,九成是代码本身的问题。最常见的三种情况:第一,文件编码不是 UTF-8,Windows 记事本默认编码存出来的文件可能是 ANSI,Go 编译器只认 UTF-8,解决办法是用 VS Code 或 Notepad++ 把文件转成 UTF-8 编码再保存;第二,大括号没配对,比如 main 函数少写了一个 },编译器会在文件末尾报 EOF 相关错误;第三,import 写的是 "FMT" 或者 fmt 后面多了空格,Go 对大小写和空格敏感,必须严格小写。

我建议新手遇到编译错误时,先看错误信息里给出的文件位置和行号。Go 编译器报错还算友好,通常能直接指出第一处出错的位置。先把第一处错误改掉,重新编译,很多时候后面的错误会跟着消失,因为编译器在第一个错误之后可能会出现连锁误报。不要想着一次性把所有错误都看完,逐次修复才是效率最高的方式。

5.3 运行了但没看到任何输出

有一种情况比较隐蔽:go run 执行了,没有报错,但终端里空荡荡的,什么都没看到。这个问题通常出在你改了输出内容上。比如你把代码改成了 fmt.Print("Hello"),而不是 Println——Print 和 Println 的区别是,Print 打印完不会自动换行。如果在一个交互式终端里运行,看起来就像什么都没输出,其实内容是有输出的,只是没有换行让输出“浮”出来了。

还有一种可能是你的程序里用了 fmt.Printf 但格式字符串写错了,比如想打印字符串却用了数字占位符,导致输出不符合预期。最直接的排查方法是在代码里加一行 fmt.Println("checkpoint") 作为标记,看看这行能不能输出。只要这行能输出,说明程序执行本身没问题,问题出在目标输出语句上。这种插桩排查法虽然原始,但在没有调试器的场景下非常管用,我在实际项目里也经常用。

5.4 版本混乱:莫名跑的是旧版

前面提到过 apt 安装和官方 tar.gz 包共存的问题。如果你在 Linux 上执行 go version 看到版本号偏旧,又确认自己装的是新版,执行 which go 看看路径。如果输出的是 /usr/bin/go,说明系统优先找到的是旧版。处理办法是把 /usr/local/go/bin 放到 PATH 前面,或者彻底卸载 apt 版本。

Windows 上也会出现类似问题:早期装过 MSI 版 Go,后来手动解压了 zip 包,两个版本并存,PATH 里两个路径都在,系统按顺序找到先匹配的那个。建议只保留一个版本的 Go,把另一个的 PATH 条目删掉。版本统一这件事越早做越好,因为当你开始用 go mod 管理依赖时,依赖包版本和 Go 工具链版本是有兼容关系的,新版本能跑的代码,老版本可能就跑不起来。

6. 我的几点真实体会

写到这里,“第一个 go 程序”这个主题已经讲透了。最后分享几点我在带新人过程中积累的体会,算是不在教科书上的内容。

第一,学 Go 的第一个程序,千万别急着抄复杂代码。我见过有同学第一天就想写一个 HTTP 服务器,代码抄过来了,也能跑,但出了错完全不知道去哪查,因为连最基本的运行原理都没建立。从 hello world 开始,按部就班地把每个概念吃透,反而是最快的一条路。

第二,尽早养成查看官方文档的习惯。Go 的官方文档做得很好,标准库每个函数都有详细说明和示例。比如 fmt.Println 这个函数,你想知道它多参数怎么处理、返回什么值,直接查官方文档比翻博客、问工具都要靠谱。因为在学习阶段,信息源的正误直接影响你建立的知识框架对不对。

第三,也是我最想强调的一点:第一个程序跑通之后,不妨顺手试一下 Go 的自动格式化工具 gofmt。VS Code 的 Go 扩展默认在保存时自动执行 gofmt,它会帮你把缩进、括号、换行都调整成标准风格。养成“写完代码就格式化”的习惯,对 Go 这种对代码风格有统一要求的语言来说,越早建立,后面看别人的代码、被别人看你的代码时都会舒服很多。

这个 hello world 项目现在看起来简单,但它后面可以延伸的方向非常多:你可以在它基础上加一个命令行参数,让程序打印出你输入的名字;可以加一个循环,把一句话打印十遍;甚至可以加一个 HTTP 接口,让浏览器访问后返回一段文字。每个方向都会用到现在打下的这些基础。Go 这条路我走了很久,回头看,这一步走得稳不稳,直接决定了后面走得顺不顺。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询