Java开发已经是红海一片,面临着35岁危机的压力,需要适时的调整策略,以应对可能会出现的不确定性。毕竟,命运掌握在自己手里,比掌握在公司手里 安全感会强很多。
尝试的其中一条路即为:Java转Go,也有其他的尝试,会开辟相应的专栏收录。
针对每一个部分,都会在最后准备练习题
,可以多做几遍,用于巩固知识,进行多动手
后续golang语言学习过程中产生的全部内容,会发布在 web3这个专栏中。
环境准备
- 准备开发工具:我这里使用的是 idea家族的
GoLand
- GO安装程序下载安装:这里MacOs直接是安装程序 下载地址
- 配置本地化,类似于Maven配置aliyun的仓库地址
- 使用开发工具,新建一个项目
- 这里提供主线路,对应的每个节点,如果有不理解的,可以去问AI
打开命令行,输入go,回车,即可看到go的信息。执行下面的命令后,即可看到配置成功
ini
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go env
Go语言基础快速突破[基础语法](1-2周)
Go语言摒弃了语句必须以分号作为语句结束标记的习惯。
1.1 变量
注意:
- Go语言引入了关键字var,而类型信息放在变量名之后
- 出现在:=左侧的变量不应该是已经被声明过的,否则会导致编译错误
- 支持多重赋值功能,比如下面这个交换i和j变量的语句: i,j=j,i
- 变量声明方式
go
var name string = "张三"
var age int = 25
var height float64 = 175.5
var isStudent bool = true
- 短变量声明(推荐方式)
go
city := "北京"
score := 95
- 多变量声明
ini
var (
firstName = "李"
lastName = "四"
phone = "13800138000"
)
1.2 常量
在Go语言中,常量是指编译期间就已知且不可改变的值。常量可以是数值类型(包括整型、浮点型和复数类型)、布尔类型、字符串类型等。
1.2.1. 字面常量
字面常量是直接在代码中写出的固定值,没有名称。它们是表达式中的硬编码值。
类型分类:
- 整型字面量 :
42
,-5
,0600
(八进制),0xBadFace
(十六进制) - 浮点型字面量 :
3.1415
,-1.23
,2.71828
- 复数类型字面量 :
1.2+3.4i
,complex(1, 2)
- 字符串字面量 :
"Hello"
,'A'
,raw string
- 布尔类型字面量 :
true
,false
go
package main
import "fmt"
func main() {
fmt.Println(42) // 整型字面量
fmt.Println(3.1415) // 浮点型字面量
fmt.Println(1.2 + 3.4i) // 复数类型字面量
fmt.Println("Hello") // 字符串字面量
fmt.Println(true) // 布尔类型字面量
}
注意:Go中没有字符类型(char)的独立类型,但可以使用单引号表示rune类型的字符字面量。
1.2.2. 常量定义
通过const
关键字来定义具名常量。
基本语法:
go
package main
import (
"fmt"
"math"
)
// 定义单个常量
const Pi = 3.14159
// 定义多个常量
const (
StatusOK = 200
StatusError = 500
)
// 显式指定类型
const Version string = "v1.0.0"
// 使用iota(见枚举部分)
const (
_ = iota
KB = 1 << (10 * iota)
MB
GB
)
func main() {
fmt.Println(Pi)
fmt.Println(StatusOK, StatusError)
fmt.Println(Version)
fmt.Println(KB, MB, GB)
}
注意:
- 必须在编译时确定其值。
- 类型可选,若不指定,则为无类型常量(untyped constant),在赋值给变量时根据上下文推断类型。
- 支持表达式计算,但所有操作数必须是常量。
1.2.3. 预定义常量 (Predeclared Constants)
Go语言内置了一些常用的预定义常量,无需额外导入即可使用。
常见预定义常量:
true
: 布尔真值。false
: 布尔假值。iota
: 特殊常量生成器,用于创建递增序列(见下节)。
go
package main
import "fmt"
func main() {
fmt.Println(true, false)
}
1.2.4. 枚举 (Enumerations)
Go没有专门的enum
关键字,但可以通过const
和iota
实现枚举功能。
基本用法:
go
package main
import "fmt"
// 使用iota定义枚举
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
// 更复杂的例子:位移操作
const (
_ = iota // 忽略第一个值
KB = 1 << (10 * iota) // 1 << (10*1) = 1024
MB // 1 << (10*2) = 1048576
GB // 1 << (10*3) = 1073741824
)
// 自定义类型枚举
type Direction int
const (
North Direction = iota
East
South
West
)
func (d Direction) String() string {
return [...]string{"North", "East", "South", "West"}[d]
}
func main() {
fmt.Println(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
fmt.Println(KB, MB, GB)
var dir Direction = North
fmt.Println(dir) // 输出: North
}
注意 iota
规则:
iota
从0开始,每行递增1。- 同一行内所有项共享相同的
iota
值。 - 可以在任意位置重新使用
iota
,但通常用于const
块中。
高级用法:
go
package main
import "fmt"
// 位掩码枚举
type Permission uint
const (
Read Permission = 1 << iota // 1 << 0 = 1
Write // 1 << 1 = 2
Execute // 1 << 2 = 4
Delete // 1 << 3 = 8
)
func (p Permission) Has(perm Permission) bool {
return p&perm != 0
}
func main() {
var userPerms Permission = Read | Write | Execute
fmt.Printf("User permissions: %b\n", userPerms)
fmt.Println("Can read?", userPerms.Has(Read))
fmt.Println("Can delete?", userPerms.Has(Delete))
}
枚举最佳实践:
- 封装到类型中:为枚举定义新类型,增加类型安全性。
- 实现Stringer接口:便于调试和日志记录。
- 使用位运算:对于互斥选项,使用位掩码节省内存。
- 分组相关常量 :将相关的常量放在同一个
const
块中,提高可读性。
1.3 类型
Go语言提供了丰富的数据类型,包括基本类型、复合类型和引用类型。
1.3.1. 布尔类型 (Boolean Type)
布尔类型 表示真/假值,只有两个可能的取值:true
和false
。
特性:
- 占用内存大小为1字节。
- 默认初始值为
false
。 - 常用于条件判断和逻辑运算。
go
package main
import "fmt"
func main() {
var b1 bool // 默认值: false
var b2 = true // 显式初始化
b3 := false // 简短声明
fmt.Printf("b1=%t, b2=%t, b3=%t\n", b1, b2, b3)
// 逻辑运算
fmt.Println(b1 && b2) // false
fmt.Println(b1 || b2) // true
fmt.Println(!b2) // false
}
注意:Go不允许将整型隐式转换为布尔型,必须显式比较。
1.3.2. 整型 (Integer Types)
Go支持多种整型,根据符号和长度分为以下几类:
类型 | 描述 | 范围 |
---|---|---|
int |
平台相关大小(32/64位) | -2^31 ~ 2^31-1 或 -2^63 ~ 2^63-1 |
int8 |
有符号8位整数 | -128 ~ 127 |
int16 |
有符号16位整数 | -32768 ~ 32767 |
int32 |
有符号32位整数 | -2147483648 ~ 2147483647 |
int64 |
有符号64位整数 | -9223372036854775808 ~ 9223372036854775807 |
uint |
无符号平台相关大小 | 0 ~ 2^32-1 或 0 ~ 2^64-1 |
uint8 |
无符号8位整数 | 0 ~ 255 |
uint16 |
无符号16位整数 | 0 ~ 65535 |
uint32 |
无符号32位整数 | 0 ~ 4294967295 |
uint64 |
无符号64位整数 | 0 ~ 18446744073709551615 |
uintptr |
无符号整数,用于指针运算 | 足够容纳指针 |
示例:
go
package main
import "fmt"
func main() {
var a int = 10
var b int32 = 20
var c uint = 30
fmt.Printf("a=%d, type=%T\n", a, a)
fmt.Printf("b=%d, type=%T\n", b, b)
fmt.Printf("c=%d, type=%T\n", c, c)
// 类型转换
d := int(b) + a
fmt.Println("d=", d)
// 溢出处理
var small uint8 = 255
small++
fmt.Println("small after ++:", small) // 0 (溢出)
}
特殊整型:
- rune : 别名
int32
,表示Unicode码点。 - byte : 别名
uint8
,表示字节。
1.3.3. 浮点型 (Floating-Point Types)
Go支持两种浮点类型:
类型 | 描述 | 精度 | 范围 |
---|---|---|---|
float32 |
单精度浮点数 | 约6-9位小数 | ±3.4e38 |
float64 |
双精度浮点数 | 约15-17位小数 | ±1.8e308 |
示例:
go
package main
import (
"fmt"
"math"
)
func main() {
var f1 float32 = 3.14159
f2 := 2.71828 // float64
fmt.Printf("f1=%g, type=%T\n", f1, f1)
fmt.Printf("f2=%g, type=%T\n", f2, f2)
// 特殊值
fmt.Println("NaN:", math.NaN())
fmt.Println("Positive infinity:", math.Inf(1))
fmt.Println("Negative infinity:", math.Inf(-1))
// 比较浮点数
const tolerance = 1e-9
x, y := 0.1+0.2, 0.3
if math.Abs(x-y) < tolerance {
fmt.Println("x ≈ y")
}
}
注意:浮点数比较时建议使用误差范围而非直接相等比较。
1.3.4. 复数类型 (Complex Types)
Go支持复数运算,有两种类型:
类型 | 描述 |
---|---|
complex64 |
实部和虚部均为float32 |
complex128 |
实部和虚部均为float64 |
构造函数:
go
complex(real, imaginary)
示例:
go
package main
import "fmt"
func main() {
c1 := complex(1, 2) // complex128
var c2 complex64 = 3 + 4i // complex64
fmt.Printf("c1=%v, type=%T\n", c1, c1)
fmt.Printf("c2=%v, type=%T\n", c2, c2)
// 访问实部和虚部
realPart := real(c1)
imagPart := imag(c1)
fmt.Printf("Real part: %g, Imaginary part: %g\n", realPart, imagPart)
// 复数运算
c3 := c1 + c2
c4 := c1 * c2
fmt.Printf("c1+c2=%v, c1*c2=%v\n", c3, c4)
}
1.3.5. 字符串 (String Type)
字符串是字节的只读序列,通常用于存储文本数据。
特性:
- 不可变性:字符串创建后不能被修改。
- UTF-8编码:默认采用UTF-8编码。
- 支持两种字面量:
- 双引号
"..."
:可包含转义字符。 - 反引号
...
:原始字符串,不支持转义。
- 双引号
示例:
go
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s1 := "Hello, 世界" // 双引号
s2 := `C:\Users\John` // 反引号
fmt.Printf("s1=%s, len=%d\n", s1, len(s1))
fmt.Printf("s2=%s, len=%d\n", s2, len(s2))
// 遍历字符(rune)
for i, r := range s1 {
fmt.Printf("%d: %c (U+%04X)\n", i, r, r)
}
// 统计Unicode字符数
fmt.Println("Rune count:", utf8.RuneCountInString(s1))
// 字符串拼接
s3 := s1 + " " + s2
fmt.Println("Concatenated:", s3)
// 子串
sub := s1[7:9]
fmt.Println("Substring:", sub) // "世界"
// 字符串比较
if s1 == "Hello, 世界" {
fmt.Println("Strings are equal")
}
}
注意:
len()
返回的是字节数而非字符数,遍历需使用range
。
1.3.6. 字符类型 (Character Type)
Go中没有专门的字符类型,使用以下两种方式表示字符:
byte
(uint8别名)
- 用于ASCII字符。
- 示例:
var ch byte = 'A'
rune
(int32别名)
- 用于Unicode码点。
- 示例:
var r rune = '世'
示例:
go
package main
import "fmt"
func main() {
var ascii byte = 'A' // ASCII字符
var unicode rune = '世' // Unicode字符
fmt.Printf("ascii=%c, type=%T, value=%d\n", ascii, ascii, ascii)
fmt.Printf("unicode=%c, type=%T, value=%U\n", unicode, unicode, unicode)
// 字符运算
next := ascii + 1
fmt.Printf("next character: %c\n", next) // B
}
1.3.7. 数组 (Array Type)
数组是具有固定长度的序列,每个元素类型相同。
特性:
- 长度是类型的一部分,
[3]int
和[5]int
是不同的类型。 - 值语义:赋值或传递时复制整个数组。
- 索引从0开始。
声明方式:
go
[length]Type
[...]Type{value1, value2, ...} // 自动推断长度
示例:
go
package main
import "fmt"
func main() {
// 声明并初始化
var arr1 [3]int // [0 0 0]
arr2 := [3]int{1, 2, 3} // [1 2 3]
arr3 := [...]int{4, 5, 6} // 自动推断长度为3
fmt.Printf("arr1=%v, len=%d, cap=%d\n", arr1, len(arr1), cap(arr1))
fmt.Printf("arr2=%v, len=%d, cap=%d\n", arr2, len(arr2), cap(arr2))
fmt.Printf("arr3=%v, len=%d, cap=%d\n", arr3, len(arr3), cap(arr3))
// 访问和修改元素
arr1[0] = 10
fmt.Println("arr1 after modification:", arr1)
// 多维数组
var matrix [2][3]int
matrix[0] = [3]int{1, 2, 3}
matrix[1] = [3]int{4, 5, 6}
fmt.Println("Matrix:", matrix)
// 数组比较
if arr2 == arr3 {
fmt.Println("arr2 equals arr3")
}
// 作为参数传递
printArray(arr2)
}
func printArray(a [3]int) {
fmt.Println("Inside function:", a)
}
1.3.8. 数组切片 (Slice Type)
切片是动态大小的、灵活的视图,基于底层数组。
特性:
- 引用语义:赋值或传递时共享底层数组。
- 动态增长:可通过
append()
函数扩展。 - 包含三个字段:指向底层数组的指针、长度、容量。
创建方式:
go
make([]Type, len, cap) // 使用make创建
[]Type{value1, value2, ...} // 直接初始化
array[start:end] // 从数组或切片截取
slice[start:end] // 从切片截取
示例:
go
package main
import "fmt"
func main() {
// 创建切片
s1 := make([]int, 3, 5) // len=3, cap=5
s2 := []int{1, 2, 3} // len=3, cap=3
s3 := s2[1:3] // 从s2截取,len=2, cap=2
fmt.Printf("s1=%v, len=%d, cap=%d\n", s1, len(s1), cap(s1))
fmt.Printf("s2=%v, len=%d, cap=%d\n", s2, len(s2), cap(s2))
fmt.Printf("s3=%v, len=%d, cap=%d\n", s3, len(s3), cap(s3))
// 修改切片
s1[0] = 10
s2[1] = 20
s3[0] = 30
fmt.Println("After modifications:")
fmt.Println("s1:", s1) // [10 0 0]
fmt.Println("s2:", s2) // [1 30 3]
fmt.Println("s3:", s3) // [30 3]
// 扩展切片
s1 = append(s1, 40, 50)
fmt.Printf("s1 after append=%v, len=%d, cap=%d\n", s1, len(s1), cap(s1))
// 复制切片
s4 := make([]int, len(s1))
copy(s4, s1)
fmt.Println("Copied slice:", s4)
// 删除元素
s5 := append(s1[:2], s1[3:]...)
fmt.Println("After deletion:", s5)
// 遍历切片
for i, v := range s1 {
fmt.Printf("Index %d: %d\n", i, v)
}
}
常见操作:
- 追加元素 :
append(slice, elem1, elem2, ...)
- 复制切片 :
copy(dst, src)
- 删除元素 :
append(s[:index], s[index+1:]...)
- 插入元素 :
append(s[:index], append([]Type{newElem}, s[index:]...)...)
1.3.9. Map类型 (Map Type)
Map是无序的键值对集合,也称为哈希表或字典。
特性:
- 引用语义:赋值或传递时共享底层数据结构。
- 键必须是可比较的类型(如整数、字符串、指针等)。
- 值可以是任意类型。
创建方式:
go
make(map[KeyType]ValueType, initialCapacity) // 使用make创建
map[KeyType]ValueType{key1: value1, ...} // 直接初始化
示例:
go
package main
import "fmt"
func main() {
// 创建map
m1 := make(map[string]int)
m2 := map[string]string{
"name": "Alice",
"city": "Beijing",
}
fmt.Printf("m1=%v, len=%d\n", m1, len(m1))
fmt.Printf("m2=%v, len=%d\n", m2, len(m2))
// 添加/更新元素
m1["age"] = 25
m1["score"] = 95
m2["age"] = "25"
// 访问元素
if age, ok := m1["age"]; ok {
fmt.Println("Age:", age)
}
// 删除元素
delete(m1, "score")
// 遍历map
fmt.Println("m1 contents:")
for k, v := range m1 {
fmt.Printf(" %s: %d\n", k, v)
}
fmt.Println("m2 contents:")
for k, v := range m2 {
fmt.Printf(" %s: %s\n", k, v)
}
// 检查键是否存在
if _, exists := m1["score"]; !exists {
fmt.Println("Score not found")
}
// 作为参数传递
printMap(m1)
}
func printMap(m map[string]int) {
fmt.Println("Inside function:", m)
}
注意事项:
- 并发不安全 : 多个goroutine同时读写map会导致panic,需使用
sync.RWMutex
保护。 - 无序性: map遍历顺序不确定,每次可能不同。
- 零值 : map的零值是
nil
,不能直接赋值,需先初始化。
附录:1~3节 习题
go
package main
import (
"fmt"
"math"
"strconv"
"strings"
)
// 练习1: 字面常量与类型推断
func practiceLiterals() {
fmt.Println("=== 练习1: 字面常量与类型 ===")
// 练习1.1: 默认类型推断
// 请观察以下字面常量的默认类型:
// - 整数: 42
// - 浮点数: 3.1415
// - 字符串: "Hello"
// - 字符: 'A'
// - 复数: 1+2i
// TODO: 使用fmt.Printf("%T")打印每个字面量的类型
// 练习1.2: 显式类型指定
// 请显式声明以下类型:
// - int32: 100
// - float32: 2.5
// - complex64: 1+3i
// TODO: 添加显式类型声明代码
// 练习1.3: 类型转换
// 请进行以下转换:
// - float64(3.14) -> int
// - "123" -> int
// - int8(127) -> int16
// TODO: 添加类型转换代码
}
// 练习2: 常量定义与枚举
func practiceConstants() {
fmt.Println("\n=== 练习2: 常量定义 ===")
// 练习2.1: 单常量声明
// 请声明以下常量:
// - PI = 3.1415926
// - LANGUAGE = "Go"
// - MAX_USERS = 1000
// TODO: 添加单常量声明代码
// 练习2.2: 常量块
// 请使用const块声明:
// - 状态码: OK=200, NotFound=404, Error=500
// TODO: 添加常量块声明代码
// 练习2.3: iota枚举
// 请使用iota定义:
// - 季节: Spring=1, Summer, Autumn, Winter
// - 权限: Read=1<<iota, Write, Execute
// TODO: 添加iota枚举代码
// 练习2.4: 无类型常量
// 请声明无类型常量并观察类型推断:
// - const Untyped = 100
// - var a int = Untyped
// - var b float64 = Untyped
// TODO: 添加无类型常量代码
}
// 练习3: 基础数据类型
func practiceBasicTypes() {
fmt.Println("\n=== 练习3: 基础类型 ===")
// 练习3.1: 整型溢出
// 请观察以下溢出行为:
// - uint8(255) + 1
// - int8(127) + 1
// TODO: 添加溢出观察代码
// 练习3.2: 浮点数比较
// 请实现浮点数比较函数:
// - 当差值小于1e-9时返回true
// TODO: 实现floatEquals函数
// 练习3.3: 复数运算
// 请计算:
// - (1+2i) + (3+4i)
// - (2+3i) * (1-1i)
// TODO: 添加复数运算代码
// 练习3.4: 字符串操作
// 请操作字符串"Hello, 世界":
// - 获取字节长度
// - 获取Unicode字符数
// - 遍历并打印每个字符的Unicode码点
// TODO: 添加字符串操作代码
// 练习3.5: rune与byte
// 请声明:
// - byte: 'A'
// - rune: '世'
// TODO: 添加rune/byte声明代码
}
// 练习4: 复合类型
func practiceCompositeTypes() {
fmt.Println("\n=== 练习4: 复合类型 ===")
// 练习4.1: 数组
// 请声明并初始化:
// - 长度为3的整型数组: [1,2,3]
// - 长度为5的字符串数组(自动推断)
// TODO: 添加数组声明代码
// 练习4.2: 切片
// 请操作切片:
// - 从数组[1,2,3,4,5]创建切片[2,3,4]
// - 修改切片后观察原数组变化
// - 使用append扩展切片
// TODO: 添加切片操作代码
// 练习4.3: Map
// 请操作Map:
// - 创建map[string]int: {"apple":10, "banana":20}
// - 实现getOrDefault函数
// - 遍历map并打印键值对
// TODO: 添加Map操作代码
// 练习4.4: 类型组合
// 请设计结构体:
// - Student: Name(string), Age(int), Grades(map[string]float64)
// - 初始化实例并添加成绩"Math":95.5
// TODO: 添加类型组合代码
}
// 练习5: 综合应用
func practiceAdvanced() {
fmt.Println("\n=== 练习5: 综合应用 ===")
// 练习5.1: 位掩码权限检查
// 请实现:
// - 用户权限: Read|Write
// - 检查用户是否有Read权限
// TODO: 添加位掩码代码
// 练习5.2: 浮点数精度处理
// 请实现:
// - 计算0.1+0.2+0.3,并与0.6比较
// TODO: 添加浮点数精度处理代码
// 练习5.3: 字符串解析
// 请实现:
// - 解析"123,456,789"为整型切片
// TODO: 添加字符串解析代码
// 练习5.4: 类型转换链
// 请实现:
// - string("255") -> int -> uint8 -> int16
// TODO: 添加类型转换链代码
}
// ======== 答案参考实现 (练习时请勿展开) ========
// 练习3.2答案
func floatEquals(a, b float64) bool {
return math.Abs(a-b) < 1e-9
}
// 练习4.3答案
func getOrDefault(m map[string]int, key string, def int) int {
if v, ok := m[key]; ok {
return v
}
return def
}
// 练习4.4答案
type Student struct {
Name string
Age int
Grades map[string]float64
}
// 练习5.3答案
func parseInts(s string) []int {
var result []int
for _, part := range strings.Split(s, ",") {
if num, err := strconv.Atoi(part); err == nil {
result = append(result, num)
}
}
return result
}
func main() {
practiceLiterals()
practiceConstants()
practiceBasicTypes()
practiceCompositeTypes()
practiceAdvanced()
fmt.Println("\n=== 练习完成 ===")
fmt.Println("请完成所有TODO部分的代码,运行验证结果!")
}
1.4 流程控制
Go语言的流程控制结构简洁而强大,包括条件语句、选择语句、循环语句和跳转语句。以下是对这些结构的全面解析:
1.4.1. 条件语句 (If Statements)
基本语法
go
if condition {
// 条件为真时执行的代码
} else if condition2 {
// 第二个条件为真时执行的代码
} else {
// 所有条件都为假时执行的代码
}
示例
简单条件判断
go
package main
import "fmt"
func main() {
x := 10
if x > 5 {
fmt.Println("x大于5")
} else {
fmt.Println("x小于等于5")
}
}
带初始化的if语句
go
if val := getValue(); val > 100 {
fmt.Println("值大于100:", val)
} else if val > 50 {
fmt.Println("值在51-100之间:", val)
} else {
fmt.Println("值小于等于50:", val)
}
条件表达式类型
- 必须是布尔类型(
bool
) - 不允许隐式类型转换(如不能用整型代替布尔型)
go
// 错误示例
if 1 { // 编译错误:非布尔类型
fmt.Println("这不会编译")
}
// 正确做法
if 1 == 1 {
fmt.Println("正确")
}
空接口类型断言
go
var i interface{} = "hello"
if s, ok := i.(string); ok {
fmt.Printf("i是字符串: %s\n", s)
} else {
fmt.Println("i不是字符串")
}
1.4.2. 选择语句 (Switch Statements)
基本语法
go
switch expression {
case value1:
// 代码块
case value2, value3: // 多值匹配
// 代码块
default:
// 默认代码块
}
示例
表达式switch
go
package main
import "fmt"
func main() {
day := "Wednesday"
switch day {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
fmt.Println("工作日")
case "Saturday", "Sunday":
fmt.Println("周末")
default:
fmt.Println("无效日期")
}
}
类型switch
go
func printType(x interface{}) {
switch v := x.(type) {
case int:
fmt.Printf("整数: %d\n", v)
case string:
fmt.Printf("字符串: %s\n", v)
case bool:
fmt.Printf("布尔值: %t\n", v)
default:
fmt.Printf("未知类型: %T\n", v)
}
}
func main() {
printType(42)
printType("hello")
printType(true)
}
无表达式switch
相当于多个if-else语句的简洁形式:
go
func grade(score int) string {
switch {
case score >= 90:
return "A"
case score >= 80:
return "B"
case score >= 70:
return "C"
case score >= 60:
return "D"
default:
return "F"
}
}
fallthrough行为
- 默认不穿透:每个case执行完后自动跳出
- 显式穿透 :使用
fallthrough
关键字继续执行下一个case
go
switch num := 2; num {
case 1:
fmt.Println("1")
fallthrough
case 2:
fmt.Println("2")
fallthrough
case 3:
fmt.Println("3")
default:
fmt.Println("default")
}
// 输出:
// 2
// 3
// default
注意:
fallthrough
必须位于case块的最后一行[^1]。
1.4.3. 循环语句 (For Statements)
Go只有for
循环,但支持三种形式:
经典for循环
go
for initialization; condition; post {
// 循环体
}
示例:
go
for i := 0; i < 5; i++ {
fmt.Printf("i = %d\n", i)
}
条件循环(while循环的替代品)
go
for condition {
// 循环体
}
示例:
go
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println("Sum:", sum)
无限循环
go
for {
// 无限循环
if someCondition {
break // 需要手动退出
}
}
示例:
go
count := 0
for {
count++
if count >= 5 {
break
}
fmt.Println("Count:", count)
}
范围循环 (Range Clause)
用于遍历数组、切片、字符串、map和channel。
基本语法:
go
for index, value := range collection {
// 使用index和value
}
示例:
数组/切片:
go
numbers := []int{10, 20, 30}
for i, num := range numbers {
fmt.Printf("索引 %d: 值 %d\n", i, num)
}
字符串(遍历rune):
go
text := "Hello, 世界"
for i, r := range text {
fmt.Printf("%d: %c (U+%04X)\n", i, r, r)
}
Map:
go
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
fmt.Printf("%s: %d\n", k, v)
}
忽略索引/值:
go
// 只关心值
for _, value := range collection {
// ...
}
// 只关心索引
for index := range collection {
// ...
}
循环控制
- break:跳出当前循环
- continue:跳过本次迭代,继续下一次
- goto:跳转到标签(见跳转语句)
go
for i := 0; i < 10; i++ {
if i == 3 {
continue // 跳过i=3
}
if i == 7 {
break // 当i=7时退出循环
}
fmt.Println(i)
}
// 输出: 0,1,2,4,5,6
1.4.4. 跳转语句 (Jump Statements)
break
- 基本break:跳出当前循环或switch
- 带标签的break:跳出指定嵌套循环
go
outerLoop:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == 1 && j == 1 {
break outerLoop // 直接跳出外层循环
}
fmt.Printf("%d-%d\n", i, j)
}
}
// 输出: 0-0, 0-1, 0-2, 1-0
continue
- 基本continue:跳过当前循环迭代
- 带标签的continue:跳过指定嵌套循环的当前迭代
go
outerLoop:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue outerLoop // 跳过外层循环的剩余部分
}
fmt.Printf("%d-%d\n", i, j)
}
}
// 输出: 0-0, 1-0, 2-0
goto
无条件跳转到标签位置。
语法:
go
goto label
// ...
label:
// 跳转目标
示例:
go
func main() {
i := 0
loop:
if i < 5 {
fmt.Println(i)
i++
goto loop
}
}
实际应用:
go
func processFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
data := make([]byte, 1024)
for {
n, err := file.Read(data)
if err == io.EOF {
goto done
}
if err != nil {
goto error
}
processData(data[:n])
}
done:
fmt.Println("处理完成")
return nil
error:
fmt.Println("处理出错")
return err
}
fallthrough
已在switch语句中介绍,仅用于switch语句中。
return
函数返回,不属于循环控制但常与流程控制结合使用。
go
func findValue(slice []int, target int) int {
for i, v := range slice {
if v == target {
return i // 找到后立即返回
}
}
return -1 // 未找到
}
注意:
- if语句
- 优先使用带初始化的if语句减少变量作用域
- 避免过长的if-else链,考虑使用switch替代
- switch语句
- 优先使用无表达式switch处理多条件分支
- 使用类型switch处理interface{}类型判断
- 避免不必要的fallthrough
- for循环
- 优先使用range遍历集合
- 无限循环务必包含退出条件
- 避免在循环内修改正在遍历的集合
- 跳转语句
- break/continue:合理使用标签处理嵌套循环
- goto:谨慎使用,优先用函数和循环控制替代
- return:早期返回减少嵌套层级
- 性能考虑
- 对于大切片,range比传统for循环略慢(因边界检查)
- 在性能敏感场景,可考虑传统for循环
go
// 传统for循环(可能更快)
for i := 0; i < len(slice); i++ {
// ...
}
// range循环(更简洁)
for _, v := range slice {
// ...
}
附录:流程控制部分习题
go
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"unicode"
)
// ============ 练习1: 条件语句 ============
func conditionals() {
fmt.Println("=== 条件语句 ===")
// 1. 判断偶数
num := 10
if num%2 == 0 {
fmt.Println(num, "是偶数")
} else {
fmt.Println(num, "是奇数")
}
// 2. 带初始化的if
if length := len("Hello World"); length > 5 {
fmt.Println("字符串长度超过5:", length)
}
// 3. 分数等级
fmt.Printf("分数85的等级: %s\n", getGrade(85))
fmt.Printf("分数50的等级: %s\n", getGrade(50))
// 4. 空接口断言
var i interface{} = "hello"
if s, ok := i.(string); ok {
fmt.Printf("i是字符串: %s\n", s)
} else if n, ok := i.(int); ok {
fmt.Printf("i是整数: %d\n", n)
} else {
fmt.Printf("i是其他类型: %T\n", i)
}
}
func getGrade(score int) string {
if score >= 90 {
return "A"
} else if score >= 80 {
return "B"
} else if score >= 70 {
return "C"
} else if score >= 60 {
return "D"
}
return "F"
}
// ============ 练习2: 选择语句 ============
func switches() {
fmt.Println("\n=== 选择语句 ===")
// 1. 季度判断
month := 5
switch {
case month >= 1 && month <= 3:
fmt.Println("Q1")
case month <= 6:
fmt.Println("Q2")
case month <= 9:
fmt.Println("Q3")
default:
fmt.Println("Q4")
}
// 2. 类型switch
var val interface{} = true
switch v := val.(type) {
case int:
fmt.Printf("整数: %d\n", v)
case string:
fmt.Printf("字符串: %s\n", v)
case bool:
fmt.Printf("布尔值: %t\n", v)
default:
fmt.Printf("未知类型: %T\n", v)
}
// 3. 无表达式switch
fmt.Printf("分数75的等级: %s\n", getGradeSwitch(75))
// 4. fallthrough
fallthroughDemo(1)
}
func getGradeSwitch(score int) string {
switch {
case score >= 90:
return "A"
case score >= 80:
return "B"
case score >= 70:
return "C"
case score >= 60:
return "D"
default:
return "F"
}
}
func fallthroughDemo(n int) {
switch n {
case 1:
fmt.Print("1,")
fallthrough
case 2:
fmt.Print("2,")
fallthrough
case 3:
fmt.Println("3")
}
}
// ============ 练习3: 循环语句 ============
func loops() {
fmt.Println("\n=== 循环语句 ===")
// 1. 经典for循环
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
// 2. 条件循环(阶乘)
fmt.Printf("5的阶乘: %d\n", factorial(5))
// 3. range遍历map
m := map[string]int{"a": 1, "b": 2, "c": 3}
for k, v := range m {
fmt.Printf("%s: %d\n", k, v)
}
// 4. 无限循环
fmt.Println("输入exit退出:")
scanner := bufio.NewScanner(os.Stdin)
for {
scanner.Scan()
if scanner.Text() == "exit" {
break
}
}
}
func factorial(n int) int {
result := 1
for i := 1; i <= n; i++ {
result *= i
}
return result
}
// ============ 练习4: 跳转语句 ============
func jumps() {
fmt.Println("\n=== 跳转语句 ===")
// 1. break退出
nums := []int{1, 2, 3, 4, 5}
for _, num := range nums {
if num == 3 {
fmt.Println("找到3,退出")
break
}
fmt.Println(num)
}
// 2. continue跳过偶数
for _, num := range nums {
if num%2 == 0 {
continue
}
fmt.Println(num)
}
// 3. 带标签的break
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i+j == 2 {
fmt.Printf("跳出外层循环: i=%d, j=%d\n", i, j)
break outer
}
fmt.Printf("%d-%d\n", i, j)
}
}
// 4. goto计数器
i := 0
loop:
if i < 5 {
fmt.Println(i)
i++
goto loop
}
}
// ============ 练习5: 综合应用 ============
func advanced() {
fmt.Println("\n=== 综合应用 ===")
// 1. 猜数字游戏
fmt.Println("猜数字游戏 (1-100):")
target := 42 // 假设随机生成42
scanner := bufio.NewScanner(os.Stdin)
for {
scanner.Scan()
input, _ := strconv.Atoi(scanner.Text())
if input == target {
fmt.Println("猜对了!")
break
} else if input < target {
fmt.Println("太小")
} else {
fmt.Println("太大")
}
}
// 2. 字符串分析
analyzeString("Hello, 世界!")
}
func analyzeString(s string) {
var letters, digits, spaces, others int
for _, r := range s {
switch {
case unicode.IsLetter(r):
letters++
case unicode.IsDigit(r):
digits++
case unicode.IsSpace(r):
spaces++
default:
others++
}
}
fmt.Printf("字母:%d, 数字:%d, 空格:%d, 其他:%d\n", letters, digits, spaces, others)
}
func calculate(operation string, num int) int {
switch operation {
case "add":
return num + 10
case "subtract":
return num - 10
case "multiply":
return num * 10
case "divide":
if num != 0 {
return num / 10
}
return 0
default:
return num
}
}
// ============ 主函数 ============
func main() {
fmt.Println("Go语言流程控制练习题")
fmt.Println("====================")
// 练习1: 条件语句
conditionals()
// 练习2: 选择语句
switches()
// 练习3: 循环语句
loops()
// 练习4: 跳转语句
jumps()
// 练习5: 综合应用
advanced()
fmt.Println("\n====================")
fmt.Println("练习完成!检查所有TODO部分并运行验证。")
}
1.5 函数
1.5.1. 函数定义 (Function Definition)
基本语法
go
func functionName(param1 type1, param2 type2) returnType {
// 函数体
return value
}
关键特性
1.1 基本函数定义
go
package main
import "fmt"
// 无参数无返回值
func greet() {
fmt.Println("Hello, Go!")
}
// 有参数无返回值
func greetPerson(name string) {
fmt.Printf("Hello, %s!\n", name)
}
// 有参数有返回值
func add(a, b int) int { // 相同类型可简写
return a + b
}
// 多参数不同时类型
func createPoint(x, y int, name string) {
fmt.Printf("Point %s: (%d, %d)\n", name, x, y)
}
1.2 命名规则
- 遵循Go的命名规范:
- 首字母大写:公开函数(包外可见)
- 首字母小写:私有函数(包内可见)
- 推荐使用驼峰命名法:
CalculateSum
1.3 函数签名
函数的类型由其参数和返回值决定,称为函数签名:
go
func(int, int) int // add函数的签名
func(string) // greetPerson函数的签名
func() // greet函数的签名
1.5.2. 函数调用 (Function Call)
基本调用方式
go
package main
func main() {
// 无参函数调用
greet()
// 带参函数调用
greetPerson("Alice")
// 带返回值函数调用
sum := add(3, 5)
fmt.Println("Sum:", sum)
// 多参数函数调用
createPoint(10, 20, "Origin")
}
特殊调用方式
2.1 忽略返回值
go
// 只关心副作用,不关心返回值
fmt.Println("Hello") // 忽略返回值(int, error)
// 显式忽略返回值
_, err := os.Open("file.txt")
if err != nil {
// 处理错误
}
2.2 链式调用
go
// 当函数返回值可用于下一个函数时
result := strings.ToUpper(strings.TrimSpace(" hello "))
fmt.Println(result) // "HELLO"
2.3 方法调用
函数作为类型的一部分:
go
type Calculator struct{}
func (c Calculator) Multiply(a, b int) int {
return a * b
}
func main() {
calc := Calculator{}
product := calc.Multiply(3, 4)
fmt.Println("Product:", product)
}
1.5.3. 不定参数 (Variadic Parameters)
基本语法
go
func functionName(param1 type1, params ...type2) returnType {
// 函数体
}
关键特性
3.1 基本使用
go
package main
import "fmt"
// 不定参数函数
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
// 混合参数
func printInfo(prefix string, values ...int) {
fmt.Print(prefix, ": ")
for _, v := range values {
fmt.Print(v, " ")
}
fmt.Println()
}
func main() {
// 调用不定参数函数
fmt.Println("Sum of 1,2,3:", sum(1, 2, 3))
fmt.Println("Sum of 4,5,6,7:", sum(4, 5, 6, 7))
// 混合参数调用
printInfo("Numbers", 10, 20, 30)
// 传递切片作为不定参数
nums := []int{1, 2, 3, 4}
fmt.Println("Sum of slice:", sum(nums...)) // 注意...操作符
}
3.2 注意事项
- 不定参数必须是函数的最后一个参数
- 不定参数在函数内部被视为切片
- 可以传递切片作为不定参数(使用
...
操作符)
3.3 性能考虑
- 不定参数会创建新的切片,可能影响性能
- 对于性能敏感的代码,考虑使用切片参数
go
// 性能优化版本
func sumOptimized(numbers []int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
1.5.4. 多返回值 (Multiple Return Values)
基本语法
go
func functionName(param1 type1) (returnType1, returnType2) {
// 函数体
return value1, value2
}
关键特性
4.1 基本使用
go
package main
import "fmt"
// 多返回值函数
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("除零错误")
}
return a / b, nil
}
// 命名返回值
func parseValue(s string) (value int, err error) {
value, err = strconv.Atoi(s)
return // 自动返回命名的返回值
}
// 多个相同类型返回值
func minMax(numbers []int) (min, max int) {
if len(numbers) == 0 {
return
}
min, max = numbers[0], numbers[0]
for _, num := range numbers {
if num < min {
min = num
}
if num > max {
max = num
}
}
return
}
func main() {
// 接收多返回值
result, err := divide(10, 3)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
// 忽略部分返回值
value, _ := parseValue("123")
fmt.Println("Value:", value)
// 命名返回值
min, max := minMax([]int{3, 1, 4, 1, 5})
fmt.Printf("Min: %d, Max: %d\n", min, max)
}
4.2 命名返回值
- 命名返回值会被初始化为对应类型的零值
- 可以使用简单的
return
语句返回 - 命名返回值在文档中有助于理解函数
4.3 错误处理
Go推荐使用多返回值进行错误处理:
go
// 标准库中的例子
file, err := os.Open("filename.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// 自定义错误处理
func safeDivide(a, b int) (int, bool) {
if b == 0 {
return 0, false
}
return a / b, true
}
1.5.5. 匿名函数与闭包 (Anonymous Functions and Closures)
基本语法
go
// 匿名函数定义
func(parameters) returnType {
// 函数体
}
// 闭包:捕获外部变量的匿名函数
func() {
// 使用外部变量
}
关键特性
5.1 匿名函数
go
package main
import "fmt"
func main() {
// 定义并立即调用匿名函数
func() {
fmt.Println("立即调用的匿名函数")
}()
// 将匿名函数赋值给变量
greet := func(name string) {
fmt.Printf("Hello, %s!\n", name)
}
greet("Alice")
// 匿名函数作为参数
applyOperation(5, 3, func(a, b int) int {
return a * b
})
// 匿名函数作为返回值
adder := createAdder(10)
fmt.Println(adder(5)) // 15
}
// 接受函数作为参数
func applyOperation(a, b int, op func(int, int) int) int {
return op(a, b)
}
// 返回函数
func createAdder(offset int) func(int) int {
return func(x int) int {
return x + offset
}
}
5.2 闭包 (Closure)
闭包是能够访问外部作用域变量的函数:
go
package main
import "fmt"
func main() {
// 基本闭包示例
x := 10
increment := func() int {
x++ // 捕获并修改外部变量x
return x
}
fmt.Println(increment()) // 11
fmt.Println(increment()) // 12
fmt.Println("x =", x) // 12
// 计数器生成器
counter := createCounter()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
fmt.Println(counter()) // 3
// 私有状态
bank := createBankAccount(100)
fmt.Println(bank.balance) // 100
bank.deposit(50)
fmt.Println(bank.balance) // 150
fmt.Println(bank.withdraw(30)) // true
fmt.Println(bank.balance) // 120
}
// 计数器生成器
func createCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
// 银行账户(封装私有状态)
func createBankAccount(initialBalance int) *BankAccount {
balance := initialBalance
return &BankAccount{
balance: balance,
deposit: func(amount int) {
balance += amount
},
withdraw: func(amount int) bool {
if balance >= amount {
balance -= amount
return true
}
return false
},
}
}
type BankAccount struct {
balance int
deposit func(int)
withdraw func(int) bool
}
5.3 闭包注意事项
5.3.1 循环中的闭包陷阱
go
// 错误示例:所有闭包共享同一个变量
func badExample() {
var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i) // 所有闭包都打印3
})
}
for _, f := range funcs {
f()
}
}
// 正确做法1:使用参数
func goodExample1() {
var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, func(n int) func() {
return func() {
fmt.Println(n)
}
}(i))
}
for _, f := range funcs {
f()
}
}
// 正确做法2:创建局部变量
func goodExample2() {
var funcs []func()
for i := 0; i < 3; i++ {
i := i // 创建局部变量副本
funcs = append(funcs, func() {
fmt.Println(i)
})
}
for _, f := range funcs {
f()
}
}
5.3.2 性能考虑
- 闭包会延长捕获变量的生命周期
- 过度使用闭包可能导致内存泄漏
- 在性能敏感场景谨慎使用
5.4 闭包的典型应用
5.4.1 装饰器模式
go
func logDuration(f func()) func() {
return func() {
start := time.Now()
f()
fmt.Printf("耗时: %v\n", time.Since(start))
}
}
func main() {
slowFunc := func() {
time.Sleep(1 * time.Second)
}
loggedFunc := logDuration(slowFunc)
loggedFunc()
}
5.4.2 函数工厂
go
func createMultiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
}
func main() {
double := createMultiplier(2)
triple := createMultiplier(3)
fmt.Println(double(5)) // 10
fmt.Println(triple(5)) // 15
}
5.4.3 延迟初始化
go
func lazyInit() func() *MyStruct {
var instance *MyStruct
var once sync.Once
return func() *MyStruct {
once.Do(func() {
instance = &MyStruct{/* 初始化 */}
})
return instance
}
}
注意
- 函数设计原则
- 单一职责:一个函数只做一件事
- 保持简短:理想函数长度不超过20行
- 参数控制:通常不超过3-4个参数
- 返回值:优先使用命名返回值
- 错误处理
- 优先使用多返回值处理错误
- 避免在函数中直接panic
- 提供清晰的错误信息
go
// 好:明确错误处理
func readFile(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("读取文件失败: %w", err)
}
return data, nil
}
- 性能优化
- 对于频繁调用的函数,考虑使用
//go:noinline
防止内联 - 避免在热路径中创建闭包
- 对于大对象,使用指针传递
- 文档注释
go
// CalculateSum 计算整数切片中所有元素的和
// 参数:
// numbers: 要相加的整数切片
// 返回值:
// 所有元素的和
// 示例:
// sum := CalculateSum([]int{1, 2, 3}) // 返回6
func CalculateSum(numbers []int) int {
sum := 0
for _, num := range numbers {
sum += num
}
return sum
}
- 测试
go
func TestCalculateSum(t *testing.T) {
tests := []struct {
input []int
expected int
}{
{[]int{1, 2, 3}, 6},
{[]int{-1, 1}, 0},
{[]int{}, 0},
}
for _, tt := range tests {
if got := CalculateSum(tt.input); got != tt.expected {
t.Errorf("CalculateSum(%v) = %d, want %d", tt.input, got, tt.expected)
}
}
}
附录:函数部分习题
注意:如果与上面的练习题在一个包下的时候,会报错,因为有相同的方法名,需要先把上面的那个相同方法给注释掉,才不会报错
scss
package main
import (
"fmt"
"strings"
"time"
)
// ============ 练习1: 函数定义 ============
func functionDefinition() {
fmt.Println("=== 函数定义 ===")
// 1. 无参数无返回值
greet()
// 2. 两数相加
sum := add(3, 5)
fmt.Println("3+5 =", sum)
// 3. 创建点
createPoint(10, 20, "Origin")
// 4. 私有函数
privateFunc()
}
func greet() {
fmt.Println("Hello, Go!")
}
func add(a, b int) int {
return a + b
}
func createPoint(x, y int, name string) {
fmt.Printf("Point %s: (%d, %d)\n", name, x, y)
}
func privateFunc() {
fmt.Println("This is private")
}
// ============ 练习2: 函数调用 ============
type Calculator struct{}
func functionCall() {
fmt.Println("\n=== 函数调用 ===")
// 1. 忽略返回值
fmt.Println("Ignoring return value")
// 2. 链式调用
result := processString(" hello ")
fmt.Println("Processed string:", result)
// 3. 方法调用
calc := Calculator{}
product := calc.Multiply(3, 4)
fmt.Println("3*4 =", product)
}
func processString(s string) string {
return strings.ToUpper(strings.TrimSpace(s))
}
func (c Calculator) Multiply(a, b int) int {
return a * b
}
// ============ 练习3: 不定参数 ============
func variadic() {
fmt.Println("\n=== 不定参数 ===")
// 1. 不定参数求和
fmt.Println("Sum of 1,2,3:", sum(1, 2, 3))
// 2. 混合参数
printInfo("Numbers", 10, 20, 30)
// 3. 切片作为不定参数
nums := []int{1, 2, 3}
fmt.Println("Sum of slice:", sum(nums...))
}
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
func printInfo(prefix string, values ...int) {
fmt.Print(prefix, ": ")
for _, v := range values {
fmt.Print(v, " ")
}
fmt.Println()
}
// ============ 练习4: 多返回值 ============
func multipleReturns() {
fmt.Println("\n=== 多返回值 ===")
// 1. 错误处理
result, err := divide(10, 3)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("10/3 =", result)
}
// 2. 命名返回值
min, max := minMax([]int{3, 1, 4, 1, 5})
fmt.Printf("Min: %d, Max: %d\n", min, max)
// 3. 成功标志
quotient, ok := safeDivide(10, 3)
if ok {
fmt.Println("10/3 =", quotient)
}
}
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
func minMax(numbers []int) (min, max int) {
if len(numbers) == 0 {
return
}
min, max = numbers[0], numbers[0]
for _, num := range numbers {
if num < min {
min = num
}
if num > max {
max = num
}
}
return
}
func safeDivide(a, b int) (int, bool) {
if b == 0 {
return 0, false
}
return a / b, true
}
// ============ 练习5: 匿名函数与闭包 ============
func closures() {
fmt.Println("\n=== 匿名函数与闭包 ===")
// 1. 立即调用
func() {
fmt.Println("Anonymous function")
}()
// 2. 变量赋值
greet := func(name string) {
fmt.Printf("Hello, %s!\n", name)
}
greet("Alice")
// 3. 计数器闭包
counter := createCounter()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
// 4. 修复闭包陷阱
var funcs []func()
for i := 0; i < 3; i++ {
i := i // 创建局部变量副本
funcs = append(funcs, func() {
fmt.Println(i)
})
}
for _, f := range funcs {
f() // 输出0,1,2
}
}
func createCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
// ============ 练习6: 综合应用 ============
func advanced() {
fmt.Println("\n=== 综合应用 ===")
// 1. 装饰器
slowFunc := func() {
time.Sleep(100 * time.Millisecond)
}
loggedFunc := logDuration(slowFunc)
loggedFunc()
// 2. 函数工厂
double := createMultiplier(2)
triple := createMultiplier(3)
fmt.Println("Double 5:", double(5)) // 10
fmt.Println("Triple 5:", triple(5)) // 15
// 3. 私有状态
account := createBankAccount(100)
fmt.Println("Balance:", account.balance)
account.deposit(50)
fmt.Println("After deposit:", account.balance)
fmt.Println("Withdraw 30:", account.withdraw(30))
}
func logDuration(f func()) func() {
return func() {
start := time.Now()
f()
fmt.Printf("Duration: %v\n", time.Since(start))
}
}
func createMultiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
}
func createBankAccount(initial int) *BankAccount {
balance := initial
return &BankAccount{
balance: balance,
deposit: func(amount int) { balance += amount },
withdraw: func(amount int) bool {
if balance >= amount {
balance -= amount
return true
}
return false
},
}
}
type BankAccount struct {
balance int
deposit func(int)
withdraw func(int) bool
}
// ============ 主函数 ============
func main() {
fmt.Println("Go语言函数练习题")
fmt.Println("================")
functionDefinition()
functionCall()
variadic()
multipleReturns()
closures()
advanced()
fmt.Println("\n================")
fmt.Println("练习完成!检查所有TODO部分并运行验证。")
}
1.6 错误处理
Go语言采用独特的错误处理机制,强调显式错误检查和处理。
1.6.1. error 接口
基本定义
go
type error interface {
Error() string
}
关键特性
1.1 创建错误
go
package main
import (
"errors"
"fmt"
)
// 使用errors.New创建简单错误
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// 使用fmt.Errorf创建带格式的错误
func validateAge(age int) error {
if age < 0 {
return fmt.Errorf("invalid age: %d (must be positive)", age)
}
if age > 150 {
return fmt.Errorf("invalid age: %d (must be <= 150)", age)
}
return nil
}
1.2 错误检查
go
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Result: %f\n", result)
// 检查特定错误
if err := validateAge(-5); err != nil {
if errors.Is(err, errors.New("invalid age")) { // 简单示例
fmt.Println("Age validation failed")
}
fmt.Println("Error details:", err)
}
}
1.3 错误包装 (Go 1.13+)
go
import "errors"
// 使用%w包装错误
func readFile(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read file %s failed: %w", path, err)
}
return data, nil
}
func main() {
_, err := readFile("nonexistent.txt")
if err != nil {
fmt.Printf("Error: %v\n", err)
// 检查底层错误
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File does not exist")
}
// 展开错误链
fmt.Printf("Unwrapped: %+v\n", errors.Unwrap(err))
}
}
1.4 自定义错误类型
go
type MyError struct {
Code int
Message string
When time.Time
}
func (e *MyError) Error() string {
return fmt.Sprintf("[%s] Error %d: %s", e.When.Format("2006-01-02 15:04:05"), e.Code, e.Message)
}
func (e *MyError) Is(target error) bool {
t, ok := target.(*MyError)
if !ok {
return false
}
return e.Code == t.Code
}
func processData(data []byte) error {
if len(data) == 0 {
return &MyError{
Code: 1001,
Message: "empty data",
When: time.Now(),
}
}
return nil
}
func main() {
if err := processData([]byte{}); err != nil {
var myErr *MyError
if errors.As(err, &myErr) {
fmt.Printf("Custom error: Code=%d, Msg=%s\n", myErr.Code, myErr.Message)
}
}
}
1.6.2. defer
基本语法
go
defer functionCall()
关键特性
2.1 资源清理
go
package main
import (
"fmt"
"os"
)
func main() {
// 文件操作
file, err := os.Open("data.txt")
if err != nil {
fmt.Printf("Failed to open file: %v\n", err)
return
}
defer file.Close() // 确保文件关闭
// 读取文件内容
data := make([]byte, 1024)
_, err = file.Read(data)
if err != nil {
fmt.Printf("Read error: %v\n", err)
return
}
fmt.Printf("Read %d bytes\n", len(data))
}
2.2 执行顺序
go
func main() {
fmt.Println("Start")
defer fmt.Println("Deferred 1") // 最后执行
defer fmt.Println("Deferred 2") // 倒数第二执行
fmt.Println("End")
}
// 输出:
// Start
// End
// Deferred 2
// Deferred 1
2.3 参数求值时机
go
func main() {
i := 1
defer fmt.Println("Deferred value:", i) // 输出1
i = 2
fmt.Println("Current value:", i) // 输出2
}
// 输出:
// Current value: 2
// Deferred value: 1
2.4 常见用途
go
// 1. 锁的释放
mu.Lock()
defer mu.Unlock()
// 2. 数据库连接关闭
db, err := sql.Open("mysql", dsn)
if err != nil {
return err
}
defer db.Close()
// 3. 事务回滚/提交
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
// 4. 性能监控
start := time.Now()
defer func() {
fmt.Printf("Execution time: %v\n", time.Since(start))
}()
// 5. 恢复panic
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered from panic: %v\n", r)
}
}()
1.6.3. panic() 和 recover()
3.1 panic()
基本用法
go
package main
import "fmt"
func main() {
fmt.Println("Before panic")
panic("something went wrong")
fmt.Println("After panic") // 不会执行
}
panic的特性
- 立即停止当前函数执行
- 向上传播到调用栈,直到被recover捕获或程序终止
- 可以传递任何类型的值(通常是字符串或错误)
go
// 传递复杂错误
panic(fmt.Errorf("detailed error: %v", err))
// 传递自定义类型
type MyPanic struct{ Message string }
panic(&MyPanic{"custom panic"})
3.2 recover()
基本用法
go
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered from panic: %v\n", r)
}
}()
fmt.Println("Before panic")
panic("test panic")
fmt.Println("After panic")
}
// 输出:
// Before panic
// Recovered from panic: test panic
recover的特性
- 只能在defer函数中调用
- 返回panic传递的值
- 如果当前goroutine没有panic,recover返回nil
go
func safeCall() {
defer func() {
if r := recover(); r != nil {
switch v := r.(type) {
case string:
fmt.Printf("Panic with string: %s\n", v)
case error:
fmt.Printf("Panic with error: %v\n", v)
default:
fmt.Printf("Panic with unknown type: %v\n", v)
}
}
}()
// 可能panic的代码
riskyOperation()
}
3.3 典型应用场景
3.3.1 库函数中的panic
go
// 在库函数中,当遇到不可恢复的错误时
func MustCompile(str string) *Regexp {
re, err := Compile(str)
if err != nil {
panic(`regexp: Compile(` + quote(str) + `): ` + err.Error())
}
return re
}
3.3.2 程序启动时的验证
go
func main() {
// 验证配置
if config.Port == 0 {
panic("port must be specified")
}
// 验证依赖
if db == nil {
panic("database connection is nil")
}
// 正常业务逻辑
startServer()
}
3.3.3 测试框架
go
func TestSomething(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Unexpected panic: %v", r)
}
}()
// 测试代码
result := functionThatShouldNotPanic()
if result != expected {
t.Errorf("Expected %v, got %v", expected, result)
}
}
3.3.4 防御性编程
go
func assert(condition bool, msg string) {
if !condition {
panic("Assertion failed: " + msg)
}
}
func processData(data []byte) {
assert(len(data) > 0, "data must not be empty")
assert(len(data) < 1024, "data must be less than 1KB")
// 处理数据
}
注意:
- 错误优先原则
go
// 好:优先检查错误
data, err := ioutil.ReadFile("file.txt")
if err != nil {
return fmt.Errorf("read file failed: %w", err)
}
// 处理数据
// 避免:先使用数据再检查错误
data, _ := ioutil.ReadFile("file.txt")
// 错误处理在后面
- 错误包装
go
// Go 1.13+
if err := doSomething(); err != nil {
return fmt.Errorf("doing something failed: %w", err)
}
// 检查特定错误
if errors.Is(err, os.ErrNotExist) {
// 处理文件不存在
}
// 类型检查
var pathErr *os.PathError
if errors.As(err, &pathErr) {
// 处理路径错误
}
- defer的合理使用
go
// 好:在资源获取后立即defer
file, err := os.Open("file.txt")
if err != nil {
return err
}
defer file.Close()
// 避免:延迟defer
file, err := os.Open("file.txt")
if err != nil {
return err
}
// ... 其他代码
defer file.Close() // 如果中间有panic,可能无法执行
- panic/recover的谨慎使用
go
// 好:在程序初始化或库函数中使用
func LoadConfig(path string) *Config {
data, err := os.ReadFile(path)
if err != nil {
panic(fmt.Sprintf("failed to load config: %v", err))
}
// 解析配置
return config
}
// 好:在web服务器中恢复panic
func handleRequest(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic: %v", r)
http.Error(w, "Internal Server Error", 500)
}
}()
// 处理请求
}
// 避免:在业务逻辑中使用panic
func calculate(a, b int) int {
if b == 0 {
panic("division by zero") // 应该返回错误
}
return a / b
}
- 错误处理模式
go
// 模式1:直接返回
func process() error {
data, err := readData()
if err != nil {
return err
}
return transform(data)
}
// 模式2:添加上下文
func process() error {
data, err := readData()
if err != nil {
return fmt.Errorf("read data failed: %w", err)
}
if err := transform(data); err != nil {
return fmt.Errorf("transform failed: %w", err)
}
return nil
}
// 模式3:使用defer处理资源
func processFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// 处理文件
return nil
}
- 性能考虑
- error:零成本,推荐使用
- panic/recover:有性能开销,应谨慎使用
- defer:有轻微开销,但现代Go优化得很好
go
// 性能敏感代码避免defer
func hotPath() {
// 直接手动管理资源
mu.Lock()
// ... 临界区代码
mu.Unlock()
}
// 非性能敏感代码使用defer
func regularFunction() {
mu.Lock()
defer mu.Unlock()
// ... 代码
}
常见陷阱与解决方案
- defer在循环中的问题
go
// 错误:在循环中defer会导致资源延迟释放
func badExample() {
for i := 0; i < 100000; i++ {
f, err := os.Create(fmt.Sprintf("file%d.txt", i))
if err != nil {
continue
}
defer f.Close() // 文件不会立即关闭,可能导致资源耗尽
f.WriteString("data")
}
}
// 正确:在循环内部分函数中使用defer
func goodExample() {
for i := 0; i < 100000; i++ {
if err := createFile(fmt.Sprintf("file%d.txt", i)); err != nil {
log.Printf("Failed to create file: %v", err)
}
}
}
func createFile(path string) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString("data")
return err
}
- recover未在defer中调用
go
// 错误:recover不在defer中
func badRecover() {
if r := recover(); r != nil { // 永远不会捕获panic
fmt.Println("Recovered")
}
panic("test")
}
// 正确:在defer中recover
func goodRecover() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered")
}
}()
panic("test")
}
- panic传播
go
// panic会传播到调用栈
func level3() {
panic("level3 panic")
}
func level2() {
level3()
}
func level1() {
level2()
}
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered: %v\n", r)
}
}()
level1()
}
- defer中的错误处理
go
// 错误:忽略defer中的错误
func badDefer() error {
file, err := os.Create("file.txt")
if err != nil {
return err
}
defer file.Close() // 忽略关闭错误
_, err = file.WriteString("data")
return err
}
// 正确:处理defer中的错误
func goodDefer() (resultErr error) {
file, err := os.Create("file.txt")
if err != nil {
return err
}
defer func() {
if err := file.Close(); err != nil && resultErr == nil {
resultErr = err
}
}()
_, resultErr = file.WriteString("data")
return
}
附录:错误处理部分习题
注意:如果与上面的练习题在一个包下的时候,会报错,因为有相同的方法名,需要先把上面的那个相同方法给注释掉,才不会报错
scss
package main
import (
"errors"
"fmt"
"log"
"net/http"
"os"
"time"
)
// ============ 练习1: error接口 ============
func errorInterface() {
fmt.Println("=== error接口 ===")
// 1. 创建简单错误
err := validateInput(-5)
if err != nil {
fmt.Println("Error:", err)
}
// 2. 带格式的错误
age := 200
if err := checkAge(age); err != nil {
fmt.Println("Age error:", err)
}
// 3. 自定义错误类型
if err := validateData("", ""); err != nil {
var verr *ValidationError
if errors.As(err, &verr) {
fmt.Printf("Validation failed: %s.%s\n", verr.Field, verr.Message)
}
}
// 4. 错误检查
_, err = os.Stat("nonexistent.txt")
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File does not exist")
}
}
var ErrInvalidInput = errors.New("invalid input")
func validateInput(value int) error {
if value < 0 {
return ErrInvalidInput
}
return nil
}
func checkAge(age int) error {
if age < 0 || age > 150 {
return fmt.Errorf("invalid age: %d", age)
}
return nil
}
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error for %s: %s", e.Field, e.Message)
}
func validateData(field1, field2 string) error {
if field1 == "" {
return &ValidationError{Field: "field1", Message: "cannot be empty"}
}
if field2 == "" {
return &ValidationError{Field: "field2", Message: "cannot be empty"}
}
return nil
}
// ============ 练习2: defer ============
func deferStatements() {
fmt.Println("\n=== defer ===")
// 1. 文件操作
readFile("data.txt")
// 2. 统计执行时间
slowOperation()
// 3. 修复循环中的defer
fixDeferInLoop()
// 4. 事务处理
simulateTransaction()
}
func readFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close() // 确保关闭
data := make([]byte, 1024)
_, err = file.Read(data)
return err
}
func slowOperation() {
start := time.Now()
defer func() {
fmt.Printf("Operation took %v\n", time.Since(start))
}()
time.Sleep(100 * time.Millisecond)
}
func fixDeferInLoop() {
for i := 0; i < 3; i++ {
if err := createFile(fmt.Sprintf("file%d.txt", i)); err != nil {
fmt.Printf("Error creating file: %v\n", err)
}
}
}
func createFile(path string) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close() // 在函数内defer
_, err = f.WriteString("data")
return err
}
func simulateTransaction() {
var err error
tx, err := beginTx()
if err != nil {
fmt.Println("Transaction failed to start:", err)
return
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
// 业务逻辑
err = doBusinessLogic()
}
type Transaction struct{}
func beginTx() (*Transaction, error) { return &Transaction{}, nil }
func (t *Transaction) Commit() { fmt.Println("Committed") }
func (t *Transaction) Rollback() { fmt.Println("Rolled back") }
func doBusinessLogic() error { return nil }
// ============ 练习3: panic和recover ============
func panicAndRecover() {
fmt.Println("\n=== panic/recover ===")
// 1. 不可恢复错误
safeProcess(nil)
// 2. 捕获panic
result, err := callWithRecover()
if err != nil {
fmt.Println("Recovered error:", err)
}
fmt.Println("Result:", result)
// 3. HTTP处理器
http.HandleFunc("/test", handleRequest)
// 4. 断言函数
assert(true, "should not panic")
assert(false, "should panic") // 此行应触发panic
}
func safeProcess(data *int) {
if data == nil {
panic("nil pointer dereference")
}
fmt.Println("Processing:", *data)
}
func callWithRecover() (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
result = riskyCalculation()
return
}
func riskyCalculation() int {
panic("calculation failed")
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic: %v", r)
http.Error(w, "Internal Server Error", 500)
}
}()
riskyOperation()
}
func riskyOperation() {
panic("server error")
}
func assert(condition bool, msg string) {
if !condition {
panic("Assertion failed: " + msg)
}
}
// ============ 练习4: 综合应用 ============
func advanced() {
fmt.Println("\n=== 综合应用 ===")
// 1. 错误包装
if err := readAndProcess("data.json"); err != nil {
fmt.Printf("Final error: %+v\n", err)
}
// 2. 数据库事务
db := NewDatabase()
db.RunTransaction()
// 3. 防御性panic
data := MustParseJSON(`{"name": "Alice", "age": 30}`)
fmt.Println("Parsed:", data)
}
func readAndProcess(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read file failed: %w", err)
}
var result map[string]interface{}
if err := unmarshal(data, &result); err != nil {
return fmt.Errorf("unmarshal failed: %w", err)
}
// 处理数据
if _, ok := result["name"]; !ok {
return fmt.Errorf("missing required field: %w", errors.New("name"))
}
return nil
}
func unmarshal(data []byte, v interface{}) error {
// 模拟JSON解析
return nil
}
type Database struct{}
func NewDatabase() *Database { return &Database{} }
func (db *Database) BeginTx() { fmt.Println("Transaction started") }
func (db *Database) Commit() { fmt.Println("Transaction committed") }
func (db *Database) Rollback() { fmt.Println("Transaction rolled back") }
func (db *Database) RunTransaction() {
db.BeginTx()
var resultErr error
defer func() {
if resultErr != nil {
db.Rollback()
} else {
db.Commit()
}
}()
// 业务逻辑
resultErr = db.doWork()
}
func (db *Database) doWork() error { return nil }
func MustParseJSON(data string) map[string]interface{} {
var result map[string]interface{}
if err := unmarshal([]byte(data), &result); err != nil {
panic(fmt.Sprintf("JSON parse failed: %v", err))
}
return result
}
// ============ 主函数 ============
func main() {
fmt.Println("Go语言错误处理练习题")
fmt.Println("==================")
errorInterface()
deferStatements()
panicAndRecover()
advanced()
fmt.Println("\n==================")
fmt.Println("练习完成!检查所有TODO部分并运行验证。")
}