1. 什么是Go语言 Go是由Google开发的一种开源编程语言,设计初衷是让开发者可以快速构建高效的现代应用。Go结合了静态语言的性能和动态语言的开发效率,特别适合开发高并发的后端服务 2. 包的概念 package main import "fmt" func main () { fmt.Println("hello go" ) fmt.Println("hello haha" ) }
几个注意点:
每个 .go 文件开头必须声明属于哪个包 左花括号 { 必须和函数名同一行,不能换行 (Go 的强制风格) 语句结尾不用加分号 注释用 // 或 /* */ 3. 变量声明 Go 声明变量有三种写法:
var name string = "Hongda" var name = "Hongda" name := "Hongda"
短声明 := 写起来最简洁,日常用得最多。但它只能在函数内部用,包级别的变量必须用 var。
var globalVar = "我是全局变量" func main () { localVar := "我是局部变量" }
零值 : 变量声明了但没赋值,Go 会给默认值,叫”零值”。
var count int var text string var flag bool
批量声明 : 多个变量可以放一起声明。
var i, j, k int var a, b, c = 1 , "hello" , true var ( host = "localhost" port = 5432 )
多变量赋值 : Go 支持一行给多个变量赋值,交换变量值特别方便。
x, y := 10 , 20 x, y = y, x
注意 := 是声明+赋值,= 是纯赋值。已经声明过的变量再用 := 会报错。
4. 赋值 Go 的赋值操作很直接:
x = 1 *p = true person.name = "bob"
复合赋值 : 简化 x = x + 5 这种写法。
自增自减 : 注意 Go 里 v++ 是语句不是表达式,不能写 x = i++。
元组赋值 可以一行给多个变量赋值,右边表达式先全部求值,再赋给左边。
x, y = y, x func gcd (x, y int ) int { for y != 0 { x, y = y, x%y } return x } func fib (n int ) int { x, y := 0 , 1 for i := 0 ; i < n; i++ { x, y = y, x+y } return x }
函数多返回值 : Go 函数可以返回多个值,常见的模式是返回结果和错误。
f, err := os.Open("foo.txt" )
ok 模式 : map 查找、类型断言、channel 接收都可以返回第二个 bool 值表示是否成功。
v, ok := m[key] v, ok := x.(T) v, ok := <-ch
丢弃不需要的值 : 用 _ 占位。
_, err := io.Copy(dst, src)
可赋值性 赋值分显式和隐式两种:
x = 1 func add (a, b int ) {} medals := []string {"gold" , "silver" , "bronze" }
赋值规则: 类型必须匹配。nil 可以赋给指针、slice、map、channel、interface 等引用类型。
var p *int = nil var m map [string ]int = nil
5. 函数基础 用 func 关键字声明函数。函数名首字母小写是包内私有,大写是公开可导出。
func sayHello () { fmt.Println("Hello" ) }
参数 参数格式是 变量名 类型,多个参数用逗号分隔。Go 不支持默认参数。
func greet (name string , age int ) { fmt.Printf("%s is %d years old\n" , name, age) } func add (a, b int ) int { return a + b }
不定参数 : 用 ... 表示,必须放在最后。
func sum (nums ...int ) int { total := 0 for _, v := range nums { total += v } return total } sum() sum(1 ) sum(1 , 2 , 3 )
返回值 单个返回值不用括号,多个要括号。
func double (x int ) int { return x * 2 } func swap (x, y string ) (string , string ) { return y, x } func split (sum int ) (x, y int ) { x = sum * 4 / 9 y = sum - x return }
丢弃不需要的返回值 : 用 _ 占位。
max, _ := MaxAndMin(10 , 20 )
函数调用流程 函数调用是栈结构,后进先出 (LIFO)。
func funcc (c int ) { fmt.Println("c =" , c) } func funcb (b int ) { funcc(b - 1 ) fmt.Println("b =" , b) } func funca (a int ) { funcb(a - 1 ) fmt.Println("a =" , a) } func main () { funca(3 ) fmt.Println("main" ) }
执行流程:
main() → funca(3) → funcb(2) → funcc(1) 打印 "c = 1" 打印 "b = 2" 打印 "a = 3" 打印 "main"
输出: c = 1 → b = 2 → a = 3 → main
递归 函数调用自身,必须有退出条件。
func sum (n int ) int { if n == 1 { return 1 } return n + sum(n-1 ) } sum(100 )
执行流程:
sum(100) = 100 + sum(99) = 100 + 99 + sum(98) = ... = 100 + 99 + ... + 1 = 5050
6. 函数进阶 函数类型 函数在 Go 里是一等公民,可以赋值给变量、作为参数传递。
func Add (a, b int ) int { return a + b } func Minus (a, b int ) int { return a - b } type CalcFunc func (int , int ) int func main () { var f CalcFunc f = Add fmt.Println(f(10 , 5 )) f = Minus fmt.Println(f(10 , 5 )) }
函数作为参数 (回调函数) : 实现多态,同一个函数传入不同的函数参数,实现不同功能。
type CalcFunc func (int , int ) int func Calc (a, b int , f CalcFunc) int { return f(a, b) } func main () { Calc(3 , 3 , Add) Calc(3 , 3 , Minus) }
匿名函数 没有名字的函数,可以赋值给变量,也可以定义后立即调用。
f := func (x, y int ) int { return x + y } f(1 , 2 ) func (x, y int ) { fmt.Println(x + y) }(1 , 2 )
闭包 闭包是引用了外部变量的匿名函数。关键点: 变量以引用方式捕获,会保留状态。
func test01 () int { var a int a++ return a * a } func test02 () func () int { var a int return func () int { a++ return a * a } } func main () { f := test02() fmt.Println(f()) fmt.Println(f()) fmt.Println(f()) fmt.Println(f()) }
defer defer 延迟执行,函数返回前才执行。常用于资源清理: 关闭文件、释放连接等。
func main () { defer fmt.Println("最后执行" ) fmt.Println("先执行" ) }
典型用法 :
func readFile () { f, err := os.Open("file.txt" ) if err != nil { return } defer f.Close() }
多个 defer : 后进先出 (LIFO),先定义的后执行。
func main () { defer fmt.Println("1" ) defer fmt.Println("2" ) defer fmt.Println("3" ) }
defer 与闭包 : 注意捕获变量的时机。
func main () { a := 10 defer func () { fmt.Println(a) }() defer func (x int ) { fmt.Println(x) }(a) a = 100 }
总结: 想要 defer 时的值,就传参数; 想要最终值,就用闭包捕获。
7. 指针 指针存的是内存地址。& 取地址,* 取值。
var a int = 10 fmt.Println(a) fmt.Println(&a) var p *int = &a fmt.Println(p) fmt.Println(*p) *p = 20 fmt.Println(a)
注意事项 不要操作未初始化的指针 : 指针默认值是 nil,解引用会崩。
var p *int var a int p = &a *p = 666
不能取常量或字面量的地址 :
不支持指针运算 : 和 C 不一样,Go 不能 p++ 或 p + 2。
值传递 vs 指针传递 值传递复制一份,改不了原变量; 指针传递传地址,能改原变量。
func swapByValue (a, b int ) { a, b = b, a } func swapByPointer (a, b *int ) { *a, *b = *b, *a } func main () { x, y := 10 , 20 swapByValue(x, y) fmt.Println(x, y) swapByPointer(&x, &y) fmt.Println(x, y) }
8. 数组 数组是固定长度、同一类型的数据集合。长度必须是常量,定义后不能改。
var arr [5 ]int fmt.Println(arr) fmt.Println(len (arr)) arr[0 ] = 10 fmt.Println(arr[0 ])
初始化 a := [5 ]int {1 , 2 , 3 , 4 , 5 } b := [5 ]int {1 , 2 , 3 } c := [5 ]int {2 : 10 , 4 : 20 } d := [...]int {1 , 2 , 3 }
遍历 arr := [5 ]int {1 , 2 , 3 , 4 , 5 } for i := 0 ; i < len (arr); i++ { fmt.Println(arr[i]) } for i, v := range arr { fmt.Printf("arr[%d] = %d\n" , i, v) } for _, v := range arr { fmt.Println(v) }
9. 切片 数组长度固定,切片长度可变。切片是引用类型。
var arr [5 ]int = [5 ]int {1 , 2 , 3 , 4 , 5 }var s []int = []int {1 , 2 , 3 , 4 , 5 }
初始化 s := []int {1 , 2 , 3 , 4 } s1 := make ([]int , 5 , 10 ) s2 := make ([]int , 5 )
len() 是当前元素个数,cap() 是底层数组能容纳的最大个数。
切片截取 切片可以从数组或另一个切片截取,语法是 s[low:high],包含 low 不包含 high。
s := []int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } s[2 ] s[:] s[3 :] s[:5 ] s[2 :7 ]
还可以用第三个参数限制容量: s[low:high:max],容量是 max - low。
10. Map map 是键值对的集合,无序。key 必须是可比较的类型 (支持 ==)。
var m1 map [string ]int m2 := make (map [string ]int ) m3 := make (map [string ]int , 10 ) m4 := map [string ]int { "alice" : 18 , "bob" : 20 , }
增删改查 m := map [string ]int {"alice" : 18 } m["bob" ] = 20 m["alice" ] = 19 age := m["alice" ] age, ok := m["charlie" ] if val, ok := m["bob" ]; ok { fmt.Println(val) } delete (m, "alice" )
遍历 map 遍历顺序是随机的。
m := map [string ]int {"a" : 1 , "b" : 2 , "c" : 3 } for k, v := range m { fmt.Printf("%s: %d\n" , k, v) } for k := range m { fmt.Println(k) }
引用类型 map 是引用类型,传参时不会复制,函数内修改会影响原 map。
func addKey (m map [string ]int ) { m["new" ] = 100 } func main () { m := map [string ]int {"a" : 1 } addKey(m) fmt.Println(m) }
11. 结构体 结构体是不同类型数据的集合。
type Student struct { ID int Name string Age int }
初始化 s1 := Student{1 , "mike" , 18 } s2 := Student{Name: "bob" , Age: 20 } var s3 Students3.ID = 1 s3.Name = "alice"
指针 p1 := &Student{1 , "mike" , 18 } p2 := &Student{Name: "bob" } p3 := new (Student) p3.ID = 1 p3.Name = "alice" fmt.Println(p1.Name)
比较和赋值 同类型结构体可以用 == 比较,也可以直接赋值。
s1 := Student{1 , "mike" , 18 } s2 := Student{1 , "mike" , 18 } s3 := Student{2 , "bob" , 20 } fmt.Println(s1 == s2) fmt.Println(s1 == s3) s4 := s1
值传递 vs 指针传递 func updateByValue (s Student) { s.Age = 100 } func updateByPointer (s *Student) { s.Age = 100 } func main () { s := Student{1 , "mike" , 18 } updateByValue(s) fmt.Println(s.Age) updateByPointer(&s) fmt.Println(s.Age) }
可见性 首字母大写是公开的 (public),小写是私有的 (private,仅包内可见)。
type Student struct { ID int Name string age int }
12. 匿名组合 Go 没有传统的继承,但可以通过匿名字段实现类似效果。
type Person struct { Name string Age int } type Student struct { Person ID int Addr string }
初始化 s1 := Student{Person{"mike" , 18 }, 1 , "beijing" } s2 := Student{ Person: Person{Name: "bob" }, ID: 2 , }
访问成员 匿名字段的成员可以直接访问,不用写完整路径。
s := Student{Person{"mike" , 18 }, 1 , "beijing" } fmt.Println(s.Name) fmt.Println(s.Age) fmt.Println(s.Person.Name) s.Name = "bob" s.Person = Person{"alice" , 20 }
同名字段 如果外层和匿名字段有同名成员,就近原则。
type Student struct { Person Name string } func main () { var s Student s.Name = "student" s.Person.Name = "person" }
指针类型匿名字段 type Student struct { *Person ID int } func main () { s1 := Student{&Person{"mike" , 18 }, 1 } var s2 Student s2.Person = new (Person) s2.Name = "bob" }
13. 方法 方法是带接收者的函数,语法: func (receiver Type) methodName(params) returns
type Person struct { Name string Age int } func (p Person) PrintInfo() { fmt.Println(p.Name, p.Age) } func (p *Person) SetAge(age int ) { p.Age = age } func main () { p := Person{"mike" , 18 } p.PrintInfo() p.SetAge(20 ) fmt.Println(p.Age) }
值接收者 vs 指针接收者 值接收者是拷贝,改不了原变量; 指针接收者是引用,能改原变量。
func (p Person) SetByValue(name string ) { p.Name = name } func (p *Person) SetByPointer(name string ) { p.Name = name } func main () { p := Person{"mike" , 18 } p.SetByValue("bob" ) fmt.Println(p.Name) p.SetByPointer("bob" ) fmt.Println(p.Name) }
方法继承 匿名字段的方法会被继承。
type Person struct { Name string } func (p Person) Say() { fmt.Println("I'm" , p.Name) } type Student struct { Person ID int } func main () { s := Student{Person{"mike" }, 1 } s.Say() }
方法重写 子类型可以定义同名方法覆盖父类型,就近原则。
func (s Student) Say() { fmt.Println("Student:" , s.Name) } func main () { s := Student{Person{"mike" }, 1 } s.Say() s.Person.Say() }
14. 接口 接口是方法签名的集合,定义行为不定义实现。实现了接口所有方法的类型就实现了该接口,不需要显式声明。
type Humaner interface { SayHi() }
实现接口 type Student struct { Name string } func (s *Student) SayHi() { fmt.Println("Student:" , s.Name) } type Teacher struct { Name string } func (t *Teacher) SayHi() { fmt.Println("Teacher:" , t.Name) } func main () { var h Humaner h = &Student{"mike" } h.SayHi() h = &Teacher{"bob" } h.SayHi() }
多态 同一个接口,不同类型有不同表现。
func WhoSayHi (h Humaner) { h.SayHi() } func main () { s := &Student{"mike" } t := &Teacher{"bob" } WhoSayHi(s) WhoSayHi(t) people := []Humaner{s, t} for _, p := range people { p.SayHi() } }
接口继承 接口可以嵌入其他接口。
type Humaner interface { SayHi() } type Personer interface { Humaner Sing(lrc string ) }
实现 Personer 必须同时实现 SayHi() 和 Sing()。
空接口 interface{} 可以存任意类型。
var i interface {}i = 1 i = "hello" i = &Student{"mike" }
类型断言 从空接口取出具体类型。
var i interface {} = "hello" if s, ok := i.(string ); ok { fmt.Println("是 string:" , s) } switch v := i.(type ) {case int : fmt.Println("int:" , v) case string : fmt.Println("string:" , v) default : fmt.Println("其他类型" ) }
15. Error 接口 error 是 Go 内建的接口,用于错误处理。
type error interface { Error() string }
创建 error import ( "errors" "fmt" ) err1 := errors.New("something went wrong" ) err2 := fmt.Errorf("user %d not found" , 123 )
错误处理模式 Go 的惯例是函数返回值最后一个是 error,调用后检查是否为 nil。
func Divide (a, b int ) (int , error ) { if b == 0 { return 0 , errors.New("division by zero" ) } return a / b, nil } func main () { result, err := Divide(10 , 0 ) if err != nil { fmt.Println("错误:" , err) return } fmt.Println("结果:" , result) }
标准模式:
result, err := someFunction() if err != nil { return err }
16. Panic panic 是严重错误,触发后程序崩溃。
func main () { fmt.Println("开始" ) panic ("出错了" ) fmt.Println("不会执行" ) }
触发方式 panic ("something went wrong" )var a [10 ]int a[20 ] = 1
panic 与 defer panic 之前注册的 defer 会执行,常用于清理资源。
func test () { defer fmt.Println("清理工作" ) panic ("出错" ) }
17. Recover recover 捕获 panic,防止程序崩溃。必须在 defer 中使用。
func test () { defer func () { if err := recover (); err != nil { fmt.Println("捕获到:" , err) } }() panic ("出错了" ) } func main () { test() fmt.Println("程序继续运行" ) }
不用 recover,panic 会让整个程序崩溃; 用了 recover,程序可以继续执行。
18. Goroutine go 关键字创建协程,非常轻量,可以轻松创建成千上万个。
func task () { fmt.Println("任务执行" ) } func main () { go task() time.Sleep(time.Second) }
主协程退出,所有子协程也会退出。
WaitGroup 用 sync.WaitGroup 等待所有协程完成。
var wg sync.WaitGroupfunc task (id int ) { defer wg.Done() fmt.Println("任务" , id) } func main () { for i := 0 ; i < 5 ; i++ { wg.Add(1 ) go task(i) } wg.Wait() }
常用函数 runtime.Gosched() runtime.Goexit() runtime.GOMAXPROCS(n)
19. Channel channel 用于 goroutine 之间通信,类似管道,一端发一端收,并发安全。
ch := make (chan int ) ch := make (chan int , 10 ) ch <- 10 x := <-ch
无缓冲 channel 发送方会阻塞,直到有接收方准备好。
ch := make (chan int ) go func () { ch <- 10 }() x := <-ch
有缓冲 channel 缓冲区没满就不阻塞,满了才阻塞。
ch := make (chan int , 3 ) ch <- 1 ch <- 2 ch <- 3 ch <- 4 <-ch
关闭和遍历 ch := make (chan int ) go func () { for i := 0 ; i < 5 ; i++ { ch <- i } close (ch) }() for v := range ch { fmt.Println(v) }
关闭后不能再发送,但可以继续接收剩余数据。
单向 channel 限制只能发或只能收,用于函数参数约束。
func send (ch chan <- int ) { ch <- 10 } func receive (ch <-chan int ) { x := <-ch }
定时器 timer := time.NewTimer(2 * time.Second) <-timer.C fmt.Println("时间到" ) ticker := time.NewTicker(time.Second) for i := 0 ; i < 5 ; i++ { <-ticker.C fmt.Println("tick" , i) } ticker.Stop()
20. Select select 同时监听多个 channel,哪个准备好就执行哪个。
select {case x := <-ch1: fmt.Println("从 ch1 收到" , x) case ch2 <- y: fmt.Println("发送到 ch2" ) default : fmt.Println("都没准备好" ) }
基本用法 ch1 := make (chan int ) ch2 := make (chan int ) go func () { time.Sleep(time.Second) ch1 <- 1 }() go func () { time.Sleep(2 * time.Second) ch2 <- 2 }() select {case x := <-ch1: fmt.Println("ch1:" , x) case x := <-ch2: fmt.Println("ch2:" , x) }
超时处理 ch := make (chan int ) select {case x := <-ch: fmt.Println("收到:" , x) case <-time.After(3 * time.Second): fmt.Println("超时" ) }
斐波那契示例 func fibonacci (ch chan <- int , quit <-chan bool ) { x, y := 1 , 1 for { select { case ch <- x: x, y = y, x+y case <-quit: return } } } func main () { ch := make (chan int ) quit := make (chan bool ) go func () { for i := 0 ; i < 8 ; i++ { fmt.Println(<-ch) } quit <- true }() fibonacci(ch, quit) }
要点 多个 case 同时准备好时随机选一个 default 在所有 case 阻塞时执行常用于超时控制和多路复用 21. 锁 (sync) 多个 goroutine 同时访问共享资源会有竞态问题,需要锁来保证安全。
互斥锁 sync.Mutex 同一时刻只有一个 goroutine 能访问。
var x = 0 var wg sync.WaitGroupvar lock sync.Mutexfunc add () { defer wg.Done() for i := 0 ; i < 5000 ; i++ { lock.Lock() x = x + 1 lock.Unlock() } } func main () { wg.Add(2 ) go add() go add() wg.Wait() fmt.Println(x) }
读写锁 sync.RWMutex 读多写少的场景用这个。多个读可以同时进行,写独占。
var rwlock sync.RWMutexfunc write () { rwlock.Lock() x = x + 1 rwlock.Unlock() } func read () { rwlock.RLock() fmt.Println(x) rwlock.RUnlock() }
sync.Once 确保某操作只执行一次,常用于单例初始化。
var once sync.Oncefunc init () { once.Do(func () { fmt.Println("只执行一次" ) }) }
sync.Map 并发安全的 map,不需要额外加锁。
var m sync.Mapm.Store("key" , "value" ) value, ok := m.Load("key" ) m.Delete("key" )