Add Chapter 2: Go Basics - Variables, Types, and Flow Control (Deep Dive)

This commit is contained in:
openclaw
2026-03-23 22:12:24 +00:00
parent b0ceda8c88
commit 3e88386e17
3 changed files with 1140 additions and 0 deletions

View File

@@ -0,0 +1,896 @@
# 第二章Go 基础语法 —— 变量、类型与流程控制
> **本章目标**:深入理解 Go 的变量声明机制、核心数据类型、运算符体系及流程控制结构,掌握 Go 独特的"零值"哲学和"类型推断"特性。
## 2.1 Go 的变量声明机制
Go 语言的变量声明方式灵活多样,理解其背后的机制是编写高效代码的基础。
### 2.1.1 四种声明方式
#### 1. `var` 声明(显式类型)
```go
var name string = "Alice"
var age int = 25
var isActive bool = true
```
**深度解析**
- `var` 是 Go 中**最正式**的声明方式
- 可以省略初始化,此时变量会被赋予**零值**(见 2.2 节)
- 支持在函数内外声明(包级变量和局部变量)
#### 2. `var` 声明(类型推断)
```go
var name = "Bob" // 编译器推断为 string
var count = 100 // 编译器推断为 int
var ratio = 3.14 // 编译器推断为 float64
```
**深度解析**
- 当提供初始值时Go 编译器会自动推断类型
- 这是**推荐**的声明方式,代码更简洁
- 类型一旦推断不可更改Go 是静态类型语言)
#### 3. 短变量声明 `:=`(最常用)
```go
name := "Charlie"
age := 30
isValid := true
```
**深度解析**
- `:=``var` + 类型推断的**语法糖**
- **只能在函数内部使用**(不能在包级作用域使用)
- 如果变量已存在,会**重新赋值**而非声明新变量
- 可以混合新旧变量:`a, b := 1, 2`(即使 `a` 已存在)
```go
// 错误示例:包级作用域不能使用 :=
package main
// x := 10 // 编译错误!
func main() {
y := 20 // 正确
}
```
#### 4. 多变量声明
```go
// 显式类型
var (
name string = "David"
age int = 28
)
// 类型推断
var (
city = "Beijing"
score = 95.5
)
// 短声明
x, y, z := 1, 2, 3
```
**深度解析**
- 使用 `var ()` 块可以批量声明变量,适合包级变量
- 短声明 `:=` 支持多个变量同时声明
- **注意**`:=` 左侧至少有一个变量是新的,否则是赋值操作
```go
a, b := 1, 2 // 声明两个新变量
a, c := 3, 4 // a 已存在c 是新变量(合法)
a, b := 5, 6 // a 和 b 都已存在,编译错误!
```
### 2.1.2 变量作用域
Go 的作用域规则非常清晰:
```go
package main
import "fmt"
// 包级变量(全局作用域)
var globalVar = "I am global"
func main() {
// 函数级变量
localVar := "I am local"
if true {
// 块级变量if 块内)
blockVar := "I am in if block"
fmt.Println(blockVar)
}
// fmt.Println(blockVar) // 编译错误blockVar 在 if 块外不可见
fmt.Println(localVar)
fmt.Println(globalVar)
}
```
**深度解析**
- **包级作用域**:在 `package` 声明后,任何函数外声明的变量
- **函数作用域**:在函数内部声明的变量
- **块级作用域**:在 `{}` 代码块内声明的变量if、for、switch 等)
- 内层作用域可以**遮蔽**外层同名变量
```go
func shadowExample() {
x := 10
if true {
x := 20 // 创建了新变量 x遮蔽了外层的 x
fmt.Println(x) // 输出 20
}
fmt.Println(x) // 输出 10外层 x 未受影响)
}
```
## 2.2 Go 的"零值"哲学
Go 语言有一个非常优雅的设计:**未初始化的变量会自动获得零值**。
### 2.2.1 零值表
| 类型 | 零值 | 说明 |
|------|------|------|
| `int`, `int8`, `int16`, `int32`, `int64` | `0` | 整数类型 |
| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | `0` | 无符号整数 |
| `float32`, `float64` | `0.0` | 浮点数 |
| `complex64`, `complex128` | `0 + 0i` | 复数 |
| `bool` | `false` | 布尔值 |
| `string` | `""` | 空字符串 |
| 指针、函数、接口、切片、映射、通道 | `nil` | 引用类型 |
| 数组 | 数组元素全为零值 | 值类型 |
### 2.2.2 零值的实际意义
```go
package main
import "fmt"
func main() {
var intVar int
var floatVar float64
var boolVar bool
var stringVar string
var ptrVar *int
fmt.Printf("int: %d\n", intVar) // 0
fmt.Printf("float: %f\n", floatVar) // 0.000000
fmt.Printf("bool: %v\n", boolVar) // false
fmt.Printf("string: '%s'\n", stringVar) // ''
fmt.Printf("ptr: %v\n", ptrVar) // <nil>
// 零值判断
if stringVar == "" {
fmt.Println("字符串为空")
}
if ptrVar == nil {
fmt.Println("指针为空")
}
}
```
**深度解析**
- 零值机制避免了"未初始化变量"的运行时错误
- 在结构体中,未设置的字段自动为零值
- 这是 Go 语言**安全性**的重要体现
```go
type User struct {
Name string
Age int
Email string
}
func main() {
u := User{} // 所有字段为零值
fmt.Printf("%+v\n", u) // {Name: Age:0 Email:}
// 零值判断
if u.Name == "" {
fmt.Println("用户未设置名称")
}
}
```
## 2.3 核心数据类型详解
### 2.3.1 整型Integer Types
Go 提供了多种整型,适应不同场景:
```go
var (
a int8 // 8 位有符号整数,范围:-128 ~ 127
b int16 // 16 位有符号整数,范围:-32768 ~ 32767
c int32 // 32 位有符号整数,范围:-2147483648 ~ 2147483647
d int64 // 64 位有符号整数,范围:-9223372036854775808 ~ 9223372036854775807
e uint8 // 8 位无符号整数范围0 ~ 255
f uint16 // 16 位无符号整数范围0 ~ 65535
g uint32 // 32 位无符号整数范围0 ~ 4294967295
h uint64 // 64 位无符号整数范围0 ~ 18446744073709551615
i int // 平台相关32 位系统为 int3264 位系统为 int64
j uint // 平台相关32 位系统为 uint3264 位系统为 uint64
)
```
**深度解析**
- `int``uint` 的大小取决于**平台架构**32 位或 64 位)
- 优先使用 `int``int64`,除非有特殊内存优化需求
- `byte``uint8` 的别名,常用于处理二进制数据
- `rune``int32` 的别名,用于表示 Unicode 字符
```go
package main
import "fmt"
func main() {
var b byte = 'A' // byte 是 uint8 的别名
var r rune = '中' // rune 是 int32 的别名,可存储 Unicode 字符
fmt.Printf("byte: %c, %d\n", b, b) // A, 65
fmt.Printf("rune: %c, %d\n", r, r) // 中20013
// 字节数测试
fmt.Printf("len('A'): %d\n", len("A")) // 1
fmt.Printf("len('中'): %d\n", len("中")) // 3UTF-8 编码占 3 字节)
}
```
### 2.3.2 浮点型Floating-Point Types
```go
var (
a float32 = 3.14
b float64 = 3.141592653589793
)
```
**深度解析**
- `float64` 是**默认**浮点类型,精度更高
- `float32` 节省内存,适合对精度要求不高的场景(如图形处理)
- 浮点数比较时注意精度问题
```go
package main
import "fmt"
import "math"
func main() {
a := 0.1 + 0.2
b := 0.3
fmt.Printf("a == b: %v\n", a == b) // false精度问题
fmt.Printf("a: %.20f\n", a) // 0.30000000000000004441
// 正确比较方式
epsilon := 1e-9
if math.Abs(a-b) < epsilon {
fmt.Println("a 和 b 近似相等")
}
}
```
### 2.3.3 复数类型Complex Types
```go
var (
c1 complex64 = 1 + 2i
c2 complex128 = 3.5 + 4.5i
)
```
**深度解析**
- `i``j` 表示虚部
- 较少使用,主要用于科学计算和信号处理
### 2.3.4 布尔类型Boolean Type
```go
var (
isTrue bool = true
isFalse bool = false
)
```
**深度解析**
- 只有 `true``false` 两个值
- **不能**与整数互相转换(`0` 不等于 `false`
- 逻辑运算符:`&&`(与)、`||`(或)、`!`(非)
```go
package main
import "fmt"
func main() {
a, b := true, false
fmt.Printf("a && b: %v\n", a && b) // false
fmt.Printf("a || b: %v\n", a || b) // true
fmt.Printf("!a: %v\n", !a) // false
// 短路求值
if false && someExpensiveFunction() {
// someExpensiveFunction() 不会被执行
}
}
```
### 2.3.5 字符串类型String Type
```go
var s1 string = "Hello"
var s2 string = "世界"
var s3 string = `多行
字符串`
```
**深度解析**
- 字符串是**不可变**的字节序列UTF-8 编码)
- 使用双引号 `"` 或反引号 `` ` ``
- 反引号表示**原始字符串**,不转义任何字符
- 字符串长度使用 `len()`,但返回的是**字节数**而非字符数
```go
package main
import "fmt"
func main() {
s := "Hello 世界"
fmt.Printf("len(s): %d\n", len(s)) // 11字节数
fmt.Printf("s[0]: %c\n", s[0]) // H
// fmt.Printf("s[6]: %c\n", s[6]) // 可能乱码(中文字符占 3 字节)
// 遍历字符串(按 rune
for i, r := range s {
fmt.Printf("位置 %d: 字符 %c (Unicode: %d)\n", i, r, r)
}
// 字符串拼接
s1 := "Hello"
s2 := "World"
s3 := s1 + " " + s2
// 使用 strings.Builder高性能拼接
var builder strings.Builder
builder.WriteString("Hello")
builder.WriteString(" ")
builder.WriteString("World")
fmt.Println(builder.String())
}
```
**注意**:需要导入 `strings` 包。
## 2.4 类型转换与类型推断
### 2.4.1 显式类型转换
Go **不支持**隐式类型转换,必须显式转换:
```go
package main
import "fmt"
func main() {
var a int = 42
var b float64 = float64(a) // 显式转换
var c int = int(b) // 显式转换
fmt.Printf("a: %d, type: %T\n", a, a)
fmt.Printf("b: %f, type: %T\n", b, b)
fmt.Printf("c: %d, type: %T\n", c, c)
// 错误示例:隐式转换(编译错误)
// var x int = 3.14 // 编译错误!
}
```
**深度解析**
- 转换必须在**兼容类型**之间进行
- 转换可能导致**精度丢失**或**溢出**
- 使用 `int64(float64)` 转换时注意范围检查
```go
package main
import "fmt"
import "math"
func main() {
var f float64 = 1.9
var i int = int(f) // 截断小数部分,结果为 1
fmt.Printf("int(1.9): %d\n", i) // 1
// 四舍五入
i = int(math.Round(f))
fmt.Printf("round(1.9): %d\n", i) // 2
// 溢出检查
var largeFloat float64 = 1e20
var smallInt int = int(largeFloat) // 可能溢出
fmt.Printf("largeFloat -> int: %d\n", smallInt)
}
```
### 2.4.2 类型推断的边界
```go
package main
import "fmt"
func main() {
// 类型推断
a := 10 // int
b := 3.14 // float64
c := "hello" // string
d := true // bool
// 无法推断(需要显式类型)
var e // 编译错误!必须提供类型或初始值
// 混合推断
f, g, h := 1, 2.5, "test"
fmt.Printf("f: %T, g: %T, h: %T\n", f, g, h)
}
```
## 2.5 运算符体系
### 2.5.1 算术运算符
```go
a, b := 10, 3
// 加法、减法、乘法、除法、取余
sum := a + b // 13
diff := a - b // 7
product := a * b // 30
quotient := a / b // 3整数除法
remainder := a % b // 1
// 注意:整数除法会截断小数部分
fmt.Printf("10 / 3 = %d\n", 10/3) // 3
fmt.Printf("10.0 / 3.0 = %f\n", 10.0/3.0) // 3.333333
```
### 2.5.2 比较运算符
```go
a, b := 10, 20
// 等于、不等于、大于、小于、大于等于、小于等于
fmt.Println(a == b) // false
fmt.Println(a != b) // true
fmt.Println(a > b) // false
fmt.Println(a < b) // true
fmt.Println(a >= b) // false
fmt.Println(a <= b) // true
```
**注意**:字符串也可以比较(按字典序):
```go
fmt.Println("apple" < "banana") // true
```
### 2.5.3 逻辑运算符
```go
a, b := true, false
fmt.Println(a && b) // false
fmt.Println(a || b) // true
fmt.Println(!a) // false
```
**短路求值**
```go
func expensive() bool {
fmt.Println("expensive function called")
return true
}
if false && expensive() {
// expensive() 不会被调用
}
```
### 2.5.4 位运算符
```go
a, b := 6, 3 // 6 = 110, 3 = 011
fmt.Printf("a & b: %d\n", a & b) // 2 (010)
fmt.Printf("a | b: %d\n", a | b) // 7 (111)
fmt.Printf("a ^ b: %d\n", a ^ b) // 5 (101)
fmt.Printf("a &^ b: %d\n", a &^ b) // 4 (100) 位清除
fmt.Printf("a << 1: %d\n", a << 1) // 12 (1100)
fmt.Printf("a >> 1: %d\n", a >> 1) // 3 (011)
```
### 2.5.5 赋值运算符
```go
a := 10
a += 5 // a = a + 5
a -= 3 // a = a - 3
a *= 2 // a = a * 2
a /= 4 // a = a / 4
a %= 3 // a = a % 3
a &= 1 // a = a & 1
a |= 2 // a = a | 2
a ^= 4 // a = a ^ 4
a <<= 1 // a = a << 1
a >>= 1 // a = a >> 1
```
## 2.6 流程控制
### 2.6.1 if-else 语句
```go
score := 85
if score >= 90 {
fmt.Println("优秀")
} else if score >= 80 {
fmt.Println("良好")
} else if score >= 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
```
**深度解析**
- `if` 条件前可以有一个**简短语句**
- 简短语句的作用域仅限于 `if-else`
```go
if x := computeValue(); x > 0 {
fmt.Println("x 为正数:", x)
} else {
fmt.Println("x 为非正数:", x)
}
// fmt.Println(x) // 编译错误x 在 if 块外不可见
```
### 2.6.2 switch 语句
```go
day := 3
switch day {
case 1:
fmt.Println("星期一")
case 2:
fmt.Println("星期二")
case 3, 4, 5:
fmt.Println("工作日")
case 6, 7:
fmt.Println("周末")
default:
fmt.Println("无效日期")
}
```
**深度解析**
- `switch` 默认包含 `break`,不需要手动写
- 可以使用 `fallthrough` 继续执行下一个 case
- 支持**无表达式**的 switch类似 if-else 链)
```go
// 多值 case
switch day {
case 1, 2, 3, 4, 5:
fmt.Println("工作日")
case 6, 7:
fmt.Println("周末")
}
// fallthrough
switch day {
case 1:
fmt.Println("星期一")
fallthrough // 继续执行 case 2
case 2:
fmt.Println("星期二(包含星期一)")
}
// 无表达式 switch
switch {
case score >= 90:
fmt.Println("优秀")
case score >= 80:
fmt.Println("良好")
default:
fmt.Println("其他")
}
```
### 2.6.3 for 循环
Go 只有 `for` 一种循环结构,但功能强大:
```go
// 1. 传统 for 循环
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// 2. while 风格(省略初始化)
i := 0
for i < 5 {
fmt.Println(i)
i++
}
// 3. 无限循环
for {
// ...
break // 必须退出
}
// 4. range 遍历
slice := []int{1, 2, 3, 4, 5}
for index, value := range slice {
fmt.Printf("索引 %d: 值 %d\n", index, value)
}
// 5. 只遍历索引
for i := range slice {
fmt.Println(i)
}
// 6. 只遍历值(使用 _ 忽略索引)
for _, v := range slice {
fmt.Println(v)
}
```
**深度解析**
- `range` 遍历字符串时,返回的是 **runeUnicode 字符)** 而非字节
- `for` 循环的初始化语句中的变量,作用域仅限于循环
```go
// 字符串遍历
s := "Hello"
for i, r := range s {
fmt.Printf("位置 %d: 字符 %c\n", i, r)
}
// 作用域测试
for i := 0; i < 3; i++ {
fmt.Println(i)
}
// fmt.Println(i) // 编译错误i 在循环外不可见
```
### 2.6.4 break 和 continue
```go
// break跳出当前循环
for i := 0; i < 10; i++ {
if i == 5 {
break
}
fmt.Println(i) // 输出 0-4
}
// continue跳过本次循环
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue
}
fmt.Println(i) // 输出 1, 3, 5, 7, 9
}
// 带标签的 break/continue跳出多层循环
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == 1 && j == 1 {
break outer // 跳出外层循环
}
fmt.Printf("i=%d, j=%d\n", i, j)
}
}
```
## 2.7 深度实践:综合案例
### 2.7.1 简易计算器
```go
package main
import (
"fmt"
"strconv"
)
func main() {
var num1, num2 float64
var operator string
fmt.Print("请输入第一个数字:")
fmt.Scan(&num1)
fmt.Print("请输入运算符 (+, -, *, /)")
fmt.Scan(&operator)
fmt.Print("请输入第二个数字:")
fmt.Scan(&num2)
var result float64
var valid bool
switch operator {
case "+":
result = num1 + num2
valid = true
case "-":
result = num1 - num2
valid = true
case "*":
result = num1 * num2
valid = true
case "/":
if num2 == 0 {
fmt.Println("错误:除数不能为零")
valid = false
} else {
result = num1 / num2
valid = true
}
default:
fmt.Println("错误:无效的运算符")
valid = false
}
if valid {
fmt.Printf("结果:%.2f %s %.2f = %.2f\n", num1, operator, num2, result)
}
}
```
### 2.7.2 素数判断
```go
package main
import "fmt"
func isPrime(n int) bool {
if n <= 1 {
return false
}
if n == 2 {
return true
}
if n%2 == 0 {
return false
}
// 只需检查到 sqrt(n)
for i := 3; i*i <= n; i += 2 {
if n%i == 0 {
return false
}
}
return true
}
func main() {
fmt.Println("1 到 100 之间的素数:")
count := 0
for i := 1; i <= 100; i++ {
if isPrime(i) {
fmt.Printf("%d ", i)
count++
if count%10 == 0 {
fmt.Println()
}
}
}
fmt.Printf("\n共 %d 个素数\n", count)
}
```
## 2.8 常见陷阱与最佳实践
### 2.8.1 变量遮蔽陷阱
```go
func shadowBug() {
x := 10
if true {
x := 20 // 创建了新变量,遮蔽了外层 x
fmt.Println(x) // 20
}
fmt.Println(x) // 10外层 x 未变)
// 正确做法:使用 = 赋值
if true {
x = 30 // 修改外层 x
fmt.Println(x) // 30
}
fmt.Println(x) // 30
}
```
### 2.8.2 整数除法陷阱
```go
// 错误:整数除法
result := 5 / 2 // 结果为 2而非 2.5
// 正确:使用浮点数
result := 5.0 / 2.0 // 结果为 2.5
// 或
result := float64(5) / float64(2)
```
### 2.8.3 字符串长度陷阱
```go
s := "你好"
fmt.Println(len(s)) // 6字节数不是字符数
// 正确获取字符数
runeCount := len([]rune(s)) // 2
fmt.Println(runeCount)
```
### 2.8.4 最佳实践
1. **优先使用短声明 `:=`**,代码更简洁
2. **利用零值**,减少不必要的初始化
3. **避免变量遮蔽**,保持代码清晰
4. **显式类型转换**,避免精度丢失
5. **使用 `range` 遍历**,避免手动索引
6. **注意字符串编码**,使用 `rune` 处理 Unicode
## 2.9 课后练习
1. **变量声明**:用四种方式声明变量,体会它们的区别
2. **零值测试**:创建各种类型的未初始化变量,观察它们的零值
3. **类型转换**:编写程序测试整数到浮点数的转换,观察精度变化
4. **字符串遍历**:遍历包含中文的字符串,输出每个字符及其 Unicode 码点
5. **素数统计**:统计 1 到 1000 之间的素数个数
6. **综合练习**:编写一个温度转换器(摄氏度 ↔ 华氏度)
## 2.10 下一步
完成本章后,你将进入第三章:**数据结构详解**,深入学习数组、切片、映射和结构体,这是 Go 语言最核心的数据组织方式。
---
**代码仓库位置**https://giter.top/openclaw/test/tree/main/chapters/chapter-2
**下一章预告**:数组与切片的区别、映射的底层原理、结构体嵌入与组合