认识函数
func funcName(input1 type1,input2 type2)(output1 type1,output2 type2){return value1,value2}/*func 用来声明函数funcName 指函数名称(匿名函数和lambda函数除外)函数名称如果小写开头,它的作用域只属于所声明的包,不能被其它包调用,如果大写开头,则函数公开,可被其它包调用。这个规则适用于所有变量、函数等实体对象的声明,类似Java中的作用域关键字 private protect publicGo语言不支持嵌套(nested)、重载(overload)、默认参数(default parameter)*/
函数的基础
多返回值
package mainimport "fmt"func SumAndProduct(A, B int) (int, int) {return A + B, A * B}func main() {x := 3y := 4xPLUSy, xTIMESy := SumAndProduct(x, y)fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)}
package mainimport "fmt"func SumAndProduct(A, B int) (add int, Multiplied int) {add = A + BMultiplied = A * Breturn}func main() {x := 3y := 4xPLUSy, xTIMESy := SumAndProduct(x, y)fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)}
函数作为参数
package mainimport "fmt"// 直接将函数作为另一个函数的入参,入参函数可以未实现,在调用传参的时候再实现func pipe(ff func() int) int {return ff()}// 先将一个函数定义为一个类型,再作为另一个函数的入参,入参函数可以未实现,在调用传参的时候再实现type FormatFunc func(s string, x, y int) stringfunc format(ff FormatFunc, s string, x, y int) string {return ff(s, x, y)}func main() {s1 := pipe(func() int { return 100 })s2 := format(func(s string, x, y int) string {return fmt.Sprintf(s, x, y)}, "%d,%d", 10, 20)fmt.Println(s1, s2) // 100 10,20}
函数作为类型
package mainimport "fmt"func isOdd(v int) bool {return v%2 != 0}func isEven(v int) bool {return v%2 == 0}func getAll(v int) bool {return true}// 将入参和出参类型相同的函数抽象定义为一个类型(关联关系仅依赖入参和出参类型)type boolFunc func(int) bool// 类型(函数)作为一个参数使用func filter(slice []int, f boolFunc) []int {var result []intfor _, value := range slice {if f(value) {result = append(result, value)}}return result}func main() {slice := []int{3, 1, 4, 5, 9, 2}fmt.Printf("slice: %v\n", slice) // slice: [3 1 4 5 9 2]odd := filter(slice, isOdd)fmt.Printf("odd: %v\n", odd) // odd: [3 1 5 9]even := filter(slice, isEven)fmt.Printf("even: %v\n", even) // even: [4 2]all := filter(slice, getAll)fmt.Printf("all: %v\n", all) // all: [3 1 4 5 9 2]}
可变参数
package mainimport "fmt"func main() {// 手动动态添加参数age := ageMinOrMax("min", 1, 3, 2, 0)fmt.Printf("最小年龄%d岁\n", age) // 最小年龄0岁// 数字提前定义参数ageArr := []int{7, 9, 3, 5, 1}age = ageMinOrMax("max", ageArr...)fmt.Printf("最大年龄%d岁\n", age) // 最大年龄9岁}func ageMinOrMax(m string, a ...int) int {if len(a) == 0 {return 0}if m == "max" {max := a[0]for _, v := range a {if v > max {max = v}}return max} else if m == "min" {min := a[0]for _, v := range a {if v < min {min = v}}return min} else {e := -1return e}}
package mainimport "fmt"func main() {ageArr := []int{7, 9, 3, 5, 1}f1(ageArr...)}// 传递格式为arr...还是arr取决于调用函数接收类型是否可变func f1(arr ...int) {f2(arr...)fmt.Println("")f3(arr)}func f2(arr ...int) {for _, char := range arr {fmt.Printf("%d ", char) // 7 9 3 5 1}}func f3(arr []int) {for _, char := range arr {fmt.Printf("%d ", char) // 7 9 3 5 1}}
// builtin.gotype any = interface{}// print.gofunc Printf(format string, a ...any) (n int, err error) {return Fprintf(os.Stdout, format, a...)}
匿名函数与闭包
package mainimport "fmt"func main() {// 匿名函数地址保存到变量中fplus := func(x, y int) int { return x + y }result := fplus(3, 4)fmt.Println(result)// 匿名函数定义时立即调用1result = func(x, y int) int { return x + y }(3, 4)fmt.Println(result)// 匿名函数定义时立即调用2func(num int) int {sum := 0for i := 1; i <= num; i++ {sum += i}fmt.Println(sum)return sum}(100)}
package mainimport "fmt"/*概念匿名函数同样被成为闭包(函数式语言的术语)闭包允许调用定义在其它环境下的变量,使函数可以捕捉到外部状态用法1. 闭包经常被用作包装函数,预先定义好一个或多个参数以用于包装2. 使用闭包完成更加简洁的错误检查*/func main() {fmt.Println(Add()(3)) // 5fmt.Println(Add2(6)(3)) // 9}// Add 无参函数,返回值是一个匿名函数,匿名函数返回一个int类型的值func Add() func(b int) int {return func(b int) int {return b + 2}}// Add2 无参数函数,返回值是一个匿名函数,匿名函数返回一个int类型的值func Add2(a int) func(b int) int {return func(b int) int {return a + b}}
package mainimport "fmt"/*1. 前后两次调用,外部变量j的值发生了变化2. 闭包函数中,只有内部匿名函数才能访问变量i,而无法通过其它途径访问,保证了i的线程安全*/func main() {j := 5a := func() func() {i := 10return func() {fmt.Printf("i = %d j = %d \n", i, j)}}()a() // i = 10 j = 5j = 10a() // i = 10 j = 10}
package mainimport "fmt"func main() {var f = adder()fmt.Println(f(1)) // [x = 0 d = 1] [1]fmt.Println(f(2)) // [x = 1 d = 2] [3]fmt.Println(f(3)) // [x = 3 d = 3] [6]}/*1. 闭包中的变量可以在闭包函数体内声明,也可以在外部函数声明2. 不管外部函数是否退出,它都能够继续操作外部函数中的局部变量*/func adder() func(int) int {var x intreturn func(d int) int {fmt.Println("x = ", x, "d = ", d)x += dreturn x}}
package mainimport "fmt"func getSequence() func() int {i := 0return func() int {i++return i}}func main() {// nextNumber 为一个函数,函数i为0nextNumber := getSequence()// 调用nextNumber函数,i变量自增1并返回fmt.Printf("%d ", nextNumber()) // 1fmt.Printf("%d ", nextNumber()) // 2fmt.Printf("%d ", nextNumber()) // 3// 创建新的函数nextNumber1,并查看结果nextNumber1 := getSequence()fmt.Printf("%d ", nextNumber1()) // 1fmt.Printf("%d ", nextNumber1()) // 2}
递归函数
大量递归调用容易导致程序栈内存分配耗尽造成栈溢出。好在通过懒惰求值可以解决这个问题,在Go中,可以用过管道(channel)和 goroutine 来解决这个问题。
内置函数
| close | 用于管道通信 |
|---|---|
| len | 用于返回某个类型的长度或数量(字符串、数组、切片、map和管道) |
| cap | 容量,用于返回某个类型的最大容量(仅用于切片和map) |
| new、make | 均用于分配内存 new用于值类型和用户自定义类型,如自定义结构 make用于内置引用类型,如切片、map 和 管道 它们的用法类似函数,但是是将类型作为参数:new(type)、make(type) new(T)分配类型T的零值并返回其地址,也就是指向类型T的指针,它也可以用于基本类型: v:=new(int) make(T)返回类型T的初始化之后的值,因此它比new做更多的工作。 new()是一个函数,不要忘记它的括号 |
| copy、append | 用于复制和连接切片 |
| panic、recover | 用于错误处理机制 |
| print、println | 底层打印函数(部署环境中建议使用fmt包) |
| complex、real imag | 用于创建和操作复数 |
函数进阶
参数传递机制
package mainimport "fmt"/*传指针的好处:1. 传指针使得多个函数能操作同一个对象2. 传指针比较轻量级(8B),毕竟只是传内存地址,可以用指针传递体积大的结构体函数调用时,像切片slice、字典map、接口interface、通道channel这样的引用类型都是默认使用引用传递的(即使没有显式地指出指针)channel、slice、map这三种类型的实现机制类似指针可直接传递而不用传地址。不过若函数需要改变slice的长度,则仍需要取址传递指针3. 传指针赋予了函数直接修改外部变量的能力,所以被修改的变量不再需要使用return返回*/func main() {x := 3fmt.Println("x =", x, "&x =", &x) // x = 3 &x = 0xc000014098y := add(x)fmt.Println("x =", x, "&y =", &y) // x = 3 &y = 0xc0000140b8z := addP(&x)fmt.Println("x =", x, "&z =", &z) // x = 4 &z = 0xc0000140e0fmt.Println("&x =", &x) // &x = 0xc000014098}func add(a int) int {a++return a}func addP(a *int) int {*a++return *a}
package mainimport "fmt"// 传指针直接修改外部变量,不需要returnfunc main() {n := 0res := &nmultiply(2, 4, res)fmt.Println("Result:", *res) // Result: 8}func multiply(a, b int, res *int) {*res = a * b}
defer与跟踪
用途:IO操作、错误处理
package mainimport "fmt"func main() {fmt.Println("return:", a())}func a() int {var i intdefer func() {i++fmt.Println("defer2:", i) // 顺序 2}()defer func() {i++fmt.Println("defer1:", i) // 顺序 1}()return i // 准备返回i(0),但是需要等到defer执行完 顺序3}/*defer1: 1defer2: 2return: 0解释:返回0而不是2,这是因为返回值没有被声明,所以函数a()的返回值还是0*/
package mainimport "fmt"func main() {fmt.Println("return:", a())}func a() (i int) {defer func() {i++fmt.Println("defer2:", i) // 顺序 2}()defer func() {i++fmt.Println("defer1:", i) // 顺序 1}()return i // 顺序 3}/*defer1: 1defer2: 2return: 2解释:返回2而不是0,这是因为返回值已被声明,即defer可以调用到真实的返回值,因此defer在return赋值返回值i之后,再一次修改了i的值,最终函数退出后的返回值才是defer修改过的值*/
/*defer后的表达式不能执行操作语句,必须是函数调用return的实现逻辑:1. 给返回值赋值(若为有名返回值则直接赋值,若为匿名返回值则先声明再赋值)2. 调用RET返回指令并传入返回值,而RET则会检查defer是否存在,若存在就先逆序插播defer语句3. 最后RET携带返回值退出函数*/
可以看出,return并不是一个原子操作,函数返回值与return返回值并不一定一致。
因此,defer、return、返回值三者的执行顺序应该是:
- return最先给返回值赋值;
- defer开始执行收尾工作;
- RET指令携带返回值退出函数
defer声明时会先计算确定参数的值,defer推迟执行的仅是其函数体,因此defer语句位置并非随意,defer的初始化还是受到外部影响的。
