导航菜单

变量与常量

变量的声明方式

Go 语言提供了多种变量声明方式,每种方式适用于不同的场景。理解它们之间的区别是写好 Go 代码的基础。

变量(Variable)

变量是程序运行过程中其值可以改变的量。在 Go 中,每个变量都有明确的类型,编译器会在编译期进行类型检查。

var 关键字声明

var 是 Go 中最基础的变量声明方式,可以出现在包级别或函数体内:

// 标准声明:指定类型
var name string = "Go"
var age int = 10

// 类型推导:省略类型,编译器根据初始值推导
var score = 95.5
var active = true

// 批量声明
var (
    width  int     = 800
    height int     = 600
    title  string  = "Hello"
)

:= 短变量声明

:= 是 Go 中最常用的声明方式,它只能用于函数体内,并且会自动进行类型推导:

package main

import "fmt"

func main() {
    name := "Alice"        // 推导为 string
    age := 25              // 推导为 int
    pi := 3.14             // 推导为 float64
    isStudent := true      // 推导为 bool

    fmt.Println(name, age, pi, isStudent)
}
// 正确:num 是新变量,err 已经存在
num, err := strconv.Atoi("42")

// 错误:name 已经声明过,且没有新变量
// name := "Bob"  // 编译报错:no new variables on left side of :=

// 正确:如果需要在同一作用域重新赋值,使用 = 而非 :=
name = "Bob"

var 与 := 的对比

特性var 声明:= 短声明
使用位置函数内外均可仅函数内部
类型推导支持支持
零值初始化支持(不赋值时为零值)必须赋初始值
批量声明支持 var ()不支持
适用场景包级变量、明确指定类型函数内的局部变量

常量

常量(Constant)

常量是在程序编译期就确定的值,运行期间不可修改。常量只能是基本类型(布尔、数值、字符串)。

const 声明

常量使用 const 关键字声明,声明时必须赋值:

const Pi = 3.14159265
const AppName = "MyApp"
const MaxRetries = 3

// 批量声明
const (
    StatusOK       = 200
    StatusNotFound = 404
    StatusError    = 500
)

// 显式指定类型
const TimeoutSeconds int = 30

iota 枚举器

iota 是 Go 语言的常量计数器,只能在 const 块中使用。在每一个 const 块中,iota0 开始,每新增一行(无论是否使用 iota)自动递增 1

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

iota 的高级用法:

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

const (
    Readable = 1 << iota  // 1 << 0 = 1(二进制: 0001)
    Writable              // 1 << 1 = 2(二进制: 0010)
    Executable            // 1 << 2 = 4(二进制: 0100)
)
const (
    A = iota    // 0
    B           // 1
    C = 100     // 100
    D           // 100(继承上一行的值,iota 仍然递增但未使用)
    E = iota    // 4(iota 恢复显式引用)
)

零值(Zero Value)

Go 中变量声明但不显式初始化时,编译器会自动赋予该类型的零值。这是 Go 的一个重要设计哲学——变量总有明确的初始值,避免了未初始化变量带来的隐患。

类型零值说明
int / int8 / int16 / int32 / int640所有整数类型
uint / uint8 / …0所有无符号整数类型
float32 / float640.0浮点类型
boolfalse布尔类型
string""空字符串
指针nil空指针
切片nil空切片
映射nil空映射
接口nil空接口
通道nil空通道
结构体各字段为零值字段为零值的结构体
func demonstrateZeroValues() {
    var i int
    var f float64
    var b bool
    var s string
    var p *int
    var slice []int

    fmt.Println(i)     // 0
    fmt.Println(f)     // 0
    fmt.Println(b)     // false
    fmt.Println(s)     // ""
    fmt.Println(p)     // <nil>
    fmt.Println(slice) // []
}

命名规范与可见性

Go 语言的命名规则直接影响变量的可见性(作用范围):

命名规则

  • 变量名由字母、数字、下划线组成
  • 必须以字母或下划线开头
  • 区分大小写
  • 不能使用 Go 语言关键字(varconstfuncif 等)

可见性规则

Go 通过首字母大小写来控制访问权限:

首字母可见性示例
大写公开(Exported)——可被其他包访问UserNameMaxSizeDB
小写私有(Unexported)——仅当前包内可访问userNamemaxSizedb
package mypackage

// 公开变量,其他包可以通过 mypackage.AppName 访问
var AppName string = "MyApp"

// 私有变量,仅 mypackage 包内可访问
var config map[string]string

// 公开常量
const Version = "1.0.0"

// 私有常量
const debugMode = true

命名风格建议

作用域

作用域(Scope)

作用域是指变量在程序中可以被访问的范围。Go 语言中的作用域分为:包级作用域、函数级作用域和块级作用域。

包级变量

在函数外部使用 varconst 声明的变量,作用域为整个包:

package main

var globalCount int = 0  // 包级变量,整个包内可见

func increment() {
    globalCount++  // 可以访问包级变量
}

func getCount() int {
    return globalCount
}

局部变量

在函数内部声明的变量,作用域仅限于该函数:

func calculateSum(a, b int) int {
    sum := a + b  // sum 只在 calculateSum 函数内可见
    return sum
}

// 错误:sum 在此处不可访问
// fmt.Println(sum)

块级作用域

ifforswitch 等控制结构中声明的变量,作用域仅限于该块:

func example() {
    x := 10

    if x > 5 {
        y := 20  // y 仅在 if 块内可见
        fmt.Println(x + y)  // 正确
    }
    // fmt.Println(y)  // 编译错误:y 未定义
}
// if 语句初始化部分声明的 err,作用域覆盖整个 if-else 块
if err := doSomething(); err != nil {
    fmt.Println("error:", err)
} else {
    fmt.Println("success")
}
// err 在此处不可访问

// for 语句同理
for i := 0; i < 10; i++ {
    fmt.Println(i)
}
// i 在此处不可访问

变量遮蔽(Shadowing)

内部作用域可以声明与外部同名变量,这会”遮蔽”外层变量:

func shadowing() {
    x := 10
    fmt.Println(x)  // 10

    {
        x := 20  // 遮蔽了外层的 x
        fmt.Println(x)  // 20
    }

    fmt.Println(x)  // 10(外层 x 不受影响)
}

练习题

练习 1:变量声明与零值

声明以下变量但不赋初始值,然后打印它们的值:一个 int、一个 float64、一个 bool、一个 string、一个 *int 指针。请写出代码并说明输出结果。

参考答案

解题思路:使用 var 声明变量但不赋初始值,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>
}

输出

int: 0
float64: 0.000000
bool: false
string: ""
pointer: <nil>

练习 2:iota 枚举练习

使用 iota 定义一个表示文件权限的常量组:Read = 1Write = 2Execute = 4。然后再定义一组表示 HTTP 状态码的常量:Continue = 100SwitchingProtocols = 101OK = 200

参考答案

解题思路:使用 iota 和位运算定义权限常量;使用 iota + 偏移量 定义状态码常量。

代码

package main

import "fmt"

const (
    Read    = 1 << iota  // 1 << 0 = 1
    Write                 // 1 << 1 = 2
    Execute               // 1 << 2 = 4
)

const (
    Continue          = 100 + iota  // 100
    SwitchingProtocols               // 101
    OK                              // 102 —— 注意这里 iota 是 2,值为 102
)

修正版本

const (
    Continue          = 100 + iota  // 100
    SwitchingProtocols               // 101
)

const (
    OK = 200 + iota  // 200(新 const 块中 iota 重置为 0)
)

练习 3:作用域与变量遮蔽

以下代码的输出是什么?请解释原因。

package main

import "fmt"

func main() {
    x := 10

    if x > 5 {
        x := x + 5
        fmt.Println("inside:", x)
    }

    fmt.Println("outside:", x)
}
参考答案

解题思路if 块内使用 := 声明了一个新变量 x,遮蔽了外层的 x

输出

inside: 15
outside: 10

解释

  • if 块内的 x := x + 5 创建了一个新的局部变量 x,它遮蔽了外层的 x
  • 内层的 x 初始化为外层 x 的值 10 加上 5,等于 15
  • if 块结束后,内层 x 销毁,外层 x 保持不变,仍为 10

搜索