1.1.3 第一个 Go 程序

1.1.3 第一个 Go 程序 #

本节将带您编写第一个完整的 Go 程序,从最简单的 “Hello World” 开始,逐步深入了解 Go 程序的结构、编译运行过程,以及基本的语法规则。

Hello World 程序 #

创建第一个程序 #

让我们从经典的 “Hello World” 程序开始:

package main

import "fmt"

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

程序结构分析 #

1. package 声明 #

package main
  • 每个 Go 文件都必须以 package 声明开始
  • package main 表示这是一个可执行程序的入口包
  • 其他包名通常与目录名相同,如 package utils

2. import 语句 #

import "fmt"
  • import 用于导入其他包
  • fmt 是 Go 标准库中的格式化包,提供输入输出功能
  • 多个导入可以使用括号分组:
import (
    "fmt"
    "os"
    "time"
)

3. main 函数 #

func main() {
    fmt.Println("Hello, World!")
}
  • main 函数是程序的入口点
  • 只有 package main 中的 main 函数会被执行
  • fmt.Println 用于输出文本并换行

运行程序 #

方法一:直接运行 #

# 创建文件
echo 'package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}' > hello.go

# 直接运行
go run hello.go

方法二:编译后运行 #

# 编译生成可执行文件
go build hello.go

# 运行可执行文件
./hello        # Linux/macOS
hello.exe      # Windows

方法三:使用模块 #

# 创建模块
mkdir hello-world
cd hello-world
go mod init hello-world

# 创建 main.go
cat > main.go << 'EOF'
package main

import "fmt"

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

# 运行
go run .

程序的基本元素 #

1. 注释 #

Go 语言支持两种注释方式:

package main

import "fmt"

// 这是单行注释
// 可以连续使用多行

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

func main() {
    fmt.Println("Hello, World!") // 行尾注释
}

2. 标识符 #

标识符用于命名变量、函数、类型等:

package main

import "fmt"

// 有效的标识符
var name string
var age int
var isStudent bool

// 函数名也是标识符
func calculateSum(a, b int) int {
    return a + b
}

func main() {
    // 变量名标识符
    userName := "Alice"
    userAge := 25

    fmt.Printf("Name: %s, Age: %d\n", userName, userAge)
}

标识符规则:

  • 必须以字母或下划线开头
  • 后续字符可以是字母、数字或下划线
  • 区分大小写
  • 不能使用 Go 关键字

3. 关键字 #

Go 语言有 25 个关键字:

break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var

4. 操作符 #

package main

import "fmt"

func main() {
    // 算术操作符
    a, b := 10, 3
    fmt.Printf("a + b = %d\n", a+b)  // 加法
    fmt.Printf("a - b = %d\n", a-b)  // 减法
    fmt.Printf("a * b = %d\n", a*b)  // 乘法
    fmt.Printf("a / b = %d\n", a/b)  // 除法
    fmt.Printf("a %% b = %d\n", a%b) // 取模

    // 比较操作符
    fmt.Printf("a == b: %t\n", a == b) // 等于
    fmt.Printf("a != b: %t\n", a != b) // 不等于
    fmt.Printf("a > b: %t\n", a > b)   // 大于
    fmt.Printf("a < b: %t\n", a < b)   // 小于

    // 逻辑操作符
    x, y := true, false
    fmt.Printf("x && y: %t\n", x && y) // 逻辑与
    fmt.Printf("x || y: %t\n", x || y) // 逻辑或
    fmt.Printf("!x: %t\n", !x)         // 逻辑非
}

更复杂的示例程序 #

1. 计算器程序 #

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 + 5")
        os.Exit(1)
    }

    // 解析参数
    num1Str := os.Args[1]
    operator := os.Args[2]
    num2Str := os.Args[3]

    // 转换字符串为数字
    num1, err1 := strconv.ParseFloat(num1Str, 64)
    num2, err2 := strconv.ParseFloat(num2Str, 64)

    if err1 != nil || err2 != nil {
        fmt.Println("Error: Invalid numbers")
        os.Exit(1)
    }

    // 执行计算
    var result float64
    switch operator {
    case "+":
        result = num1 + num2
    case "-":
        result = num1 - num2
    case "*":
        result = num1 * num2
    case "/":
        if num2 == 0 {
            fmt.Println("Error: Division by zero")
            os.Exit(1)
        }
        result = num1 / num2
    default:
        fmt.Printf("Error: Unknown operator '%s'\n", operator)
        os.Exit(1)
    }

    // 输出结果
    fmt.Printf("%.2f %s %.2f = %.2f\n", num1, operator, num2, result)
}

使用示例:

go run calculator.go 10 + 5
# 输出: 10.00 + 5.00 = 15.00

go run calculator.go 20 / 4
# 输出: 20.00 / 4.00 = 5.00

2. 文件信息查看器 #

package main

import (
    "fmt"
    "os"
    "time"
)

func main() {
    // 检查命令行参数
    if len(os.Args) < 2 {
        fmt.Println("Usage: fileinfo <filename>")
        os.Exit(1)
    }

    filename := os.Args[1]

    // 获取文件信息
    fileInfo, err := os.Stat(filename)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        os.Exit(1)
    }

    // 显示文件信息
    fmt.Printf("File Information for: %s\n", filename)
    fmt.Printf("Size: %d bytes\n", fileInfo.Size())
    fmt.Printf("Mode: %s\n", fileInfo.Mode())
    fmt.Printf("Modified: %s\n", fileInfo.ModTime().Format(time.RFC3339))
    fmt.Printf("Is Directory: %t\n", fileInfo.IsDir())

    // 显示权限信息
    mode := fileInfo.Mode()
    fmt.Printf("Permissions: ")
    if mode&0400 != 0 {
        fmt.Print("r")
    } else {
        fmt.Print("-")
    }
    if mode&0200 != 0 {
        fmt.Print("w")
    } else {
        fmt.Print("-")
    }
    if mode&0100 != 0 {
        fmt.Print("x")
    } else {
        fmt.Print("-")
    }
    fmt.Println()
}

3. 简单的 HTTP 服务器 #

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

// 处理根路径请求
func homeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "<h1>Welcome to Go Web Server!</h1>")
    fmt.Fprintf(w, "<p>Current time: %s</p>", time.Now().Format(time.RFC3339))
    fmt.Fprintf(w, "<p>Request URL: %s</p>", r.URL.Path)
    fmt.Fprintf(w, "<p>Request Method: %s</p>", r.Method)
}

// 处理 API 请求
func apiHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    response := `{
        "message": "Hello from Go API!",
        "timestamp": "` + time.Now().Format(time.RFC3339) + `",
        "status": "success"
    }`
    fmt.Fprint(w, response)
}

// 处理健康检查
func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    fmt.Fprint(w, "OK")
}

func main() {
    // 注册路由处理器
    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/api", apiHandler)
    http.HandleFunc("/health", healthHandler)

    // 启动服务器
    port := ":8080"
    fmt.Printf("Starting server on http://localhost%s\n", port)
    fmt.Println("Available endpoints:")
    fmt.Println("  http://localhost:8080/")
    fmt.Println("  http://localhost:8080/api")
    fmt.Println("  http://localhost:8080/health")

    // 启动 HTTP 服务器
    log.Fatal(http.ListenAndServe(port, nil))
}

测试服务器:

# 启动服务器
go run server.go

# 在另一个终端测试
curl http://localhost:8080/
curl http://localhost:8080/api
curl http://localhost:8080/health

Go 程序的编译和构建 #

1. 基本编译 #

# 编译当前目录的程序
go build

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

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

# 编译并安装到 GOPATH/bin
go install

2. 跨平台编译 #

# 编译 Linux 64位版本
GOOS=linux GOARCH=amd64 go build -o myapp-linux main.go

# 编译 Windows 64位版本
GOOS=windows GOARCH=amd64 go build -o myapp.exe main.go

# 编译 macOS 64位版本
GOOS=darwin GOARCH=amd64 go build -o myapp-mac main.go

# 编译 ARM 版本(如树莓派)
GOOS=linux GOARCH=arm go build -o myapp-arm main.go

3. 构建标签和条件编译 #

// +build linux darwin

package main

import "fmt"

func main() {
    fmt.Println("This runs only on Linux and macOS")
}
// +build windows

package main

import "fmt"

func main() {
    fmt.Println("This runs only on Windows")
}

4. 编译优化 #

# 减小可执行文件大小
go build -ldflags="-s -w" main.go

# 禁用调试信息
go build -ldflags="-s" main.go

# 静态链接
CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' main.go

调试和测试 #

1. 使用 fmt 包调试 #

package main

import "fmt"

func main() {
    name := "Alice"
    age := 30

    // 基本输出
    fmt.Println("Hello, World!")

    // 格式化输出
    fmt.Printf("Name: %s, Age: %d\n", name, age)

    // 输出变量类型和值
    fmt.Printf("name: %T = %v\n", name, name)
    fmt.Printf("age: %T = %v\n", age, age)

    // 调试输出
    fmt.Printf("Debug: name=%q, age=%d\n", name, age)
}

2. 使用 log 包 #

package main

import (
    "log"
    "os"
)

func main() {
    // 设置日志格式
    log.SetFlags(log.LstdFlags | log.Lshortfile)

    // 基本日志
    log.Println("Program started")

    // 错误日志
    if err := someFunction(); err != nil {
        log.Printf("Error occurred: %v", err)
    }

    // 致命错误(会退出程序)
    if criticalError() {
        log.Fatal("Critical error, exiting...")
    }
}

func someFunction() error {
    return nil // 模拟无错误
}

func criticalError() bool {
    return false // 模拟无致命错误
}

3. 简单的单元测试 #

创建 math_test.go 文件:

package main

import "testing"

// 被测试的函数
func Add(a, b int) int {
    return a + b
}

func Multiply(a, b int) int {
    return a * b
}

// 测试函数
func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5

    if result != expected {
        t.Errorf("Add(2, 3) = %d; expected %d", result, expected)
    }
}

func TestMultiply(t *testing.T) {
    tests := []struct {
        a, b     int
        expected int
    }{
        {2, 3, 6},
        {0, 5, 0},
        {-1, 4, -4},
    }

    for _, test := range tests {
        result := Multiply(test.a, test.b)
        if result != test.expected {
            t.Errorf("Multiply(%d, %d) = %d; expected %d",
                test.a, test.b, result, test.expected)
        }
    }
}

运行测试:

go test
go test -v  # 详细输出

常见错误和解决方法 #

1. 语法错误 #

错误示例:

package main

import "fmt"

func main() {
    fmt.Println("Hello World")  // 缺少分号(实际上Go不需要分号)
    if true {
        fmt.Println("True")
    // 缺少闭合括号
}

正确写法:

package main

import "fmt"

func main() {
    fmt.Println("Hello World")
    if true {
        fmt.Println("True")
    }
}

2. 导入包未使用 #

错误示例:

package main

import (
    "fmt"
    "os"    // 导入但未使用
)

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

解决方法:

package main

import "fmt"

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

3. 变量声明但未使用 #

错误示例:

package main

import "fmt"

func main() {
    name := "Alice"  // 声明但未使用
    fmt.Println("Hello World")
}

解决方法:

package main

import "fmt"

func main() {
    name := "Alice"
    fmt.Printf("Hello, %s!\n", name)
}

项目实践:待办事项管理器 #

让我们创建一个简单的命令行待办事项管理器:

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

// 待办事项结构
type Todo struct {
    ID          int
    Description string
    Completed   bool
}

// 全局待办事项列表
var todos []Todo
var nextID int = 1

// 添加待办事项
func addTodo(description string) {
    todo := Todo{
        ID:          nextID,
        Description: description,
        Completed:   false,
    }
    todos = append(todos, todo)
    nextID++
    fmt.Printf("Added todo: %s (ID: %d)\n", description, todo.ID)
}

// 列出所有待办事项
func listTodos() {
    if len(todos) == 0 {
        fmt.Println("No todos found.")
        return
    }

    fmt.Println("\nTodo List:")
    fmt.Println("----------")
    for _, todo := range todos {
        status := "[ ]"
        if todo.Completed {
            status = "[✓]"
        }
        fmt.Printf("%d. %s %s\n", todo.ID, status, todo.Description)
    }
    fmt.Println()
}

// 完成待办事项
func completeTodo(id int) {
    for i := range todos {
        if todos[i].ID == id {
            todos[i].Completed = true
            fmt.Printf("Completed todo: %s\n", todos[i].Description)
            return
        }
    }
    fmt.Printf("Todo with ID %d not found.\n", id)
}

// 删除待办事项
func deleteTodo(id int) {
    for i, todo := range todos {
        if todo.ID == id {
            todos = append(todos[:i], todos[i+1:]...)
            fmt.Printf("Deleted todo: %s\n", todo.Description)
            return
        }
    }
    fmt.Printf("Todo with ID %d not found.\n", id)
}

// 显示帮助信息
func showHelp() {
    fmt.Println("\nAvailable commands:")
    fmt.Println("  add <description>    - Add a new todo")
    fmt.Println("  list                 - List all todos")
    fmt.Println("  complete <id>        - Mark todo as completed")
    fmt.Println("  delete <id>          - Delete a todo")
    fmt.Println("  help                 - Show this help")
    fmt.Println("  quit                 - Exit the program")
    fmt.Println()
}

func main() {
    fmt.Println("Welcome to Todo Manager!")
    fmt.Println("Type 'help' for available commands.")

    scanner := bufio.NewScanner(os.Stdin)

    for {
        fmt.Print("> ")
        if !scanner.Scan() {
            break
        }

        input := strings.TrimSpace(scanner.Text())
        if input == "" {
            continue
        }

        parts := strings.Fields(input)
        command := parts[0]

        switch command {
        case "add":
            if len(parts) < 2 {
                fmt.Println("Usage: add <description>")
                continue
            }
            description := strings.Join(parts[1:], " ")
            addTodo(description)

        case "list":
            listTodos()

        case "complete":
            if len(parts) < 2 {
                fmt.Println("Usage: complete <id>")
                continue
            }
            id, err := strconv.Atoi(parts[1])
            if err != nil {
                fmt.Println("Invalid ID. Please enter a number.")
                continue
            }
            completeTodo(id)

        case "delete":
            if len(parts) < 2 {
                fmt.Println("Usage: delete <id>")
                continue
            }
            id, err := strconv.Atoi(parts[1])
            if err != nil {
                fmt.Println("Invalid ID. Please enter a number.")
                continue
            }
            deleteTodo(id)

        case "help":
            showHelp()

        case "quit", "exit":
            fmt.Println("Goodbye!")
            return

        default:
            fmt.Printf("Unknown command: %s\n", command)
            fmt.Println("Type 'help' for available commands.")
        }
    }
}

使用示例:

go run todo.go

Welcome to Todo Manager!
Type 'help' for available commands.
> add Buy groceries
Added todo: Buy groceries (ID: 1)
> add Write Go tutorial
Added todo: Write Go tutorial (ID: 2)
> list

Todo List:
----------
1. [ ] Buy groceries
2. [ ] Write Go tutorial

> complete 1
Completed todo: Buy groceries
> list

Todo List:
----------
1. [] Buy groceries
2. [ ] Write Go tutorial

> quit
Goodbye!

小结 #

通过本节的学习,您已经:

  1. 掌握了 Go 程序的基本结构:package 声明、import 语句、main 函数
  2. 了解了 Go 语言的基本元素:注释、标识符、关键字、操作符
  3. 学会了编译和运行 Go 程序:使用 go run、go build 等命令
  4. 实践了调试和测试:使用 fmt、log 包和基本的单元测试
  5. 完成了实际项目:待办事项管理器,综合运用了所学知识

现在您已经具备了 Go 语言编程的基础能力,可以开始学习更深入的语法和特性了。在接下来的章节中,我们将详细学习 Go 语言的数据类型、控制结构、函数等核心概念。


练习题:

  1. 修改 Hello World 程序,让它接受命令行参数并打印个性化问候语
  2. 创建一个程序,计算并显示当前年份是否为闰年
  3. 编写一个简单的猜数字游戏
  4. 为待办事项管理器添加编辑功能
  5. 创建一个程序,读取文本文件并统计单词数量