资料 🔗
- 目前 golang 的主要 maintainer 之一: Russ Cox。个人主页:swtch.com/~rsc/
- awesome-go 系列
练习操场 playground: go.dev/play。 rust 后来也有 playground。
官方地址改动 🔗
早期都是 golang.org => 现在 go.dev, play.golang.org => go.dev/play
构建 golang 开发环境 🔗
- 下载安装包 wget https://golang.google.cn/dl/go1.19.2.src.tar.gz
google 域名下的地址可能被墙不能访问。
或者 https://go.dev/dl/ 地址。 - 解压并安装到指定目录 tar -zxvf xx.tar.gz -C /usr/local
- 配置环境变量
vim .bash_profile
export GOROOT=~/..
export PATH=$PATH:$GOROOT/bin
export GOPATH=~/..
source .bash_profile
- 最后,验证一下 go version。go env。
编译 golang 的源码项目 🔗
从源码去编译 golang 项目。
- git clone https://github.com/golang/go.git
- 设置 golang 自举编译器的地址。 export GOROOT_BOOTSTRAP=$GOROOT(或者其他)
- 进去源码目录中的 all.bash 脚本。 ./go/src/all.bash。 或者./go/src/make.bash 脚本会利用自居编译器 GOROOT_BOOTSTRAP 去编译 golang 源码项目。
- 最后的编译产生了~/go/src/github.com/golang/go, ~/go/src/github.com/golang/go/bin, pkg 库文件等。
- 验证改动后的执行命令 利用新编译器的绝对路径。 ~/go/src/github.com/golang/go run test_print.go。
go build -n main.go 不编译并打印编译的过程。 -n 参数和 make 的-n 参数是一致的。
变量的内存大小 🔗
没有char类型,特添加type rune = int32类型处理utf-8的变长字符编码问题。
import (
"fmt"
"runtime"
"unsafe"
)
func main() {
fmt.Printf("arch=%s, os=%s\n", runtime.GOARCH, runtime.GOOS) //arch=amd64, os=linux
var a int = 190
fmt.Printf("type=%T, size=%d\n", a, unsafe.Sizeof(a)) //type=int, size=8
var b int32 = 190
fmt.Printf("type=%T, size=%d\n", b, unsafe.Sizeof(b)) //type=int32, size=4
var c uint8 = 'a'
fmt.Printf("type=%T, size=%d\n", c, unsafe.Sizeof(c)) //type=uint8, size=1
var d rune = '中'
fmt.Printf("type=%T, size=%d\n", d, unsafe.Sizeof(d)) //type=int32, size=4
var e string = "a"
fmt.Printf("type=%T, size=%d\n", e, unsafe.Sizeof(e)) //type=string, size=16
var f string = "中"
fmt.Printf("type=%T, size=%d\n", f, unsafe.Sizeof(f)) //type=string, size=16
}
多态,继承, 范型 🔗
golang 中实现的面向对象的方式
继承 🔗
golang 中没有继承,利用组合结构体实现类似继承的功能。
一个结构体嵌入另一个结构体,能够实现对嵌入结构体的字段以及其实现的方法的继承。
type person struct {
name string
age int
}
type student struct {
person // 匿名字段,通过组合的方式
school string
}
func (p * person) talk() {
fmt.Println(p.name, p.age)
}
func (s *student) getSchool() {
return s.school
}
func main() {
s := student{person{"brettkk", 18}, "yidu"}
fmt.Println(s.name) // 继承person的name字段
s.talk() // 继承person实现的方法
}
why 为什么需要在 golang 使用 embedding 🔗
golang 中没有 extends, implement 等继承概念。
golang 中通过组合来实现类似继承的功能。
embedding 比较方便组合 golang 中的 struct,interface 来实现类似其他语言中的继承和实现的概念。
what 什么是嵌入 🔗
有三种嵌入的情况:
- embedding struct in struct
- embedding interface in interface
- embedding interface in struct
在 struct 中嵌入 struct 🔗
- 可以直接访问嵌入的结构体
- 可以直接调用嵌入的结构体上的方法
- 上一条满足,自然地被嵌入的结构体实现了接口,外层结构体也就实现了接口
例如: 在 go 中目标结构体 A 中嵌入 sync.Mutex。方便使用 A.lock()可以加锁。如果 lock 只在内部使用,还是正常使用mu sync.Mutex
在 interface 中嵌入 interface 🔗
interface 可以被嵌入到另一个 interface 中,也可以嵌入到 struct 中。
interface 被嵌入到 interface 中,通过组合的方式扩展了接口中的函数集合。
golang 标准库中 Reader 和 Writer 接口被嵌入(组合)到 ReadWriter 接口。
golang 的 container 包中子包 heap 中,通过 embedding interface 的方式表明若要实现 heap.Interface 接口必须要先实现 sort.Interface 接口。
// heap.Interface
type Interface interface {
sort.Interface
Push(x interface{}) // add x as element Len()
Pop() interface{} // remove and return element Len() - 1.
}
// sort.Interface
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
Push(x interface{}) // add x as element Len()
Pop() interface{} // remove and return element Len() - 1.
}
在 struct 中嵌入 interface 🔗
这个是三种 embedding 中让人疑惑的一种。
- struct 类型的对象可以访问内嵌接口中的方法(提升到外层结构体中)
- 上一条满足,自然地 struct 类型的对象也实现了内嵌接口。
如果初始化 struct 类型对象时,没有初始化 embedding interface 则 embedding interface 被初始化为 nil, 则访问 embedding interface 中的方法时 panic。
type StatsConn struct {
net.Conn
BytesRead uint64
}
// 拦截net.Conn.Read方法。
func (sc *StatsConn) Read(p []byte) (int, error) {
n, err := sc.Conn.Read(p)
sc.BytesRead += uint64(n)
return n, err
}
resp, err := ioutil.ReadAll(sconn)
if err != nil {
log.Fatal(err)
}
fmt.Println(sconn.BytesRead)
type reverse struct {
sort.Interface
}
func (r reverse) Less(i, j int) bool {
return r.Interface.Less(j, i)
}
func Reverse(data sort.Interface) sort.Interface {
return &reverse{data}
}
sort.Sort(sort.Reverse(sort.IntSlice(lst)))
fmt.Println(lst)
Example: context.WithValue.
how 🔗
语法糖,依赖编译阶段的语法解析的处理。
多态 🔗
go 没有 implements, extends 关键字, golang 是去鸭子类型:看起来像鸭子, 那么它就是鸭子。
多态: 父类指针或引用去调用方法,在执行的时候,能够根据子类的类型去执行子类当中的方法。
type Person interface {
func gender() string
}
type Men struct{
Gender string
Age int
Name string
}
type Woman struct {
Gender string
Age int
Name strings
}
func (m Men) gender() {
return m.Gender
}
func (w Woman) gender() {
return w.Gender
}
func JudgeFunc() bool {
// may has many code
// can't not get boolean result until running this JudgeFunc
}
func main() {
var a Person
// so compiler can't not known clearly about the type info of a
a = JudgeFunc() ? Men{} : Woman{}
var b Person
// compiler can judge the type info of a
b = Men{} // or b = Woman{}
}
范型 🔗
空 interface{} 类型,没有方法集的接口,只需要存类型和类型对应的值即可。
空interface{} = (type, value) ==> (nil, nil) == nil
// 不含方法的空接口
type eface struct { // 16 byte
_type *_type // 指向类型
data unsafe.Pointer // 指向数据
}
// 含有方法的接口
type iface struct { // 16 byte
tab *itab //
data unsafe.Pointer //
}
type _type struct {
size uintptr // 类型占用的内存大小
ptrdata uintptr //
hash int32
align uint8
...
}
type itab struct { // 32 byte
inter *interfacetype // 类型信息
_type *_type // 类型信息
hash uint32 //
_ [4]byte // padding align size = 8 byte
fun [1]uintptr // 用户运行时动态派发的虚函数表,存储函数指针。
}
接口的使用实践 🔗
golang 源码里的较好实现方式是: 使用小的接口,尤其是只包含一个方法的接口;通过小接口的组合来定义大接口。
好处是:使用者可以只依赖于必要功能的最小接口。
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
func store(reader Reader) error {
// 入参可以只依赖于必要功能的最小接口, 让函数的返回更广
}
func store(readWriter ReadWriter) error {
// 入参依赖了不必要功能的接口,让限制了函数的使用范围为更小
}
易模糊的点 🔗
字符编码 🔗
ascii 码,占一个字节,实际使用了 7 bit,128 个字符,表示英语够了。
在 ascii 码的第 8 位置为 0,这个特点深深影响了其他国家和后来的字符编码方式。
欧洲法语 é 等字符,采用 Latin1, 利用 acsii 码的最高位, 可以表示了。
亚洲国家的文字符号更多,汉字 10 万加,必须多个字节表示一个符号。 例如 GB2312
unicode 万国码。只是定义了符号的二进制代码,如何区分不同的编码?没有规定二进制如何存放。
utf-8 历史, 开发 plan 9 操作系统的产物,发明了后来广泛使用的字符编码。
utf-8 是一种针对 unicode 的变长字符编码的实现方式。使用 1-4 个字节表示一个字符。
二进制代码第一位是 0, 则这个字符只占一个字节。
如果第一位是 1,连续有多少个 1,表示当前字符占用多少个字节, 后面字节的前两位均以 10 开头。
例如 严。 unicode = 100111000100101 = 4E25,
utf-8=11100100 10111000 10100101 = E4B8A5.
浮点数的存储与处理 🔗
复杂 todo
rune 类型 🔗
golang 为应对 utf-8 字符处理设计出来的一种类型type rune = int32
字符串有字符组成, 字符分 2 种, 一种是占 1byte 的字符,如英文字符;一种是占 1-4 byte 的字符用 rune 表示,如中文。
ascii 码: 48-0, 65-A, 97-a...
在 builtin/builtin.go 中定义
type rune = int32
fmt.Println(len("hallo中文"))// 5 + 3 + 3
fmt.Println(len([]rune("hallo中文"))) //5 + 1 + 1
for (index, item) range "ABC你好" {}
//等价于
for (index, item) range []rune("ABC你好"){}
数组与切片 🔗
数组的初始化 (元素类型, 数组大小)。 数组是值类型, 切片是引用类型。 切片作为函数的参数。
// cmd/compile/internal/types.NewArray
func NewArray(elem *Type, bound int64) *Type {...}
2 种初始化方式,显式的指定数组的大小
- arr := [3]int{1, 2, 3}
- [...]T 声明数组 // arr := [...]int{1, 2, 3}
数组的访问越界检查。 编译时 cmd/compile/internal/gc.typecheck1 函数 op=OINDEX 时处理。ssa 阶段也会插入检查函数 runtime.panicIndex。
切片,动态数组。
- 声明切片 []int, []interface{}
- 初始化切片 []int{1,2,3}, slice := make([]int, 3)
// cmd/compile/internal/types.NewSlice
func NewSlice(elem *Type) *Type {
...
}
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
type StringHeader struct {
Data uintptr
Len int
}
for range 的注意点 🔗
for range x x 是原对象的拷贝。
函数的接受者 🔗
函数接受者为指针类型:
- 函数可以修改接受者的内容
- 避免方法调用时变量的拷贝,而是指针的拷贝
实现接受者为值类型的方法,会自动实现接受者为指针类型的方法。
golang 值拷贝 🔗
go 严格上只有复制拷贝传递, 总是创建副本按值传递,只不过这个副本可以是变量的副本,也可以是指针的副本
闭包 🔗
闭包=函数+引用环境。
unsafe 包 🔗
*T <==> unsafe.Pointer <==> uintptr, uintptr 与 unsafe.Pointer(类似 void*)
- T1 指针与 T2 指针类型之间的转换
- 修改内存数据, uintptr 常用于与 unsafe.Pointer 配合,用于做指针运算
//结构体的成员变量在内存存储上是一段连续的内存
type Num struct{
i string
j int64
}
func main(){
n := Num{i: "EDDYCJY", j: 1}
nPointer := unsafe.Pointer(&n)
// 结构体的初始地址就是第一个成员变量的内存地址
niPointer := (*string)(unsafe.Pointer(nPointer))
*niPointer = "wuhan"
// 基于结构体的成员地址去计算偏移量。就能够得出其他成员变量的内存地址
//uintptr 是 Go 的内置类型。返回无符号整数,可存储一个完整的地址。后续常用于指针运算
//unsafe.Offsetof:返回成员变量 x 在结构体当中的偏移量。更具体的讲,就是返回结构体初始位置到 x 之间的字节数
//uintptr 类型是不能存储在临时变量中的 临时变量是可能被垃圾回收掉的 之后的内存操作有迷茫了
njPointer := (*int64)(unsafe.Pointer(uintptr(nPointer) + unsafe.Offsetof(n.j)))
*njPointer = 91
fmt.Printf("n.i: %s, n.j: %d", n.i, n.j)
}
指针是对内存区域的地址, 与指针相配合的类型 说明区域有哪些属性,如何去解析。
反射 🔗
反射 Reflection ,interface 对象=(Type, Value)
反射类型 reflect.Type, reflect.Value)
三种反射的基本操作:
- 反射可以将 interface 类型的变量 转换为 反射对象(reflect.Type reflect.Value)
- 反射可以将反射对象 还原为 interface 对象
- 反射对象可以修改,提前是 value 值是变量的指针
var x float= 3.1
v:= reflect.ValueOf(&x) //需要传入x的指针
v.Elem().SetFloat(3.4);
作用:
- 运行时动态调用方法
- 运行时构造函数进行 mock 插桩
内存管理 🔗
- 基本想法:局部 P 上(per cpu)和全局队列想结合的均衡思想。局部分配无需加锁,局部分配无法满足时加锁全局分配
- src/runtime/mheap.go:mspan
- 内存回收原理
- 三色标记法 (未使用,已使用,带处理)
- root 对象开始,BFS 遍历标记,
- 逃逸分析(对象是否被函数外面引用 例如闭包; 对象过大也可能在堆中)
- 逃逸分析的目的是决定变量的内存分配地址是在栈还是堆
- 逃逸分析在编译阶段完成的
- 传递指针真的比传值高效吗?不一定 由于指针传递会产生逃逸,可能会使用堆,增加 GC 负担。
- go build -gcflags=-m // escapes to heap
golang 中的特色语法 🔗
defer, recover 🔗
defer 调用的函数是一个function literal,也就是闭包或者匿名函数
多个 defer 的执行顺序为后进先出 stack。
defer 声明时会先计算确定参数的值
func f() {
i := 3
// print 3
defer fmt.Println(i)
i++
return
}
defer 修改有名返回值函数的返回值
// f return 6
func f() (result int) {
defer func() {
result *= 3;
}()
return 2
}
只能修改有名返回值(named result parameters)函数,匿名返回值函数是无法修改的
// f return 10
func f() int {
i := 9
defer func() {
i++
}()
//匿名返回值函数是在return执行时被声明
return i
}
recover只能在 defer 中才能生效。
nil 🔗
- what is nil
- nil is a kind of zero
- what is nil in go
- 各种类型的零值
- 基本类型的零值不为 nil
- bool 为 false
- number 为 0
- string 为""
- 非基本类型的零值为 nil
- pointer, slice, map, channel, function, interface{} ==> nil
- interface{}类型 需要 type 和 value 均为 nil 时,才会为 nil
- structure{},因为有结构 type 存在,type 不为 nil,所以结构体变量不会是 nil
函数调用的类型 🔗
- 顶层函数 func f()
- 函数 with 值调用者
- 函数 with 指针调用者
- func literal 匿名函数 or 闭包(A function literal represents an anonymous function)
type Aer interface {
a()
b()
}
type AStruct struct {
a_ int32
b_ string
}
func (a AStruct) a() {
fmt.Printf("call a(), a=%d\n", a.a_)
}
func (a *AStruct) b() {
fmt.Printf("call b(), b=%s\n", a.b_)
}
func main() {
var aStruct = AStruct{a_: 1, b_: "c"}
aStruct.a()
aStruct.b()
var aStructPtr = &AStruct{a_: 2, b_: "cc"}
aStructPtr.a()
aStructPtr.b()
var aInterfz Aer = &AStruct{a_: 3, b_: "ccc"}
aInterfz.a()
aInterfz.b()
// AStruct does not implement Aer (method b has pointer receiver)
// var aInterfz Aer = AStruct{}
}
goroutine 🔗
GMP 运行时。 G:代表一个 Goroutine,每个 Goroutine 都有自己独立的栈。M:表示内核线程。
P:代表一个虚拟的 Processor 处理器,它维护一个 local Goroutine Queue,工作线程优先使用自己的局部运行队列,只有必要时才会去访问 Global Queue。
interface与interface{} 🔗
接口的类型转换、类型断言以及动态派发机制
接口是一组方法签名。
当一个类型(结构体)为接口中的所有方法提供定义时,被称为实现了该接口, 也称为duck typing。
空接口interface{}, (类型+数据)。所有类型都实现了空接口, 空接口可以用来做泛型。
一个接口值由两个指针: 一个指向该值底层类型的方法表,另一个指向实际数据。
go 设计模式 🔗
search keyword go patterns
创建型, 结构型, 行为型
创建型 🔗
type Builder interface {
SetA() Builder
SetB() Builder
Build() AInterfz
}
type AInterfz interface {
A()
B()
}
assembly.SetA().SetB().Build().A()
type singleton map[string]string
var (
once sync.Once
instance singleton
)
func new() singleton {
once.Do(func() {
instance = make(singleton)
})
return instance
}
装饰器模式 decorator pattern 🔗
type Object func(int) int
func log_decorator(fn Object) Object {
return func(n int) int {
log.Println("before")
result := fn(n)
log.Println("after")
return result
}
}
func double(n int) int { return n*2}
f := log_decorator(double)
f(3)
代理模式 proxy pattern 🔗
type IObject interface {
objDo(action string)
}
type Object struct { action string}
func (obj *Object) objDo(action string) {
fmt.Println("%s.", action)
}
type ProxyObject struct {object *Object}
func (p *ProxyObject) objDo(action string) {
if p.object == nil {p.object = new(Object)}
if action == "sing" {
p.object.objDo(action)
}
}
观察者模式 observer pattern 🔗
策略模式 strategy pattern 🔗
责任链 🔗
通过闭包实现的 http 中间件,在原有函数的基础上包裹中间功能,而不破坏原有函数
func main() {
http.HandleFunc("/hello", timewrapper(hello))
http.ListenAndServe(":8888", nil)
}
func timewrapper(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
f(w, r)
end := time.Now()
fmt.Println("take time: ", end.Sub(start))
}
}
reference 🔗
[1] Embedding structs in structs
[2] Go by Example: Struct Embedding
[3] Embedding Interfaces in Go (Golang)
[4] Embedding in Go: Part 1 - structs in structs
[5] Embedding in Go: Part 3 - interfaces in structs
[6]Go 程序员面试笔试宝典