phase2-core.md 28 KB

第二阶段:Go语言核心概念

学习目标

  • 深入理解Go语言的核心特性
  • 掌握面向对象编程在Go中的实现
  • 理解并发编程模型
  • 能够设计中等复杂度的Go程序

详细学习内容

2.1 指针与内存管理

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 结构体与方法

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 接口与多态

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 错误处理

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 并发编程基础

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.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

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

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

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. 依赖管理

    # 初始化模块
    go mod init github.com/username/project
       
    # 添加依赖
    go get github.com/gin-gonic/gin
       
    # 整理依赖
    go mod tidy
       
    # 下载依赖
    go mod download
       
    # 查看依赖图
    go mod graph
    

练习项目

项目1:学生信息管理系统

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:并发文件下载器

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:简单的聊天服务器

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 <port>")
        fmt.Println("  客户端模式: go run chat.go client <server:port> <name>")
        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语言高级编程》- 系统学习高级特性

在线资源

开源项目

评估方式

代码质量评估

  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都能正常退出

下一步

完成本阶段学习后,进入第三阶段:实战模块开发