在 Go 语言中,变量(Variable)和常量(Constant)是程序存储和操作数据的基本单元。Go 对变量和常量的声明、使用有严格的语法规范,同时提供了灵活的特性以适应不同场景。下面深入讲解 Go 中变量和常量的定义、特性、使用规则及最佳实践。
一、变量(Variable)
变量是程序运行过程中可以修改的存储单元,用于存储临时数据。Go 语言的变量声明方式灵活,且强调 "显式声明、类型安全"。
1. 变量的声明与初始化
Go 提供了多种变量声明方式,核心原则是 "声明即需使用"(未使用的变量会导致编译错误)。
(1)标准声明(显式类型)
语法:
go
var 变量名 类型
声明后可单独初始化,也可在声明时初始化:
go
package main
import "fmt"
func main() {
// 声明变量(默认零值)
var age int // 整数类型默认值:0
var name string // 字符串默认值:""(空字符串)
var isStudent bool// 布尔类型默认值:false
// 声明并初始化
var score float64 = 95.5
var address string = "Beijing"
fmt.Println(age, name, isStudent, score, address)
// 输出:0 false 95.5 Beijing
}
零值规则:未初始化的变量会被赋予默认 "零值",不同类型的零值不同:
-
数值类型(int、float 等):
0
-
布尔类型:
false
-
字符串:
""
(空字符串) -
指针、切片、map 等引用类型:
nil
(2)类型推断(省略类型)
声明变量时若直接初始化,可省略类型,Go 会根据初始值自动推断类型:
go
func main() {
var a = 10 // 推断为 int 类型
var b = 3.14 // 推断为 float64 类型
var c = "hello" // 推断为 string 类型
var d = true // 推断为 bool 类型
fmt.Printf("a: %T, b: %T, c: %T, d: %T\n", a, b, c, d)
// 输出:a: int, b: float64, c: string, d: bool
}
(3)短变量声明(:=
操作符)
在函数内部,可使用 :=
进行更简洁的声明(只能在函数内使用,不能用于包级变量):
go
func main() {
// 短声明:变量名 := 初始值
name := "Alice" // 等价于 var name string = "Alice"
age := 20 // 等价于 var age int = 20
height, weight := 1.75, 65.5 // 同时声明多个变量
fmt.Println(name, age, height, weight)
}
特点:
-
必须至少声明一个新变量(否则编译错误):
go
a := 10 a := 20 // 错误:no new variables on left side of := a, b := 20, 30 // 正确:b是新变量
-
可用于变量重新赋值(但至少有一个新变量):
go
x, y := 1, 2 x, z := 3, 4 // 正确:x重新赋值,z是新变量
(4)批量声明
使用 var
块批量声明变量,适合声明多个同类型或不同类型的变量:
go
// 包级批量声明(可在函数外)
var (
version string = "1.0.0"
port int = 8080
debug bool = false
)
func main() {
// 函数内批量声明
var (
name string
age int
)
name = "Bob"
age = 25
fmt.Println(name, age, version, port)
}
2. 变量的作用域
变量的作用域指其可被访问的代码范围,Go 中变量作用域分为:
(1)包级变量(Package-level)
-
声明在函数外,整个包内可见(跨文件,只要在同一包中)
-
首字母大写的包级变量可被其他包访问(导出变量)
go
运行
package main
import "fmt"
// 包级变量(整个包可见)
var globalVar = "I'm global"
func printGlobal() {
fmt.Println(globalVar) // 可访问包级变量
}
func main() {
printGlobal() // 输出:I'm global
}
(2)函数级变量(Function-level)
-
声明在函数内,仅在该函数内可见
-
包括函数参数和函数内声明的变量
go
运行
func add(a, b int) int { // a、b是函数参数,作用域为add函数内
sum := a + b // sum是函数内变量,作用域为add函数内
return sum
}
func main() {
// fmt.Println(sum) // 错误:sum未定义(超出作用域)
}
(3)块级变量(Block-level)
-
声明在代码块内(如
if
、for
、switch
的{}
中) -
仅在当前代码块内可见
go
运行
func main() {
if flag := true; flag { // flag的作用域仅限if块内
fmt.Println("flag is true")
}
// fmt.Println(flag) // 错误:flag未定义
}
3. 变量的类型
Go 是强类型语言,变量类型一旦确定(或被推断),就不能存储其他类型的值:
go
运行
func main() {
var a int = 10
// a = "hello" // 错误:cannot assign string to type int
var b interface{} = 10 // 空接口可存储任意类型
b = "hello" // 正确:空接口类型可变
}
二、常量(Constant)
常量是程序运行过程中不可修改的固定值,用于存储编译期即可确定的不变数据(如配置参数、数学常数等)。
1. 常量的声明与初始化
常量声明使用 const
关键字,必须在声明时初始化(且初始值必须是编译期可计算的表达式)。
(1)基本声明
go
运行
func main() {
// 显式类型
const Pi float64 = 3.1415926
// 类型推断
const Version = "1.0.0"
// 布尔常量
const Debug = false
fmt.Println(Pi, Version, Debug)
}
(2)批量声明
与变量类似,常量也支持批量声明:
go
运行
const (
MaxSize = 1024
MinSize = 1
DefaultTimeout = 30 // 单位:秒
)
2. 常量的特性
-
不可修改:常量声明后不能重新赋值(编译错误):
go
运行
const Pi = 3.14 // Pi = 3.1415 // 错误:cannot assign to Pi
-
编译期确定:常量的值必须在编译时可计算,不能依赖运行时数据:
go
运行
// 错误:rand.Int() 是运行时函数,不能用于常量初始化 // const Random = rand.Int()
-
隐式类型转换:常量之间支持隐式类型转换(变量不支持):
go
运行
const a int = 10 const b float64 = a // 正确:常量隐式转换 // var c float64 = a // 错误:变量需要显式转换(float64(a))
3. iota:常量计数器
iota
是 Go 的特殊常量,用于生成自增序列,仅在 const
声明块中有效,每次 const
声明块中 iota
从 0 开始,每新增一行常量声明,iota
自动加 1。
(1)基本用法
go
运行
func main() {
const (
c0 = iota // c0 = 0
c1 // c1 = 1(默认使用上一行的表达式 iota)
c2 // c2 = 2
)
fmt.Println(c0, c1, c2) // 输出:0 1 2
}
(2)进阶用法
iota
可结合表达式生成复杂序列:
go
运行
const (
_ = iota // 忽略第一个值(从0开始)
KB = 1 << (10 * iota) // 1 << 10(1024),iota=1
MB = 1 << (10 * iota) // 1 << 20(1048576),iota=2
GB = 1 << (10 * iota) // 1 << 30,iota=3
)
const (
North = iota // 0
East // 1
South // 2
West // 3
)
func main() {
fmt.Println(KB, MB, GB) // 输出:1024 1048576 1073741824
fmt.Println(North, East, South, West) // 输出:0 1 2 3
}
(3)重置计数器
每次进入新的 const
块,iota
都会重置为 0:
go
运行
const (
a = iota // 0
b // 1
)
const (
c = iota // 0(新块,重置)
d // 1
)
三、变量与常量的核心区别
特性 | 变量(Variable) | 常量(Constant) |
---|---|---|
关键字 | var 或 := (函数内) |
const |
可修改性 | 运行时可修改 | 不可修改(编译期确定) |
初始化要求 | 可声明后初始化(默认零值) | 必须声明时初始化 |
类型推断 | 支持(声明时初始化可省略类型) | 支持 |
作用域 | 包级、函数级、块级 | 包级、函数级、块级 |
初始化值来源 | 可来自运行时计算(如函数返回值) | 必须是编译期可计算的表达式 |
隐式转换 | 不支持(需显式转换) | 支持(同类型系列的常量间) |
四、最佳实践与常见错误
1. 变量使用的最佳实践
-
优先使用短声明(
:=
) :函数内变量推荐用:=
简化代码,但避免过度使用导致可读性下降。 -
最小作用域原则
:变量声明在使用的最近位置(如循环内的变量声明在循环内),减少命名冲突。
go
运行
// 推荐 for i := 0; i < 10; i++ { ... } // i的作用域仅限循环内 // 不推荐(扩大了作用域) var i int for i = 0; i < 10; i++ { ... }
-
避免未使用的变量 :Go 编译器会报错,需删除或用
_
替换(但_
不能单独作为变量使用)。
2. 常量使用的最佳实践
-
用常量存储固定值:如配置参数(端口号、超时时间)、数学常数等,增强代码可维护性。
-
枚举场景用
iota
:替代魔法数字,使代码更清晰(如状态码、方向枚举)。
go
运行
// 推荐:用iota定义枚举 const ( StatusSuccess = iota // 0 StatusError // 1 StatusTimeout // 2 ) // 不推荐:魔法数字 // if code == 0 { ... } // 0代表什么?
3. 常见错误
-
函数外使用
:=
::=
只能在函数内使用,包级变量必须用var
声明:go
运行
// 错误:函数外不能用 := // name := "global" // 正确 var name = "global"
-
常量使用运行时数据初始化:
go
运行
import "time" // 错误:time.Now() 是运行时函数 // const StartTime = time.Now()
-
变量类型不匹配:Go 是强类型,不同类型变量赋值需显式转换:
go
运行
var a int = 10 var b float64 = float64(a) // 正确:显式转换 // var b float64 = a // 错误:类型不匹配
总结
变量和常量是 Go 语言的基础构建块:
-
变量 :用于存储可变数据,支持多种声明方式(
var
、:=
等),有明确的作用域和类型,需显式转换。 -
常量 :用于存储不可变数据,必须在声明时初始化,值在编译期确定,
iota
简化了枚举序列的生成。
理解两者的特性和使用规则,能帮助你编写更规范、高效的 Go 代码,尤其是 iota
的灵活运用和变量作用域的合理控制,是写出高质量 Go 程序的关键。