3、Go语言基础语法:变量与常量、基本数据类型、类型转换、零值机制
好,咱们正式开始啃 Go 的语法基础。
说实话,我见过不少新手一上来就急着写业务逻辑,结果连变量声明都写错。其实 Go 的语法设计得非常克制,你只要花半小时把今天这几个概念理清楚,后面写代码会顺畅很多。
3.1 变量声明:不止一种写法
Go 的变量声明有好几种方式,我个人的习惯是:能用短变量声明就用短声明,但有些场景必须用 var。
3.1.1 标准 var 声明
var name string = "张三"
var age int = 25
var isStudent bool = false
这种写法最完整,类型和初始值都写出来了。我在项目中写包级别的全局变量时,基本都用这种形式——因为短变量声明在包级别不能用。
3.1.2 类型推断
var name = "张三" // 编译器自动推断为 string
var age = 25 // 自动推断为 int
嗯,这里要注意:Go 是静态类型语言,类型推断只是帮你省了打字,类型在编译期就定死了。你后面不能把 name 赋值为数字。
3.1.3 短变量声明(最常用)
name := "张三"
age := 25
这个 := 符号,说白了就是「声明 + 赋值」的简写。我写函数内部代码时,90% 的情况都用它。但注意:短声明只能在函数内部使用。
:=,编译器直接报错。当时找了半天原因,后来才意识到包级别只能用 var。
3.1.4 多变量声明
var x, y int = 1, 2
a, b := "hello", true
这个特性在交换变量值时特别爽:
a, b = b, a // 不用临时变量,一行搞定
3.2 常量:用 const 锁定值
常量在 Go 里用 const 关键字声明。你想想看,有些值在程序运行期间不应该变,比如数学常数、配置项的 key。
const Pi = 3.14159
const MaxRetries = 3
const AppName string = "MyApp"
我个人习惯:常量名用大写驼峰,这样一眼就能看出它是常量。不过 Go 没有强制要求。
3.2.1 iota:枚举常量的利器
const (
StatusOK = iota // 0
StatusNotFound // 1
StatusError // 2
)
iota 会在每个 const 块中自动递增。我在项目中用它定义错误码、状态枚举,比手写数字靠谱多了。
_ 占位。比如 const ( _ = iota; A; B ),A 就是 1,B 是 2。
3.3 基本数据类型:就这四种
Go 的基本类型不多,但够用。我把它分成四类来讲:
| 类型 | 示例 | 说明 |
|---|---|---|
int |
42 |
整数,根据平台可能是 32 或 64 位 |
float64 |
3.14 |
浮点数,默认是 float64 |
string |
"hello" |
字符串,不可变 |
bool |
true |
布尔值,只有 true/false |
3.3.1 int 的变体
除了 int,还有 int8、int16、int32、int64,以及无符号的 uint 系列。为什么需要这么多?
说白了就是为了节省内存。比如你存年龄,0-120 之间,用 int8 就够了。我在写高性能网络库时,经常用 int32 来对齐内存,避免 padding 浪费。
3.3.2 float 的坑
var a float32 = 0.1
var b float64 = 0.1
fmt.Println(a + a + a == 0.3) // 猜猜结果?
结果是 false。浮点数精度问题,所有语言都有。我的建议是:比较浮点数时不要用 ==,而是用差值判断:
const epsilon = 1e-9
if math.Abs(a+b - 0.3) < epsilon {
fmt.Println("近似相等")
}
3.3.3 string 的不可变性
Go 的字符串是只读的。你不能像 C 语言那样直接修改某个字符:
s := "hello"
// s[0] = 'H' // 编译错误!
要修改?得转成 []byte 再改回来。这个设计是为了保证字符串安全,也方便做哈希。
3.4 类型转换:必须显式
这是 Go 和很多语言不一样的地方。Go 没有隐式类型转换。你想想看,这其实是个好事——代码里每个类型转换都明明白白,不会出现「咦,这里怎么自动转成了 float?」的困惑。
var i int = 42
var f float64 = float64(i) // 必须显式转换
var s string = string(i) // 注意:这是把 42 转成 ASCII 字符 '*',不是 "42"
strconv 包:
s := strconv.Itoa(42) // "42"
n, _ := strconv.Atoi("42") // 42
我曾经在解析配置文件时,忘了把 string 转 int,结果直接拿字符串去计算,编译没报错,但运行结果全错了。从那以后,我每次做类型转换都会多看一眼。
3.5 零值机制:Go 的贴心设计
在 Go 里,变量声明后如果没有赋值,会自动有一个默认值,这就是「零值」。这个设计太贴心了——你永远不会遇到「未初始化变量」的运行时错误。
| 类型 | 零值 |
|---|---|
int |
0 |
float64 |
0.0 |
string |
""(空字符串) |
bool |
false |
指针/切片/map |
nil |
var i int
var s string
var b bool
fmt.Println(i, s, b) // 输出:0 "" false
if config.Port == 0 { config.Port = 8080 }。
嗯,这里要提醒一下:零值不等于 nil。比如 var m map[string]int 的零值是 nil,如果你直接往里面写数据,会 panic。所以 map 和切片一定要用 make 初始化。
3.6 小结
今天的内容其实就四个核心点:
- 变量声明:
var和:=各有用途,函数内多用短声明 - 常量:用
const+iota做枚举很优雅 - 类型转换:必须显式,别指望 Go 帮你自动转
- 零值机制:每个类型都有默认值,但要注意 nil 的陷阱
下一章咱们聊控制结构——if、for、switch,这些才是写逻辑的骨架。