Go 语言万字教程
Go(又称 Golang)是 Google 于 2009 年发布的开源编程语言,由 Robert Griesemer、Rob Pike 和 Ken Thompson 设计。Go 语言以其简洁的语法、出色的并发支持和高效的编译速度而闻名,特别适合构建网络服务、分布式系统和云原生应用。
第一章:环境搭建与基础概念
Go 语言的安装非常简单。在官网 golang.org 下载对应操作系统的安装包后,按照提示完成安装即可。安装完成后,打开终端输入 go version 验证安装是否成功。Go 语言需要设置工作空间,传统上使用 GOPATH 环境变量指定,但从 Go 1.11 开始引入了 Go Modules,使得项目可以放在任意位置。
一个标准的 Go 程序由包(package)组成。每个 Go 源文件都必须在开头声明它属于哪个包。main 包是一个特殊的包,它定义了一个独立的可执行程序而非库。main 包中必须包含一个 main 函数,这是程序执行的入口点。
package main
import "fmt"
func main() {
fmt.Println("你好,Go语言!")
}
这个最简单的程序展示了 Go 的基本结构。package main 声明这是主程序包,import "fmt" 导入了格式化输入输出的标准库,func main() 定义了程序入口函数。使用 go run main.go 可以直接运行程序,使用 go build main.go 可以编译生成可执行文件。
第二章:变量与数据类型
Go 是静态类型语言,每个变量在编译时都有确定的类型。Go 提供了多种声明变量的方式,可以根据场景选择最合适的写法。
package main
import "fmt"
func main() {
// 完整声明方式
var name string = "张三"
// 类型推断(编译器自动推断类型)
var age = 25
// 短变量声明(只能在函数内部使用)
city := "北京"
// 多变量声明
var x, y, z int = 1, 2, 3
// 批量声明
var (
isStudent bool = true
score float64 = 95.5
)
fmt.Println(name, age, city, x, y, z, isStudent, score)
}
Go 的基本数据类型包括布尔型、数值型和字符串型。布尔型只有 true 和 false 两个值。数值型包括多种整数类型如 int8、int16、int32、int64 及其无符号版本 uint8 等,还有浮点数 float32 和 float64,以及复数 complex64 和 complex128。int 和 uint 的大小取决于平台,在 64 位系统上是 64 位。byte 是 uint8 的别名,rune 是 int32 的别名,用于表示 Unicode 码点。
package main
import "fmt"
func main() {
// 整数类型
var i int = 42
var u uint = 42
var i64 int64 = 9223372036854775807
// 浮点数类型
var f32 float32 = 3.14
var f64 float64 = 3.141592653589793
// 布尔类型
var b bool = true
// 字符串类型
var s string = "Go语言"
// byte 和 rune
var ch byte = 'A' // ASCII 字符
var r rune = '中' // Unicode 字符
fmt.Printf("int: %d, uint: %d, int64: %d\n", i, u, i64)
fmt.Printf("float32: %f, float64: %.15f\n", f32, f64)
fmt.Printf("bool: %t\n", b)
fmt.Printf("string: %s, 长度: %d\n", s, len(s))
fmt.Printf("byte: %c, rune: %c\n", ch, r)
}
常量使用 const 关键字声明,必须在编译时确定值。Go 还提供了特殊的常量生成器 iota,它在 const 块中从 0 开始,每行自动递增 1,非常适合定义枚举。
package main
import "fmt"
const Pi = 3.14159265358979323846
const (
StatusPending = iota // 0
StatusRunning // 1
StatusCompleted // 2
StatusFailed // 3
)
const (
_ = iota // 忽略第一个值
KB = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20
GB // 1 << 30
TB // 1 << 40
)
func main() {
fmt.Printf("Pi = %.20f\n", Pi)
fmt.Printf("状态值: %d, %d, %d, %d\n", StatusPending, StatusRunning, StatusCompleted, StatusFailed)
fmt.Printf("KB=%d, MB=%d, GB=%d, TB=%d\n", KB, MB, GB, TB)
}
零值是 Go 的一个重要概念。当变量声明但未初始化时,Go 会自动赋予该类型的零值。数值类型的零值是 0,布尔型是 false,字符串是空字符串 "",指针、切片、映射、通道、函数和接口的零值是 nil。
第三章:复合数据类型
数组是固定长度的同类型元素序列。数组的长度是类型的一部分,因此 [3]int 和 [4]int 是不同的类型。
package main
import "fmt"
func main() {
// 声明数组
var arr1 [5]int // 默认零值
arr2 := [3]string{"Go", "Python", "Java"}
arr3 := [...]int{1, 2, 3, 4, 5} // 编译器推断长度
arr4 := [5]int{1: 10, 3: 30} // 指定索引初始化
fmt.Println("arr1:", arr1)
fmt.Println("arr2:", arr2)
fmt.Println("arr3:", arr3, "长度:", len(arr3))
fmt.Println("arr4:", arr4)
// 访问和修改元素
arr1[0] = 100
fmt.Println("修改后的arr1:", arr1)
// 遍历数组
for i, v := range arr2 {
fmt.Printf("索引 %d: %s\n", i, v)
}
}
切片是 Go 中最常用的数据结构,它是对数组的抽象,提供了动态大小的灵活序列。切片由三部分组成:指向底层数组的指针、长度和容量。
package main
import "fmt"
func main() {
// 创建切片的多种方式
var s1 []int // nil 切片
s2 := []int{1, 2, 3, 4, 5} // 字面量创建
s3 := make([]int, 5) // make 创建,长度 5,容量 5
s4 := make([]int, 3, 10) // 长度 3,容量 10
fmt.Printf("s1: %v, len=%d, cap=%d, nil=%t\n", s1, len(s1), cap(s1), s1 == nil)
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))
fmt.Printf("s4: %v, len=%d, cap=%d\n", s4, len(s4), cap(s4))
// 从数组创建切片
arr := [5]int{10, 20, 30, 40, 50}
slice := arr[1:4] // 包含索引 1、2、3
fmt.Println("从数组切片:", slice)
// append 添加元素
s2 = append(s2, 6, 7, 8)
fmt.Println("append后:", s2)
// 合并切片
s5 := []int{100, 200}
s2 = append(s2, s5...) // 使用 ... 展开切片
fmt.Println("合并后:", s2)
// copy 复制切片
src := []int{1, 2, 3}
dst := make([]int, len(src))
copied := copy(dst, src)
fmt.Printf("复制了 %d 个元素: %v\n", copied, dst)
}
映射(Map)是键值对的无序集合,类似于其他语言中的字典或哈希表。映射的键必须是可比较的类型,如数字、字符串、布尔值等。
package main
import "fmt"
func main() {
// 创建映射
var m1 map[string]int // nil 映射,不能直接写入
m2 := map[string]int{} // 空映射,可以写入
m3 := make(map[string]int) // make 创建
m4 := map[string]int{ // 字面量初始化
"apple": 5,
"banana": 3,
"orange": 8,
}
fmt.Println("m1 == nil:", m1 == nil)
fmt.Println("m2:", m2)
fmt.Println("m3:", m3)
fmt.Println("m4:", m4)
// 添加和修改
m3["go"] = 100
m3["python"] = 90
m3["go"] = 95 // 修改已存在的键
fmt.Println("m3:", m3)
// 访问元素
value := m4["apple"]
fmt.Println("apple:", value)
// 检查键是否存在
if v, ok := m4["grape"]; ok {
fmt.Println("grape:", v)
} else {
fmt.Println("grape 不存在")
}
// 删除元素
delete(m4, "banana")
fmt.Println("删除后:", m4)
// 遍历映射
for key, value := range m4 {
fmt.Printf("%s: %d\n", key, value)
}
fmt.Println("映射长度:", len(m4))
}
结构体是将多个不同类型的字段组合在一起的复合类型,类似于其他语言中的类。
package main
import "fmt"
// 定义结构体
type Person struct {
Name string
Age int
Email string
Address Address // 嵌套结构体
}
type Address struct {
City string
Street string
ZipCode string
}
// 带标签的结构体(常用于 JSON 序列化)
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Password string `json:"-"` // 忽略此字段
}
func main() {
// 创建结构体实例
var p1 Person // 零值初始化
p2 := Person{Name: "张三", Age: 25} // 指定字段初始化
p3 := Person{ // 完整初始化
Name: "李四",
Age: 30,
Email: "lisi@example.com",
Address: Address{
City: "北京",
Street: "长安街1号",
ZipCode: "100000",
},
}
fmt.Printf("p1: %+v\n", p1)
fmt.Printf("p2: %+v\n", p2)
fmt.Printf("p3: %+v\n", p3)
// 访问和修改字段
p1.Name = "王五"
p1.Age = 28
fmt.Println("p1.Name:", p1.Name)
// 访问嵌套结构体
fmt.Println("p3 的城市:", p3.Address.City)
// 结构体指针
p4 := &Person{Name: "赵六", Age: 35}
fmt.Println("p4.Name:", p4.Name) // 自动解引用
// 匿名结构体
point := struct {
X, Y int
}{10, 20}
fmt.Printf("point: %+v\n", point)
}
第四章:流程控制
Go 的流程控制语句简洁而强大。if 语句可以在条件前包含一个简短的初始化语句。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
// 基本 if-else
x := 10
if x > 5 {
fmt.Println("x 大于 5")
} else if x < 5 {
fmt.Println("x 小于 5")
} else {
fmt.Println("x 等于 5")
}
// if 带初始化语句
if num := rand.Intn(100); num > 50 {
fmt.Printf("随机数 %d 大于 50\n", num)
} else {
fmt.Printf("随机数 %d 不大于 50\n", num)
}
// num 的作用域仅限于 if-else 块内
}
for 是 Go 中唯一的循环结构,但它可以模拟其他语言中的 while 和 do-while 循环。
package main
import "fmt"
func main() {
// 标准 for 循环
for i := 0; i < 5; i++ {
fmt.Printf("%d ", i)
}
fmt.Println()
// 类似 while 循环
j := 0
for j < 5 {
fmt.Printf("%d ", j)
j++
}
fmt.Println()
// 无限循环
k := 0
for {
if k >= 5 {
break
}
fmt.Printf("%d ", k)
k++
}
fmt.Println()
// range 遍历
nums := []int{10, 20, 30, 40, 50}
for index, value := range nums {
fmt.Printf("索引 %d: %d\n", index, value)
}
// 只需要值时忽略索引
for _, value := range nums {
fmt.Printf("值: %d\n", value)
}
// 遍历字符串
for i, ch := range "Go语言" {
fmt.Printf("位置 %d: %c\n", i, ch)
}
// continue 跳过当前迭代
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue
}
fmt.Printf("%d ", i)
}
fmt.Println()
}
switch 语句在 Go 中更加灵活,不需要 break(默认不会贯穿),可以使用 fallthrough 明确贯穿。
package main
import (
"fmt"
"time"
)
func main() {
// 基本 switch
day := 3
switch day {
case 1:
fmt.Println("星期一")
case 2:
fmt.Println("星期二")
case 3:
fmt.Println("星期三")
case 4, 5: // 多值匹配
fmt.Println("星期四或星期五")
default:
fmt.Println("周末")
}
// switch 带初始化语句
switch today := time.Now().Weekday(); today {
case time.Saturday, time.Sunday:
fmt.Println("周末休息")
default:
fmt.Println("工作日")
}
// 无表达式 switch(类似 if-else 链)
score := 85
switch {
case score >= 90:
fmt.Println("优秀")
case score >= 80:
fmt.Println("良好")
case score >= 60:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
// fallthrough 贯穿
num := 1
switch num {
case 1:
fmt.Println("一")
fallthrough
case 2:
fmt.Println("二")
case 3:
fmt.Println("三")
}
// 类型 switch
var i interface{} = "hello"
switch v := i.(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)
}
}
第五章:函数
函数是 Go 程序的基本构建块。Go 函数可以返回多个值,这是处理错误的常见模式。
package main
import (
"errors"
"fmt"
)
// 基本函数
func add(a, b int) int {
return a + b
}
// 多返回值
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为零")
}
return a / b, nil
}
// 命名返回值
func rectangle(width, height float64) (area, perimeter float64) {
area = width * height
perimeter = 2 * (width + height)
return // 裸返回
}
// 可变参数
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
// 函数作为参数
func apply(nums []int, f func(int) int) []int {
result := make([]int, len(nums))
for i, v := range nums {
result[i] = f(v)
}
return result
}
// 返回函数(闭包)
func multiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
}
func main() {
// 调用基本函数
fmt.Println("3 + 5 =", add(3, 5))
// 多返回值
result, err := divide(10, 3)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Printf("10 / 3 = %.2f\n", result)
}
_, err = divide(10, 0)
if err != nil {
fmt.Println("错误:", err)
}
// 命名返回值
area, perimeter := rectangle(5, 3)
fmt.Printf("面积: %.2f, 周长: %.2f\n", area, perimeter)
// 可变参数
fmt.Println("求和:", sum(1, 2, 3, 4, 5))
nums := []int{10, 20, 30}
fmt.Println("切片求和:", sum(nums...)) // 展开切片
// 函数作为参数
double := func(x int) int { return x * 2 }
fmt.Println("加倍:", apply([]int{1, 2, 3}, double))
// 闭包
triple := multiplier(3)
fmt.Println("三倍 5:", triple(5))
fmt.Println("三倍 10:", triple(10))
// 匿名函数
func(msg string) {
fmt.Println("匿名函数:", msg)
}("Hello")
}
defer 语句将函数调用推迟到外层函数返回时执行。defer 常用于资源清理、解锁、关闭文件等操作。多个 defer 按照后进先出(LIFO)的顺序执行。
package main
import "fmt"
func main() {
// 基本 defer
defer fmt.Println("第一个 defer")
defer fmt.Println("第二个 defer")
defer fmt.Println("第三个 defer")
fmt.Println("主函数执行")
// defer 的参数在声明时求值
x := 10
defer fmt.Println("defer 中的 x:", x)
x = 20
fmt.Println("当前 x:", x)
// defer 与闭包
y := 100
defer func() {
fmt.Println("闭包中的 y:", y) // 捕获变量的最终值
}()
y = 200
// 常见用法:确保资源释放
demoFileOperation()
}
func demoFileOperation() {
fmt.Println("\n=== 模拟文件操作 ===")
fmt.Println("打开文件")
defer fmt.Println("关闭文件") // 确保文件被关闭
fmt.Println("读取文件内容")
fmt.Println("处理文件内容")
}
panic 和 recover 用于处理程序中的异常情况。panic 会导致程序崩溃,但可以被 recover 捕获。
package main
import "fmt"
func main() {
fmt.Println("程序开始")
safeCall()
fmt.Println("程序继续执行")
}
func safeCall() {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获到 panic:", r)
}
}()
dangerousOperation()
fmt.Println("这行不会执行")
}
func dangerousOperation() {
fmt.Println("执行危险操作")
panic("出错了!")
}
第六章:方法与接口
方法是绑定到特定类型的函数。Go 没有类,但可以为任何类型定义方法。
package main
import (
"fmt"
"math"
)
// 定义类型
type Circle struct {
Radius float64
}
type Rectangle struct {
Width, Height float64
}
// 值接收者方法
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// 指针接收者方法(可以修改接收者)
func (c *Circle) Scale(factor float64) {
c.Radius *= factor
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
func main() {
c := Circle{Radius: 5}
fmt.Printf("圆形 - 面积: %.2f, 周长: %.2f\n", c.Area(), c.Perimeter())
c.Scale(2)
fmt.Printf("放大后 - 面积: %.2f, 周长: %.2f\n", c.Area(), c.Perimeter())
r := Rectangle{Width: 4, Height: 3}
fmt.Printf("矩形 - 面积: %.2f, 周长: %.2f\n", r.Area(), r.Perimeter())
}
接口是方法签名的集合。任何实现了这些方法的类型都隐式地实现了该接口。
package main
import (
"fmt"
"math"
)
// 定义接口
type Shape interface {
Area() float64
Perimeter() float64
}
// 更小的接口
type Areaer interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// 使用接口作为参数
func printShapeInfo(s Shape) {
fmt.Printf("类型: %T, 面积: %.2f, 周长: %.2f\n", s, s.Area(), s.Perimeter())
}
// 计算总面积
func totalArea(shapes []Shape) float64 {
total := 0.0
for _, s := range shapes {
total += s.Area()
}
return total
}
func main() {
c := Circle{Radius: 5}
r := Rectangle{Width: 4, Height: 3}
// 接口变量可以持有任何实现了该接口的值
var s Shape
s = c
printShapeInfo(s)
s = r
printShapeInfo(s)
// 接口切片
shapes := []Shape{
Circle{Radius: 2},
Rectangle{Width: 3, Height: 4},
Circle{Radius: 3},
}
for _, shape := range shapes {
printShapeInfo(shape)
}
fmt.Printf("总面积: %.2f\n", totalArea(shapes))
// 类型断言
if circle, ok := s.(Circle); ok {
fmt.Printf("这是圆形,半径: %.2f\n", circle.Radius)
} else {
fmt.Println("不是圆形")
}
// 类型 switch
for _, shape := range shapes {
switch v := shape.(type) {
case Circle:
fmt.Printf("圆形,半径: %.2f\n", v.Radius)
case Rectangle:
fmt.Printf("矩形,宽: %.2f, 高: %.2f\n", v.Width, v.Height)
}
}
}
空接口 interface{} 可以持有任何类型的值。从 Go 1.18 开始,any 是 interface{} 的别名。
package main
import "fmt"
func describe(i interface{}) {
fmt.Printf("值: %v, 类型: %T\n", i, i)
}
func main() {
describe(42)
describe("hello")
describe(true)
describe([]int{1, 2, 3})
describe(map[string]int{"a": 1})
// 空接口切片可以存储任意类型
var items []interface{}
items = append(items, 1, "two", 3.0, true)
for _, item := range items {
describe(item)
}
}
第七章:并发编程
并发是 Go 语言最强大的特性之一。Go 使用 goroutine 和 channel 来实现并发。goroutine 是由 Go 运行时管理的轻量级线程。
package main
import (
"fmt"
"time"
)
func sayHello(name string) {
for i := 0; i < 3; i++ {
fmt.Printf("Hello, %s! (%d)\n", name, i)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
// 启动 goroutine
go sayHello("Alice")
go sayHello("Bob")
// 主函数中的代码
sayHello("Main")
// 匿名函数作为 goroutine
go func(msg string) {
fmt.Println("匿名 goroutine:", msg)
}("异步消息")
time.Sleep(500 * time.Millisecond)
fmt.Println("程序结束")
}
Channel 是 goroutine 之间通信的管道。channel 是类型化的,确保发送和接收的数据类型一致。
package main
import "fmt"
func main() {
// 创建无缓冲 channel
ch := make(chan int)
// 在 goroutine 中发送数据
go func() {
ch <- 42
fmt.Println("数据已发送")
}()
// 接收数据
value := <-ch
fmt.Println("收到:", value)
// 带缓冲的 channel
bufferedCh := make(chan string, 3)
bufferedCh <- "第一条"
bufferedCh <- "第二条"
bufferedCh <- "第三条"
// bufferedCh <- "第四条" // 会阻塞,因为缓冲区已满
fmt.Println(<-bufferedCh)
fmt.Println(<-bufferedCh)
fmt.Println(<-bufferedCh)
// 关闭 channel
dataCh := make(chan int, 5)
go func() {
for i := 0; i < 5; i++ {
dataCh <- i
}
close(dataCh) // 发送完毕后关闭
}()
// range 遍历 channel(直到 channel 关闭)
for v := range dataCh {
fmt.Println("接收:", v)
}
// 检查 channel 是否关闭
closedCh := make(chan int)
close(closedCh)
v, ok := <-closedCh
fmt.Printf("值: %d, channel 打开: %t\n", v, ok)
}
select 语句用于在多个 channel 操作中进行选择。
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
ch1 <- "来自 channel 1"
}()
go func() {
time.Sleep(200 * time.Millisecond)
ch2 <- "来自 channel 2"
}()
// 接收两次
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
// 带超时的 select
ch3 := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch3 <- "延迟消息"
}()
select {
case msg := <-ch3:
fmt.Println(msg)
case <-time.After(1 * time.Second):
fmt.Println("超时!")
}
// 非阻塞操作
ch4 := make(chan int, 1)
select {
case ch4 <- 100:
fmt.Println("发送成功")
default:
fmt.Println("channel 满或不可用")
}
select {
case v := <-ch4:
fmt.Println("接收到:", v)
default:
fmt.Println("没有数据可接收")
}
}
sync 包提供了更底层的同步原语,如互斥锁和等待组。
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
// WaitGroup 等待一组 goroutine 完成
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Goroutine %d 完成\n", id)
}(i)
}
wg.Wait()
fmt.Println("所有 goroutine 已完成")
// Mutex 互斥锁
var mu sync.Mutex
counter := 0
var wg2 sync.WaitGroup
for i := 0; i < 1000; i++ {
wg2.Add(1)
go func() {
defer wg2.Done()
mu.Lock()
counter++
mu.Unlock()
}()
}
wg2.Wait()
fmt.Println("计数器(互斥锁):", counter)
// RWMutex 读写锁
var rwMu sync.RWMutex
data := make(map[string]int)
// 写操作需要写锁
rwMu.Lock()
data["key"] = 100
rwMu.Unlock()
// 读操作只需读锁(允许并发读)
rwMu.RLock()
_ = data["key"]
rwMu.RUnlock()
// 原子操作
var atomicCounter int64 = 0
var wg3 sync.WaitGroup
for i := 0; i < 1000; i++ {
wg3.Add(1)
go func() {
defer wg3.Done()
atomic.AddInt64(&atomicCounter, 1)
}()
}
wg3.Wait()
fmt.Println("计数器(原子操作):", atomicCounter)
// Once 确保函数只执行一次
var once sync.Once
initialize := func() {
fmt.Println("初始化只执行一次")
}
for i := 0; i < 5; i++ {
once.Do(initialize)
}
}
第八章:错误处理
Go 使用显式的错误返回值而非异常来处理错误。error 是一个内置接口,任何实现了 Error() string 方法的类型都可以作为错误。
package main
import (
"errors"
"fmt"
"os"
)
// 自定义错误类型
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("验证错误 - 字段 %s: %s", e.Field, e.Message)
}
// 哨兵错误(用于比较)
var ErrNotFound = errors.New("未找到")
var ErrPermissionDenied = errors.New("权限拒绝")
func findUser(id int) (string, error) {
if id <= 0 {
return "", ErrNotFound
}
if id == 999 {
return "", ErrPermissionDenied
}
return fmt.Sprintf("用户%d", id), nil
}
func validateAge(age int) error {
if age < 0 {
return &ValidationError{Field: "age", Message: "年龄不能为负"}
}
if age > 150 {
return &ValidationError{Field: "age", Message: "年龄不能超过150"}
}
return nil
}
// 错误包装(Go 1.13+)
func readConfig(filename string) ([]byte, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("读取配置文件失败: %w", err)
}
return data, nil
}
func main() {
// 基本错误处理
user, err := findUser(1)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println("找到用户:", user)
}
// 错误比较
_, err = findUser(0)
if errors.Is(err, ErrNotFound) {
fmt.Println("用户未找到")
}
// 自定义错误类型
err = validateAge(-5)
if err != nil {
fmt.Println(err)
// 类型断言获取详细信息
var validErr *ValidationError
if errors.As(err, &validErr) {
fmt.Printf("字段: %s, 消息: %s\n", validErr.Field, validErr.Message)
}
}
// 错误包装和解包
_, err = readConfig("不存在的文件.txt")
if err != nil {
fmt.Println("包装后的错误:", err)
// 检查底层错误
if errors.Is(err, os.ErrNotExist) {
fmt.Println("文件不存在")
}
// 解包错误
unwrapped := errors.Unwrap(err)
if unwrapped != nil {
fmt.Println("原始错误:", unwrapped)
}
}
}
第九章:标准库概览
Go 的标准库非常丰富,涵盖了字符串处理、I/O、网络、编码、测试等众多领域。
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"time"
)
func main() {
// === strings 包 ===
fmt.Println("=== strings 包 ===")
s := "Hello, Go World!"
fmt.Println("包含 Go:", strings.Contains(s, "Go"))
fmt.Println("以 Hello 开头:", strings.HasPrefix(s, "Hello"))
fmt.Println("Go 的位置:", strings.Index(s, "Go"))
fmt.Println("替换:", strings.Replace(s, "World", "语言", 1))
fmt.Println("分割:", strings.Split(s, " "))
fmt.Println("大写:", strings.ToUpper(s))
fmt.Println("去除空格:", strings.TrimSpace(" hello "))
fmt.Println("连接:", strings.Join([]string{"a", "b", "c"}, "-"))
// === strconv 包(字符串转换)===
fmt.Println("\n=== strconv 包 ===")
numStr := "42"
num, _ := strconv.Atoi(numStr)
fmt.Println("字符串转整数:", num)
fmt.Println("整数转字符串:", strconv.Itoa(100))
f, _ := strconv.ParseFloat("3.14", 64)
fmt.Println("字符串转浮点数:", f)
fmt.Println("浮点数转字符串:", strconv.FormatFloat(3.14159, 'f', 2, 64))
// === sort 包 ===
fmt.Println("\n=== sort 包 ===")
ints := []int{3, 1, 4, 1, 5, 9, 2, 6}
sort.Ints(ints)
fmt.Println("排序后:", ints)
strs := []string{"banana", "apple", "cherry"}
sort.Strings(strs)
fmt.Println("字符串排序:", strs)
// 自定义排序
people := []struct {
Name string
Age int
}{
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35},
}
sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age
})
fmt.Println("按年龄排序:", people)
// === time 包 ===
fmt.Println("\n=== time 包 ===")
now := time.Now()
fmt.Println("当前时间:", now)
fmt.Println("格式化:", now.Format("2006-01-02 15:04:05"))
fmt.Println("年:", now.Year(), "月:", now.Month(), "日:", now.Day())
t, _ := time.Parse("2006-01-02", "2024-12-25")
fmt.Println("解析时间:", t)
future := now.Add(24 * time.Hour)
fmt.Println("明天:", future.Format("2006-01-02"))
duration := future.Sub(now)
fmt.Println("间隔:", duration)
// === regexp 包(正则表达式)===
fmt.Println("\n=== regexp 包 ===")
re := regexp.MustCompile(`\d+`)
fmt.Println("匹配:", re.MatchString("abc123def"))
fmt.Println("查找:", re.FindString("abc123def456"))
fmt.Println("查找全部:", re.FindAllString("abc123def456ghi789", -1))
fmt.Println("替换:", re.ReplaceAllString("abc123def456", "NUM"))
// 捕获组
emailRe := regexp.MustCompile(`(\w+)@(\w+)\.(\w+)`)
matches := emailRe.FindStringSubmatch("user@example.com")
fmt.Println("邮箱匹配:", matches)
// === encoding/json 包 ===
fmt.Println("\n=== encoding/json 包 ===")
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}
p := Person{Name: "张三", Age: 25}
jsonBytes, _ := json.Marshal(p)
fmt.Println("JSON 编码:", string(jsonBytes))
jsonStr := `{"name":"李四","age":30,"email":"lisi@example.com"}`
var p2 Person
json.Unmarshal([]byte(jsonStr), &p2)
fmt.Printf("JSON 解码: %+v\n", p2)
// 美化输出
prettyJSON, _ := json.MarshalIndent(p2, "", " ")
fmt.Println("美化 JSON:\n", string(prettyJSON))
// === io 和 os 包 ===
fmt.Println("\n=== io 和 os 包 ===")
// 写文件
content := []byte("Hello, Go 文件操作!\n这是第二行。")
os.WriteFile("test.txt", content, 0644)
// 读文件
data, _ := os.ReadFile("test.txt")
fmt.Println("文件内容:", string(data))
// 使用 bufio 逐行读取
file, _ := os.Open("test.txt")
defer file.Close()
scanner := bufio.NewScanner(file)
lineNum := 1
for scanner.Scan() {
fmt.Printf("第 %d 行: %s\n", lineNum, scanner.Text())
lineNum++
}
// 清理测试文件
os.Remove("test.txt")
// === filepath 包 ===
fmt.Println("\n=== filepath 包 ===")
path := "/home/user/documents/file.txt"
fmt.Println("目录:", filepath.Dir(path))
fmt.Println("文件名:", filepath.Base(path))
fmt.Println("扩展名:", filepath.Ext(path))
fmt.Println("连接:", filepath.Join("home", "user", "file.txt"))
// === bytes 包 ===
fmt.Println("\n=== bytes 包 ===")
var buf bytes.Buffer
buf.WriteString("Hello")
buf.WriteString(", ")
buf.WriteString("World!")
fmt.Println("Buffer 内容:", buf.String())
// 复制到另一个 buffer
var buf2 bytes.Buffer
io.Copy(&buf2, &buf)
}
第十章:HTTP 与 Web 开发
Go 的 net/http 包提供了构建 HTTP 客户端和服务器的完整支持。
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
)
// 用于 JSON 响应的结构体
type Response struct {
Message string `json:"message"`
Timestamp time.Time `json:"timestamp"`
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
// 简单的处理函数
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, Go Web!")
}
// JSON 响应
func jsonHandler(w http.ResponseWriter, r *http.Request) {
resp := Response{
Message: "这是 JSON 响应",
Timestamp: time.Now(),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
// 处理不同 HTTP 方法
func usersHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
users := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
case http.MethodPost:
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
user.ID = 3 // 模拟分配 ID
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
default:
http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
}
}
// 中间件示例
func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("开始 %s %s", r.Method, r.URL.Path)
next(w, r)
log.Printf("完成 %s %s 耗时 %v", r.Method, r.URL.Path, time.Since(start))
}
}
func main() {
// 注册路由
http.HandleFunc("/", helloHandler)
http.HandleFunc("/json", jsonHandler)
http.HandleFunc("/users", loggingMiddleware(usersHandler))
// 静态文件服务
fs := http.FileServer(http.Dir("./static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
// HTTP 客户端示例
go func() {
time.Sleep(time.Second) // 等待服务器启动
// GET 请求
resp, err := http.Get("http://localhost:8080/json")
if err != nil {
log.Println("GET 错误:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
log.Println("GET 响应:", string(body))
// 自定义客户端(带超时)
client := &http.Client{
Timeout: 10 * time.Second,
}
req, _ := http.NewRequest("GET", "http://localhost:8080/users", nil)
req.Header.Set("Accept", "application/json")
resp2, err := client.Do(req)
if err != nil {
log.Println("请求错误:", err)
return
}
defer resp2.Body.Close()
var users []User
json.NewDecoder(resp2.Body).Decode(&users)
log.Printf("用户列表: %+v\n", users)
}()
// 启动服务器
fmt.Println("服务器启动在 http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
第十一章:测试
Go 内置了强大的测试框架。测试文件以 _test.go 结尾,测试函数以 Test 开头。
// calculator.go
package main
import "errors"
func Add(a, b int) int {
return a + b
}
func Subtract(a, b int) int {
return a - b
}
func Multiply(a, b int) int {
return a * b
}
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为零")
}
return a / b, nil
}
// calculator_test.go
package main
import (
"testing"
)
// 基本测试
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) = %d; 期望 %d", result, expected)
}
}
// 表格驱动测试
func TestAddTableDriven(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"正数相加", 2, 3, 5},
{"负数相加", -1, -2, -3},
{"正负相加", 5, -3, 2},
{"零相加", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d; 期望 %d", tt.a, tt.b, result, tt.expected)
}
})
}
}
// 测试错误情况
func TestDivide(t *testing.T) {
// 正常情况
result, err := Divide(10, 2)
if err != nil {
t.Fatalf("Divide(10, 2) 返回错误: %v", err)
}
if result != 5 {
t.Errorf("Divide(10, 2) = %f; 期望 5", result)
}
// 错误情况
_, err = Divide(10, 0)
if err == nil {
t.Error("Divide(10, 0) 应该返回错误")
}
}
// 基准测试
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2)
}
}
func BenchmarkMultiply(b *testing.B) {
for i := 0; i < b.N; i++ {
Multiply(10, 20)
}
}
// 示例测试(同时作为文档)
func ExampleAdd() {
result := Add(2, 3)
fmt.Println(result)
// Output: 5
}
运行测试的命令包括 go test(运行所有测试)、go test -v(详细输出)、go test -run TestAdd(运行特定测试)、go test -bench .(运行基准测试)、go test -cover(测试覆盖率)。
第十二章:泛型(Go 1.18+)
Go 1.18 引入了泛型,允许编写类型参数化的代码。
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
// 泛型函数
func Min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
func Max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
// 泛型切片函数
func Filter[T any](slice []T, predicate func(T) bool) []T {
result := make([]T, 0)
for _, v := range slice {
if predicate(v) {
result = append(result, v)
}
}
return result
}
func Map[T, U any](slice []T, f func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = f(v)
}
return result
}
func Reduce[T, U any](slice []T, initial U, f func(U, T) U) U {
result := initial
for _, v := range slice {
result = f(result, v)
}
return result
}
// 泛型结构体
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
func (s *Stack[T]) Peek() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
return s.items[len(s.items)-1], true
}
func (s *Stack[T]) Size() int {
return len(s.items)
}
// 自定义类型约束
type Number interface {
~int | ~int32 | ~int64 | ~float32 | ~float64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
// 泛型映射
type Pair[K comparable, V any] struct {
Key K
Value V
}
func Keys[K comparable, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
func Values[K comparable, V any](m map[K]V) []V {
values := make([]V, 0, len(m))
for _, v := range m {
values = append(values, v)
}
return values
}
func main() {
// 泛型函数
fmt.Println("Min(3, 5):", Min(3, 5))
fmt.Println("Min(3.14, 2.71):", Min(3.14, 2.71))
fmt.Println("Max(\"apple\", \"banana\"):", Max("apple", "banana"))
// Filter
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
fmt.Println("偶数:", evens)
// Map
doubled := Map(nums, func(n int) int { return n * 2 })
fmt.Println("加倍:", doubled)
strs := Map(nums, func(n int) string { return fmt.Sprintf("num_%d", n) })
fmt.Println("转字符串:", strs)
// Reduce
sum := Reduce(nums, 0, func(acc, n int) int { return acc + n })
fmt.Println("求和:", sum)
// 泛型栈
intStack := &Stack[int]{}
intStack.Push(1)
intStack.Push(2)
intStack.Push(3)
for intStack.Size() > 0 {
v, _ := intStack.Pop()
fmt.Println("弹出:", v)
}
stringStack := &Stack[string]{}
stringStack.Push("Go")
stringStack.Push("语言")
if top, ok := stringStack.Peek(); ok {
fmt.Println("栈顶:", top)
}
// Sum
ints := []int{1, 2, 3, 4, 5}
floats := []float64{1.1, 2.2, 3.3}
fmt.Println("整数求和:", Sum(ints))
fmt.Println("浮点数求和:", Sum(floats))
// Keys 和 Values
m := map[string]int{"a": 1, "b": 2, "c": 3}
fmt.Println("Keys:", Keys(m))
fmt.Println("Values:", Values(m))
}
第十三章:实战项目 - 简单的待办事项 API
让我们把所学知识整合到一个实际项目中。
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"sync"
"time"
)
// 待办事项结构
type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// 待办事项存储(线程安全)
type TodoStore struct {
mu sync.RWMutex
todos map[int]Todo
nextID int
}
func NewTodoStore() *TodoStore {
return &TodoStore{
todos: make(map[int]Todo),
nextID: 1,
}
}
func (s *TodoStore) Create(title string) Todo {
s.mu.Lock()
defer s.mu.Unlock()
now := time.Now()
todo := Todo{
ID: s.nextID,
Title: title,
Completed: false,
CreatedAt: now,
UpdatedAt: now,
}
s.todos[s.nextID] = todo
s.nextID++
return todo
}
func (s *TodoStore) GetAll() []Todo {
s.mu.RLock()
defer s.mu.RUnlock()
todos := make([]Todo, 0, len(s.todos))
for _, todo := range s.todos {
todos = append(todos, todo)
}
return todos
}
func (s *TodoStore) GetByID(id int) (Todo, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
todo, ok := s.todos[id]
return todo, ok
}
func (s *TodoStore) Update(id int, title string, completed bool) (Todo, bool) {
s.mu.Lock()
defer s.mu.Unlock()
todo, ok := s.todos[id]
if !ok {
return Todo{}, false
}
todo.Title = title
todo.Completed = completed
todo.UpdatedAt = time.Now()
s.todos[id] = todo
return todo, true
}
func (s *TodoStore) Delete(id int) bool {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.todos[id]; !ok {
return false
}
delete(s.todos, id)
return true
}
// HTTP 处理器
type TodoHandler struct {
store *TodoStore
}
func NewTodoHandler(store *TodoStore) *TodoHandler {
return &TodoHandler{store: store}
}
func (h *TodoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
path := strings.TrimPrefix(r.URL.Path, "/api/todos")
path = strings.TrimPrefix(path, "/")
if path == "" {
switch r.Method {
case http.MethodGet:
h.getAll(w, r)
case http.MethodPost:
h.create(w, r)
default:
http.Error(w, `{"error":"方法不允许"}`, http.StatusMethodNotAllowed)
}
return
}
id, err := strconv.Atoi(path)
if err != nil {
http.Error(w, `{"error":"无效的 ID"}`, http.StatusBadRequest)
return
}
switch r.Method {
case http.MethodGet:
h.getByID(w, r, id)
case http.MethodPut:
h.update(w, r, id)
case http.MethodDelete:
h.delete(w, r, id)
default:
http.Error(w, `{"error":"方法不允许"}`, http.StatusMethodNotAllowed)
}
}
func (h *TodoHandler) getAll(w http.ResponseWriter, r *http.Request) {
todos := h.store.GetAll()
json.NewEncoder(w).Encode(todos)
}
func (h *TodoHandler) getByID(w http.ResponseWriter, r *http.Request, id int) {
todo, ok := h.store.GetByID(id)
if !ok {
http.Error(w, `{"error":"待办事项不存在"}`, http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(todo)
}
func (h *TodoHandler) create(w http.ResponseWriter, r *http.Request) {
var input struct {
Title string `json:"title"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
http.Error(w, `{"error":"无效的 JSON"}`, http.StatusBadRequest)
return
}
if strings.TrimSpace(input.Title) == "" {
http.Error(w, `{"error":"标题不能为空"}`, http.StatusBadRequest)
return
}
todo := h.store.Create(input.Title)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(todo)
}
func (h *TodoHandler) update(w http.ResponseWriter, r *http.Request, id int) {
var input struct {
Title string `json:"title"`
Completed bool `json:"completed"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
http.Error(w, `{"error":"无效的 JSON"}`, http.StatusBadRequest)
return
}
todo, ok := h.store.Update(id, input.Title, input.Completed)
if !ok {
http.Error(w, `{"error":"待办事项不存在"}`, http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(todo)
}
func (h *TodoHandler) delete(w http.ResponseWriter, r *http.Request, id int) {
if !h.store.Delete(id) {
http.Error(w, `{"error":"待办事项不存在"}`, http.StatusNotFound)
return
}
w.WriteHeader(http.StatusNoContent)
}
// 日志中间件
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("开始 %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("完成 %s %s 耗时 %v", r.Method, r.URL.Path, time.Since(start))
})
}
// CORS 中间件
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
store := NewTodoStore()
// 添加一些示例数据
store.Create("学习 Go 语言基础")
store.Create("完成并发编程练习")
store.Create("构建 Web API 项目")
handler := NewTodoHandler(store)
mux := http.NewServeMux()
mux.Handle("/api/todos", handler)
mux.Handle("/api/todos/", handler)
// 健康检查端点
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})
// 应用中间件
finalHandler := corsMiddleware(loggingMiddleware(mux))
fmt.Println("待办事项 API 服务器启动在 http://localhost:8080")
fmt.Println("端点:")
fmt.Println(" GET /api/todos - 获取所有待办事项")
fmt.Println(" POST /api/todos - 创建待办事项")
fmt.Println(" GET /api/todos/:id - 获取单个待办事项")
fmt.Println(" PUT /api/todos/:id - 更新待办事项")
fmt.Println(" DELETE /api/todos/:id - 删除待办事项")
fmt.Println(" GET /health - 健康检查")
log.Fatal(http.ListenAndServe(":8080", finalHandler))
}
总结与进阶建议
本教程涵盖了 Go 语言的核心概念和实用技能。Go 语言以其简洁的语法、强大的并发支持和优秀的工具链,在云原生开发、微服务架构、网络编程等领域占据重要地位。
学习 Go 之后,建议继续探索以下方向。在 Web 框架方面,可以学习 Gin、Echo、Fiber 等流行框架。在数据库操作方面,可以了解 GORM、sqlx 等 ORM 和数据库工具。在微服务方面,可以研究 gRPC、Protocol Buffers、服务发现等技术。在云原生方面,可以学习 Docker、Kubernetes 相关的 Go 开发,如 client-go。在工具开发方面,可以使用 Cobra 构建 CLI 工具,使用 Viper 管理配置。
Go 社区活跃,资源丰富,官方文档是最好的学习材料。建议多阅读优秀开源项目的代码,如 Docker、Kubernetes、Prometheus 等,这些都是学习 Go 最佳实践的绝佳范例。