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。

核心要点: iota 从 0 开始,每行递增 1。可以用 _ 跳过,也可以结合位运算实现复杂枚举。

基本数据类型

Go 的基本数据类型不多,但每个都很实在。咱们一个一个过。

bool(布尔型)

只有两个值:truefalse。默认值是 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 就够了。只有在需要精确控制内存或与外部系统交互时,才用 int8int32 这些。

float(浮点型)

Go 有两种浮点类型:float32float64。没有 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 这些。到时候见。