目录
- 一、函数
- 1. 函数的基本形式
- 2. 递归函数
- 3. 匿名函数
- 4. 闭包
- 5. 延迟调用defer
- 6. 异常处理
- 二、面向接口编程
- 1. 接口的基本概念
- 2. 接口的使用
- 3. 接口的赋值
- 4. 接口嵌入
- 5. 空接口
- 6. 类型断言
- 7. 面向接口编程
一、函数
1. 函数的基本形式
| |
| func add(a int, b int) { |
| a = a + b |
| } |
| var x, y int =, 6 |
| add(x, y) |
- 形参是函数内部的局部变量,实参的值会拷贝给形参
- 函数定义时的第一个的大括号不能另起一行
- 形参可以有0个或多个,支持使用可边长参数
- 参数类型相同时可以只写一次,比如add(a,b int)
- 在函数内部修改形参的值,实参的值不受影响
- 如果想通过函数修改实参,就需要传递指针类型
| func change(a, b *int) { |
| *a = *a + *b |
| *b = |
| } |
| var x, y int =, 6 |
| change(&x, &y) |
slice、map、channel都是引用类型,它们作为函数参数时其实跟普通struct没什么区别,都是对struct内部的各个字段做一次拷贝传到函数内部
| package main |
| import "fmt" |
| |
| func sliceChange(arr []int) { |
| arr[] = 1 |
| arr = append(arr,) |
| } |
| func main() { |
| arr := []int{} |
| sliceChange(arr) |
| fmt.Println(arr[]) |
| fmt.Println(len(arr)) |
| } |
关于函数返回值
- 可以返回0个或多个参数
- 可以在func行直接声明要返回的变量
- return后面的语句不会执行
- 无返回参数时return可以不写
| |
| func returnf(a, b int) (c int) { |
| a = a + b |
| c = a |
| return |
| } |
不定长参数实际上是slice类型
| |
| func args(a int, other ...int) int { |
| sum := a |
| |
| for _, ele := range other { |
| sum += ele |
| } |
| fmt.Printf("len %d cap %d\n", len(other), cap(other)) |
| return sum |
| } |
| args() |
| args(,2,3,4) |
append函数接收的就是不定长参数
| arr = append(arr,, 2, 3) |
| arr = append(arr,) |
| arr = append(arr) |
| slice := append([]byte("hello "), "world"...) |
| slice := append([]rune("hello "), []rune("world")...) |
在很多场景下string都隐式的转换成了byte切片,而非rune切片,比如"a中"[1]获取到的值为228而非"中"
2. 递归函数
最经典的斐波那契数列的递归求法
| func fibonacci(n int) int { |
| if n == || n == 1 { |
| return n |
| } |
| return fibonacci(n-) + fibonacci(n-2) |
| } |
3. 匿名函数
函数也是一种数据类型
| func functionArg(f func(a, b int) int, b int) int { |
| a := * b |
| return f(a, b) |
| } |
| type foo func(a, b int) int |
| func functionArg(f foo, b int) int { |
| a := * b |
| return f(a, b) |
| } |
| type User struct { |
| Name string |
| bye foo |
| hello func(name string) string |
| } |
| ch := make(chan func(string) string,) |
| |
| ch <- func(name string) string { |
| return "hello " + name |
| } |
4. 闭包
闭包(Closure)是引用了自由变量的函数,自由变量将和函数一同存在,即使已经离开了创造它的环境,闭包复制的是原对象的指针
| package main |
| import "fmt" |
| func sub() func() { |
| i := |
| fmt.Printf("%p\n", &i) |
| b := func() { |
| fmt.Printf("i addr %p\n", &i) |
| i-- |
| fmt.Println(i) |
| } |
| return b |
| } |
| |
| func add(base int) func(int) int { |
| return func(i int) int { |
| fmt.Printf("base addr %p\n", &base) |
| base += i |
| return base |
| } |
| } |
| func main() { |
| b := sub() |
| b() |
| b() |
| fmt.Println() |
| tmp := add(10) |
| fmt.Println(tmp(1), tmp1(2)) |
| |
| tmp := add(100) |
| fmt.Println(tmp(1), tmp2(2)) |
| } |

5. 延迟调用defer
- defer用于注册一个延迟调用(在函数返回之前调用)
- defer典型的应用场景是释放资源,比如关闭文件句柄,释放数据库连接等
- 如果同一个函数里有多个defer,则后注册的先执行,相当于是一个栈
- defer后可以跟一个func,func内部如果发生panic,会把panic暂时搁置,当把其他defer执行完之后再来执行这个
- defer后不是跟func,而直接跟一条执行语句,则相关变量在注册defer时被拷贝或计算
| func basic() { |
| fmt.Println("A") |
| defer fmt.Println() fmt.Println("B") |
| |
| defer fmt.Println() |
| fmt.Println("C") |
| } |
| func deferExecTime() (i int) { |
| i = |
| |
| defer func() { |
| fmt.Printf("first i=%d\n", i) |
| }() |
| defer func(i int) { |
| fmt.Printf("second i=%d\n", i) |
| }(i) |
| defer fmt.Printf("third i=%d\n", i) |
| return |
| } |
6. 异常处理
go语言没有try catch,它提倡直接返回error
| func divide(a, b int) (int, error) { |
| if b == { |
| return -, errors.New("divide by zero") |
| } |
| return a / b, nil |
| } |
| |
| if res, err := divide(, 0); err != nil { |
| fmt.Println(err.Error()) |
| } |
Go语言定义了error这个接口,自定义的error要实现Error()方法
| |
| type PathError struct { |
| path string |
| op string |
| createTime string |
| message string |
| } |
| |
| func (err PathError) Error() string { |
| return err.createTime + ": " + err.op + " " + err.path + " " + err.message |
| } |
何时会发生panic:
- 运行时错误会导致panic,比如数组越界、除0
- 程序主动调用panic(error)
panic会执行什么:
- 逆序执行当前goroutine的defer链(recover从这里介入)
- 打印错误信息和调用堆栈
- 调用exit(2)结束整个进程
| func soo() { |
| fmt.Println("enter soo") |
| |
| defer func() { |
| |
| if err := recover(); err != nil { |
| fmt.Printf("soo panic:%s\n", err) |
| } |
| }() |
| fmt.Println("regist recover") |
| defer fmt.Println("hello") |
| defer func() { |
| n := |
| _ = / n |
| defer fmt.Println("how are you") |
| }() |
| } |

二、面向接口编程
1. 接口的基本概念
接口是一组行为规范的集合
| |
| type Transporter interface { |
| |
| move(src string, dest string) (int, error) |
| whistle(int) int |
| } |
只要结构体拥有接口里声明的所有方法,就称该结构体“实现了接口”,一个struct可以同时实现多个接口
| |
| type Car struct { |
| price int |
| } |
| func (car Car) move(src string, dest string) (int, error) { |
| return car.price, nil |
| } |
| func (car Car) whistle(n int) int { |
| return n |
| } |
接口值有两部分组成, 一个指向该接口的具体类型的指针和另外一个指向该具体类型真实数据的指针
| car := Car{"宝马",} |
| var transporter Transporter |
| transporter = car |

2. 接口的使用
| func transport(src, dest string, transporter Transporter) error { |
| _,err := transporter.move(src, dest) |
| return err |
| } |
| var car Car |
| var ship Shiper |
| transport("北京", "天津", car) |
| transport("北京", "天津", ship) |
3. 接口的赋值
| |
| func (car Car) whistle(n int) int { |
| } |
| |
| func (ship *Shiper) whistle(n int) int { |
| } |
| car := Car{} |
| ship := Shiper{} |
| var transporter Transporter |
| transporter = car |
| transporter = &car |
| transporter = &ship |
4. 接口嵌入
| type Transporter interface { |
| whistle(int) int |
| } |
| type Steamer interface { |
| Transporter |
| displacement() int |
| } |
5. 空接口
空接口类型用interface{}表示,注意有{}
var i interface{}
空接口没有定义任何方法,因此任意类型都实现了空接口
| var a int = |
| i = a |
| func square(x interface{}){} // 该函数可以接收任意数据类型 |
注意:slice的元素、map的key和value都可以是空接口类型,map中的key可以是任意能够用==操作符比较的类型,不能是函数、map、切片,以及包含上述3中类型成员变量的的struct,map的value可以是任意类型
6. 类型断言
| |
| if v, ok := i.(int); ok { |
| fmt.Printf("i是int类型,其值为%d\n", v) |
| } else { |
| fmt.Println("i不是int类型") |
| } |
当要判断的类型比较多时,就需要写很多if-else,更好的方法是使用switch i.(type),这也是标准的写法
| switch v := i.(type) { |
| case int: |
| fmt.Printf("ele is int, value is %d\n", v) |
| |
| case float: |
| fmt.Printf("ele is float, value is %f\n", v) |
| case int, int32, byte: |
| fmt.Printf("ele is %T, value is %d\n", v, v) |
| } |
7. 面向接口编程
电商推荐流程

为每一个步骤定义一个接口
| type Recaller interface { |
| Recall(n int) []*common.Product |
| } |
| type Sorter interface { |
| Sort([]*common.Product) []*common.Product |
| } |
| type Filter interface { |
| Filter([]*common.Product) []*common.Product |
| } |
| type Recommender struct { |
| Recallers []recall.Recaller |
| Sorter sort.Sorter |
| Filters []filter.Filter |
| } |
使用纯接口编写推荐主流程
| func (rec *Recommender) Rec() []*common.Product { |
| RecallMap := make(map[int]*common.Product,) |
| |
| for _, recaller := range rec.Recallers { |
| products := recaller.Recall() |
| for _, product := range products { |
| RecallMap[product.Id] = product |
| } |
| } |
| |
| RecallSlice := make([]*common.Product,, len(RecallMap)) |
| for _, product := range RecallMap { |
| RecallSlice = append(RecallSlice, product) |
| } |
| SortedResult := rec.Sorter.Sort(RecallSlice) |
| |
| FilteredResult := SortedResult |
| for _, filter := range rec.Filters { |
| FilteredResult = filter.Filter(FilteredResult) |
| } |
| return FilteredResult |
| } |
面向接口编程,在框架层面全是接口。具体的实现由不同的开发者去完成,每种实现单独放到一个go文件里,大家的代码互不干扰。通过配置选择采用哪种实现,也方便进行效果对比