# 第二章: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) // // 零值判断 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 位系统为 int32,64 位系统为 int64 j uint // 平台相关:32 位系统为 uint32,64 位系统为 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("中")) // 3(UTF-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` 遍历字符串时,返回的是 **rune(Unicode 字符)** 而非字节 - `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 **下一章预告**:数组与切片的区别、映射的底层原理、结构体嵌入与组合