第二章:Go语法进阶——变量、常量、数据类型与流程控制
好,咱们接着往下聊。上一章我们把Go的环境搭好了,也跑通了第一个Hello World。这一章,咱们得把地基打牢——变量、常量、数据类型、流程控制,这些是写任何Go程序都绕不开的东西。
说实话,Go的语法设计得很克制。它不像某些语言那样花里胡哨,但每个设计都有它的道理。我刚开始从Python转Go的时候,觉得这语言怎么这么啰嗦?用久了才发现,这种啰嗦恰恰是它的优点——代码可读性极强,团队协作时几乎不会产生歧义。
2.1 变量声明:Go的几种写法
Go的变量声明有好几种方式,咱们一个一个来看。
2.1.1 标准声明
最传统的写法,用var关键字。我个人习惯在包级别变量或者需要明确类型时用这种。
var name string = "张三"
var age int = 25
var isStudent bool = true
2.1.2 类型推断
如果声明的同时赋值,Go可以自动推断类型。省事多了。
var name = "张三" // 自动推断为 string
var age = 25 // 自动推断为 int
2.1.3 短变量声明(最常用)
在函数内部,用:=是最爽的写法。我写业务代码时,90%的场景都用这个。
name := "张三"
age := 25
:=,包级别变量用var。这样代码看起来层次分明,别人一看就知道变量的作用域。
2.1.4 多变量声明
一次声明多个变量,Go也支持得很优雅。
var x, y int = 1, 2
a, b := "hello", 42
我曾经在写一个配置解析器时,一口气声明了七八个变量,用这种多变量声明方式,代码看起来特别清爽。
2.2 常量:用const锁死它
常量就是不会变的值。Go里用const关键字声明。
const Pi = 3.14159
const MaxRetries = 3
常量可以是字符、字符串、布尔或数值类型。注意,常量不能用:=声明。
2.2.1 iota:枚举的利器
Go没有专门的枚举类型,但iota可以帮我们实现类似的效果。我第一次看到iota时觉得这玩意儿有点玄乎,用多了才发现真香。
const (
StatusPending = iota // 0
StatusRunning // 1
StatusDone // 2
StatusFailed // 3
)
iota在const块中每行递增1。如果你跳过某行,它也会继续递增。
iota只在const块中有效。我曾经在函数内部试图用iota,结果编译报错,愣是找了半天才反应过来。
2.3 基本数据类型
Go的数据类型不算多,但每个都很实在。咱们快速过一遍。
2.3.1 布尔型
bool,只有true和false。没啥好说的,注意Go不允许把非布尔值当布尔用。
var isReady bool = true
2.3.2 数值型
整数类型分有符号和无符号,还有不同位数。
| 类型 | 范围 | 用途 |
|---|---|---|
int |
32位或64位(取决于平台) | 通用整数 |
int8 |
-128 ~ 127 | 小范围整数 |
int16 |
-32768 ~ 32767 | 中等范围 |
int32 |
-2^31 ~ 2^31-1 | 常用 |
int64 |
-2^63 ~ 2^63-1 | 大整数 |
uint |
0 ~ 取决于平台 | 非负整数 |
uint8 |
0 ~ 255 | 字节 |
float32 |
约±3.4e38 | 单精度浮点 |
float64 |
约±1.8e308 | 双精度浮点(推荐) |
我个人建议,整数默认用int,浮点默认用float64。除非你有明确的性能或内存约束,否则别瞎折腾。
2.3.3 字符串
Go的字符串是UTF-8编码的,不可变。用双引号或反引号。
s1 := "hello"
s2 := `多行
字符串`
字符串拼接用+,但大量拼接时建议用strings.Builder,性能更好。
2.3.4 零值
Go有个很贴心的设计:变量声明后如果没有赋值,会自动给一个零值。不会像C语言那样出现未初始化的问题。
var i int // 0
var f float64 // 0.0
var s string // ""
var b bool // false
map变量,结果运行时直接panic。因为map的零值是nil,往nil map里写数据会崩溃。记住:map、slice、channel这些引用类型,用之前一定要make一下。
2.4 流程控制
Go的流程控制关键字不多,但每个都设计得很精炼。
2.4.1 if 语句
Go的if没有括号,但必须有花括号。这个设计我很喜欢,强迫你写出结构清晰的代码。
if score >= 90 {
fmt.Println("优秀")
} else if score >= 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
Go的if还支持一个特殊写法:在条件前加一个简单语句。
if err := doSomething(); err != nil {
fmt.Println("出错了:", err)
}
这个写法在Go里非常常见,尤其是处理错误时。变量err的作用域只在if-else块内,不会污染外部命名空间。
2.4.2 for 循环
Go只有一个循环关键字——for。但它能玩出三种花样。
标准for循环:
for i := 0; i < 10; i++ {
fmt.Println(i)
}
类似while的写法:
n := 0
for n < 10 {
n++
}
无限循环:
for {
// 一直跑,直到 break
}
我写后台服务时,经常用无限循环配合select做事件驱动。这个后面讲到并发时再细说。
2.4.3 switch 语句
Go的switch比C语言聪明多了。它默认每个case后面自动break,不会往下穿透。如果你想穿透,得显式用fallthrough。
day := "周一"
switch day {
case "周一", "周二", "周三", "周四", "周五":
fmt.Println("工作日")
case "周六", "周日":
fmt.Println("周末")
default:
fmt.Println("未知")
}
Go的switch还可以不带表达式,直接当if-else链用。
score := 85
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
default:
fmt.Println("C")
}
这种写法在条件分支较多时,比if-else链更清晰。我个人很喜欢用。
2.5 函数与多返回值
函数是Go的核心。Go的函数有几个特点:支持多返回值、支持命名返回值、函数是一等公民。
2.5.1 基本函数定义
func add(a int, b int) int {
return a + b
}
如果参数类型相同,可以简写:
func add(a, b int) int {
return a + b
}
2.5.2 多返回值
这是Go最实用的特性之一。最常见的用法是返回结果和错误。
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为0")
}
return a / b, nil
}
调用时:
result, err := divide(10, 2)
if err != nil {
fmt.Println("出错了:", err)
} else {
fmt.Println("结果:", result)
}
这种模式在Go里无处不在。我刚开始写Go时觉得每次都要检查err很烦,但写久了发现,这种显式的错误处理反而让代码更健壮。你不会忘记处理错误,因为编译器会提醒你。
2.5.3 命名返回值
Go允许给返回值起名字。这样在函数体内可以直接给这些变量赋值,最后用return裸返回。
func getRectangle(width, height float64) (area, perimeter float64) {
area = width * height
perimeter = 2 * (width + height)
return
}
命名返回值在复杂函数中很有用,但别滥用。我建议只在返回值含义明确、函数逻辑清晰时使用。
2.5.4 可变参数
如果你不确定参数个数,可以用...。
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// 调用
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(1, 2, 3, 4)) // 10
2.5.5 函数作为值
在Go里,函数也是一种类型。你可以把函数赋值给变量,或者作为参数传递。
// 函数赋值给变量
add := func(a, b int) int {
return a + b
}
fmt.Println(add(3, 4))
// 函数作为参数
func applyOperation(a, b int, op func(int, int) int) int {
return op(a, b)
}
fmt.Println(applyOperation(5, 3, add))
这个特性在写回调函数、中间件时特别有用。后面讲HTTP服务时,你会看到大量这种用法。
2.6 小结
这一章的内容不少,但都是基本功。变量声明、常量、数据类型、流程控制、函数——这些是写任何Go程序都离不开的。
我个人觉得,Go的语法设计很务实。它没有太多花哨的语法糖,但每个特性都解决了一个实际问题。比如多返回值解决了错误处理的痛点,短变量声明让代码更简洁,iota让枚举更优雅。
下一章,咱们要进入Go的核心领域——复合类型。数组、切片、映射、结构体,这些才是真正让你写出复杂程序的基础。准备好了吗?