2、Go基础语法:变量声明、常量与iota、基本数据类型与类型转换

好,咱们正式开始啃Go的语法基础。说实话,Go的语法设计得很「干净」,没有那么多花里胡哨的东西。但越是简单的东西,越容易在细节上翻车。我带过不少新人,很多bug其实就出在变量声明和类型转换上。

今天这一章,咱们把变量、常量、基本类型和零值机制一次性讲透。你跟着我走一遍,以后写Go代码会顺手很多。

2.1 变量声明:三种方式,各有各的讲究

Go里声明变量有三种方式,我按使用频率给你排个序。

2.1.1 var 声明 —— 最正统的方式

var 是Go最原始的声明方式。它的好处是——清晰、明确、适合包级别变量。

var name string = "张三"
var age int = 25
var isStudent bool = true

你也可以省略类型,让编译器自己推断:

var name = "张三"   // 编译器自动推断为 string
var age = 25        // 推断为 int

我个人习惯:在函数外面(包级别)声明变量时,我一定用 var。为什么?因为包级别变量如果用短变量声明,编译器会报错。这是Go的硬性规定。

小技巧:如果你声明了变量但不赋值,Go会给它一个「零值」。后面我们会专门讲零值机制。

2.1.2 短变量声明 := —— 最常用的方式

在函数内部,我几乎只用 :=。它简洁、高效,写起来很爽。

func main() {
    name := "张三"
    age := 25
    fmt.Println(name, age)
}

:= 会同时完成声明和初始化,而且类型由编译器自动推断。你想想看,这比Java那种 String name = "张三"; 省了多少事?

注意:= 只能在函数内部使用。在函数外面用,编译器会直接报错。我曾经见过一个新手把包级别变量写成 name := "张三",然后一脸懵地问我为什么编译不过。

2.1.3 多变量声明 —— 批量处理的好帮手

Go支持一次声明多个变量,这在处理多个返回值时特别有用。

// 方式一:使用 var
var x, y int = 10, 20

// 方式二:使用短变量
a, b := "hello", 42

// 方式三:不同类型混搭
var (
    name   string = "张三"
    age    int    = 25
    height float64 = 1.75
)

我在项目中遇到过最典型的场景——调用一个返回多个值的函数:

file, err := os.Open("test.txt")
if err != nil {
    // 处理错误
}

这种写法在Go里随处可见。说白了,多变量声明就是为这种「函数返回多个值」的场景量身定做的。

2.2 常量与 iota —— 枚举的优雅实现

常量用 const 关键字声明。Go的常量在编译时确定,所以性能上没有任何开销。

const Pi = 3.14159
const MaxRetries = 3

但真正让我觉得Go设计得巧妙的地方,是 iota。它用来生成一组递增的常量,特别适合做枚举。

const (
    StatusPending = iota  // 0
    StatusApproved        // 1
    StatusRejected        // 2
    StatusCancelled       // 3
)

为什么会从0开始?因为 iotaconst 块中每出现一次就自增1。你也可以跳过某些值:

const (
    _  = iota           // 0,用下划线忽略
    KB = 1 << (10 * iota)  // 1 << 10 = 1024
    MB                   // 1 << 20 = 1048576
    GB                   // 1 << 30
)

我记得有一次在项目中定义HTTP状态码枚举,用 iota 配合位运算,几行代码就搞定了原本需要几十行switch-case的逻辑。嗯,这就是Go的哲学——用简单的语法解决复杂的问题。

核心要点iota 只在 const 块中有效,每遇到一个 const 块就重置为0。

2.3 基本数据类型 —— 就这几种,但够用

Go的基本类型不多,我列个表给你看:

类型 名称 默认零值 说明
int 整型 0 32位系统占4字节,64位系统占8字节
int8 ~ int64 指定长度整型 0 明确指定位数,节省内存
uint 无符号整型 0 只能存非负数
float32 单精度浮点 0.0 精度约6位小数
float64 双精度浮点 0.0 精度约15位小数,推荐使用
bool 布尔型 false 只有 true 和 false
string 字符串 ""(空字符串) UTF-8编码,不可变

这里我想特别说两点:

第一,关于 float 的选择。 我个人建议,除非你有特殊的内存限制,否则一律用 float64。为什么?因为Go标准库里的数学函数(比如 math.Sqrt)都要求 float64 参数。你用 float32 还得来回转换,麻烦不说,还容易丢精度。

第二,string 是不可变的。 这意味着你不能像C++那样直接修改字符串中的某个字符。我曾经在项目中试图用 s[0] = 'a' 来修改字符串,结果编译直接报错。正确的做法是转成 []byte 再改。

2.4 零值机制 —— Go的贴心设计

Go有一个很人性化的设计——变量声明后如果没有显式赋值,会自动赋予一个「零值」。这跟C语言那种未初始化变量里存着随机值的情况完全不同。

var i int       // i = 0
var f float64   // f = 0.0
var b bool      // b = false
var s string    // s = ""
var p *int      // p = nil

你想想看,这能避免多少bug?我见过太多C语言的bug是因为忘记初始化变量,导致程序行为不可预测。Go直接把这个坑给填平了。

零值规则速记:数值类型为0,布尔为false,字符串为空串,指针/切片/map/channel为nil。

2.5 类型转换 —— Go是强类型语言,别想偷懒

Go是强类型语言,这意味着不同类型之间不能隐式转换。你必须显式地写转换代码。

var i int = 42
var f float64 = float64(i)  // 必须显式转换
var s string = string(i)    // 注意:这是把整数转成对应的Unicode字符,不是数字字符串

这里有个常见的坑——把整数转成字符串。很多人以为 string(42) 会得到 "42",但实际上得到的是 "*"(ASCII码42对应的字符)。正确的做法是用 strconv.Itoa(42)

我曾经在代码审查时看到过这样的bug:

// 错误写法
port := 8080
addr := "localhost:" + string(port)  // 结果:localhost:✈(乱码)

// 正确写法
addr := "localhost:" + strconv.Itoa(port)  // 结果:localhost:8080

嗯,这种错误在Go新手身上特别常见。记住一句话:数值转字符串,用 strconv 包,别用 string()

另外,浮点转整型会直接截断小数部分,不会四舍五入:

var f float64 = 3.99
var i int = int(f)  // i = 3,不是4

如果你需要四舍五入,得用 math.Round 先处理一下。

避坑指南:Go的类型转换是「截断」不是「四舍五入」。做金融计算时一定要注意,我曾经因为这个原因导致报表对不上账,排查了半天才发现是类型转换的问题。

好了,这一章的内容就到这里。变量声明、常量与iota、基本数据类型、零值机制、类型转换——这些都是Go最基础的语法,但也是你写出健壮代码的基石。下一章咱们聊控制结构,if、for、switch这些,到时候你会发现,Go在这些地方也有很多「反常识」的设计。