Go 语言快速入门教程

0 阅读8分钟

Go 语言快速入门教程

来源说明
本文档基于《Go语言高级编程(第2版)》(作者:柴树杉、曹春晖)第1章内容整理而成
原文版权归属原作者,本文档为学习笔记,仅供参考使用

目录


1. Go 语言简介

1.1 起源与特点

Go 语言由 Google 开发,结合了 C 语言的性能、Python 的简洁性和并发编程的便利性。

核心特点:

  • 并发编程:基于 CSP(通信顺序进程)理论,goroutine 使并发编程变得简单
  • 编译型语言:编译速度快,执行效率高
  • 垃圾回收:自动内存管理
  • 简洁的语法:语法精简,易于学习和使用

1.2 打印 Hello World

package main

import "fmt"

func main() {
    fmt.Println("Hello, 世界")
}

运行:

go run hello.go

2. 基础语法

2.1 变量声明

Go 语言有多种变量声明方式:

// 方式1:声明并指定类型
var name string = "Go"

// 方式2:声明后赋值(类型推断)
var name = "Go"

// 方式3:短变量声明(最常用)
name := "Go"

// 方式4:批量声明
var (
    name string = "Go"
    age  int    = 10
)

2.2 常量

const Pi = 3.14159
const Greeting = "你好, 世界"

2.3 数据类型

// 基本类型
var b bool       = true
var i int        = 42
var f float64    = 3.14
var s string     = "Go"

// 复合类型
var arr [3]int           // 数组
var slice []int          // 切片
var m map[string]int     // 映射

2.4 控制流

if 语句:

if x > 0 {
    fmt.Println("正数")
} else if x < 0 {
    fmt.Println("负数")
} else {
    fmt.Println("零")
}

for 循环:

// 方式1:传统 for 循环
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// 方式2:相当于 while
for x > 0 {
    x--
}

// 方式3:无限循环
for {
    // 做某事
    break
}

// 方式4:range 遍历
arr := []string{"a", "b", "c"}
for index, value := range arr {
    fmt.Println(index, value)
}

switch 语句:

switch x {
case 1:
    fmt.Println("一")
case 2:
    fmt.Println("二")
default:
    fmt.Println("未知")
}

2.5 defer 语句

defer 用于延迟执行函数,常用于资源清理:

func readFile() {
    f, err := os.Open("file.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()  // 函数返回前会执行
    // 使用 f
}

要点:

  • defer 执行顺序是 LIFO(后进先出)
  • 避免在循环中使用 defer(可能累积大量延迟函数)

3. 数组、字符串和切片

3.1 数组

Go 语言中数组是值类型,传递数组会复制整个数组。

// 数组声明
var arr1 [5]int                    // 默认值为 0
arr2 := [3]int{1, 2, 3}           // 指定初始值
arr3 := [...]int{1, 2, 3}         // 自动推断长度

fmt.Printf("%T\n", arr2)  // 输出: [3]int

特点:

  • 长度是类型的一部分:[3]int[5]int 是不同的类型
  • 传递大数组时,使用指针避免复制:func f(arr *[1000]int)

3.2 切片

切片是 Go 语言的动态数组,是对数组的抽象。

// 创建切片
slice1 := []int{1, 2, 3}          // 直接初始化
slice2 := make([]int, 3)          // 长度为 3
slice3 := make([]int, 3, 5)       // 长度为 3,容量为 5

// 从数组创建
arr := [5]int{1, 2, 3, 4, 5}
slice4 := arr[1:3]                // [2, 3]

添加元素:

slice := []int{1, 2, 3}
slice = append(slice, 4)          // 尾部追加
slice = append(slice, 5, 6, 7)   // 追加多个

删除元素:

// 删除尾部元素(最快)
slice := slice[:len(slice)-1]

// 删除开头元素
slice := slice[1:]

// 删除中间元素
slice = append(slice[:i], slice[i+1:]...)

要点:

  • 切片包含指针、长度和容量
  • 多个切片可以共享底层数组
  • 使用 len() 获取长度,cap() 获取容量

3.3 字符串

字符串在底层是一个只读的字节数组

str := "Hello, 世界"
fmt.Println(len(str))              // 字节数,可能不是字符数

// 遍历字符串
for i, b := range str {
    fmt.Printf("%d: %c\n", i, b)
}

要点:

  • 字符串是不可变的
  • 传递字符串只复制地址和长度(类似切片,但不可变)
  • 使用 strings 包进行字符串操作

4. 函数、方法和接口

4.1 函数

函数可以有多个返回值和命名返回值。

// 多返回值
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("除数不能为0")
    }
    return a / b, nil
}

// 使用
result, err := divide(10, 2)
if err != nil {
    fmt.Println(err)
} else {
    fmt.Println(result)
}

命名返回值:

func add(a, b int) (sum int) {
    sum = a + b  // 直接赋值,不需要 return sum
    return
}

可变参数:

func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

fmt.Println(sum(1, 2, 3, 4))  // 10

4.2 方法

方法是绑定到特定类型的函数。

type Person struct {
    Name string
    Age  int
}

// 值接收者(会复制)
func (p Person) GetName() string {
    return p.Name
}

// 指针接收者(修改原值)
func (p *Person) SetAge(age int) {
    p.Age = age
}

4.3 接口

Go 语言通过隐式接口实现面向对象编程(鸭子类型)。

// 定义接口
type Writer interface {
    Write([]byte) (int, error)
}

// 实现接口(无需显式声明)
type ConsoleWriter struct{}

func (cw ConsoleWriter) Write(data []byte) (int, error) {
    n, err := fmt.Println(string(data))
    return n, err
}

空接口:

var i interface{}  // 可以是任何类型

func printValue(v interface{}) {
    fmt.Printf("值: %v, 类型: %T\n", v, v)
}

类型断言:

value, ok := i.(string)
if ok {
    fmt.Println("是字符串:", value)
}

5. 并发编程

5.1 goroutine

goroutine 是 Go 的轻量级线程,用 go 关键字启动。

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("世界")  // 启动 goroutine
    say("你好")     // 主 goroutine
}

特点:

  • 启动代价极小(2-8 KB 栈)
  • 可启动成千上万个 goroutine
  • 由 Go 运行时管理

5.2 channel

channel 用于 goroutine 之间的通信和同步。

// 创建 channel
ch := make(chan int)           // 无缓冲
buffered := make(chan int, 3)  // 有缓冲(容量为3)

// 发送和接收
ch <- 42    // 发送
x := <-ch   // 接收

无缓冲 channel(同步):

ch := make(chan int)

go func() {
    ch <- 42  // 发送
}()

value := <-ch  // 接收(会阻塞直到有数据)
fmt.Println(value)

有缓冲 channel:

ch := make(chan int, 2)  // 容量为2

ch <- 1
ch <- 2  // 不会阻塞

fmt.Println(<-ch)  // 1
fmt.Println(<-ch)  // 2

关闭 channel:

close(ch)

// 接收时检查是否关闭
value, ok := <-ch
if !ok {
    fmt.Println("channel 已关闭")
}

range 遍历:

for value := range ch {
    fmt.Println(value)
}

5.3 select

select 用于同时等待多个 channel。

ch1 := make(chan string)
ch2 := make(chan string)

go func() { ch1 <- "第一个" }()
go func() { ch2 <- "第二个" }()

select {
case msg1 := <-ch1:
    fmt.Println(msg1)
case msg2 := <-ch2:
    fmt.Println(msg2)
}

超时控制:

select {
case value := <-ch:
    fmt.Println(value)
case <-time.After(2 * time.Second):
    fmt.Println("超时")
}

5.4 同步原语

互斥锁:

import "sync"

var mu sync.Mutex
var counter int

func increment() {
    mu.Lock()
    defer mu.Unlock()
    counter++
}

原子操作:

import "sync/atomic"

var counter int64

func increment() {
    atomic.AddInt64(&counter, 1)
}

WaitGroup:

var wg sync.WaitGroup

for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        // 执行任务
        fmt.Println("任务", id)
    }(i)
}

wg.Wait()  // 等待所有 goroutine 完成

5.5 内存模型

规则:

  • 同一 goroutine 内:顺序一致性保证
  • 不同 goroutine 间:需要显式同步
  • 无缓冲 channel 的发送完成在接收之前
  • sync 包的同步原语提供额外保证

6. 泛型编程

Go 1.18+ 开始支持泛型(使用方括号表示类型参数)。

6.1 基本语法

// 泛型函数
func Max[T comparable](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// 使用
fmt.Println(Max(3, 5))           // 5
fmt.Println(Max(3.14, 2.71))     // 3.14

6.2 类型约束

// 定义类型约束
type Number interface {
    int | float64
}

func Add[T Number](a, b T) T {
    return a + b
}

fmt.Println(Add(1, 2))           // 3
fmt.Println(Add(1.5, 2.5))       // 4.0

6.3 泛型结构体

type Stack[T any] struct {
    items []T
}

func NewStack[T any]() *Stack[T] {
    return &Stack[T]{items: make([]T, 0)}
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() T {
    if len(s.items) == 0 {
        panic("栈为空")
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item
}

注意:

  • 方法不能有独立的类型参数(必须使用结构体的类型参数)
  • 接口方法不能使用泛型
  • 方括号 [] 表示泛型参数(非尖括号 <>

7. 实践示例

7.1 命令行参数处理

package main

import (
    "flag"
    "fmt"
)

func main() {
    name := flag.String("name", "世界", "姓名")
    age := flag.Int("age", 0, "年龄")
    flag.Parse()
    
    fmt.Printf("姓名: %s, 年龄: %d\n", *name, *age)
}

7.2 HTTP 服务器

package main

import (
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

7.3 并发下载

package main

import (
    "fmt"
    "sync"
)

func download(url string, wg *sync.WaitGroup) {
    defer wg.Done()
    // 模拟下载
    fmt.Printf("下载: %s\n", url)
}

func main() {
    urls := []string{"url1", "url2", "url3"}
    var wg sync.WaitGroup
    
    for _, url := range urls {
        wg.Add(1)
        go download(url, &wg)
    }
    
    wg.Wait()
    fmt.Println("所有下载完成")
}

7.4 JSON 处理

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    // 序列化
    p := Person{Name: "张三", Age: 30}
    jsonData, _ := json.Marshal(p)
    fmt.Println(string(jsonData))
    
    // 反序列化
    var p2 Person
    json.Unmarshal(jsonData, &p2)
    fmt.Println(p2)
}

7.5 Worker Pool

package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d 处理作业 %d\n", id, job)
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    
    // 启动 3 个 worker
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    
    // 发送作业
    for j := 1; j <= 9; j++ {
        jobs <- j
    }
    close(jobs)
    
    // 收集结果
    for r := 1; r <= 9; r++ {
        fmt.Printf("结果: %d\n", <-results)
    }
}

总结

核心要点

  1. 数组是值类型,切片是指向数组的引用
  2. 切片、字符串传递时只复制指针和长度
  3. 函数支持多返回值和命名返回值
  4. 接口通过隐式实现(鸭子类型)
  5. goroutine 轻量级并发,用 channel 通信
  6. 泛型使用方括号 [],在结构体级别定义类型参数

建议

  • 多实践,通过编写代码加深理解
  • 阅读 Go 标准库源码学习最佳实践
  • 使用 go fmt 格式化代码
  • 使用 go test 编写测试
  • 利用 go tool pprof 进行性能分析

下一步

  • 深入学习:结构体、指针、反射
  • 探索标准库:net/httpdatabase/sqlencoding/json
  • 学习框架:Gin、Echo、Beego 等
  • 阅读《Go语言圣经》和《Go语言进阶之路》

祝你学习愉快!


版权声明

本文档为《Go语言高级编程(第2版)》(作者:柴树杉、曹春晖)的学习笔记整理。

原书信息:

  • 书名:《Go语言高级编程(第2版)》
  • 作者:柴树杉、曹春晖
  • 出版社:电子工业出版社

本文档说明:

  • 本文档基于原书内容整理,是个人学习笔记
  • 原文版权归原作者和出版社所有
  • 本文档仅供学习交流使用,请勿用于商业目的
  • 如需引用,请标注原书来源