Appearance
02|变量与常量
程序需要存储和操作数据。变量是一块命名的内存空间,里面的值在运行时可以改变;常量是一块在编译期就确定的值,运行过程中不能修改。Go 的变量声明有多种方式,每种适用的场景不同。理解这些差异,能避免在包级别、函数级别和块级别作用域里出现命名冲突或意外覆盖。
一、var 声明
var 是 Go 最基本的变量声明方式,可以在包级别或函数级别使用:
go
package main
import "fmt"
var appName = "go-gateway" // 包级别变量
func main() {
var port int = 8080
var enabled bool = true
fmt.Println(appName, port, enabled)
}var 声明时,类型可以显式写在等号后面,也可以让编译器根据右侧的值自动推断:
go
var port int = 8080 // 显式类型
var host = "0.0.0.0" // 编译器推断为 string多个同类型变量可以合并声明:
go
var host, port = "0.0.0.0", 8080包级别的 var 变量在程序启动时初始化,初始化顺序按它们在代码中出现的顺序执行。如果包 A 依赖包 B 的变量,包 B 的变量会先初始化。
二、短变量声明 :=
函数内部最常用的是 :=,编译器根据右侧的值自动推断类型,不需要写 var 和类型名:
go
func main() {
port := 8080 // int
host := "0.0.0.0" // string
ratio := 0.75 // float64
active := true // bool
fmt.Println(port, host, ratio, active)
}:= 只能在函数内部使用,包级别声明必须用 var。:= 还有一个限制:左侧至少有一个变量是首次在当前作用域声明。如果左侧所有变量都已经在当前作用域存在,编译会报错:
go
port := 8080
port := 9090 // 编译错误:no new variables on left side of :=重新赋值应该用 =:
go
port := 8080
port = 9090 // 正确如果 := 左侧既有新变量也有已存在的变量,已存在的变量会被重新赋值,新变量会被声明。这种"声明+赋值"混合的写法在错误处理里很常见:
go
result, err := doSomething() // 新声明 result 和 err
if err != nil {
return err
}
other, err := doAnother() // other 是新声明,err 是重新赋值
if err != nil {
return err
}三、作用域
变量的可见范围由它声明的位置决定。
包级别作用域:在函数外面用 var 声明的变量,整个包内的文件都能访问。首字母大写的变量还能被其他包导入后访问:
go
package config
var DefaultTimeout = 30 // 其他包可以通过 config.DefaultTimeout 访问
var defaultLogLevel = "info" // 只在 config 包内可见函数级别作用域:在函数内部用 var 或 := 声明的变量,只在函数内部可见。参数和返回值也属于函数级别作用域。
块级别作用域:在 if、for、switch 的代码块里声明的变量,只在该块内部可见:
go
func checkPort(host string) {
if host == "" {
host = "127.0.0.1" // 这个 host 和参数 host 是同一个变量
}
fmt.Println(host)
}
func checkWithLocal() {
if err := connect(); err != nil { // err 只在 if 块内可见
fmt.Println(err)
}
// err 在这里不可见
}短语句里的变量有更小的作用域,这能避免错误变量被误用到后面的代码里。
四、零值
变量声明后如果没有显式赋值,会自动获得该类型的零值。零值不是未定义,也不是 nil:
| 类型 | 零值 | 说明 |
|---|---|---|
string | "" | 空字符串 |
int / int64 | 0 | 数字零 |
float64 | 0.0 | 零 |
bool | false | 假 |
| 指针、接口、切片、映射、通道、函数 | nil | 未指向任何对象 |
零值是 Go 的一个安全设计。变量始终有确定的初始值,不会因为忘记初始化而读到随机数据。但这也有隐患——布尔标志忘了赋值时默认是 false,计数器默认是 0,如果业务逻辑里把它们当成"未配置"处理,就会误判。
五、常量
不会改变的值用 const 声明。常量在编译期确定,不能赋值为运行时才有的结果(比如函数返回值、变量值):
go
const defaultTimeout = 30
const appName = "go-gateway"常量可以没有显式类型,此时它是无类型常量,可以自动匹配多种类型:
go
const maxRetries = 3
var retries int = maxRetries // 匹配为 int
var scale float64 = maxRetries // 也能匹配为 float64需要限制常量类型时,显式声明:
go
const maxTimeout int = 30多个常量可以批量声明:
go
const (
StatusOK = 0
StatusWarning = 1
StatusCritical = 2
)六、iota
iota 用于生成连续的常量值,从 0 开始自动递增。它只在 const 块内有效:
go
const (
StatusOK = iota // 0
StatusWarning // 1
StatusCritical // 2
)iota 常用于枚举状态码,减少手写数字的出错概率。插入新状态时,后续值会自动后移:
go
const (
StatusOK = iota // 0
StatusWarning // 1
StatusUnknown // 新增,值为 2
StatusCritical // 3
)iota 也可以配合位移运算生成位掩码:
go
const (
Read = 1 << iota // 1
Write // 2
Execute // 4
)七、常见错误
包级别用 :=
go
package main
app := "go-gateway" // 编译错误:unexpected := at top level包级别必须用 var。
未使用变量导致编译失败
go
func main() {
var unused string = "hello" // 编译错误:unused 未使用
fmt.Println("ok")
}Go 编译器拒绝编译存在未使用变量的程序。这个规则比 Python、JavaScript 严格,但能减少"声明了但忘了用"的脏代码。如果变量暂时不用,用 _ 接收丢弃的值:
go
result, _ := someFunction() // 忽略第二个返回值:= 在同作用域重复声明
go
port := 80
port := "8080" // 编译错误:port 已经声明为 int同名变量必须保持同一类型。:= 不等于"重新声明",在同一作用域里只能声明一次。
短语句变量作用域混淆
go
func main() {
err := doSomething()
if err != nil {
result, err := doAnother() // 这里的 err 是新的局部变量
fmt.Println(result)
}
// 外层的 err 没有被重新赋值
}if 块内的 := 声明了新的 err,外层的 err 不受影响。如果意图是让外层 err 被更新,不能用 :=:
go
func main() {
err := doSomething()
if err != nil {
var result string
result, err = doAnother() // 重新赋值,不声明新变量
fmt.Println(result)
}
// 外层的 err 被更新了
}