[Go-001] Hello World

201 阅读7分钟

1 下载与安装

chao@localhost ~ % go version
go version go1.22.2 darwin/arm64
  • 终端输入 go env -w GOPROXY=https://goproxy.cn,direct 设置代理,并通过 go env | grep GOPROXY 验证是否设置成功
chao@localhost ~ % go env -w GOPROXY=https://goproxy.cn,direct
chao@localhost ~ % go env | grep GOPROXY
GOPROXY='https://goproxy.cn,direct'

2 下载 Goland

3 Hello World

  • 打开 goland 新建项目 go-primer-code,并新建 main.go 文件,得到如下目录结构
- go-primier-code
	- go.mod
	- main.go
  • main.go 文件中输入如下代码,通过 IDE 执行,可获得 hello world 的输出
package main

import "fmt"

func main() {
    fmt.Println("hello world")
}
  • 终端通过 go build / go run 命令执行

go build 用于编译 Go 语言程序。执行go build命令时,它会编译当前目录下的所有 Go 源文件,并生成可执行文件(或库文件),默认情况下,可执行文件的名称将与当前目录的名称相同。例如,如果当前目录名为myproject,则go build将生成名为myproject的可执行文件。

go run用于直接运行 Go 语言程序而无需显式编译成可执行文件。它会在后台执行与go build相同的编译操作,然后将生成的可执行文件直接运行。这在开发和测试时非常方便,因为可以通过一条命令运行程序而不必手动进行编译和执行两个步骤。

chao@U-NX7XHL40-2303 go-primer-code % go build -o main .    
chao@U-NX7XHL40-2303 go-primer-code % ./main 
hello world
go run main.go

4 Go 简介

4.1 包(package)

Go 语言中的包(package)是代码的组织单元,提供了模块化和可重用性。每个 Go 源文件都需要声明所属的包,以便其他文件可以引用和使用其中的代码。

main 包与自定义包

main包是Go语言中的特殊包,用于包含主程序的入口函数和组织可执行程序的代码。当创建一个程序入口时,必须使用main作为包名,且在main包中,必须包含一个特殊的main函数,作为程序的入口函数。其他自定义包可以被主程序或其他包引用和使用,用于提供功能模块或库的封装和复用。

main包可直接位于项目根目录下,而对于其他自定义包(非main包),鼓励按照约定将每个包存放在以包名命名的目录下。即除 main 包外,每个包应该有自己的目录,目录名与包名相同。在该目录下可以包含多个.go源代码文件,这些文件都属于同一个包。

通过 import 导入包

通过 import 声明导入需要的包,例如标准包 fmt 中包含了格式化输入输出的函数,导入后可通过 packagename.funcname 的方式调用相关函数。 如果缺少了必要的包或导入了多余的包都会导致程序无法通过编译,该规定避免了程序在开发过程中引入未使用的包。

示例

如果有一个名为utils的自定义包,那么它的文件在项目中的目录结构可以是这样的:

- project
	- go.mod
  - main.go
  - utils
    - calculator.go

在上面的例子中,utils包的文件存放在一个名为utils的目录中,而main.go属于main包,位于项目的根目录下。如下述代码块所示,在 main 包中可以这样调用 utils 包中的函数,从而实现复用。

package main

import (
	"fmt"
	"go-primer-code/utils"
)

func main() {
	res := utils.Add(2, 3)
	fmt.Println(res)
}

4.2 模块(module)

依赖与版本管理 Go Modules

关于 Go 包管理机制的发展过程请参考:Go 包管理变迁过程

Go 的包管理和版本控制是通过 Go Modules 来实现的。Go Modules 是在 Go 1.11 版本中引入的一种用于包管理和版本控制的机制。它通过在项目的根目录中添加一个名为 go.mod 的文件来管理依赖关系和版本。使用 Go Modules 进行包管理可以帮助开发者在不同的项目中使用不同的依赖包版本,而不会相互干扰。同时,Go Modules 还能够自动解决依赖关系,根据项目的需要下载和管理依赖包。

使用 Go Modules 进行包管理的基本步骤:

  1. 在项目的根目录中初始化 Go Modules,可以通过在终端中运行go mod init 命令来完成。这会创建一个go.mod 文件,用来记录项目的依赖关系和版本信息。
  2. 在 go.mod 文件中添加需要的依赖包。可以使用 go get命令来下载和安装依赖包,例如 go get github.com/example/package
  3. 如果需要指定依赖包的版本,可以在 go.mod 文件中使用require语句来指定特定的版本。例如 require github.com/example/package v1.2.3
  4. 如果需要更新依赖包的版本,可以使用 go get -u 命令来更新所有的依赖包,或者使用 go get github.com/example/package@latest 来更新特定的依赖包。

除了包管理外,Go Modules 还提供了一些工具和命令来进行版本控制,例如:

  • go mod tidy: 该命令会检查项目的依赖关系,并删除未使用的依赖包。
  • go mod download: 该命令会下载项目的所有依赖包到本地缓存中。这可以用来离线使用依赖包。
  • go mod vendor: 该命令会将所有的依赖包复制到项目的 vendor 目录中,以便开发者可以在不使用Go Modules的情况下继续使用。

示例

使用 Goland 新建一个项目后会自动生成 go.mod 文件,如果使用 vscode 等编辑器则需要自行初始化。在项目根目录下执行如下命令:

chao@U-NX7XHL40-2303 go-primer-code % go mod init go-primer-code
go: creating new go.mod: module go-primer-code

4.3 语言风格

Go 在代码格式上采取了很强硬的态度。gofmt 工具会把代码格式化为标准格式。这样做的好处是减少了争议,便于做多种自动源码转换。很多文本编辑器都可以配置为保存文件时自动执行gofmt,这样源代码总会被恰当格式化。

关于 Go 的语言风格后续会在实践中逐步介绍,一般遵循如下约定:

  • 变量类型后置
  • 花括号不换行
  • Go语言不需要添加分号
  • if、for 后的条件语句不需要括号
  • 通过 import 导入的包,必须被使用
  • 声明的变量或常量必须使用
  • ......

4.4 基本结构

下面的程序展示了一个 Go 程序的推荐结构。这种结构并没有被强制要求,编译器也不关心 main 函数在前还是变量在前,但使用统一的结构能够在从上至下阅读 Go 代码时有更好的体验。

此外,Go 并 没有先声明后使用 这样的严格要求,但遵循这样的书写习惯有助于提升代码可读性。

// 申明所属的包
package main

// 导入依赖的包
import "fmt"

// 定义常量
const str string = "Hello World"

// 定义常量
var v int  = 5

// 自定义类型
type T struct{}

// init 函数
func init() {

}

// main 函数
func main() {
    var a int
    Func1()
    // ..
    fmt.Println(a)
}

// 方法
func (t T) Method1() {

}

// 函数
func Func1() {

}

4.5 执行顺序

  • 按顺序导入所有被 main 包引用的其他包,然后在每个包中执行如下流程。
  • 如果该包又导入了其他的包,则从第一步开始递归执行,但是每个包只会被导入一次。
  • 在每个包中初始化常量和变量,如果该包中含有 init 函数的话,则调用该函数。
  • 在完成这一切之后,main 包也进行常量、变量的初始化以及 init 函数的调用,最后调用 main 函数开始执行程序。

23 届秋招海能达面试

  • 问题:init 函数的执行顺序
  • 回答:
    • 同一个源文件的 init 函数执行顺序与其定义顺序一致,从上到下;
    • 同一个包中不同文件的 init 函数的执行顺序按照文件名的字典序;
    • 不同的包不存在依赖关系的话,按照 main 包中 import 的顺序调用;
    • 不同的包存在依赖关系的话,按照依赖关系决定 init 函数的执行顺序。

init() 函数之间的关系可能会使代码变得脆弱和容易出错,因此在编码时避免依赖 init() 函数的执行顺序。