3、变量与常量:变量声明(var、:=)、常量(const)、iota枚举、基本数据类型(bool、int、float、string)
好,咱们今天聊聊 Go 语言里最基础、也最绕不开的东西——变量和常量。
说实话,我刚开始学 Go 的时候,觉得变量声明不就是个语法糖嘛,有啥好讲的?后来写项目写多了才发现,这里面的门道还真不少。尤其是那个 :=,用好了是真爽,用不好……嗯,你可能会被自己坑一把。
变量声明:var 与 :=
Go 语言里声明变量有两种主流方式:var 和 :=。我个人习惯是,在函数内部多用 :=,在包级别或者需要明确类型时用 var。
先看 var 的几种写法:
// 声明一个变量,默认值为 0
var age int
// 声明并初始化
var name string = "张三"
// 类型推断,Go 会自动推导类型
var city = "北京"
// 批量声明,省事
var (
a int
b string = "hello"
c bool
)
这里有个细节:如果你只声明不赋值,Go 会给每个变量一个「零值」。int 是 0,string 是空字符串,bool 是 false。这个设计我觉得挺贴心的,至少不会像 C 语言那样出现未初始化的野指针。
再说 :=,这是 Go 的「短变量声明」。它只能在函数内部使用,而且必须同时声明和初始化:
func main() {
// 短声明,简洁明了
count := 10
msg := "你好,Go"
isReady := true
// 可以同时声明多个变量
x, y := 100, 200
// 注意:左侧至少有一个新变量
x, z := 300, 400 // 合法,z 是新变量
}
我在项目中遇到过一个问题:有人把 := 用在了包级别,结果编译报错。记住,包级别只能用 var,不能用 :=。这个坑我曾经踩过一次,排查了半天才发现是作用域的问题。
:= 左侧必须至少有一个新变量。如果所有变量都已经声明过,编译器会报错。另外,:= 不能用于全局作用域。
常量:const
常量用 const 关键字声明,一旦赋值就不能改了。说白了,它就是写死在代码里的值。
const Pi = 3.14159
const AppName = "Go学习系统"
// 也可以批量声明
const (
StatusOK = 200
StatusNotFound = 404
StatusError = 500
)
Go 的常量有个特点:它可以是无类型的。比如 Pi 在声明时没有指定类型,它就是一个「无类型浮点常量」。使用时可以隐式转换成需要的类型。这个特性在写一些通用代码时特别方便。
我个人习惯把常量放在文件顶部,或者单独建一个 const.go 文件来管理。尤其是项目里那些魔数(magic number),一定要用常量代替,不然过两个月你自己都看不懂那串数字是什么意思。
iota 实现自增,这在枚举场景下非常有用。咱们下面细说。
iota 枚举
iota 是 Go 语言里一个很巧妙的设计。它会在 const 块中自动递增,从 0 开始。
const (
Monday = iota // 0
Tuesday // 1
Wednesday // 2
Thursday // 3
Friday // 4
Saturday // 5
Sunday // 6
)
你看,我只写了第一个 Monday = iota,后面的变量会自动继承这个模式,依次加 1。省事吧?
更骚的操作是,你可以用 _ 跳过某些值:
const (
_ = iota // 跳过 0
KB = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20 = 1048576
GB // 1 << 30 = 1073741824
)
这个写法我在做文件大小转换时用过,非常优雅。不过要提醒一句:iota 只在 const 块内有效,每次遇到新的 const 块都会重置为 0。
_ 跳过,也可以结合位运算实现复杂枚举。
基本数据类型
Go 的基本数据类型不多,但每个都很实在。咱们一个一个过。
bool(布尔型)
只有两个值:true 和 false。默认值是 false。
var isActive bool
isActive = true
// 短声明
isDone := false
注意,Go 里不能把 0 或 1 当成 bool 用。这点跟 C 语言不一样,刚开始写 Go 的人容易犯这个错。
int(整型)
Go 的整型分有符号和无符号,还有不同位数:
| 类型 | 范围 | 说明 |
|---|---|---|
| int | 32位系统:-2^31 ~ 2^31-1 64位系统:-2^63 ~ 2^63-1 |
平台相关,最常用 |
| int8 | -128 ~ 127 | 8位有符号 |
| int16 | -32768 ~ 32767 | 16位有符号 |
| int32 | -2^31 ~ 2^31-1 | 32位有符号 |
| int64 | -2^63 ~ 2^63-1 | 64位有符号 |
| uint | 0 ~ 平台相关最大值 | 无符号,平台相关 |
| uint8 | 0 ~ 255 | 无符号8位,也叫 byte |
我个人建议,日常开发用 int 就够了。只有在需要精确控制内存或与外部系统交互时,才用 int8、int32 这些。
float(浮点型)
Go 有两种浮点类型:float32 和 float64。没有 float 这个简写。
var price float32 = 19.99
var pi float64 = 3.141592653589793
// 默认推断为 float64
rate := 0.05
为什么没有 float?因为 Go 的设计者希望开发者明确知道自己在用多少位精度。我遇到过一个问题:用 float32 做累加,结果精度丢失导致计算结果偏差。从那以后,涉及金额或精确计算的场景,我坚决用 float64,甚至直接用 decimal 库。
== 比较。因为精度问题,两个看起来相等的浮点数可能在底层不相等。建议用差值小于某个极小值(如 1e-9)来判断。
string(字符串)
Go 的字符串是 UTF-8 编码的,用双引号或反引号包裹:
// 双引号,支持转义
s1 := "Hello\nWorld"
// 反引号,原样输出,适合多行文本
s2 := `第一行
第二行
第三行`
// 字符串拼接
name := "Go"
version := "1.21"
full := name + " " + version // "Go 1.21"
字符串在 Go 里是不可变的。你没法直接修改某个字符,只能重新赋值。这个设计一开始让我有点不习惯,但后来发现它让并发编程更安全了。
另外,获取字符串长度时要注意:len() 返回的是字节数,不是字符数。如果字符串包含中文,一个汉字占 3 个字节(UTF-8 编码)。要获取字符数,得用 utf8.RuneCountInString()。
s := "你好Go"
fmt.Println(len(s)) // 输出 8(2个汉字×3字节 + 2个英文字母)
fmt.Println(utf8.RuneCountInString(s)) // 输出 4(2个汉字 + 2个字母)
这个坑我踩过不止一次。尤其是做字符串截取时,直接用 s[:n] 可能会把中文字符截断,导致乱码。嗯,这里要注意,处理中文时最好用 []rune 转换一下。
strings 包和 strconv 包。前者提供各种字符串操作函数,后者负责字符串和基本类型之间的转换。
总结一下
变量和常量是 Go 语言的基石。虽然看起来简单,但每个细节都值得认真对待。我个人觉得,Go 在这方面的设计很「实在」——没有花里胡哨的语法,但每个特性都恰到好处。
最后送你一句话:写代码时,变量名要起好,常量要定义清楚,类型要选对。这些基础打牢了,后面学什么都会顺手很多。
下一章咱们聊聊流程控制,if、for、switch 这些。到时候见。