# Go 程序结构

Go 程序有着清晰、规范的组织结构。理解程序的基本构成单元——包声明、导入、函数、变量作用域等——是编写 Go 代码的基础。本篇将详细讲解 Go 程序的各个组成部分，帮助你建立良好的代码组织习惯。

## 包声明

每个 Go 源文件都必须以 `package` 声明开头，表明该文件属于哪个包。

```go
package main
```

### 包的分类

- **`main` 包**：可执行程序的入口包，必须包含 `main()` 函数。
- **库包**：用于提供可复用的功能，包名通常与目录名一致。

```go
// 文件: mathutil/add.go
package mathutil

func Add(a, b int) int {
    return a + b
}
```

一个包可以包含多个源文件，它们共享同一个包命名空间：

```go
// 文件: mathutil/subtract.go
package mathutil

func Subtract(a, b int) int {
    return a - b
}
```

## 导入包

### 单导入

导入单个包时使用圆括号或直接导入：

```go
import "fmt"
```

### 分组导入

导入多个包时，推荐使用分组导入的写法：

```go
import (
    "fmt"
    "os"
    "strings"
    "time"
)
```

Go 官方工具 `goimports` 会自动将导入分为三组：标准库、第三方包、项目内部包。

### 匿名导入 `_`

有时候我们只需要某个包的初始化副作用（`init` 函数），而不直接使用它。这时可以使用匿名导入：

```go
import (
    "fmt"
    _ "image/png" // 注册 PNG 解码器，不使用 png 包中的任何标识符
)

func main() {
    fmt.Println("PNG 解码器已注册")
}
```

### 导入别名

当两个包的名称冲突时，可以为导入指定别名：

```go
import (
    crand "crypto/rand"
    mrand "math/rand"
)

func main() {
    _ = crand.Reader
    _ = mrand.Int
}
```

### 未使用的导入会导致编译错误

Go 编译器严格要求：导入但未使用的包会导致编译失败。这是 Go 的设计哲学之一——保持代码整洁。

```go
package main

import (
    "fmt"
    "os" // 编译错误：os 包已导入但未使用
)

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

## 函数

### main 函数

`main` 函数是程序的入口，它没有参数也没有返回值：

```go
package main

import "fmt"

func main() {
    fmt.Println("程序从这里开始执行")
}
```

### 导出与非导出

Go 通过**首字母大小写**来控制标识符的可见性：

- **首字母大写**：导出（相当于 public），其他包可以访问。
- **首字母小写**：非导出（相当于 private），仅限本包内使用。

```go
package calculator

// Add 是导出函数，其他包可以调用
func Add(a, b int) int {
    return a + b
}

// multiply 是非导出函数，仅限本包使用
func multiply(a, b int) int {
    return a * b
}
```

同样的规则适用于变量、常量、结构体、接口等所有标识符。

## 变量作用域

### 包级变量

在函数外部声明的变量是包级变量，作用域为整个包：

```go
package main

import "fmt"

// 包级变量，所有函数都可以访问
var version = "1.0.0"
var debug = false

func main() {
    fmt.Println("版本:", version)
    fmt.Println("调试模式:", debug)
}
```

### 函数内变量

在函数内部声明的变量只能在函数内使用：

```go
package main

import "fmt"

var globalVar = "我是全局变量"

func main() {
    localVar := "我是局部变量"
    fmt.Println(globalVar) // 可以访问
    fmt.Println(localVar)  // 可以访问
    helper()
}

func helper() {
    fmt.Println(globalVar) // 可以访问全局变量
    // fmt.Println(localVar) // 编译错误：localVar 未定义
}
```

### 代码块作用域

在 `if`、`for` 等代码块中声明的变量，仅在该代码块内可见：

```go
package main

import "fmt"

func main() {
    x := 10

    if x > 5 {
        y := 20 // y 仅在 if 代码块内可见
        fmt.Println("if 内部:", x, y)
    }

    fmt.Println("if 外部:", x)
    // fmt.Println(y) // 编译错误：y 未定义
}
```

## 语句与表达式

Go 的语句有一些独特的规则：

### 不需要分号

Go 编译器会自动在行尾添加分号，开发者不需要手动写分号：

```go
// 不需要写分号
x := 10
fmt.Println(x)
```

### 左大括号不能换行

Go 使用分号自动插入规则，左大括号 `{` 必须和语句在同一行：

```go
// 正确写法
func main() {
    if x > 5 {
        fmt.Println("大于5")
    }
}

// 错误写法 —— 编译失败
// func main()
// {
//     if x > 5
//     {
//         fmt.Println("大于5")
//     }
// }
```

## 代码组织最佳实践

### 一个文件一个主要概念

每个源文件应聚焦于一个功能模块，文件名应反映其内容：

```
myproject/
├── main.go          # 程序入口
├── handler.go       # HTTP 处理函数
├── model.go         # 数据模型定义
├── service.go       # 业务逻辑
└── utils.go         # 工具函数
```

### init 函数

每个 Go 源文件可以包含一个或多个 `init` 函数，它在 `main` 函数之前自动执行，常用于初始化：

```go
package main

import "fmt"

var config map[string]string

func init() {
    config = make(map[string]string)
    config["db_host"] = "localhost"
    config["db_port"] = "3306"
    fmt.Println("配置初始化完成")
}

func main() {
    fmt.Println("数据库地址:", config["db_host"])
    fmt.Println("数据库端口:", config["db_port"])
}
```

执行顺序：包级变量初始化 → `init()` 函数 → `main()` 函数。

## 完整小程序示例

下面是一个完整的命令行计算器程序，展示了包声明、导入、函数定义和变量作用域的综合运用：

```go
package main

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

// 版本信息（包级常量）
const version = "1.0.0"

func main() {
    if len(os.Args) != 4 {
        fmt.Println("用法: calculator <数字1> <运算符> <数字2>")
        fmt.Println("支持的运算符: + - * /")
        return
    }

    a, err1 := strconv.ParseFloat(os.Args[1], 64)
    op := os.Args[2]
    b, err2 := strconv.ParseFloat(os.Args[3], 64)

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

    result, err := calculate(a, op, b)
    if err != nil {
        fmt.Println("错误:", err)
        return
    }

    fmt.Printf("%.2f %s %.2f = %.2f\n", a, op, b, result)
}

func calculate(a float64, op string, b float64) (float64, error) {
    switch op {
    case "+":
        return a + b, nil
    case "-":
        return a - b, nil
    case "*":
        return a * b, nil
    case "/":
        if b == 0 {
            return 0, fmt.Errorf("除数不能为零")
        }
        return a / b, nil
    default:
        return 0, fmt.Errorf("不支持的运算符: %s", op)
    }
}
```

运行示例：

```bash
$ go run main.go 10 + 3
10.00 + 3.00 = 13.00

$ go run main.go 10 / 0
错误: 除数不能为零
```

## 总结

本篇详细介绍了 Go 程序的基本结构：包声明决定了代码的归属，导入语句管理依赖关系，函数是代码的执行单元，而命名规则（首字母大小写）控制了标识符的可见性。我们还学习了变量作用域的层级关系、Go 独特的语句规则（自动分号、大括号位置），以及 `init` 函数的初始化机制。良好的代码组织习惯是编写可维护 Go 程序的第一步。

