贺胖娇的编程之旅......

学习go_20220806_02

2022.08.06

函数式编程

与其他编程语言的差异

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
		}()
	}
}
发表评论