导航菜单

第一个 Go 程序

Hello World 程序

让我们从一个最经典的 Go 程序开始:

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

运行这段代码:

# 方法一:使用 go run 直接运行
go run main.go

# 输出:
# Hello, World!

这短短 5 行代码已经包含了 Go 程序的几个核心要素。让我们逐行解析。

程序结构详解

第 1 行:package main

package(包)

Go 程序由包(package)组成。每个 Go 文件必须属于一个包,包名声明在文件的第一行。package main 是一个特殊的包名,表示这是一个可执行程序。Go 编译器会将 package main 中的 main() 函数作为程序的入口点。

package main  // 声明当前文件属于 main 包

关于包的要点:

  • 每个目录下的所有 .go 文件必须属于同一个包
  • package main 是可执行程序的入口包,必须有 main() 函数
  • 其他包名(如 package utils)是库包,不能直接运行
  • 包名通常使用小写字母,不使用下划线或驼峰命名

第 2-3 行:import "fmt"

import "fmt"  // 导入标准库中的 fmt 包
import(导入)

import 语句用于导入其他包,使其功能可以在当前文件中使用。fmt 是 Go 标准库中提供格式化 I/O 功能的包,包含 PrintlnPrintfSprintf 等常用函数。

导入多个包的方式:

// 方式一:逐行导入
import "fmt"
import "os"
import "strings"

// 方式二:分组导入(推荐)
import (
    "fmt"
    "os"
    "strings"
)

// 方式三:带别名的导入
import (
    "fmt"
    f "fmt"           // 给 fmt 起别名 f
    _ "image/png"     // 只执行 init 函数,不直接使用
    . "math"          // 不需要包名前缀,直接使用 Sin、Cos 等
)

第 4-6 行:func main()

func main() {              // 定义 main 函数,程序入口
    fmt.Println("Hello, World!")  // 调用 fmt 包的 Println 函数
}
main 函数

main() 函数是 Go 可执行程序的入口点。它必须定义在 package main 中,不接受任何参数,也没有返回值。当程序运行时,main() 函数是第一个被执行的代码。

main 函数的特点:

  • 无参数、无返回值
  • 必须在 package main 中定义
  • 一个可执行程序只能有一个 main() 函数
  • main() 函数执行完毕后,程序退出

fmt 包常用函数

fmt 是 Go 中最常用的标准库包之一,提供了丰富的格式化输出功能。

Println、Print、Printf

package main

import "fmt"

func main() {
    // Println:打印内容后换行,多个参数之间自动添加空格
    fmt.Println("Hello", "World", 2024)
    // 输出:Hello World 2024

    // Print:打印内容,不自动换行
    fmt.Print("Hello ")
    fmt.Print("World\n")
    // 输出:Hello World

    // Printf:格式化输出(类似 C 语言的 printf)
    name := "Go"
    version := 1.23
    fmt.Printf("Language: %s, Version: %.2f\n", name, version)
    // 输出:Language: Go, Version: 1.23

    // Sprint / Sprintf:返回字符串而不是打印
    s := fmt.Sprintf("Name: %s, Age: %d", "Alice", 30)
    fmt.Println(s)
    // 输出:Name: Alice, Age: 30
}

常用格式化动词

动词说明示例输出
%s字符串fmt.Printf("%s", "hello")hello
%d十进制整数fmt.Printf("%d", 42)42
%f浮点数fmt.Printf("%f", 3.14)3.140000
%.2f保留 2 位小数fmt.Printf("%.2f", 3.14)3.14
%t布尔值fmt.Printf("%t", true)true
%v默认格式fmt.Printf("%v", []int{1, 2})[1 2]
%T类型fmt.Printf("%T", 42)int
%p指针地址fmt.Printf("%p", &x)0x...
%q带引号的字符串fmt.Printf("%q", "hi")"hi"
\n换行fmt.Printf("a\nb")a(换行)b
\t制表符fmt.Printf("a\tb")a b

go run 与 go build

go run:编译并运行

go run 命令会在临时目录中编译你的程序,然后立即执行,执行完毕后删除临时文件。

go run main.go

特点:

  • 适合开发调试阶段快速测试
  • 不会生成可执行文件
  • 编译和运行一步完成
  • 可以同时指定多个文件:go run file1.go file2.go
  • 也可以运行整个目录:go run .

go build:编译生成可执行文件

go build 命令编译你的程序,生成一个可执行文件(但不运行它)。

# 基本编译(在当前目录生成可执行文件)
go build

# 指定输出文件名
go build -o myapp

# 指定要编译的文件
go build main.go

# 编译并优化(减小文件大小)
go build -ldflags="-s -w"

# 交叉编译
GOOS=linux GOARCH=amd64 go build -o myapp-linux

特点:

  • 生成可执行文件,方便分发和部署
  • 默认输出文件名与目录名相同(或使用 -o 指定)
  • 可以通过 -ldflags 参数注入版本信息、减小文件大小
  • 适合生产环境部署

对比总结

特性go rungo build
是否编译
是否运行
是否生成可执行文件否(临时编译后删除)
适用场景开发调试生产部署
可指定输出文件名不适用-o 参数
速度每次都要编译编译一次,多次运行

gofmt 与代码格式化

gofmt

gofmt 是 Go 语言自带的代码格式化工具,它会自动将 Go 代码格式化为统一的风格。go fmt 命令是 gofmt 的包装,提供了更简洁的用法。

为什么需要格式化?

Go 团队认为,代码风格的一致性比个人偏好更重要。通过强制统一的代码风格:

  • 减少团队中关于代码格式的争论
  • 提高代码可读性
  • 让 code review 更专注于逻辑而非格式

使用 gofmt

# 格式化单个文件(直接输出到终端)
gofmt main.go

# 格式化并覆盖原文件
gofmt -w main.go

# 格式化整个项目
gofmt -w .

# 显示哪些文件需要格式化(但不实际格式化)
gofmt -l .

使用 go fmt

# 格式化当前包下的所有 .go 文件
go fmt

# 格式化指定文件
go fmt main.go

# 格式化所有子包
go fmt ./...

Go 代码风格要点

以下是一些 Go 代码的风格约定,gofmt 会自动帮你处理:

package main

import (
    "fmt"
    "math"
)

// 使用 Tab 缩进(不是空格)
// 左大括号不换行
func add(a, b int) int {
    return a + b
}

// 多行导入使用分组形式
// 导入的包按顺序排列:标准库、第三方库、本地包
import (
    "fmt"                       // 标准库
    "github.com/gin-gonic/gin"  // 第三方库
    "myproject/internal/utils"  // 本地包
)

// 变量命名使用驼峰命名法
var userName string
var maxCount int
var isReady bool

// 导出标识符(首字母大写)可以被外部包访问
// 未导出标识符(首字母小写)只能在包内访问

// 错误处理不使用异常,而是返回 error
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

// 字符串使用双引号
s := "hello world"

// 字符使用单引号
c := 'A'

一个更完整的示例

让我们写一个稍微复杂一点的 Go 程序,综合运用前面学到的知识:

package main

import (
    "fmt"
    "os"
    "strings"
)

// 程序版本信息
const version = "1.0.0"

func main() {
    // 获取命令行参数
    args := os.Args

    // 显示帮助信息
    if len(args) < 2 {
        printUsage()
        return
    }

    // 根据参数执行不同操作
    switch strings.ToLower(args[1]) {
    case "hello":
        name := "World"
        if len(args) > 2 {
            name = args[2]
        }
        fmt.Printf("Hello, %s! 👋\n", name)
    case "version":
        fmt.Printf("App version: %s\n", version)
    default:
        fmt.Printf("Unknown command: %s\n", args[1])
        printUsage()
    }
}

func printUsage() {
    fmt.Println("Usage:")
    fmt.Println("  myapp hello [name]  - Say hello")
    fmt.Println("  myapp version       - Show version")
}

运行示例:

# 编译
go build -o myapp

# 运行
./myapp
# Usage:
#   myapp hello [name]  - Say hello
#   myapp version       - Show version

./myapp hello
# Hello, World! 👋

./myapp hello Go
# Hello, Go! 👋

./myapp version
# App version: 1.0.0

注释

Go 支持两种注释方式:

package main

import "fmt"

// 这是单行注释
// 注释不会被执行

/*
   这是多行注释(块注释)
   可以跨越多行
   通常用于函数或包的文档说明
*/

/*
 Package main 演示了 Go 中注释的用法。

 在 Go 中,包级别的注释(紧跟 package 声明之前)
 会被 godoc 工具提取为包文档。
*/
func main() {
    // 行内注释:解释代码的功能
    fmt.Println("Comments in Go") // 行尾注释
}
// Add 返回两个整数的和。
func Add(a, b int) int {
    return a + b
}

// User 表示一个系统用户。
type User struct {
    Name string
    Age  int
}

练习题

练习 1

编写一个 Go 程序,使用 fmt.Printf 打印你的名字、年龄和爱好,使用不同的格式化动词。

参考答案
package main

import "fmt"

func main() {
    name := "张三"
    age := 25
    hobby := "编程"

    // 使用不同的格式化动词
    fmt.Printf("姓名: %s\n", name)
    fmt.Printf("年龄: %d\n", age)
    fmt.Printf("爱好: %s\n", hobby)

    // 使用 Printf 一次打印
    fmt.Printf("\n我是 %s,今年 %d 岁,爱好是 %s\n", name, age, hobby)

    // 使用 %T 查看类型
    fmt.Printf("\nname 的类型: %T\n", name)
    fmt.Printf("age 的类型: %T\n", age)
}

运行结果:

姓名: 张三
年龄: 25
爱好: 编程

我是 张三,今年 25 岁,爱好是 编程。

name 的类型: string
age 的类型: int

练习 2

以下代码有什么问题?请找出并修正:

package main

import "fmt"
import "os"

func main() {
    fmt.Println(os.Args)
}
参考答案

这段代码本身没有语法错误,但有两个风格问题:

  1. 导入语句应该使用分组形式
import (
    "fmt"
    "os"
)
  1. 修正后的完整代码:
package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Println(os.Args)
}

gofmt 工具会自动将逐行导入的语句格式化为分组形式。

练习 3

请解释 go rungo build 的区别。在什么情况下你会使用 go run?在什么情况下使用 go build

参考答案
方面go rungo build
编译在临时目录编译在当前目录编译
运行编译后自动运行只编译不运行
输出文件不生成可执行文件生成可执行文件

使用场景:

  • 使用 go run:开发调试阶段,快速验证代码逻辑,不需要保留编译产物。
  • 使用 go build:需要生成可执行文件进行部署或分发时;需要通过 -ldflags 注入版本信息时;需要交叉编译生成其他平台的二进制文件时。

练习 4

编写一个 Go 程序,实现一个简易的计算器:接收两个整数和一个运算符(+、-、*、/),输出运算结果。使用 fmt.Printf 进行格式化输出。

参考答案
package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    // 检查参数数量
    if len(os.Args) != 4 {
        fmt.Println("Usage: calculator <num1> <operator> <num2>")
        fmt.Println("Example: calculator 10 + 3")
        return
    }

    // 解析参数
    num1, err1 := strconv.Atoi(os.Args[1])
    num2, err2 := strconv.Atoi(os.Args[3])
    operator := os.Args[2]

    if err1 != nil || err2 != nil {
        fmt.Println("Error: 请输入有效的整数")
        return
    }

    // 执行运算
    switch operator {
    case "+":
        fmt.Printf("%d + %d = %d\n", num1, num2, num1+num2)
    case "-":
        fmt.Printf("%d - %d = %d\n", num1, num2, num1-num2)
    case "*":
        fmt.Printf("%d * %d = %d\n", num1, num2, num1*num2)
    case "/":
        if num2 == 0 {
            fmt.Println("Error: 除数不能为零")
            return
        }
        fmt.Printf("%d / %d = %d\n", num1, num2, num1/num2)
    default:
        fmt.Printf("Error: 不支持的运算符 '%s'\n", operator)
    }
}

运行示例:

go run calculator.go 10 + 3
# 10 + 3 = 13

go run calculator.go 20 / 4
# 20 / 4 = 5

go run calculator.go 7 * 8
# 7 * 8 = 56

练习 5

以下 Go 代码能编译通过吗?为什么?

package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Println("Hello")
}
参考答案

不能编译通过。 编译器会报类似以下错误:

imported and not used: "math"

Go 语言有一个严格的规定:导入的包必须被使用。 math 包虽然被导入了,但在代码中没有任何地方引用它。这是一个编译错误,不是警告。

修复方法: 删除未使用的导入:

package main

import "fmt"

func main() {
    fmt.Println("Hello")
}

搜索