入门
Hello World
每个 Go 程序都从 package main 的 main() 函数开始。使用 'go run' 执行,'go build' 编译。gofmt 自动格式化代码(制表符、空格)。import 语句引入包 —— fmt 处理格式化 I/O。
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
fmt.Printf("Name: %s, Age: %d\n", "Alice", 30)
}
// Run: go run main.go
// Build: go build -o app main.go
// Format: gofmt -w main.goGo 模块 (go.mod)
Go 模块 (go.mod) 自 Go 1.11 起管理依赖项。'go mod init' 创建模块文件。'go get' 添加依赖项。'go mod tidy' 移除未使用的导入并添加缺失的导入。模块路径是包的导入路径。
// Initialize a new module
// $ go mod init github.com/user/project
// go.mod file:
module github.com/user/project
go 1.21
require (
github.com/gin-gonic/gin v1.9.1
golang.org/x/sync v0.3.0
)
// Add dependencies:
// $ go get github.com/gin-gonic/gin
// $ go mod tidy // clean up unused deps包结构
Go 以包组织代码 —— 每个目录一个包。大写名称 (Println, Add) 是导出的(公开的);小写名称是包私有的。'internal' 目录限制只能被父模块导入。包名应与目录名匹配。
// project structure:
// myproject/
// go.mod
// main.go // package main
// utils/
// helper.go // package utils
// math.go
// internal/
// api/ // private (only importable within parent)
// main.go
package main
import (
"fmt"
"github.com/user/project/utils"
)
func main() {
fmt.Println(utils.Add(1, 2))
}
// Exported names start with uppercase letter
// unexported names start with lowercase letter变量与常量
使用 'var name type = value' 进行显式声明,'name := value' 进行短声明(仅限函数内)。Go 有零值:数字为 0,字符串为 '',布尔值为 false,指针/切片/map 为 nil。常量 (const) 在编译时确定,不能使用 :=。
package main
import "fmt"
// Package-level declarations
var version = "1.0.0" // inferred type
const Pi = 3.14159
func main() {
// var with type
var name string = "Alice"
var age int = 30
// Short declaration (only inside functions)
city := "NYC"
// Multiple assignment
a, b, c := 1, 2, 3
// Zero values (default)
var x int // 0
var s string // ""
var ok bool // false
var p *int // nil
fmt.Println(name, age, city, a, b, c, x, s, ok, p)
}基本类型与转换
Go 要求显式类型转换 —— 没有 C 中的隐式转换。rune 是 int32 的别名(表示 Unicode 码点)。byte 是 uint8 的别名。在数字类型之间转换可能丢失精度(float 转 int 会截断)。string(65) 将码点转换为对应字符。
// Numeric types
var i int = 42
var i8 int8 = 127
var u uint = 42
var f32 float32 = 3.14
var f64 float64 = 3.141592653589793
// Other types
var b bool = true
var s string = "hello"
var r rune = 'A' // int32 alias for Unicode code point
var by byte = 255 // uint8 alias
// Type conversion (explicit, no implicit conversion)
var n int = 42
var f float64 = float64(n) // int -> float64
var s string = string(65) // 65 -> "A" (rune to string)
var i2 int = int(f) // float64 -> int (truncates)
// Type inference
var x = 42 // int
var y = 3.14 // float64
var z = len("hi") // int字符串与格式化
字符串基础
Go 字符串是不可变的字节序列。len() 返回字节数而非字符数 —— 使用 utf8.RuneCountInString() 获取 Unicode 字符数。字符串用 ==、<、> 按字典序比较。range 遍历字符串时按 rune(Unicode 码点)迭代,正确处理多字节字符。
package main
import (
"fmt"
"strings"
)
func main() {
s := "Hello, World"
// Length (bytes, not runes!)
fmt.Println(len(s)) // 12
// Concatenation
s1 := "Hello" + " " + "World"
s2 := fmt.Sprintf("%s = %d", "age", 30)
// Comparison
fmt.Println("abc" == "abc") // true
fmt.Println("a" < "b") // true (lexicographic)
// Iteration (by rune)
for i, r := range "Go语" {
fmt.Printf("%d: %c\n", i, r)
}
}strings 包
strings 包提供常用字符串操作。Contains/HasPrefix/HasSuffix 检查子串。Split 按分隔符拆分;Join 合并。Replace 接受计数参数(-1 表示全部)。TrimSpace 移除首尾空白。所有函数都返回新字符串(字符串不可变)。
import (
"fmt"
"strings"
)
s := "Hello, World"
// Case
strings.ToUpper(s) // "HELLO, WORLD"
strings.ToLower(s) // "hello, world"
strings.Title("hello world") // "Hello World"
// Search
strings.Contains(s, "World") // true
strings.HasPrefix(s, "Hello") // true
strings.HasSuffix(s, "World") // true
strings.Index(s, "World") // 7 (-1 if not found)
strings.Count(s, "l") // 3
// Split & Join
parts := strings.Split("a,b,c", ",") // ["a", "b", "c"]
joined := strings.Join(parts, "-") // "a-b-c"
// Replace
strings.Replace(s, "l", "L", 1) // "HeLlo, World" (1 occurrence)
strings.ReplaceAll(s, "l", "L") // "HeLLo, WorLd"
strings.TrimSpace(" hi ") // "hi"strconv 与格式化
strconv 在字符串和数字之间转换 —— 始终检查错误返回值。fmt.Printf 格式化输出:%d (int)、%f (float)、%s (string)、%t (bool)、%T (类型)、%x (十六进制)、%q (带引号)。使用 %.2f 保留 2 位小数,%05d 进行零填充。Sprintf 返回字符串而非打印。
import (
"fmt"
"strconv"
)
// String to number
n, err := strconv.Atoi("42") // string -> int
f, err := strconv.ParseFloat("3.14", 64)
// Number to string
s1 := strconv.Itoa(42) // int -> string
s2 := strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
// Printf formatting
fmt.Printf("Int: %d\n", 42)
fmt.Printf("Float: %.2f\n", 3.14159)
fmt.Printf("String: %s\n", "hi")
fmt.Printf("Bool: %t\n", true)
fmt.Printf("Type: %T\n", 42) // int
fmt.Printf("Hex: %x\n", 255) // ff
fmt.Printf("Pad: %05d\n", 42) // 00042
fmt.Printf("Quote: %q\n", "hi") // "hi"
// Sprintf returns formatted string
result := fmt.Sprintf("Name: %s, Age: %d", "Alice", 30)原始字符串与多行
原始字符串(反引号)原样保留所有内容 —— 没有转义序列,可跨多行。用于正则表达式、SQL、HTML 模板。解释字符串(双引号)处理 \n、\t 等。在循环中高效拼接字符串时,使用 strings.Builder(避免 O(n²) 分配)。
// Raw string literal (backticks) - no escape processing
raw := `This is a
multiline string
with \n (literal backslash-n)`
// Interpreted string (double quotes) - processes escapes
interpreted := "Line1\nLine2\tTabbed"
// Raw strings are useful for:
// - Regex patterns
// - HTML/SQL templates
// - File paths on Windows
regex := `^d{4}-d{2}-d{2}$`
// String builder (efficient concatenation)
var sb strings.Builder
for i := 0; i < 1000; i++ {
sb.WriteString("line\n")
}
result := sb.String()Unicode 与 Rune
Go 字符串是 UTF-8 编码的字节序列。len() 返回字节数;utf8.RuneCountInString() 返回字符数。range 遍历字符串时自动解码 UTF-8。要修改字符串,转换为 []rune,修改后再转回。这对国际化文本处理至关重要。
import (
"fmt"
"unicode/utf8"
)
s := "Hello, 世界"
// Byte length vs rune length
fmt.Println(len(s)) // 13 (bytes)
fmt.Println(utf8.RuneCountInString(s)) // 9 (runes/characters)
// Decode runes manually
for i := 0; i < len(s); {
r, size := utf8.DecodeRuneInString(s[i:])
fmt.Printf("%c ", r)
i += size
}
// Range automatically decodes runes
for i, r := range s {
fmt.Printf("%d:%c ", i, r)
}
// Rune slice (mutable)
runes := []rune("Hello")
runes[0] = 'J'
s2 := string(runes) // "Jello"控制流
If / Else
Go 的 if 不需要在条件周围加括号,但花括号是必需的(即使是单行体)。初始化语句 (if x := f(); x > 0) 的 作用域限于 if/else 块 —— 常用于错误检查。此模式使变量作用域保持紧凑。
// Basic if/else
score := 85
if score >= 90 {
fmt.Println("A")
} else if score >= 80 {
fmt.Println("B")
} else {
fmt.Println("C")
}
// If with initialization statement
if n := computeValue(); n > 100 {
fmt.Println("big:", n)
} else {
fmt.Println("small:", n)
}
// n is not accessible here (scoped to if)
// No parentheses needed, but braces are requiredFor 循环
Go 只有一个循环关键字:'for'。它处理 C 风格、while 风格 (for cond)、无限循环 (for) 和迭代 (for range)。range 适用于切片、map、字符串和通道。使用 _ 跳过索引或值。map 迭代顺序按设计是随机的。
// C-style for
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// While-style (condition only)
n := 10
for n > 0 {
n--
}
// Infinite loop
for {
break
}
// Range (iterate slices, maps, strings)
nums := []int{10, 20, 30}
for index, value := range nums {
fmt.Printf("%d: %d\n", index, value)
}
// Skip index or value with _
for _, value := range nums {
fmt.Println(value)
}
// Range over map (unordered!)
m := map[string]int{"a": 1, "b": 2}
for key, val := range m {
fmt.Println(key, val)
}Switch
Go 的 switch 默认不穿透(不同于 C/Java)—— 每个 case 是独立分支。使用 fallthrough 强制穿透。每个 case 可用逗号匹配多个值。无表达式的 switch 作为更清晰的 if/else 链。带初始化语句的 switch 将变量作用域限制在 switch 内。
// Basic switch
day := 3
switch day {
case 1:
fmt.Println("Mon")
case 2, 3, 4, 5:
fmt.Println("Weekday")
case 6, 7:
fmt.Println("Weekend")
default:
fmt.Println("Invalid")
}
// Switch with no expression (like if/else chain)
switch {
case score >= 90:
grade = "A"
case score >= 80:
grade = "B"
default:
grade = "C"
}
// Switch with init
switch os := runtime.GOOS; os {
case "linux":
fmt.Println("Linux")
case "darwin":
fmt.Println("macOS")
}
// Fallthrough (rare, goes to next case unconditionally)
switch 1 {
case 1:
fmt.Println("one")
fallthrough
case 2:
fmt.Println("two") // executes even though value is 1
}Defer
defer 调度一个函数调用在 surrounding 函数返回时执行 —— 后进先出 (LIFO)。用于清理(关闭文件、释放锁、关闭连接)。延迟调用的参数会立即求值,但调用在返回时执行。即使函数 panic,defer 也会运行。
// Defer runs when function returns (LIFO order)
func main() {
defer fmt.Println("third")
defer fmt.Println("second")
fmt.Println("first")
}
// Output: first, second, third
// Common: resource cleanup
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // runs when readFile returns
// ... use f ...
return nil
}
// Defer evaluates arguments immediately
i := 1
defer fmt.Println(i) // prints 1 (not 2)
i = 2Goto、Break、Continue
break 退出最内层循环;continue 跳到下一次迭代。标签 (break outer) 退出嵌套循环。goto 存在但很少使用 —— 优先使用结构化控制流。标签也可与 continue 一起使用,跳到外层循环的下一次迭代。
// Break and continue
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue // skip even numbers
}
if i > 7 {
break // exit loop
}
fmt.Println(i)
}
// Break with label (for nested loops)
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == 1 && j == 1 {
break outer // exits both loops
}
}
}
// Goto (rarely used, avoid)
i := 0
loop:
if i < 5 {
fmt.Println(i)
i++
goto loop
}Select(通道操作)
select 类似于通道的 switch —— 它等待多个通道操作并选择第一个就绪的。default case 使其非阻塞。在循环中使用 select 实现事件驱动模式。time.After() 创建超时通道。如果多个 case 同时就绪,select 随机选择,防止饥饿。
// Select waits on multiple channel operations
select {
case msg := <-messages:
fmt.Println("received:", msg)
case <-timeout:
fmt.Println("timed out")
default:
fmt.Println("no activity") // non-blocking
}
// Select with send
ch1 := make(chan int)
ch2 := make(chan int)
go func() { ch1 <- 1 }()
go func() { ch2 <- 2 }()
select {
case v := <-ch1:
fmt.Println("ch1:", v)
case v := <-ch2:
fmt.Println("ch2:", v)
}
// Select in a loop (event loop pattern)
for {
select {
case msg := <-ch:
fmt.Println(msg)
case <-time.After(5 * time.Second):
return // timeout after 5s of inactivity
}
}函数
定义与多返回值
Go 函数可返回多个值 —— 规范模式是 (result, error)。命名返回值作为已声明的变量初始化为零值;'裸' return 使用它们。命名返回值能提高复杂函数的可读性,但过度使用可能造成困惑。始终立即检查错误。
// Function with multiple return values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("divide by zero")
}
return a / b, nil
}
// Named return values
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // "naked" return (uses named values)
}
// Usage
result, err := divide(10, 2)
if err != nil {
log.Fatal(err)
}
fmt.Println(result) // 5
x, y := split(100)
fmt.Println(x, y)可变参数与闭包
可变参数函数 (...T) 接受任意数量的参数,作为切片接收。用 ... 展开切片。闭包通过引用从其外围作用域捕获变量 —— counter() 函数返回一个记住 'count' 的闭包。闭包适用于回调、迭代器和有状态函数。
// Variadic function (variable arguments)
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
// Spread a slice
nums := []int{1, 2, 3}
fmt.Println(sum(nums...)) // 6
// Closure (function that captures variables)
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
next := counter()
fmt.Println(next()) // 1
fmt.Println(next()) // 2
fmt.Println(next()) // 3函数作为值
Go 中的函数是一等值 —— 可以赋值给变量、作为参数传递、存储在数据结构中。用 'type Name func(params) returns' 定义函数类型。函数 map 适用于分发表、命令处理程序和策略模式。
// Functions are first-class values
func apply(f func(int) int, x int) int {
return f(x)
}
func double(x int) int { return x * 2 }
func square(x int) int { return x * x }
fmt.Println(apply(double, 5)) // 10
fmt.Println(apply(square, 5)) // 25
// Function type
type MathFunc func(int) int
var fn MathFunc = func(x int) int { return x + 1 }
fmt.Println(fn(10)) // 11
// Map of functions
ops := map[string]func(int, int) int{
"add": func(a, b int) int { return a + b },
"sub": func(a, b int) int { return a - b },
"mul": func(a, b int) int { return a * b },
}
fmt.Println(ops["add"](3, 4)) // 7init() 与匿名函数
init() 函数在 main() 之前自动运行,按声明顺序执行。用于设置(配置加载、验证、注册)。每个文件允许多个 init() 函数。匿名函数可立即调用 (IIFE) 或赋值给变量。它们也用于 goroutine。
// init() runs before main(), once per package
// Multiple init() functions allowed, run in order declared
package main
import "fmt"
var config string
func init() {
// Setup code: config, connections, validation
config = "production"
fmt.Println("init 1")
}
func init() {
fmt.Println("init 2, config:", config)
}
func main() {
fmt.Println("main")
}
// Output: init 1, init 2, main
// Anonymous function (IIFE)
result := func(x int) int {
return x * 2
}(5) // immediately invoked, result = 10
// Goroutine with anonymous function
go func(msg string) {
fmt.Println(msg)
}("async")泛型 (Go 1.18+)
Go 1.18+ 支持带类型参数 [T any] 的泛型。类型约束(接口)限制允许的类型 —— 使用 'any' 表示无约束,'comparable' 用于 == / != 运算符。联合约束 (int | float64) 允许特定类型。泛型支持类型安全的可复用数据结构和算法,无需代码重复。
// Generic function with type parameter
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
}
nums := []int{1, 2, 3}
doubled := Map(nums, func(n int) int { return n * 2 })
// [2, 4, 6]
strs := Map(nums, func(n int) string {
return fmt.Sprintf("num%d", n)
})
// ["num1", "num2", "num3"]
// Type constraints
type Number interface {
int | int64 | float64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
// comparable constraint (for maps/slices keys)
func Contains[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}方法
方法是带接收者参数的函数。值接收者 (r Rectangle) 在副本上操作 —— 无法修改原始值。指针接收者 (r *Rectangle) 可修改且避免复制大型结构体。一致性很重要:如果一个方法使用指针接收者,所有方法都应如此。方法可在同一包中的任何类型上定义。
type Rectangle struct {
Width, Height float64
}
// Method with value receiver
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// Method with pointer receiver (can modify)
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
// Method on non-struct types
type MyString string
func (s MyString) Shout() string {
return strings.ToUpper(string(s)) + "!"
}
// Usage
r := Rectangle{Width: 10, Height: 5}
fmt.Println(r.Area()) // 50
r.Scale(2)
fmt.Println(r.Width) // 20
s := MyString("hello")
fmt.Println(s.Shout()) // HELLO!数据结构
数组与切片
数组长度固定;切片是动态的(基于数组)。append() 添加元素,按需增长容量。make([]T, len, cap) 预分配以提高效率。切片创建视图(共享底层数组)—— 使用 copy() 获得独立数据。优化时始终检查 len/cap。
// Array (fixed length, rarely used directly)
var arr [3]int = [3]int{1, 2, 3}
arr2 := [...]int{4, 5, 6} // size inferred
// Slice (dynamic array, most common)
nums := []int{1, 2, 3}
nums = append(nums, 4) // [1, 2, 3, 4]
nums = append(nums, 5, 6, 7) // [1, 2, 3, 4, 5, 6, 7]
// Make a slice with capacity
s := make([]int, 3, 10) // len=3, cap=10
// Slicing
sub := nums[1:4] // [2, 3, 4]
first := nums[:2] // [1, 2]
last := nums[3:] // [4, 5, 6, 7]
// Copy
dst := make([]int, len(nums))
copy(dst, nums)
// Length and capacity
fmt.Println(len(nums), cap(nums))Map
map 是 Go 的哈希表 —— 无序的键/值对。comma-ok 模式 (val, ok := m[key]) 检查键是否存在。delete() 移除键。map 迭代顺序按设计是随机的。map 是引用类型 —— 传递给函数会共享底层数据。nil map 不能写入(使用 make())。
// Create a map
m := map[string]int{
"Alice": 30,
"Bob": 25,
}
// Using make
ages := make(map[string]int)
ages["Charlie"] = 35
// Access
fmt.Println(m["Alice"]) // 30
// Check existence (comma-ok pattern)
age, ok := m["David"]
if !ok {
fmt.Println("not found")
}
// Delete
delete(m, "Bob")
// Iterate (random order)
for name, age := range m {
fmt.Printf("%s: %d\n", name, age)
}
// Length
fmt.Println(len(m))
// Nested maps
matrix := map[string]map[string]int{}
matrix["row1"] = map[string]int{"col1": 1}结构体
结构体将相关字段分组。使用命名初始化 (Person{Name: ...}) 以提高清晰度。new() 返回带零值的指针。匿名结构体适用于一次性数据形状。结构体嵌入(无字段名)会提升被嵌入结构体的字段和方法 —— Go 中继承的替代方案。
// Define a struct
type Person struct {
Name string
Age int
Address string // field names are exported (capitalized)
}
// Create instances
p1 := Person{Name: "Alice", Age: 30}
p2 := Person{"Bob", 25, "NYC"} // positional (not recommended)
p3 := new(Person) // returns *Person, zero values
p3.Name = "Charlie"
// Anonymous struct (one-off)
config := struct {
Port int
Host string
}{
Port: 8080,
Host: "localhost",
}
// Struct embedding (composition)
type Employee struct {
Person // embedded (promotes fields)
Salary float64
}
emp := Employee{
Person: Person{Name: "Dave", Age: 40},
Salary: 50000,
}
fmt.Println(emp.Name) // "Dave" (promoted from Person)指针
Go 指针 (*T) 持有内存地址。& 取地址,* 解引用。不同于 C,Go 没有指针运算(更安全)。结构体指针允许简写 (u.Name 而非 (*u).Name)。nil 指针在解引用时会导致 panic。Go 有垃圾回收 —— 无需手动 free()。
// Pointer basics
x := 42
p := &x // p is *int, points to x
fmt.Println(*p) // 42 (dereference)
*p = 100 // modify x through pointer
fmt.Println(x) // 100
// new() allocates and returns pointer
p2 := new(int) // *int, value 0
*p2 = 42
// Pointers to structs
type User struct{ Name string }
u := &User{Name: "Alice"} // *User
u.Name = "Bob" // (*u).Name shorthand
fmt.Println(u.Name) // Bob
// nil pointer
var p3 *int // nil
// *p3 // panic: nil pointer dereference
// No pointer arithmetic (unlike C)
// p++ is illegal切片操作
Go 没有内置的 filter/map/reduce —— 自己编写或使用 slices 包 (Go 1.21+)。插入/删除需要用 append+copy 移动元素。注意切片别名:s[:i] 和 s[i+1:] 共享底层数组。对于二维切片,需分别分配每一行。
// Filter
func filter(nums []int, pred func(int) bool) []int {
result := []int{}
for _, n := range nums {
if pred(n) {
result = append(result, n)
}
}
return result
}
evens := filter([]int{1,2,3,4,5}, func(n int) bool {
return n%2 == 0
}) // [2, 4]
// Insert at index
func insert(s []int, i, v int) []int {
s = append(s, 0)
copy(s[i+1:], s[i:])
s[i] = v
return s
}
// Remove at index
func remove(s []int, i int) []int {
return append(s[:i], s[i+1:]...)
}
// 2D slice
matrix := make([][]int, 3)
for i := range matrix {
matrix[i] = make([]int, 4)
}排序与搜索
sort.Ints/Strings/Float64s 原地排序。sort.Slice 配合比较器处理自定义类型。sort.Search* 对已排序切片进行二分搜索。Go 1.21+ 添加了带泛型排序函数的 slices 包。所有排序都是原地的 —— 如需保留原始顺序,请先复制。
import "sort"
// Sort a slice
nums := []int{3, 1, 4, 1, 5, 9, 2, 6}
sort.Ints(nums) // [1 1 2 3 4 5 6 9]
fmt.Println(nums)
// Sort strings
strs := []string{"banana", "apple", "cherry"}
sort.Strings(strs) // [apple banana cherry]
// Sort with custom comparator
type Person struct {
Name string
Age int
}
people := []Person{
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35},
}
sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age // by age ascending
})
// Binary search (sorted slice)
idx := sort.SearchInts(nums, 4) // index of 4
// Sort package (Go 1.21+): slices.Sort
import "slices"
slices.Sort(people) // needs Less method or cmp func方法与接口
定义接口
接口定义方法签名。Go 使用结构化类型 —— 如果类型拥有所有必需的方法,则自动实现接口(无需显式 'implements' 声明)。这实现了解耦设计:在使用处定义接口,而非在实现处。
// Interface: a set of method signatures
type Shape interface {
Area() float64
Perimeter() float64
}
// Implement implicitly (no 'implements' keyword)
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
}
// Use the interface
func describe(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n",
s.Area(), s.Perimeter())
}
c := Circle{Radius: 5}
describe(c) // works! Circle implements Shape空接口与类型断言
interface{}(或 Go 1.18+ 中的 'any')可持有任何值。类型断言 (v.(T)) 提取具体类型 —— 类型错误时会 panic,因此使用 comma-ok 模式。类型 switch (switch v.(type)) 优雅处理多种类型。空接口适用于通用容器但失去类型安全 —— 优先使用泛型。
// Empty interface (any type)
// Go 1.18+: 'any' is an alias for interface{}
func printAny(v any) {
fmt.Println(v)
}
printAny(42)
printAny("hello")
printAny([]int{1, 2, 3})
// Type assertion
var i any = "hello"
s := i.(string) // panics if not string
fmt.Println(s)
// Safe type assertion (comma-ok)
s, ok := i.(string)
if ok {
fmt.Println("string:", s)
}
// Type switch
func describe(v any) {
switch x := v.(type) {
case int:
fmt.Printf("int: %d\n", x)
case string:
fmt.Printf("string: %s\n", x)
case []int:
fmt.Printf("int slice: %v\n", x)
default:
fmt.Printf("unknown type: %T\n", x)
}
}接口组合
接口可以嵌入其他接口(组合)。io.Reader 和 io.Writer 是 Go 最重要的接口 —— 由文件、网络连接、缓冲区等实现。这实现了强大的抽象:接受 io.Reader 的函数适用于任何可读源。小型、专注的接口(1-3 个方法)是惯用法。
// Compose interfaces
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// ReadWriter combines Reader and Writer
type ReadWriter interface {
Reader
Writer
}
// io.Reader and io.Writer are built-in interfaces
// Many types implement them: *os.File, *bytes.Buffer, net.Conn
func copyData(r io.Reader, w io.Writer) error {
buf := make([]byte, 1024)
for {
n, err := r.Read(buf)
if n > 0 {
if _, err := w.Write(buf[:n]); err != nil {
return err
}
}
if err == io.EOF {
break
}
if err != nil {
return err
}
}
return nil
}指针接收者 vs 值接收者
指针接收者可修改结构体并避免复制大值。值接收者是安全的(无法修改)且允许在值和指针上调用方法。如果任何方法使用指针接收者,所有方法都应如此(保持一致性)。指针接收者方法对 T 和 *T 都满足接口;值接收者方法仅对 T 满足。
type Counter struct {
count int
}
// Value receiver: works on a copy, can't modify
func (c Counter) Get() int {
return c.count
}
// Pointer receiver: can modify the original
func (c *Counter) Increment() {
c.count++
}
c := Counter{}
c.Increment() // Go auto-takes address (&c).Increment()
c.Increment()
fmt.Println(c.Get()) // 2
// Interface implementation note:
// *Counter implements both Get() and Increment()
// Counter (value) implements only Get()
// So *Counter satisfies an interface requiring bothStringer 接口
Stringer 接口 (String() string) 控制类型在 Print/Printf 中的显示方式 —— 类似 Java/JS 中的 toString()。%v 使用 String();%+v 显示字段名;%#v 显示 Go 语法。error 接口工作方式相同:实现 Error() string 使任何类型成为 error。这些是 Go 最常见的内置接口。
// Stringer interface (like toString() in other languages)
type Stringer interface {
String() string
}
type Person struct {
Name string
Age int
}
// Implement Stringer
func (p Person) String() string {
return fmt.Sprintf("%s (%d years)", p.Name, p.Age)
}
p := Person{Name: "Alice", Age: 30}
fmt.Println(p) // Alice (30 years) — uses String()
fmt.Printf("%v\n", p) // Alice (30 years)
fmt.Printf("%+v\n", p) // Name:Alice Age:30
fmt.Printf("%#v\n", p) // main.Person{Name:"Alice", Age:30}
// Error interface is similar:
// type error interface { Error() string }
// Implement Error() to make a type usable as an error类型嵌入与组合
Go 使用组合而非继承。嵌入结构体会将其字段和方法提升到外部结构体。可以通过在外部类型上定义同名方法来覆盖被提升的方法。嵌入接口允许装饰/委托 —— 外部类型满足接口并可转发调用。
// Embed a struct (composition over inheritance)
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return a.Name + " makes a sound"
}
type Dog struct {
Animal // embedded — promotes Name and Speak()
Breed string
}
d := Dog{
Animal: Animal{Name: "Rex"},
Breed: "Labrador",
}
fmt.Println(d.Name) // "Rex" (promoted)
fmt.Println(d.Speak()) // "Rex makes a sound" (promoted)
fmt.Println(d.Animal.Name) // explicit access also works
// Override a promoted method
func (d Dog) Speak() string {
return d.Name + " barks!"
}
fmt.Println(d.Speak()) // "Rex barks!"
// Embed an interface
type Logger struct {
io.Writer // embed interface
}
// Logger now has Write() method, delegating to the embedded Writer错误处理
错误基础
Go 将错误作为值处理,而非异常。函数返回 (result, error) —— 始终立即检查 err != nil。errors.New() 创建简单错误;fmt.Errorf() 添加格式化。哨兵错误 (var ErrX = errors.New()) 支持用 == 比较。切勿忽略错误(仅在有意时使用 _)。
// Functions return errors as last return value
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}
// Always check errors immediately
result, err := divide(10, 0)
if err != nil {
log.Fatal(err) // or handle gracefully
}
fmt.Println(result)
// fmt.Errorf for formatted errors
func validate(age int) error {
if age < 0 {
return fmt.Errorf("invalid age: %d (must be positive)", age)
}
return nil
}
// Sentinel errors
var ErrNotFound = errors.New("not found")
if err := lookup("key"); err == ErrNotFound {
// handle not found
}自定义错误类型
自定义错误类型实现 error 接口 (Error() string)。它们携带结构化数据(字段、代码、上下文),超越简单的消息。使用类型断言 (*ValidationError) 访问数据。这对领域特定的错误处理至关重要 —— 例如 HTTP 状态码、验证详情、重试逻辑。
// Custom error type (implements error interface)
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error on '%s': %s", e.Field, e.Message)
}
// Use the custom error
func validateUser(name string) error {
if name == "" {
return &ValidationError{
Field: "name",
Message: "cannot be empty",
}
}
return nil
}
// Type-assert to access fields
err := validateUser("")
if ve, ok := err.(*ValidationError); ok {
fmt.Println("Field:", ve.Field) // "name"
fmt.Println("Message:", ve.Message) // "cannot be empty"
}errors.Is 与 errors.As (Go 1.13+)
Go 1.13+ 添加了用 %w 进行错误包装 (fmt.Errorf)。errors.Is() 检查错误是否匹配哨兵错误(解包链)。errors.As() 从链中提取特定错误类型。这实现了分层错误处理:底层错误被上下文包装,在高层检查。始终用 %w(而非 %v)包装以保留链。
import "errors"
// Wrapped errors with %w
var ErrNotFound = errors.New("not found")
func getUser(id int) error {
if id == 0 {
return fmt.Errorf("getUser(%d): %w", id, ErrNotFound)
}
return nil
}
// errors.Is: check if error matches (unwraps the chain)
err := getUser(0)
if errors.Is(err, ErrNotFound) {
fmt.Println("user not found")
}
// errors.As: extract a specific error type
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println("field:", ve.Field)
}
// errors.Unwrap: get the wrapped error
inner := errors.Unwrap(err)Panic 与 Recover
panic() 用于不可恢复的错误(bug、不变量违反)—— 不用于正常错误处理。recover() 捕获 panic 但仅在延迟函数中有效。在以下情况使用 panic/recover:编程错误(索引越界)、包初始化失败、保护 goroutine 不使程序崩溃。对于预期失败,优先返回错误。
// Panic: unrecoverable error (like throw)
func mustParse(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(fmt.Sprintf("invalid number: %s", s))
}
return n
}
// Recover: catch a panic (only in deferred functions)
func safeCall() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}()
panic("something went wrong")
}
// Practical: safe goroutine
func safeGo(fn func()) {
go func() {
defer func() {
if r := recover(); r != nil {
log.Println("goroutine panic:", r)
}
}()
fn()
}()
}错误包装模式
在每一层使用 fmt.Errorf 配合 %w 包装错误并添加上下文。这创建错误链:顶层看到完整路径 (getUserProfile → fetchUser → sql error)。上下文(函数名、参数)有助于调试。使用 errors.Is/As 检查链中的特定错误。避免用相同上下文多次包装同一错误。
// Wrap errors with context as they propagate up
func fetchUser(id int) (*User, error) {
row := db.QueryRow("SELECT ... WHERE id = ?", id)
var u User
if err := row.Scan(&u.Name, &u.Age); err != nil {
return nil, fmt.Errorf("fetchUser(%d): %w", id, err)
}
return &u, nil
}
func getUserProfile(id int) (*Profile, error) {
user, err := fetchUser(id)
if err != nil {
return nil, fmt.Errorf("getUserProfile(%d): %w", id, err)
}
// ...
}
// At the top level, log the full chain
profile, err := getUserProfile(42)
if err != nil {
log.Printf("error: %v", err)
// Output: getUserProfile(42): fetchUser(42): sql: no rows
}
// golang.org/x/xerrors or pkg/errors for stack tracesDefer、Panic、Recover 组合使用
组合 defer(清理)、panic(致命错误)和 recover(捕获 panic)实现健壮的资源管理。defer 按 LIFO 顺序执行,即使在 panic 期间。命名返回值 (err error) 可在延迟的 recover 中设置。此模式确保文件/连接被关闭、事务被回滚,即使代码 panic。
// Complete pattern: cleanup + panic recovery
func processFile(path string) (err error) {
file, e := os.Open(path)
if e != nil {
return e
}
defer func() {
// Close file regardless of panic
file.Close()
// Recover from panic and convert to error
if r := recover(); r != nil {
err = fmt.Errorf("panic in processFile: %v", r)
}
}()
// Do work that might panic
data := parseFile(file) // might panic
return saveData(data)
}
// Defer for resource cleanup (always runs)
func withDatabase(db *sql.DB, fn func(*sql.DB) error) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
return fn(tx)
}并发
Goroutine
Goroutine 是 Go 的轻量级线程 —— 用 'go' 启动。它们很廉价(约 2KB 栈)并由 Go 运行时调度器管理(M:N 调度)。main 函数不会等待 goroutine —— 使用 sync.WaitGroup 或通道同步。生产环境切勿使用 time.Sleep 同步(使用 WaitGroup)。
package main
import (
"fmt"
"time"
)
func sayHello(name string) {
for i := 0; i < 3; i++ {
fmt.Println(name, i)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
// Launch goroutine with 'go' keyword
go sayHello("Alice")
go sayHello("Bob")
// Anonymous goroutine
go func() {
fmt.Println("anonymous goroutine")
}()
// Wait for goroutines (simple approach)
time.Sleep(1 * time.Second)
fmt.Println("done")
}
// Goroutines are lightweight (~2KB stack, grows as needed)
// Millions of goroutines can run concurrently通道
通道是 goroutine 通信的类型化管道。无缓冲 (make(chan T)) 阻塞直到发送方和接收方都就绪(同步)。有缓冲 (make(chan T, n)) 仅在满时阻塞(异步)。发送方应关闭通道,接收方不应。range 遍历通道直到关闭。通道实现'通过通信共享内存'。
// Unbuffered channel (synchronous)
ch := make(chan string)
// Send and receive
go func() {
ch <- "hello" // blocks until received
}()
msg := <-ch // blocks until sent
fmt.Println(msg)
// Buffered channel (asynchronous)
buf := make(chan int, 3)
buf <- 1 // doesn't block (buffer has space)
buf <- 2
buf <- 3
// buf <- 4 // would block (buffer full)
fmt.Println(<-buf) // 1
// Close a channel (sender closes, never receiver)
close(buf)
// Range over channel (until closed)
for v := range buf {
fmt.Println(v)
}
// Check if closed
v, ok := <-buf
if !ok {
fmt.Println("channel closed")
}Select 语句
select 让 goroutine 等待多个通道操作 —— 选择第一个就绪的(多个就绪时随机选择)。default case 使其非阻塞。time.After() 创建超时通道。select 是并发 Go 的核心:事件循环、扇入/扇出、超时。始终包含超时以避免死锁。
// Select: multiplex channel operations
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "two"
}()
// Wait for first to arrive
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("received:", msg1)
case msg2 := <-ch2:
fmt.Println("received:", msg2)
}
}
// Timeout with select
select {
case result := <-slowOperation():
fmt.Println(result)
case <-time.After(3 * time.Second):
fmt.Println("timeout!")
}
// Non-blocking receive (default case)
select {
case msg := <-ch:
fmt.Println(msg)
default:
fmt.Println("no message") // runs if no data
}sync.WaitGroup
sync.WaitGroup 等待一组 goroutine 完成。Add(n) 增加计数器,Done() 递减(使用 defer),Wait() 阻塞直到为零。始终将循环变量作为参数传递给 goroutine,以避免闭包捕获 bug(Go 1.22 已修复但仍推荐)。对于即发即忘的并发,WaitGroup 比通道更简单。
import "sync"
func main() {
var wg sync.WaitGroup
// Launch 5 goroutines
for i := 0; i < 5; i++ {
wg.Add(1) // increment counter
go func(id int) {
defer wg.Done() // decrement when done
fmt.Printf("Worker %d started\n", id)
time.Sleep(time.Duration(id) * 100 * time.Millisecond)
fmt.Printf("Worker %d done\n", id)
}(i)
}
wg.Wait() // block until all Done() called
fmt.Println("all workers finished")
}
// Common mistake: passing loop variable to goroutine
// Always pass as parameter: go func(i int) { ... }(i)
// (Go 1.22+ fixes loop variable scoping, but still good practice)Mutex 与 Sync
sync.Mutex 保护共享状态免受并发访问 —— Lock/Unlock 配合 defer。RWMutex 允许多个读取者或一个写入者(更适合读密集型工作负载)。sync.Once 确保初始化只发生一次(单例模式)。优先用通道通信,用互斥锁保护共享状态。'通过通信共享内存;不要通过共享内存通信。'