# Go 变量与常量

变量和常量是编程中最基本的概念。Go 语言提供了灵活的变量声明方式，同时通过严格的编译检查来避免常见的错误。本篇将全面讲解 Go 中变量和常量的声明、命名规范、零值概念以及 `iota` 枚举器的用法。

## 变量声明

### 使用 var 关键字

Go 中使用 `var` 关键字声明变量，语法为 `var 变量名 类型`：

```go
package main

import "fmt"

func main() {
    var name string     // 声明一个 string 类型变量
    var age int         // 声明一个 int 类型变量
    var score float64   // 声明一个 float64 类型变量

    name = "张三"
    age = 25
    score = 95.5

    fmt.Println("姓名:", name)
    fmt.Println("年龄:", age)
    fmt.Println("分数:", score)
}
```

### 声明时初始化

可以在声明变量的同时赋予初始值，Go 会自动推导类型：

```go
package main

import "fmt"

func main() {
    var name = "李四"        // 类型推导为 string
    var age = 30             // 类型推导为 int
    var pi = 3.14159         // 类型推导为 float64
    var isStudent = false    // 类型推导为 bool

    fmt.Printf("%T %T %T %T\n", name, age, pi, isStudent)
}
```

### 显式指定类型

如果需要明确指定类型（而非让编译器推导），可以这样写：

```go
var count int = 100
var price float32 = 9.99
```

### 多变量声明

同时声明多个变量：

```go
package main

import "fmt"

func main() {
    // 方式一：逐个声明
    var a, b, c int
    a, b, c = 1, 2, 3
    fmt.Println(a, b, c)

    // 方式二：同时初始化
    var x, y, z = 10, 20, 30
    fmt.Println(x, y, z)

    // 方式三：分组声明
    var (
        name    string = "王五"
        age     int    = 28
        city    string = "北京"
        isVip   bool   = true
    )
    fmt.Println(name, age, city, isVip)
}
```

## 短变量声明

在函数内部，可以使用 `:=` 进行短变量声明，Go 会自动推导类型：

```go
package main

import "fmt"

func main() {
    name := "赵六"       // 等价于 var name string = "赵六"
    age := 22            // 等价于 var age int = 22
    isMarried := false   // 等价于 var isMarried bool = false

    fmt.Println(name, age, isMarried)
}
```

### 短变量声明的限制

- 只能在函数内部使用，包级变量必须用 `var`。
- 至少有一个新变量（如果变量已存在，则只赋值不重新声明）：

```go
package main

import "fmt"

func main() {
    x := 10
    y := 20

    // x 已存在，y 是新变量，合法
    x, y := 30, 40
    fmt.Println(x, y) // 输出: 30 40
}
```

## 变量命名规范

Go 的变量命名遵循以下规则：

- 由字母、数字、下划线组成，但不能以数字开头。
- 区分大小写：`name` 和 `Name` 是不同的变量。
- 推荐使用**驼峰命名法**（camelCase）。

### 首字母决定可见性

```go
package user

// 首字母大写 —— 导出，其他包可访问
var DefaultRole = "guest"

// 首字母小写 —— 非导出，仅本包可访问
var maxLoginAttempts = 5
```

### 命名建议

- 局部变量尽量用短名称：`i`、`n`、`err`、`ctx`。
- 作用域越大，名称越要有意义：`userCount` 比 `n` 更好。
- 布尔变量常用 `is`、`has`、`can` 前缀：`isValid`、`hasError`。

## 零值概念

Go 中声明变量但未显式赋值时，变量会被赋予其类型的**零值**：

| 类型 | 零值 |
|------|------|
| `int`, `int8`, `int16`, `int32`, `int64` | `0` |
| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | `0` |
| `float32`, `float64` | `0.0` |
| `bool` | `false` |
| `string` | `""`（空字符串） |
| 指针、函数、接口、slice、channel、map | `nil` |

```go
package main

import "fmt"

func main() {
    var i int
    var f float64
    var b bool
    var s string
    var p *int

    fmt.Printf("int: %d\n", i)           // 0
    fmt.Printf("float64: %f\n", f)       // 0.000000
    fmt.Printf("bool: %t\n", b)          // false
    fmt.Printf("string: %q\n", s)        // ""
    fmt.Printf("pointer: %v\n", p)       // <nil>
}
```

零值设计的好处是：变量声明后即可安全使用，不需要额外的初始化代码。

## 常量声明

常量使用 `const` 关键字声明，其值在编译时确定，不可修改：

```go
package main

import "fmt"

func main() {
    const pi = 3.14159265
    const appName = "MyApp"
    const maxRetry = 3

    // pi = 3.14  // 编译错误：不能修改常量
    fmt.Println(pi, appName, maxRetry)
}
```

### 常量分组

```go
const (
    StatusPending  = "pending"
    StatusRunning  = "running"
    StatusComplete = "complete"
    StatusFailed   = "failed"
)
```

### 无类型常量

Go 的常量可以没有显式类型，称为"无类型常量"，它们可以兼容更多类型：

```go
package main

import "fmt"

const threshold = 100 // 无类型常量

func main() {
    var a int = threshold     // 可以赋给 int
    var b float64 = threshold // 可以赋给 float64
    var c int64 = threshold   // 可以赋给 int64
    fmt.Println(a, b, c)
}
```

## iota 枚举器

`iota` 是 Go 的特殊常量生成器，每出现一次 `iota`，其值自动递增 1。它在常量组中从 0 开始计数。

### 基本用法

```go
package main

import "fmt"

const (
    a = iota // 0
    b = iota // 1
    c = iota // 2
    d = iota // 3
)

func main() {
    fmt.Println(a, b, c, d) // 输出: 0 1 2 3
}
```

在同一常量组中，`iota` 可以省略不写（会自动沿用）：

```go
const (
    a = iota // 0
    b        // 1
    c        // 2
    d        // 3
)
```

### 实际应用：星期枚举

```go
package main

import "fmt"

const (
    Sunday    = iota // 0
    Monday           // 1
    Tuesday          // 2
    Wednesday        // 3
    Thursday         // 4
    Friday           // 5
    Saturday         // 6
)

func main() {
    fmt.Println("Sunday =", Sunday)   // 0
    fmt.Println("Monday =", Monday)   // 1
    fmt.Println("Friday =", Friday)   // 5
    fmt.Println("Saturday =", Saturday) // 6
}
```

### 实际应用：文件大小单位

```go
package main

import "fmt"

const (
    B  = 1 << (iota * 10) // 1
    KB                     // 1024
    MB                     // 1048576
    GB                     // 1073741824
    TB                     // 1099511627776
)

func main() {
    fmt.Printf("B  = %d\n", B)
    fmt.Printf("KB = %d\n", KB)
    fmt.Printf("MB = %d\n", MB)
    fmt.Printf("GB = %d\n", GB)
    fmt.Printf("TB = %d\n", TB)
}
```

### iota 跳过值

使用 `_` 可以跳过某些 `iota` 值：

```go
package main

import "fmt"

const (
    A = iota // 0
    _        // 1（跳过）
    B = iota // 2
    C        // 3
)

func main() {
    fmt.Println(A, B, C) // 输出: 0 2 3
}
```

### iota 配合表达式

```go
package main

import "fmt"

const (
    One   = iota + 1 // 1
    Two              // 2
    Three            // 3
    Four             // 4
)

func main() {
    fmt.Println(One, Two, Three, Four) // 输出: 1 2 3 4
}
```

## 未使用变量的编译错误

Go 编译器严格要求：**声明但未使用的变量会导致编译失败**。这一设计避免了遗留无用变量的问题。

```go
package main

import "fmt"

func main() {
    x := 10
    // fmt.Println("Hello")
    // 编译错误：x declared but not used
}
```

如果遇到确实需要声明但暂时不使用的变量，可以用 `_` 丢弃：

```go
package main

import "fmt"

func main() {
    _ = 10 // 不会报错

    // 函数返回多个值时，丢弃不需要的
    // result, _ := someFunc()
    fmt.Println("编译通过")
}
```

## 总结

本篇全面介绍了 Go 中变量和常量的使用方式。变量可以通过 `var` 或 `:=` 声明，未赋值时自动获得零值；常量通过 `const` 声明，配合 `iota` 可以方便地生成枚举值。Go 通过严格的编译检查（未使用变量报错、常量不可修改）来保证代码质量。命名方面，首字母的大小写决定了标识符的可见性，这是 Go 独特的访问控制方式。

