1.2.1 变量与常量

1.2.1 变量与常量 #

变量和常量是编程语言的基础概念。在 Go 语言中,变量和常量的声明和使用有其独特的语法和规则。本节将详细介绍 Go 语言中变量和常量的各种用法。

变量声明 #

1. 使用 var 关键字 #

Go 语言使用 var 关键字声明变量,基本语法为:

var 变量名 类型

基本声明示例:

package main

import "fmt"

func main() {
    // 声明单个变量
    var name string
    var age int
    var isStudent bool

    fmt.Printf("name: %q, age: %d, isStudent: %t\n", name, age, isStudent)
    // 输出: name: "", age: 0, isStudent: false
}

2. 变量初始化 #

可以在声明变量的同时进行初始化:

package main

import "fmt"

func main() {
    // 声明并初始化
    var name string = "Alice"
    var age int = 25
    var height float64 = 165.5

    fmt.Printf("姓名: %s, 年龄: %d, 身高: %.1f cm\n", name, age, height)

    // 批量声明并初始化
    var (
        username = "bob"
        password = "123456"
        email    = "[email protected]"
    )

    fmt.Printf("用户名: %s, 密码: %s, 邮箱: %s\n", username, password, email)
}

3. 类型推断 #

Go 编译器可以根据初始值自动推断变量类型:

package main

import "fmt"

func main() {
    // 类型推断 - 省略类型声明
    var name = "Charlie"        // 推断为 string
    var age = 30               // 推断为 int
    var salary = 50000.50      // 推断为 float64
    var isManager = true       // 推断为 bool

    fmt.Printf("name: %T, age: %T, salary: %T, isManager: %T\n",
               name, age, salary, isManager)
    // 输出: name: string, age: int, salary: float64, isManager: bool
}

4. 短变量声明 #

在函数内部,可以使用 := 操作符进行短变量声明:

package main

import "fmt"

func main() {
    // 短变量声明(只能在函数内使用)
    name := "David"
    age := 28
    city := "北京"

    fmt.Printf("姓名: %s, 年龄: %d, 城市: %s\n", name, age, city)

    // 多变量同时声明
    x, y, z := 1, 2, 3
    fmt.Printf("x: %d, y: %d, z: %d\n", x, y, z)

    // 混合赋值(至少有一个新变量)
    name, country := "Eva", "中国"  // name 重新赋值,country 新声明
    fmt.Printf("姓名: %s, 国家: %s\n", name, country)
}

5. 零值概念 #

Go 语言中,所有变量都有零值(zero value):

package main

import "fmt"

func main() {
    // 各种类型的零值
    var (
        intVar     int
        floatVar   float64
        boolVar    bool
        stringVar  string
        pointerVar *int
        sliceVar   []int
        mapVar     map[string]int
        chanVar    chan int
    )

    fmt.Printf("int零值: %d\n", intVar)           // 0
    fmt.Printf("float64零值: %f\n", floatVar)     // 0.000000
    fmt.Printf("bool零值: %t\n", boolVar)         // false
    fmt.Printf("string零值: %q\n", stringVar)     // ""
    fmt.Printf("pointer零值: %v\n", pointerVar)   // <nil>
    fmt.Printf("slice零值: %v\n", sliceVar)       // []
    fmt.Printf("map零值: %v\n", mapVar)           // map[]
    fmt.Printf("channel零值: %v\n", chanVar)      // <nil>
}

6. 变量作用域 #

package main

import "fmt"

// 包级别变量
var globalVar = "我是全局变量"

func main() {
    // 函数级别变量
    localVar := "我是局部变量"

    fmt.Println(globalVar)
    fmt.Println(localVar)

    // 块级作用域
    if true {
        blockVar := "我是块级变量"
        fmt.Println(blockVar)
        fmt.Println(localVar)  // 可以访问外层变量
    }

    // fmt.Println(blockVar)  // 错误:blockVar 在此作用域不可见

    // for 循环中的变量作用域
    for i := 0; i < 3; i++ {
        fmt.Printf("循环变量 i: %d\n", i)
    }
    // fmt.Println(i)  // 错误:i 在此作用域不可见
}

常量声明 #

1. 基本常量声明 #

使用 const 关键字声明常量:

package main

import "fmt"

func main() {
    // 单个常量声明
    const pi = 3.14159
    const greeting = "Hello, World!"

    // 显式类型声明
    const maxUsers int = 1000
    const version string = "1.0.0"

    fmt.Printf("π = %.5f\n", pi)
    fmt.Printf("问候语: %s\n", greeting)
    fmt.Printf("最大用户数: %d\n", maxUsers)
    fmt.Printf("版本: %s\n", version)
}

2. 常量组 #

可以使用括号声明一组常量:

package main

import "fmt"

func main() {
    const (
        StatusOK       = 200
        StatusNotFound = 404
        StatusError    = 500
    )

    const (
        Red   = "红色"
        Green = "绿色"
        Blue  = "蓝色"
    )

    fmt.Printf("HTTP状态码 - OK: %d, NotFound: %d, Error: %d\n",
               StatusOK, StatusNotFound, StatusError)
    fmt.Printf("颜色 - 红: %s, 绿: %s, 蓝: %s\n", Red, Green, Blue)
}

3. iota 枚举器 #

iota 是 Go 语言的常量计数器,用于生成枚举值:

package main

import "fmt"

func main() {
    // 基本 iota 使用
    const (
        Sunday = iota    // 0
        Monday           // 1
        Tuesday          // 2
        Wednesday        // 3
        Thursday         // 4
        Friday           // 5
        Saturday         // 6
    )

    fmt.Printf("星期日: %d, 星期一: %d, 星期二: %d\n", Sunday, Monday, Tuesday)

    // iota 表达式
    const (
        _  = iota             // 0 (忽略)
        KB = 1 << (10 * iota) // 1 << 10 = 1024
        MB                    // 1 << 20 = 1048576
        GB                    // 1 << 30 = 1073741824
        TB                    // 1 << 40
    )

    fmt.Printf("KB: %d, MB: %d, GB: %d\n", KB, MB, GB)

    // 复杂的 iota 表达式
    const (
        a = iota * 2    // 0 * 2 = 0
        b               // 1 * 2 = 2
        c               // 2 * 2 = 4
        d = iota + 10   // 3 + 10 = 13
        e               // 4 + 10 = 14
    )

    fmt.Printf("a: %d, b: %d, c: %d, d: %d, e: %d\n", a, b, c, d, e)
}

4. 类型化常量和无类型常量 #

package main

import "fmt"

func main() {
    // 无类型常量(更灵活)
    const untypedInt = 42
    const untypedFloat = 3.14
    const untypedString = "hello"

    // 类型化常量
    const typedInt int = 42
    const typedFloat float64 = 3.14
    const typedString string = "hello"

    // 无类型常量可以赋值给兼容的类型
    var i8 int8 = untypedInt
    var i16 int16 = untypedInt
    var i32 int32 = untypedInt
    var i64 int64 = untypedInt

    fmt.Printf("i8: %d, i16: %d, i32: %d, i64: %d\n", i8, i16, i32, i64)

    // 类型化常量只能赋值给相同类型
    var typedVar int = typedInt
    // var wrongVar int8 = typedInt  // 错误:类型不匹配

    fmt.Printf("typedVar: %d\n", typedVar)
}

变量和常量的最佳实践 #

1. 命名规范 #

package main

import "fmt"

// 包级别的导出变量(首字母大写)
var GlobalConfig = "全局配置"

// 包级别的私有变量(首字母小写)
var internalState = "内部状态"

func main() {
    // 使用有意义的变量名
    userAge := 25
    userName := "张三"
    isLoggedIn := true

    // 避免使用单字母变量名(除了短循环)
    for i := 0; i < 5; i++ {  // i 在短循环中是可接受的
        fmt.Printf("循环 %d\n", i)
    }

    // 常量使用大写字母和下划线
    const MAX_RETRY_COUNT = 3
    const DEFAULT_TIMEOUT = 30

    fmt.Printf("用户: %s, 年龄: %d, 已登录: %t\n", userName, userAge, isLoggedIn)
    fmt.Printf("最大重试次数: %d, 默认超时: %d秒\n", MAX_RETRY_COUNT, DEFAULT_TIMEOUT)
}

2. 变量初始化模式 #

package main

import (
    "fmt"
    "time"
)

func main() {
    // 延迟初始化
    var config map[string]string
    if needConfig() {
        config = make(map[string]string)
        config["host"] = "localhost"
        config["port"] = "8080"
    }

    // 条件初始化
    var message string
    if time.Now().Hour() < 12 {
        message = "早上好"
    } else {
        message = "下午好"
    }

    // 多重赋值
    name, age := getUserInfo()

    fmt.Printf("配置: %v\n", config)
    fmt.Printf("问候: %s\n", message)
    fmt.Printf("用户信息: %s, %d岁\n", name, age)
}

func needConfig() bool {
    return true
}

func getUserInfo() (string, int) {
    return "李四", 30
}

3. 常量的组织方式 #

package main

import "fmt"

// 相关常量分组
const (
    // HTTP 状态码
    StatusOK                  = 200
    StatusBadRequest         = 400
    StatusUnauthorized       = 401
    StatusNotFound           = 404
    StatusInternalServerError = 500
)

const (
    // 用户角色
    RoleAdmin = iota
    RoleUser
    RoleGuest
)

const (
    // 配置常量
    DefaultPort     = 8080
    DefaultHost     = "localhost"
    DefaultTimeout  = 30 * time.Second
    MaxConnections  = 1000
)

func main() {
    fmt.Printf("服务器配置: %s:%d\n", DefaultHost, DefaultPort)
    fmt.Printf("用户角色 - 管理员: %d, 普通用户: %d, 访客: %d\n",
               RoleAdmin, RoleUser, RoleGuest)
    fmt.Printf("HTTP状态码 - 成功: %d, 未找到: %d\n", StatusOK, StatusNotFound)
}

实际应用示例 #

配置管理系统 #

package main

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

// 应用配置常量
const (
    AppName    = "MyWebApp"
    AppVersion = "1.2.3"

    // 默认配置值
    DefaultPort        = 8080
    DefaultHost        = "0.0.0.0"
    DefaultTimeout     = 30 * time.Second
    DefaultMaxRequests = 1000
)

// 配置结构
type Config struct {
    Host        string
    Port        int
    Timeout     time.Duration
    MaxRequests int
    Debug       bool
}

func main() {
    // 从环境变量或使用默认值初始化配置
    config := loadConfig()

    fmt.Printf("应用: %s v%s\n", AppName, AppVersion)
    fmt.Printf("配置信息:\n")
    fmt.Printf("  主机: %s\n", config.Host)
    fmt.Printf("  端口: %d\n", config.Port)
    fmt.Printf("  超时: %v\n", config.Timeout)
    fmt.Printf("  最大请求数: %d\n", config.MaxRequests)
    fmt.Printf("  调试模式: %t\n", config.Debug)
}

func loadConfig() Config {
    config := Config{
        Host:        DefaultHost,
        Port:        DefaultPort,
        Timeout:     DefaultTimeout,
        MaxRequests: DefaultMaxRequests,
        Debug:       false,
    }

    // 从环境变量读取配置
    if host := os.Getenv("APP_HOST"); host != "" {
        config.Host = host
    }

    if portStr := os.Getenv("APP_PORT"); portStr != "" {
        if port, err := strconv.Atoi(portStr); err == nil {
            config.Port = port
        }
    }

    if debugStr := os.Getenv("APP_DEBUG"); debugStr == "true" {
        config.Debug = true
    }

    return config
}

状态机实现 #

package main

import "fmt"

// 状态常量
const (
    StateIdle = iota
    StateRunning
    StatePaused
    StateStopped
    StateError
)

// 状态名称映射
var stateNames = map[int]string{
    StateIdle:    "空闲",
    StateRunning: "运行中",
    StatePaused:  "已暂停",
    StateStopped: "已停止",
    StateError:   "错误",
}

// 任务结构
type Task struct {
    name  string
    state int
}

func main() {
    // 创建任务
    task := Task{
        name:  "数据处理任务",
        state: StateIdle,
    }

    // 状态转换演示
    fmt.Printf("任务: %s\n", task.name)

    task.start()
    task.pause()
    task.resume()
    task.stop()
}

func (t *Task) start() {
    if t.state == StateIdle {
        t.state = StateRunning
        fmt.Printf("状态变更: %s -> %s\n", stateNames[StateIdle], stateNames[StateRunning])
    }
}

func (t *Task) pause() {
    if t.state == StateRunning {
        t.state = StatePaused
        fmt.Printf("状态变更: %s -> %s\n", stateNames[StateRunning], stateNames[StatePaused])
    }
}

func (t *Task) resume() {
    if t.state == StatePaused {
        t.state = StateRunning
        fmt.Printf("状态变更: %s -> %s\n", stateNames[StatePaused], stateNames[StateRunning])
    }
}

func (t *Task) stop() {
    if t.state == StateRunning || t.state == StatePaused {
        oldState := t.state
        t.state = StateStopped
        fmt.Printf("状态变更: %s -> %s\n", stateNames[oldState], stateNames[StateStopped])
    }
}

常见错误和注意事项 #

1. 变量遮蔽(Variable Shadowing) #

package main

import "fmt"

var globalVar = "全局变量"

func main() {
    fmt.Printf("外层: %s\n", globalVar)

    // 变量遮蔽
    if true {
        globalVar := "局部变量"  // 遮蔽了全局变量
        fmt.Printf("内层: %s\n", globalVar)
    }

    fmt.Printf("外层: %s\n", globalVar)  // 仍然是全局变量

    // 正确的做法
    if true {
        globalVar = "修改全局变量"  // 修改全局变量
        fmt.Printf("修改后: %s\n", globalVar)
    }
}

2. 短变量声明的陷阱 #

package main

import "fmt"

func main() {
    var err error

    // 错误:这里创建了新的 err 变量,而不是使用上面声明的
    if data, err := readData(); err != nil {
        fmt.Printf("错误: %v\n", err)
        return
    } else {
        fmt.Printf("数据: %s\n", data)
    }

    // 这里的 err 仍然是 nil
    fmt.Printf("外层 err: %v\n", err)

    // 正确的做法
    var data string
    data, err = readData()
    if err != nil {
        fmt.Printf("错误: %v\n", err)
        return
    }
    fmt.Printf("数据: %s\n", data)
}

func readData() (string, error) {
    return "测试数据", nil
}

3. 常量的限制 #

package main

import "fmt"

func main() {
    // 常量必须在编译时确定
    const validConst = 10 * 20  // 正确

    // const invalidConst = len("hello")  // 错误:len() 不是常量表达式
    // const invalidConst2 = time.Now()   // 错误:time.Now() 不是常量表达式

    // 常量不能修改
    const pi = 3.14159
    // pi = 3.14  // 错误:不能给常量赋值

    fmt.Printf("有效常量: %d\n", validConst)
    fmt.Printf("π: %.5f\n", pi)
}

小结 #

本节详细介绍了 Go 语言中变量和常量的声明、初始化和使用方法。主要要点包括:

变量 #

  • 使用 var 关键字或 := 短变量声明
  • 支持类型推断和零值初始化
  • 注意变量作用域和遮蔽问题

常量 #

  • 使用 const 关键字声明
  • 支持 iota 枚举器生成序列值
  • 区分类型化常量和无类型常量

最佳实践 #

  • 使用有意义的变量名
  • 合理组织常量定义
  • 避免变量遮蔽
  • 正确使用短变量声明

掌握变量和常量的使用是学习 Go 语言的基础,为后续学习数据类型和控制结构打下坚实基础。


练习题:

  1. 声明一个包含个人信息的变量组,包括姓名、年龄、城市
  2. 使用 iota 创建一个表示文件权限的常量组(读、写、执行)
  3. 编写一个函数,演示变量作用域的不同层级
  4. 创建一个配置管理的示例,结合环境变量和默认常量
  5. 实现一个简单的状态机,使用常量表示不同状态