Go - receiver
影响12func (t T) M1() <=> F1(t T)func (t *T) M2() <=> F2(t *T)
Method
Desc
M1
基于值拷贝,传递的是 T 类型实例的副本
M2
基于值拷贝,传递的是 T 类型实例的指针
123456789101112131415161718192021222324252627282930type T struct { a int}func (t T) M1() { t.a = -1}func (t *T) M2() { t.a += 1}func main() { var t T println(t.a) // 0 t.M1() println(t.a) // 0 t.M2() println(t.a) // 1 p := &t p.M1() println(t.a) // 1 p.M2() println(t.a) // 2}
方法 ...
Go - Method
OOP
Go 并不支持 OOP 的语法元素(类、对象、继承等),但仍支持方法(Method)
Go 引入 Method 并非为了实现 OOP 编程范式,而是出自 Go 的组合设计哲学下类型系统实现层面的需要
Method 本质上是一个以 receiver 参数作为第 1 个参数的 Function(Go 编译器协助转换)
形式
receiver 参数是 Method 和 Type 之间的纽带
ListenAndServeTLS 归属于 *Server 类型,而非 Server 类型
receiver
Method 必须归属于某个 Type,即 receiver 参数的类型
123func (receiver *T或T) MethodName(参数列表) (返回值列表) { // 方法体}
无论 receiver 参数的类型是 *T 还是 T,receiver 参数的基类型都为 T
如果 receiver 的类型为 T,则这个 Method 是类型 T 的一个方法
如果 receiver 的类型为 *T,则这个 Method 是类型 *T 的一个方法
每个 Me ...
Go - defer
defer
defer 是 Go 提供的一种延迟调用机制,defer 的运作离不开函数
只有函数或者方法内部才能使用 defer
defer 关键字后只能接受函数或者方法,被称为 deferred 函数
defer 将 deferred 函数注册到其所在的 goroutine 中,用于存放 deferred 函数的栈数据结构
这些 deferred 函数将在执行 defer 的函数退出前,按 LIFO 的顺序被调度
无论执行到函数体尾部成功返回,还是在某个错误处理分支显式 return,或者出现 panic
已经存储到 deferred 函数栈中的函数,都会被调度执行 - 收尾
1234567891011121314151617181920212223242526272829303132package mainimport "sync"type Closable interface { Close()}type Resource struct {}func (r *Resource) Close() {} ...
Go - panic
原则
不要相信任何外部输入的参数
函数需要对所有输入的参数进行合法性的检查
一旦发现问题,立即终止函数的执行,返回预设的错误值
不要忽略任何一个错误
显式检查这些函数调用返回的错误值
一旦发现错误,要及时终止函数执行,防止错误继续传播
不要假定异常不会发生
异常不是错误
错误是可预期的,也会经常发生,有对应的公开错误码和错误处理方案
异常是不可预期的,通常指的是硬件异常、操作系统异常、语言运行时异常、代码 Bug(数组越界访问)等
异常是小概率事件,但不能假定异常不会发生
根据函数的角色和使用场景,考虑是否要在函数内设置异常捕获和恢复的环节
panic
在 Go 中,由 panic 来表达异常的概念
panic 指的是 Go 程序在运行时出现的一个异常情况
如果异常出现了,但没有被捕获并恢复,则 Go 程序的执行会被终止
即便出现异常的位置不在主 goroutine
panic 来源:Go 运行时 / 开发者通过 panic 函数主动触发
当 panic 被触发,后续的执行过程称为 panicking
手动调用 panic 函数,主动触发 panicking
...
Go - error
C vs Go
Go 语言的错误处理机制是在 C 语言错误处理机制基础上的再创新
通常使用类型为整型的函数返回值作为错误状态标识,函数调用者会基于值比较的方式来处理错误
返回值为 0,代表函数调用成功,否则函数调用出现错误
优点
要求开发者必须显式地关注和处理每个错误
错误一般为普通值,不需要额外的语言机制来处理,让代码更容易调试
C 语言错误处理机制具有简单和显式结合的特征,非常符合 Go 的设计哲学
因此 Go 继承了 C 的错误处理机制
缺点
C 语言中的函数最多仅支持一个返回值
一值多用
承载函数要返回给调用者的信息
承载函数调用的最终错误状态
当返回值为其它类型,如字符串,很难将数据与错误融合在一起
做法不一,很难形成统一的错误处理策略
因此 Go 函数新增了多返回值机制,用于支持数据与错误(返回值列表的末尾)的分离
fmt/print.go123456789// Fprintf formats according to a format specifier and writes to w.// It returns the number of bytes writ ...
Go - Function
声明
关键字 func
Go 函数声明必须以关键字 func 开始
函数名
在同一个 Go 包中,函数名应该是唯一的
遵循 Go 标识符的导出规则
参数列表
Go 函数支持变长参数,即一个形式参数对应多个实际参数
返回值列表
支持具名返回值,比较少用
函数体
可选,如果没有函数体,说明该函数可能在 Go 语言之外实现的
可能使用汇编语言,然后通过链接器将实现与声明中的函数名链接在一起
类比
将函数声明等价转换为变量声明的形式 - 声明一个类型为函数类型的变量
Key
Value
变量名
函数名
函数签名
参数列表 + 返回值列表
函数类型
func + 参数列表 + 返回值列表func + 函数签名
函数签名:决定两个函数类型是否相同
在表述函数类型时,通常会省略函数签名参数列表中的参数名,以及返回值列表中的返回值变量名
12// 函数类型,只关注入参和出参func (io.Writer, string, ...interface{}) (int, error)
相同的函数签名,相同的函数类型
12345678func (a ...
Go - switch
语法12345678910switch initStmt; expr { case expr1: // 分支 1 case expr2_1, expr2_2: // 分支 2 case exprN: // 分支 N default: // 默认分支}
顺序
按照定义顺序(从上到下,从左到右),先到先得,匹配后中断
123456789101112131415161718192021222324252627282930313233343536373839404142434445package mainfunc case1() int { println("eval case1") return 1}func case2_1() int { println("eval case2_1") return 0}func case2_2() int { println("eval case2_2") r ...
Go - for
经典C1234567891011#include <stdio.h>int main() { int i; int sum = 0; for (i = 0; i < 10; i++) { sum += i; } printf("%d\n", sum);}
Go
i 为循环变量,作用域仅限于 for 语句的隐式代码块范围内
123456789package mainfunc main() { var sum int for i := 0; i < 10; i++ { sum += i } println(sum)}
多变量123456789package mainfunc main() { var sum int for i, j, k := 0, 1, 2; (i < 10) && (j < 20) && (k < 20); ...
Go - if
概述
if 语句是 Go 语言中提供的一种分支控制结构,根据布尔表达式的值,在两个分支中选择一个执行
12345if boolean_expression { // 新分支}// 原分支
二分支结构
12345if boolean_expression { // 分支 1} else { // 分支 2}
多分支结构
123456789if boolean_expression1 { // 分支 1} else if boolean_expression2 { // 分支 2} else if boolean_expression3 { // 分支 3} else { // 分支 4}
12345678910111213if boolean_expression1 { // 分支 1} else { if boolean_expression2 { // ...
Go - Struct
新类型类型定义
Type Definition,基于已有类型
1type T S // S 为任意的已定义类型,包括原生类型
新类型 T1 是基于原生类型 int 定义的新类型,而新类型 T2 则是基于刚定义的类型 T1 定义的新类型
12type T1 inttype T2 T1
底层类型
如果一个新类型是基于某个原生类型定义的,那么该原生类型为新类型的底层类型(Underlying Type)
如果一个新类型不是基于原生类型定义的,那么就就递归查找
12type T1 int // T1 的底层类型为 inttype T2 T1 // T2 基于 T1 创建,而 T1 的底层类型为 int,所以 T2 的底层类型也为 int
底层类型用来判断两个类型本质上是否相同(identical)
本质相同的两个类型,它们的变量可以通过显式转型进行相互赋值
本质不同的两个类型,变量之间都无法显式转型
1234567891011121314type T1 inttype T2 T1 // T2 与 T1 本质相同type T3 string // T3 与 T1 本质不同,无法进行显 ...