函数式编程
与其他编程语言的差异
1 可以有多个返回值
2 所有参数都是值传递:slice,map,channel会有传引用的错觉
3 函数可以作为变量的值
4 函数可以作为参数和返回值
可变参数
...
传过来的参数,例如求和:
func TestSum(t *testing.T) {
sumFunc := func(args ...int) int {
var ret int
for _, op := range args {
ret += op
}
return ret
}
t.Logf("the sum is: %v\n", sumFunc(1, 2, 3, 4, 5))
}
defer函数
1 panic后defer会继续执行
2 os.Exit()会直接退出,defer不会执行
示例:装饰者模式实现计时功能
func getTimeSpent(inner func(op int) int) func(op int) int {
return func(n int) int {
start := time.Now()
ret := inner(n)
fmt.Println("time spent: ", time.Since(start).Seconds())
return ret
}
}
func runSlow(op int) int {
time.Sleep(time.Second * 3)
fmt.Println("runSlow run...")
return op * op
}
func runFast(op int) int {
time.Sleep(time.Second * 1)
fmt.Println("runFast run...")
return op * op * op
}
func TestTimer(t *testing.T) {
tsSf := getTimeSpent(runSlow)
tsFs := getTimeSpent(runFast)
t.Log("exec....")
t.Logf("runSlow ret is : %v\n", tsSf(10))
t.Logf("runFast ret is : %v\n", tsFs(10))
}
以上请求的输出是:
=== RUN TestTimer
main_test.go:61: exec....
runSlow run...
time spent: 3.0143102
main_test.go:63: runSlow ret is : 100
runFast run...
time spent: 1.0120686
main_test.go:64: runFast ret is : 1000
--- PASS: TestTimer (4.03s)
PASS
Process finished with the exit code 0
defer
defer执行顺序是栈
defer中函数如果包含的参数也是函数,会直接使用函数的值作为参数执行,而不是稍后执行
面向对象编程
结构体
定义
// 例如定义结构体:学生
type Student Struct {
Age int
Name string
}
初始化
e := Student{18, "Tom"} // 直接根据结构初始化,不建议
e1 := Student{Age: 18, Name: "Tom"}
e2 := new(Student) //这里返回的是指针/引用,相当于e := &Student{}
e2.Age = 18
e2.Nane = "Tom"
方法
定义
// 在实例对应方法被调用时,实例成员会进行值复制
func (e Employee) String() string {
fmt.Printf("Name: %s", e.Name)
}
// 通常为避免内存拷贝,使用第二种调用方式
func (e *Employee) String() string {
fmt.Printf("Name: %s", e.Name)
}
接口
与其他编程语言的差别:
1 接口为非入侵性,实现不依赖于接口定义
2 所以接口的定义可以包含在接口使用者包内
type Phone interface {
Call() string
}
type AndroidPhone struct {
}
type Iphone struct {
}
func (nokia AndroidPhone) Call() string {
return "nokia is calling..."
}
func (iphone7 Iphone) Call() string {
return "iphone7 is calling..."
}
func TestNewPhone(t *testing.T) {
var phone Phone
phone = new(AndroidPhone)
t.Logf("1: android: %s\n", phone.Call()) // android: nokia is calling...
phone = new(Iphone)
t.Logf("2: iphone: %s\n", phone.Call()) // iphone: iphone7 is calling...
}
确保程序实现接口
上一个示例中可以采用如下方法确保实现了接口:
var _ Phone = &AndroidPhone{}
自定义类型
1 type IntconvFunc func(n int) int
2 type MyPoint int
例如之前的获取时间差代码,可以改写为:
var GetInner func(op int) int
func getTimeSpent(inner GetInner) GetInner {
return func(n int) int {
start := time.Now()
ret := inner(n)
fmt.Println("time spent: ", time.Since(start).Seconds())
return ret
}
}
匿名类型嵌入
1 注意不是继承
go多态实现
依旧以上文为例,实现类似多态的功能:
type Phone interface {
Photo() string
}
type AndroidPhone struct {
}
type Iphone struct {
}
func (nokia AndroidPhone) Photo() string {
return "nokia is photograph..."
}
func (iphone7 Iphone) Photo() string {
return "iphone7 is photograph..."
}
func photoUseDiffPhone(inter Phone) {
fmt.Printf("interface : %T, res: %s\n", inter, inter.Photo())
}
func TestDiffInterface(t *testing.T) {
android := &AndroidPhone{}
iphone := &Iphone{}
photoUseDiffPhone(android)
photoUseDiffPhone(iphone)
}
空接口与断言
1 空接口可以表示任何类型
2 通过断言将空接口转化为指定类型
空接口示例
设置一个类型为空接口interface{}
,初始化或赋值是可以给定任意类型
type NilInterfaceTest struct {
Name string
Age int8
PhoneNumber interface{}
}
func TestNilInterface(t *testing.T) {
a := NilInterfaceTest{Name: "Tom", Age: 18, PhoneNumber: 13333333333}
b := NilInterfaceTest{Name: "Jerry", Age: 17, PhoneNumber: "011-73313"}
t.Logf("nil interface as int: %v\n", a) // nil interface as int: {Tom 18 13333333333}
t.Logf("nil interface as string: %v\n", b) // nil interface as string: {Jerry 17 011-73313}
}
断言示例
断言判断接口类型
注意,断言是用来判断接口的,类似a := 10
这种,无法使用断言
// 断言
v, ok := p.(int) // ok为true是表示成功
// 具体使用
func TestAssertData(t *testing.T) {
var a interface{}
var b interface{}
a = 10
b = "test"
aAssert, ok := a.(int)
t.Logf("assert a %v, ret %v\n", aAssert, ok)
bAssert, ok := b.(int)
t.Logf("assert b %v, ret %v\n", bAssert, ok)
}
最佳实践
使用switch p.(type)判断空接口的类型:
func getType(p interface{}, t *testing.T) {
switch p.(type) {
case string:
t.Log("the type is string...")
case int:
t.Log("the type is int...")
default:
t.Log("the type is other...")
}
}
func TestAssertType(t *testing.T) {
var p interface{}
var q interface{}
p = 12
q = "test"
getType(p, t) // the type is int...
getType(q, t) // the type is string...
}
go接口最佳实践
保持接口最小,较大的接口定义,可以组合较小的接口
错误处理
error
内部自己定义的错误,要想程序稳健,尽量不要错过处理任何一个error
与其他编程语言的差异
1 没有异常机制
2 error类型实现了error接口
3 可以通过error.New来快速创建错误实例
最佳实践
定义不同的错误常量,以便于判断错误类型,函数最后一个返回值最好设置为nil,方便接收方判断
func divisionData(divisor int32, dividend int32) (float32, error) {
var DividendZeroError error = errors.New("dividend cannot be 0")
if dividend == 0 {
return 0, DividendZeroError
}
return float32(divisor) / float32(dividend), nil
}
func TestErrorMessage(t *testing.T) {
var a int32 = 3
var b int32 = 6
ret, err := divisionData(a, b)
if err != nil {
t.Logf("device error: %v\n", err)
}
t.Logf("device ret: %v\n", ret) // device ret: 0.5
var c int32 = 3
var d int32 = 0
ret, err = divisionData(c, d)
if err != nil {
t.Logf("device error: %v\n", err) // device error: Dividend cannot be 0
}
t.Logf("device ret: %v\n", ret) // device ret: 0
}
panic的处理
1 不可恢复的错误
2 退出前会调用defer指定的函数
panic与os.Exit()的区别
1 os.Exit()退出时不会调用defer指定的函数
2 os.Exit()退出时不输出当前调用栈信息
recover
Recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效,在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果,如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。
recover可能会形成僵尸服务进程,导致health check失效
模块化和包
package
1 基本复用模块单元,以首字母大写表明可被包外部访问
2 代码的package可以和所在目录不一致
3 同一个目录里的package名称要保持一致
init
可以有多个init,按照顺序执行
package引入的init先执行,按照顺序执行
在main执行前,所有init都会被执行
go语言进阶
并发编程模式
MPG
machine,一个 machine 对应一个内核线程,相当于内核线程在 Golang 进程中的映射
processor,一个 prcessor 表示执行 Go 代码片段的所必需的上下文环境,可以理解为用户代码逻辑的处理器
goroutine,是对 Golang 中代码片段的封装,其实是一种轻量级的用户线程。
注意事项
调度顺序不固定,如果不传值,那么在循环中执行可能只能拿到相同的值
func TestGoroutine(t *testing.T) {
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Println(i) // 随机,有调用时间
}(i)
go func() {
fmt.Println(i) // 循环次数较小,机器较好的情况下,基本是10
}()
}
}