Golang 基础 基础 1. 基本属性 1 2 3 4 5 6 1. fmt.Println 打印并换行2. fmt.Printf("Name: %s\n" , name) fmt.Printf("Age: %d\n" , age) fmt.Printf("Height: %.2f\n" , height) fmt.Printf("Type: %T\n" , name) 3. fmt.Print - 基本打印 不会自动换行 多个参数之间没有空格分隔
2.声明变量的基础方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport "fmt" func main () { var a int fmt.Println("a=" , a) var b int = 100 fmt.Println("b=" , b) var c = 100 fmt.Println("c=" , c) d := 100 fmt.Println("d=" , d) e, f := 100 , "a" fmt.Println("e=" , e, "f=" , f) var ( g int = 1 h string = "world" ) fmt.Println("g=" , g, "h=" , h) }
3. 枚举iota 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func main () { const ( RED = 10 * iota GREEN BLUE ) const ( a = 10 * iota b = iota c = iota + 5 ) fmt.Println(GREEN) fmt.Println(a, b, c) }iota : 一行加1 只能出现在const 里面
函数返回 1.常用函数调用 多返回值 命名返回变量 函数是值拷贝 如果需要引用传递 就使用指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport "fmt" func foo1 (a int , b int ) (int , int ) { fmt.Println(a) fmt.Println(b) a += 1 b += 1 return a + b, a }func foo2 (a1 int , a2 int ) (r1, r2 int ) { r1 = a1 + 2 r2 = a2 + 2 return }func main () { println (3333 ) d, e := foo1(1 , 2 ) fmt.Println("d=" , d, "e=" , e) f, g := foo2(1 , 2 ) fmt.Println("f=" , f, "g=" , g) } 首字母大写对外开发 首字母小写就是私有的
2. import init 初始化流程
import :
mod: go mode init [moduleName] 初始化go mod的名称
1 2 3 4 import _ "fmt" import aa "fmt" import . "fmt"
### 3. defer
指针 Go 语言中的指针(Pointer) 是一个核心特性,用于直接操作内存地址 ,提升性能或实现引用语义。指针是一个变量,存储的是另一个变量的内存地址 。通过指针,可以间接访问或修改 原始变量的值
1.基础使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func add (a *int , b int ) { *a += b }func swap (a *int , b *int ) { *a, *b = *b, *a }func main () { a := 1 p := &a add(p, 1 ) fmt.Println(a) }
& → 取引用 * → 解引用
2. 指针解决啥问题
避免大对象的拷贝
允许函数直接修改原始变量
为空的时候语义
slice 切片 实际就是动态数组,切片是 对底层数组的一个动态“窗口”或“视图” ,它 不是数组本身 ,而是包含三个字段的结构体: 切片是引用类型
1 2 3 4 5 type slice struct { ptr *T len int cap int }
1 2 3 var slice1 []int slice1 = append (slice1, 1 , 2 , 3 , 4 , 5 ) fmt.Println(slice1)
切片共享底层数组切片的零值是 nil
1.创建切片
2.切片注意事项
Map Map(映射) 是一种内置的、无序的键值对(key-value)集合 ,用于根据“键”快速查找、存储或删除对应的“值”。
1.nil map 1 2 var n map [string ]int n["one" ] = 1
2.map注意事项
map 不是并发安全的
**map 的 key 必须是可比较类型 **
基本类型:int, string, bool
指针、channel
结构体(所有字段都可比较
接口(动态值可比较)
遍历顺序是随机的
不要对 map 取地址 ,map 是引用类型,本身就是一个“句柄”,再取地址意义不大
map 在增长时会触发 rehash(重建哈希表),可能导致短暂延迟,如果知道大致容量,可预分配(但 make(map, hint)
Struct 结构体 ,结构体(struct) 是一种用户自定义的复合数据类型 ,用于将多个不同类型的字段(fields)组合在一起,表示一个“对象”或“实体”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" type Book struct { title string author string }func main () { var b1 = Book{ title: "hello" , author: "world" , } fmt.Println(b1) }
1. 结构体方法(封装) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport "fmt" type Person struct { name string age int }func (this Person) getName() string { fmt.Println("this.name:" , this.name) return this.name }func (this Person) setName(name string ) { this.name = name }func main () { p1 := Person{"张三" , 18 } p1 = Person{name: "李四" , age: 12 } p1.setName("李四" ) fmt.Println(p1.getName()) }
2.继承(组合优于继承 ) 结构体“嵌入”到另一个结构体中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport "fmt" type Animal struct { Name string }func (a Animal) Speak() { fmt.Println("I am" , a.Name) }type Dog struct { Animal Breed string }func main () { d := Dog{ Animal: Animal{Name: "Buddy" }, Breed: "Golden Retriever" , } d.Speak() fmt.Println(d.Name) fmt.Println(d.Breed) }
3. 多态 同一个接口,多种实现;同一操作,作用于不同对象,产生不同行为
只要一个类型实现了接口的所有方法,它就“自动”实现了该接口,无需显式声明
go的多态通过接口实现 接口本质上是个指针,比如 doEat(interfaceAnimal)
doEat(cat) 执行的是cat的eat方法
doEat(dog) 执行的的是dog的eat方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 package mainimport "fmt" type Person struct { Name string Age int }func (p Person) String() string { return fmt.Sprintf("%v (%v years)" , p.Name, p.Age) }type Student struct { Person score int }func (s Student) printScore() { fmt.Println(s.Name, "的分数:" , s.score) }func (s Student) sayHello(){ fmt.Println("大家好,我是" , s.Name,"同学" ) }type Teacher struct { Person salary int }func (t Teacher) printSalary() { fmt.Println(t.Name, "的工资:" , t.salary) }func (t Teacher) sayHello(){ fmt.Println("大家好,我是" , t.Name,"老师" ) }func sayHello (p PersonAction) { fmt.Println("大家好,我是" , p) }type PersonAction interface { sayHello() }func main () { t :=Teacher{Person{"李老师" , 18 }, 5000 } t.printSalary() d :=Student{Person{"张同学" , 18 }, 90 } d.printScore() sayHello(d) sayHello(t) }
4. 接口 *** 空接口万能类型*** interface {} 接口变量包含两个指针
第一个指针 :指向类型信息(type descriptor)
第二个指针 :指向实际存储的值(value)
1 2 3 4 5 6 7 8 9 10 11 package mainfunc main () { var s string s = "hello world" var allType interface {} allType = s println (allType) str, _ := allType.(string ) println (str) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package maintype Reader interface { ReadBook() }type Writer interface { WriteBook() }type Book struct { Title string }func (b Book) ReadBook() { println ("reading book: " + b.Title) }func (b *Book) WriteBook() { println ("writing book: " + b.Title) }func main () { b := Book{"Go" } var r1 Reader = b var r2 Reader = &b var w2 Writer = &b r1.ReadBook() r2.ReadBook() w2.WriteBook() }
1 2 3 4 5 6 7 8 9 10 11 12 var r Reader = b var r Reader = &b
注意事项
类名、属性名 、方法名 大小写是可以对其他包开放 小写只能当前包访问
反射 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport ( "fmt" "reflect" )type Person struct { Name string `key:"desc" required:"true"` Age int `key:"ageDesc" required:"true"` }func (p *Person) Say() { fmt.Println("hello world" ) }func reflectNum (input interface {}) { type1 := reflect.TypeOf(input) fmt.Println(type1) fmt.Println(reflect.ValueOf(input)) for i := 0 ; i < type1.NumField(); i++ { field := type1.Field(i) fmt.Println(field.Name, field.Type) } }func main () { p := Person{"张三" , 18 } reflectNum(p) }
结构体标签应用 json标签、orm映射
为啥不设计 结构体整体标签 ? 方法标签?
goroutine & channel 并行: 多核同时执行进程 多个任务真正同时 执行。 一起处理
并发: 同一时间段内交替执行, 感觉是同时 但是实际不是一起执行
多进程 高内存占用 虚拟内存
一个线程: 用户空间–内核空间 用户线程(协程 co-routine) <==> 内核线程
CPU绑定内核线程, N个内核线程通过协程调度器轮训 绑定M个协程
Go 语言中的 GMP 模型 是其并发调度的核心机制 ,用于高效地管理和执行 Goroutine(Go 的轻量级线程)。GMP 是三个核心组件的缩写:
G(Goroutine)
表示一个 Go 协程,即用户态的轻量级线程。
由 Go 运行时(runtime)管理,创建成本极低(初始栈仅 2KB),可轻松创建成千上万个。
每个 G 都包含自己的栈、指令指针、状态等。
M(Machine / OS Thread)
对应一个操作系统线程 (OS thread)。
真正执行代码的实体,由操作系统调度。
M 必须绑定到一个 P 才能运行 G。
P(Processor)
是 Go 调度器中的“逻辑处理器”,不是 CPU 核心 ,但数量通常等于 CPU 核数(可通过 GOMAXPROCS 设置)。
每个 P 拥有一个本地 Goroutine 队列(runq) ,用于存放待执行的 G。
P 是 M 和 G 之间的桥梁:M 必须持有 P 才能执行 G。
GMP 调度流程简述:
当你用 go func() 启动一个协程时,会创建一个 G 。
这个 G 会被放入某个 P 的本地队列 中。
一个 M (系统线程)绑定到这个 P ,从 P 的队列中取出 G 并执行。
如果某个 G 发生
阻塞
(如 I/O、系统调用),Go 调度器会:
将该 M 与 P 解绑;
把 P 绑定到另一个空闲的 M 上,继续执行其他 G;
原来的 M 在阻塞结束后,会尝试重新获取一个 P 来继续执行(或进入休眠)。
P 还会定期从全局队列 或其他 P 的队列中“偷取”(work-stealing)G,实现负载均衡。
worksteal M闲置后偷取另外一
handofff 阻塞后新开线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport ( "fmt" "time" )func task () { for i := 0 ; i < 10000 ; i++ { println ("次线程" , i) time.Sleep(1 * time.Second) } }func main () { go task() fmt.Print("主线程结束" ) }
主线程结束后对应的goroutine也会结束
channel 通信
基本操作 初始化chan 写入数据 for range读取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport ( "fmt" "time" )func main () { c := make (chan int , 3 ) fmt.Println(len (c), cap (c)) go func () { fmt.Println("go rout 结束" ) for i := 0 ; i < 10 ; i++ { fmt.Println("写入数据:" , i) c <- i } close (c) }() time.Sleep(5 * time.Second) for data := range c { fmt.Println("读取数据:" , data) } }
select: 单流程下一个go只能监控一个chan的状态 ,select 可以完成监控多个channel的状态。多路监控chan状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport "fmt" func fiba (c, quit chan int ) { a, b := 1 , 1 for { select { case c <- a: a, b = b, a+b case <-quit: return } } }func main () { c := make (chan int ) quit := make (chan int ) go func () { for i := 0 ; i < 6 ; i++ { fmt.Println(<-c) } quit <- 0 }() fiba(c, quit) }
Do not communicate by sharing memory; instead, share memory by communicating
不要通过共享内存来通信,而要通过通信来共享内存
它牺牲了部分性能和灵活性,换来了代码清晰性、安全性和可维护性
1 2 3 4 5 6 7 传统并发模型(如 C / C ++/ Java ): 多个线程共享同一块内存(比如全局变量、堆对象); 为了防止数据竞争(race condition ),必须用 锁(mutex )、信号量、原子操作等机制来同步; 容易出错:死锁、竞态、性能瓶颈golang 你不是“一起用”一块内存,而是“把内存里的值传给我”,之后各自独立使用
对比项
传统共享内存
Go 的 channel
通信方式
读写同一变量
发送/接收消息
同步机制
显式加锁(mutex)
channel 自带同步语义
数据安全
容易出错(竞态)
编译器+运行时保障
思维模式
“谁在改这个变量?”
“数据从哪来,到哪去?”
go modules 1 2 3 4 5 6 7 新项目初始化 go mod init <name > 添加依赖 go get pkg@version 清理/同步依赖 go mod tidy 提交前验证 go mod verify + git add go.mod go.sum 分析依赖来源 go mod why pkg 或 go mod graph 离线构建准备 go mod vendor go download (单纯下载) 和 go tidy 会修改go.mod go.sum
go.mod
依赖清单 :声明项目需要哪些模块及版本要求 Go 版本 只列直接依赖
go.sum
完整性校验 :记录每个依赖内容的哈希值,防止篡改 所有传递依赖 (整个依赖树)
练习
照着写 IM系统
Q&A 1. 什么是“逃逸”?
如果一个变量的地址被传递到函数外部 (比如作为返回值、被全局变量引用、被 goroutine 捕获等),就说它“逃逸”了。
逃逸的变量必须分配在堆(heap) 上,因为栈内存在函数返回后就销毁了。
未逃逸的变量 可以安全地分配在栈(stack) 上,效率更高(分配/回收快,无需 GC)。
不必要的堆分配可能导致的问题,增加 GC 压力(最常见),频繁 GC ,GC 停顿可能影响响应时间