# 第二阶段:Go语言核心概念 ## 学习目标 - 深入理解Go语言的核心特性 - 掌握面向对象编程在Go中的实现 - 理解并发编程模型 - 能够设计中等复杂度的Go程序 ## 详细学习内容 ### 2.1 指针与内存管理 ```go package main import "fmt" func main() { // 指针基础 var x int = 10 var p *int = &x fmt.Printf("x的值: %d, x的地址: %p\n", x, &x) fmt.Printf("p存储的地址: %p, p指向的值: %d\n", p, *p) *p = 20 // 修改x的值 fmt.Printf("修改后x的值: %d\n", x) // new函数分配内存 ptr := new(int) *ptr = 100 fmt.Printf("new分配的内存地址: %p, 值: %d\n", ptr, *ptr) // 指针作为函数参数 changeValue(&x) fmt.Printf("函数调用后x的值: %d\n", x) } func changeValue(p *int) { *p = 50 } ``` ### 2.2 结构体与方法 ```go package main import "fmt" // 结构体定义 type Person struct { Name string Age int } // 构造函数 func NewPerson(name string, age int) *Person { return &Person{ Name: name, Age: age, } } // 值接收者方法 func (p Person) SayHello() string { return "Hello, " + p.Name } // 指针接收者方法 func (p *Person) SetAge(age int) { p.Age = age } func (p *Person) HaveBirthday() { p.Age++ fmt.Printf("%s 过生日了,现在 %d 岁\n", p.Name, p.Age) } func main() { // 创建结构体实例 p1 := Person{Name: "张三", Age: 25} fmt.Println(p1.SayHello()) // 使用构造函数 p2 := NewPerson("李四", 30) fmt.Println(p2.SayHello()) // 使用指针方法修改值 p2.SetAge(32) fmt.Printf("%s 现在 %d 岁\n", p2.Name, p2.Age) // 调用修改状态的方法 p2.HaveBirthday() } ``` ### 2.3 接口与多态 ```go package main import ( "fmt" "math" ) // 接口定义 type Shape interface { Area() float64 Perimeter() float64 } // 实现接口 - 矩形 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) } // 实现接口 - 圆形 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 Triangle struct { Base, Height float64 } func (t Triangle) Area() float64 { return 0.5 * t.Base * t.Height } func (t Triangle) Perimeter() float64 { // 假设是等腰三角形 side := math.Sqrt(t.Base*t.Base/4 + t.Height*t.Height) return t.Base + 2*side } // 多态函数 func PrintShapeInfo(s Shape) { fmt.Printf("形状面积: %.2f, 周长: %.2f\n", s.Area(), s.Perimeter()) } func main() { // 创建不同的形状 shapes := []Shape{ Rectangle{Width: 5, Height: 3}, Circle{Radius: 4}, Triangle{Base: 6, Height: 4}, } // 多态调用 for i, shape := range shapes { fmt.Printf("形状 %d: ", i+1) PrintShapeInfo(shape) } // 类型断言 for _, shape := range shapes { if rect, ok := shape.(Rectangle); ok { fmt.Printf("矩形的宽: %.2f, 高: %.2f\n", rect.Width, rect.Height) } else if circle, ok := shape.(Circle); ok { fmt.Printf("圆形的半径: %.2f\n", circle.Radius) } } } ``` ### 2.4 错误处理 ```go package main import ( "errors" "fmt" "strconv" ) // 错误处理模式 func divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil } // 自定义错误类型 type MyError struct { Code int Message string } func (e MyError) Error() string { return fmt.Sprintf("Error %d: %s", e.Code, e.Message) } // 使用自定义错误的函数 func processValue(value int) (string, error) { if value < 0 { return "", MyError{Code: 400, Message: "值不能为负数"} } if value > 100 { return "", MyError{Code: 413, Message: "值超出范围"} } return "处理成功: " + strconv.Itoa(value), nil } // 错误包装 func wrapError(value string) (int, error) { num, err := strconv.Atoi(value) if err != nil { return 0, fmt.Errorf("转换失败: %w", err) } return num, nil } func main() { // 测试基本错误处理 result, err := divide(10, 2) if err != nil { fmt.Println("错误:", err) } else { fmt.Println("结果:", result) } // 测试除零错误 _, err = divide(10, 0) if err != nil { fmt.Println("错误:", err) } // 测试自定义错误 values := []int{50, -5, 150} for _, v := range values { result, err := processValue(v) if err != nil { // 类型断言获取自定义错误 if myErr, ok := err.(MyError); ok { fmt.Printf("自定义错误 - 代码: %d, 消息: %s\n", myErr.Code, myErr.Message) } else { fmt.Println("其他错误:", err) } } else { fmt.Println(result) } } // 测试错误包装 inputs := []string{"123", "abc", "45.6"} for _, input := range inputs { num, err := wrapError(input) if err != nil { fmt.Printf("输入 '%s' 转换失败: %v\n", input, err) } else { fmt.Printf("转换成功: %d\n", num) } } } ``` ### 2.5 并发编程基础 ```go package main import ( "fmt" "sync" "time" ) // goroutine示例 func sayHello(name string) { for i := 0; i < 3; i++ { fmt.Printf("你好, %s! (第%d次)\n", name, i+1) time.Sleep(100 * time.Millisecond) } } // channel通信示例 func channelDemo() { fmt.Println("\n=== Channel通信示例 ===") // 创建无缓冲channel ch := make(chan int) // 启动发送数据的goroutine go func() { for i := 0; i < 5; i++ { fmt.Printf("发送: %d\n", i) ch <- i time.Sleep(200 * time.Millisecond) } close(ch) // 关闭channel }() // 接收数据 for value := range ch { fmt.Printf("接收: %d\n", value) } } // 缓冲channel示例 func bufferedChannelDemo() { fmt.Println("\n=== 缓冲Channel示例 ===") // 创建缓冲channel ch := make(chan string, 3) // 发送数据(不需要立即接收) ch <- "消息1" ch <- "消息2" ch <- "消息3" fmt.Println("已发送3条消息到缓冲channel") // 接收数据 fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch) } // 使用WaitGroup等待多个goroutine func waitGroupDemo() { fmt.Println("\n=== WaitGroup示例 ===") var wg sync.WaitGroup for i := 1; i <= 3; i++ { wg.Add(1) go func(id int) { defer wg.Done() fmt.Printf("Goroutine %d 开始工作\n", id) time.Sleep(time.Duration(id) * 200 * time.Millisecond) fmt.Printf("Goroutine %d 完成工作\n", id) }(i) } fmt.Println("等待所有goroutine完成...") wg.Wait() fmt.Println("所有goroutine已完成") } // select语句示例 func selectDemo() { fmt.Println("\n=== Select语句示例 ===") ch1 := make(chan string) ch2 := make(chan string) go func() { time.Sleep(1 * time.Second) ch1 <- "来自channel 1的消息" }() go func() { time.Sleep(2 * time.Second) ch2 <- "来自channel 2的消息" }() for i := 0; i < 2; i++ { select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) case <-time.After(3 * time.Second): fmt.Println("超时!") } } } func main() { // 启动多个goroutine fmt.Println("=== Goroutine示例 ===") go sayHello("张三") go sayHello("李四") // 等待一段时间让goroutine执行 time.Sleep(1 * time.Second) // 演示channel通信 channelDemo() // 演示缓冲channel bufferedChannelDemo() // 演示WaitGroup waitGroupDemo() // 演示select selectDemo() } ``` ### 2.6 包管理与模块化 ```go // go.mod文件示例 module github.com/username/project go 1.19 require ( github.com/gin-gonic/gin v1.9.0 github.com/go-sql-driver/mysql v1.7.0 ) ``` #### 包管理示例 创建以下目录结构: ``` myproject/ ├── go.mod ├── main.go └── calculator/ ├── add.go └── subtract.go ``` **calculator/add.go** ```go package calculator // Add 返回两个整数的和 func Add(a, b int) int { return a + b } // AddMultiple 返回多个整数的和 func AddMultiple(nums ...int) int { sum := 0 for _, num := range nums { sum += num } return sum } ``` **calculator/subtract.go** ```go package calculator // Subtract 返回两个整数的差 func Subtract(a, b int) int { return a - b } // SubtractMultiple 从第一个数中减去后续所有数 func SubtractMultiple(first int, nums ...int) int { result := first for _, num := range nums { result -= num } return result } ``` **main.go** ```go package main import ( "fmt" "github.com/username/project/calculator" ) func main() { // 使用自定义包中的函数 sum := calculator.Add(10, 20) fmt.Printf("10 + 20 = %d\n", sum) diff := calculator.Subtract(30, 15) fmt.Printf("30 - 15 = %d\n", diff) multipleSum := calculator.AddMultiple(1, 2, 3, 4, 5) fmt.Printf("1 + 2 + 3 + 4 + 5 = %d\n", multipleSum) multipleDiff := calculator.SubtractMultiple(100, 10, 20, 30) fmt.Printf("100 - 10 - 20 - 30 = %d\n", multipleDiff) } ``` #### 模块化最佳实践 1. **包命名规范** - 使用简短、小写的名称 - 避免使用下划线或混合大小写 - 包名应与目录名一致 2. **可见性规则** - 首字母大写的名称是公开的(可被其他包访问) - 首字母小写的名称是私有的(只能在包内访问) 3. **依赖管理** ```bash # 初始化模块 go mod init github.com/username/project # 添加依赖 go get github.com/gin-gonic/gin # 整理依赖 go mod tidy # 下载依赖 go mod download # 查看依赖图 go mod graph ``` ## 练习项目 ### 项目1:学生信息管理系统 ```go package main import ( "fmt" "sort" ) // 学生结构体 type Student struct { ID int Name string Grade int Score float64 } // 学生管理器 type StudentManager struct { students []Student nextID int } // 创建新的学生管理器 func NewStudentManager() *StudentManager { return &StudentManager{ students: make([]Student, 0), nextID: 1, } } // 添加学生 func (sm *StudentManager) AddStudent(name string, grade int, score float64) error { if name == "" { return fmt.Errorf("学生姓名不能为空") } if grade < 1 || grade > 12 { return fmt.Errorf("年级必须在1-12之间") } if score < 0 || score > 100 { return fmt.Errorf("分数必须在0-100之间") } student := Student{ ID: sm.nextID, Name: name, Grade: grade, Score: score, } sm.students = append(sm.students, student) sm.nextID++ fmt.Printf("成功添加学生: %s (ID: %d)\n", name, student.ID) return nil } // 根据ID查找学生 func (sm *StudentManager) FindStudentByID(id int) (Student, error) { for _, student := range sm.students { if student.ID == id { return student, nil } } return Student{}, fmt.Errorf("未找到ID为%d的学生", id) } // 更新学生信息 func (sm *StudentManager) UpdateStudent(id int, name string, grade int, score float64) error { for i, student := range sm.students { if student.ID == id { if name != "" { sm.students[i].Name = name } if grade >= 1 && grade <= 12 { sm.students[i].Grade = grade } if score >= 0 && score <= 100 { sm.students[i].Score = score } fmt.Printf("成功更新学生信息 (ID: %d)\n", id) return nil } } return fmt.Errorf("未找到ID为%d的学生", id) } // 删除学生 func (sm *StudentManager) DeleteStudent(id int) error { for i, student := range sm.students { if student.ID == id { sm.students = append(sm.students[:i], sm.students[i+1:]...) fmt.Printf("成功删除学生: %s (ID: %d)\n", student.Name, id) return nil } } return fmt.Errorf("未找到ID为%d的学生", id) } // 列出所有学生 func (sm *StudentManager) ListAllStudents() { if len(sm.students) == 0 { fmt.Println("暂无学生信息") return } fmt.Println("所有学生信息:") fmt.Println("ID\t姓名\t年级\t分数") for _, student := range sm.students { fmt.Printf("%d\t%s\t%d\t%.1f\n", student.ID, student.Name, student.Grade, student.Score) } } // 按分数排序 func (sm *StudentManager) SortByScore(descending bool) { sort.Slice(sm.students, func(i, j int) bool { if descending { return sm.students[i].Score > sm.students[j].Score } return sm.students[i].Score < sm.students[j].Score }) } // 计算平均分 func (sm *StudentManager) CalculateAverageScore() float64 { if len(sm.students) == 0 { return 0 } total := 0.0 for _, student := range sm.students { total += student.Score } return total / float64(len(sm.students)) } // 按年级筛选 func (sm *StudentManager) FilterByGrade(grade int) []Student { var filtered []Student for _, student := range sm.students { if student.Grade == grade { filtered = append(filtered, student) } } return filtered } func main() { // 创建学生管理器 manager := NewStudentManager() // 添加学生 manager.AddStudent("张三", 10, 85.5) manager.AddStudent("李四", 11, 92.0) manager.AddStudent("王五", 10, 78.5) manager.AddStudent("赵六", 12, 95.5) // 列出所有学生 manager.ListAllStudents() // 查找学生 student, err := manager.FindStudentByID(2) if err != nil { fmt.Println("错误:", err) } else { fmt.Printf("\n找到学生: %+v\n", student) } // 更新学生信息 err = manager.UpdateStudent(2, "李四华", 11, 93.5) if err != nil { fmt.Println("错误:", err) } // 按分数降序排序 manager.SortByScore(true) fmt.Println("\n按分数降序排序:") manager.ListAllStudents() // 计算平均分 avg := manager.CalculateAverageScore() fmt.Printf("\n所有学生的平均分: %.2f\n", avg) // 按年级筛选 grade10Students := manager.FilterByGrade(10) fmt.Println("\n10年级学生:") for _, student := range grade10Students { fmt.Printf("%s: %.1f分\n", student.Name, student.Score) } // 删除学生 err = manager.DeleteStudent(3) if err != nil { fmt.Println("错误:", err) } // 再次列出所有学生 fmt.Println("\n删除后的学生列表:") manager.ListAllStudents() } ``` ### 项目2:并发文件下载器 ```go package main import ( "fmt" "io" "net/http" "os" "path/filepath" "sync" "time" ) // 下载结果 type DownloadResult struct { URL string FilePath string Error error Size int64 Duration time.Duration } // 下载单个文件 func downloadFile(url, outputDir string, resultChan chan<- DownloadResult, wg *sync.WaitGroup) { defer wg.Done() startTime := time.Now() // 获取文件名 fileName := filepath.Base(url) if fileName == "." || fileName == "/" { fileName = "downloaded_file" } filePath := filepath.Join(outputDir, fileName) // 创建输出目录 if err := os.MkdirAll(outputDir, 0755); err != nil { resultChan <- DownloadResult{ URL: url, FilePath: "", Error: fmt.Errorf("创建目录失败: %v", err), Size: 0, Duration: time.Since(startTime), } return } // 创建文件 file, err := os.Create(filePath) if err != nil { resultChan <- DownloadResult{ URL: url, FilePath: "", Error: fmt.Errorf("创建文件失败: %v", err), Size: 0, Duration: time.Since(startTime), } return } defer file.Close() // 发送HTTP请求 resp, err := http.Get(url) if err != nil { resultChan <- DownloadResult{ URL: url, FilePath: "", Error: fmt.Errorf("下载失败: %v", err), Size: 0, Duration: time.Since(startTime), } return } defer resp.Body.Close() // 检查响应状态 if resp.StatusCode != http.StatusOK { resultChan <- DownloadResult{ URL: url, FilePath: "", Error: fmt.Errorf("服务器返回错误状态码: %d", resp.StatusCode), Size: 0, Duration: time.Since(startTime), } return } // 复制数据到文件 size, err := io.Copy(file, resp.Body) if err != nil { resultChan <- DownloadResult{ URL: url, FilePath: "", Error: fmt.Errorf("写入文件失败: %v", err), Size: 0, Duration: time.Since(startTime), } return } // 发送成功结果 resultChan <- DownloadResult{ URL: url, FilePath: filePath, Error: nil, Size: size, Duration: time.Since(startTime), } } // 并发下载多个文件 func DownloadFiles(urls []string, outputDir string, maxConcurrency int) []DownloadResult { // 限制并发数 semaphore := make(chan struct{}, maxConcurrency) var wg sync.WaitGroup resultChan := make(chan DownloadResult, len(urls)) // 启动下载goroutines for _, url := range urls { wg.Add(1) go func(url string) { defer wg.Done() // 获取信号量 semaphore <- struct{}{} defer func() { <-semaphore }() downloadFile(url, outputDir, resultChan, &wg) }(url) } // 等待所有下载完成 wg.Wait() close(resultChan) // 收集结果 var results []DownloadResult for result := range resultChan { results = append(results, result) } return results } // 显示下载进度 func showProgress(results []DownloadResult) { var totalSize int64 var totalTime time.Duration var successCount, failCount int fmt.Println("\n=== 下载结果 ===") for _, result := range results { if result.Error != nil { fmt.Printf("❌ %s: %v\n", result.URL, result.Error) failCount++ } else { fmt.Printf("✅ %s -> %s (%.2f KB, 耗时: %v)\n", result.URL, result.FilePath, float64(result.Size)/1024, result.Duration) totalSize += result.Size totalTime += result.Duration successCount++ } } fmt.Printf("\n总计: %d个文件, 成功: %d, 失败: %d\n", len(results), successCount, failCount) fmt.Printf("总大小: %.2f KB, 总耗时: %v\n", float64(totalSize)/1024, totalTime) if successCount > 0 { avgSpeed := float64(totalSize) / totalTime.Seconds() / 1024 fmt.Printf("平均下载速度: %.2f KB/s\n", avgSpeed) } } func main() { // 示例URL列表(使用一些公开的测试文件) urls := []string{ "https://httpbin.org/bytes/1024", // 1KB测试文件 "https://httpbin.org/bytes/5120", // 5KB测试文件 "https://httpbin.org/uuid", // UUID文本 "https://httpbin.org/ip", // IP地址 "https://httpbin.org/user-agent", // 用户代理 } // 设置输出目录 outputDir := "./downloads" // 设置最大并发数 maxConcurrency := 3 fmt.Printf("开始下载 %d 个文件,最大并发数: %d\n", len(urls), maxConcurrency) // 开始下载 start := time.Now() results := DownloadFiles(urls, outputDir, maxConcurrency) totalDuration := time.Since(start) // 显示结果 showProgress(results) fmt.Printf("总下载时间: %v\n", totalDuration) } ``` ### 项目3:简单的聊天服务器 ```go package main import ( "bufio" "fmt" "log" "net" "os" "strings" "sync" "time" ) // 客户端结构体 type Client struct { conn net.Conn name string messages chan string } // 聊天室结构体 type ChatRoom struct { clients map[*Client]bool register chan *Client unregister chan *Client broadcast chan string mutex sync.RWMutex } // 创建新的聊天室 func NewChatRoom() *ChatRoom { return &ChatRoom{ clients: make(map[*Client]bool), register: make(chan *Client), unregister: make(chan *Client), broadcast: make(chan string), } } // 运行聊天室 func (cr *ChatRoom) Run() { for { select { case client := <-cr.register: cr.mutex.Lock() cr.clients[client] = true cr.mutex.Unlock() fmt.Printf("客户端 %s 加入聊天室\n", client.name) // 广播欢迎消息 cr.broadcast <- fmt.Sprintf("系统: %s 加入了聊天室", client.name) case client := <-cr.unregister: cr.mutex.Lock() if _, ok := cr.clients[client]; ok { delete(cr.clients, client) close(client.messages) fmt.Printf("客户端 %s 离开聊天室\n", client.name) // 广播离开消息 cr.broadcast <- fmt.Sprintf("系统: %s 离开了聊天室", client.name) } cr.mutex.Unlock() case message := <-cr.broadcast: cr.mutex.RLock() for client := range cr.clients { select { case client.messages <- message: default: // 如果客户端消息通道阻塞,跳过 close(client.messages) delete(cr.clients, client) } } cr.mutex.RUnlock() } } } // 处理客户端连接 func (cr *ChatRoom) handleClient(client *Client) { // 发送欢迎消息 client.messages <- "欢迎来到聊天室!" // 读取客户端消息 scanner := bufio.NewScanner(client.conn) for scanner.Scan() { message := scanner.Text() if strings.TrimSpace(message) == "/quit" { break } // 广播消息 cr.broadcast <- fmt.Sprintf("%s: %s", client.name, message) } // 客户端断开连接 cr.unregister <- client client.conn.Close() } // 启动聊天服务器 func startServer(port string) { chatRoom := NewChatRoom() go chatRoom.Run() listener, err := net.Listen("tcp", ":"+port) if err != nil { log.Fatalf("无法启动服务器: %v", err) } defer listener.Close() fmt.Printf("聊天服务器启动,监听端口: %s\n", port) for { conn, err := listener.Accept() if err != nil { log.Printf("接受连接错误: %v", err) continue } // 创建新客户端 client := &Client{ conn: conn, name: fmt.Sprintf("用户%d", time.Now().Unix()%1000), messages: make(chan string, 10), } // 注册客户端 chatRoom.register <- client // 处理客户端消息 go chatRoom.handleClient(client) // 发送消息给客户端 go func(c *Client) { for msg := range c.messages { _, err := fmt.Fprintln(c.conn, msg) if err != nil { break } } }(client) } } // 聊天客户端 func startClient(serverAddr, name string) { conn, err := net.Dial("tcp", serverAddr) if err != nil { log.Fatalf("无法连接到服务器: %v", err) } defer conn.Close() // 发送客户端名称 fmt.Fprintf(conn, "%s\n", name) // 启动接收消息的goroutine go func() { scanner := bufio.NewScanner(conn) for scanner.Scan() { fmt.Println(scanner.Text()) } }() // 读取用户输入并发送 reader := bufio.NewReader(os.Stdin) for { fmt.Print("输入消息 (/quit 退出): ") text, err := reader.ReadString('\n') if err != nil { break } fmt.Fprintln(conn, text) if strings.TrimSpace(text) == "/quit" { break } } } func main() { if len(os.Args) < 2 { fmt.Println("使用方法:") fmt.Println(" 服务器模式: go run chat.go server ") fmt.Println(" 客户端模式: go run chat.go client ") return } mode := os.Args[1] switch mode { case "server": if len(os.Args) < 3 { fmt.Println("请指定端口号") return } port := os.Args[2] startServer(port) case "client": if len(os.Args) < 4 { fmt.Println("请指定服务器地址和用户名") return } serverAddr := os.Args[2] name := os.Args[3] startClient(serverAddr, name) default: fmt.Println("无效的模式,请使用 'server' 或 'client'") } } ``` ## 推荐资源 ### 书籍 - 《Go语言实战》- 实践性强的进阶教材 - 《Go并发编程实战》- 深入理解并发 - 《Go语言高级编程》- 系统学习高级特性 ### 在线资源 - [Effective Go](https://go.dev/doc/effective_go) - Go官方最佳实践 - [Go标准库文档](https://pkg.go.dev/std) - 标准库详细说明 - [Go语言规范](https://go.dev/ref/spec) - 语言规范文档 ### 开源项目 - [Gin Web框架](https://github.com/gin-gonic/gin) - 学习Web开发 - [Cobra命令行库](https://github.com/spf13/cobra) - 学习CLI应用 ## 评估方式 ### 代码质量评估 1. **接口设计**:接口是否合理,是否遵循单一职责原则 2. **错误处理**:错误处理是否完整,是否提供有意义的错误信息 3. **并发安全**:并发程序是否正确处理竞态条件 ### 项目评估标准 - 学生管理系统:完整的CRUD操作,合理的错误处理 - 文件下载器:并发下载,进度显示,错误恢复 - 聊天服务器:多客户端支持,消息广播,连接管理 ### 时间安排(2-3周) - 第1-4天:指针、结构体、方法 - 第5-8天:接口、错误处理 - 第9-12天:并发编程基础 - 第13-18天:项目练习与优化 ## 常见问题 ### Q: 什么时候使用值接收者,什么时候使用指针接收者? A: 需要修改接收者状态时用指针,否则用值接收者 ### Q: channel的缓冲和非缓冲有什么区别? A: 缓冲channel可以存储多个值,非缓冲channel需要同步发送接收 ### Q: 如何避免goroutine泄漏? A: 使用context控制goroutine生命周期,确保所有goroutine都能正常退出 ## 下一步 完成本阶段学习后,进入第三阶段:实战模块开发