Files
test/chapters/chapter-2-basics.md

897 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第二章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
**下一章预告**:数组与切片的区别、映射的底层原理、结构体嵌入与组合