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)
}
}
总结
核心要点
- 数组是值类型,切片是指向数组的引用
- 切片、字符串传递时只复制指针和长度
- 函数支持多返回值和命名返回值
- 接口通过隐式实现(鸭子类型)
- goroutine 轻量级并发,用 channel 通信
- 泛型使用方括号
[],在结构体级别定义类型参数
建议
- 多实践,通过编写代码加深理解
- 阅读 Go 标准库源码学习最佳实践
- 使用
go fmt格式化代码 - 使用
go test编写测试 - 利用
go tool pprof进行性能分析
下一步
- 深入学习:结构体、指针、反射
- 探索标准库:
net/http、database/sql、encoding/json - 学习框架:Gin、Echo、Beego 等
- 阅读《Go语言圣经》和《Go语言进阶之路》
祝你学习愉快!
版权声明
本文档为《Go语言高级编程(第2版)》(作者:柴树杉、曹春晖)的学习笔记整理。
原书信息:
- 书名:《Go语言高级编程(第2版)》
- 作者:柴树杉、曹春晖
- 出版社:电子工业出版社
本文档说明:
- 本文档基于原书内容整理,是个人学习笔记
- 原文版权归原作者和出版社所有
- 本文档仅供学习交流使用,请勿用于商业目的
- 如需引用,请标注原书来源