认识函数
func funcName(input1 type1,input2 type2)(output1 type1,output2 type2){
return value1,value2
}
/*
func 用来声明函数
funcName 指函数名称(匿名函数和lambda函数除外)
函数名称如果小写开头,它的作用域只属于所声明的包,不能被其它包调用,如果大写开头,则函数公开,可被其它包调用。
这个规则适用于所有变量、函数等实体对象的声明,类似Java中的作用域关键字 private protect public
Go语言不支持嵌套(nested)、重载(overload)、默认参数(default parameter)
*/
函数的基础
多返回值
package main
import "fmt"
func SumAndProduct(A, B int) (int, int) {
return A + B, A * B
}
func main() {
x := 3
y := 4
xPLUSy, xTIMESy := SumAndProduct(x, y)
fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
}
package main
import "fmt"
func SumAndProduct(A, B int) (add int, Multiplied int) {
add = A + B
Multiplied = A * B
return
}
func main() {
x := 3
y := 4
xPLUSy, xTIMESy := SumAndProduct(x, y)
fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
}
函数作为参数
package main
import "fmt"
// 直接将函数作为另一个函数的入参,入参函数可以未实现,在调用传参的时候再实现
func pipe(ff func() int) int {
return ff()
}
// 先将一个函数定义为一个类型,再作为另一个函数的入参,入参函数可以未实现,在调用传参的时候再实现
type FormatFunc func(s string, x, y int) string
func 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 main
import "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 []int
for _, 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 main
import "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 := -1
return e
}
}
package main
import "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.go
type any = interface{}
// print.go
func Printf(format string, a ...any) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}
匿名函数与闭包
package main
import "fmt"
func main() {
// 匿名函数地址保存到变量中
fplus := func(x, y int) int { return x + y }
result := fplus(3, 4)
fmt.Println(result)
// 匿名函数定义时立即调用1
result = func(x, y int) int { return x + y }(3, 4)
fmt.Println(result)
// 匿名函数定义时立即调用2
func(num int) int {
sum := 0
for i := 1; i <= num; i++ {
sum += i
}
fmt.Println(sum)
return sum
}(100)
}
package main
import "fmt"
/*
概念
匿名函数同样被成为闭包(函数式语言的术语)
闭包允许调用定义在其它环境下的变量,使函数可以捕捉到外部状态
用法
1. 闭包经常被用作包装函数,预先定义好一个或多个参数以用于包装
2. 使用闭包完成更加简洁的错误检查
*/
func main() {
fmt.Println(Add()(3)) // 5
fmt.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 main
import "fmt"
/*
1. 前后两次调用,外部变量j的值发生了变化
2. 闭包函数中,只有内部匿名函数才能访问变量i,而无法通过其它途径访问,保证了i的线程安全
*/
func main() {
j := 5
a := func() func() {
i := 10
return func() {
fmt.Printf("i = %d j = %d \n", i, j)
}
}()
a() // i = 10 j = 5
j = 10
a() // i = 10 j = 10
}
package main
import "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 int
return func(d int) int {
fmt.Println("x = ", x, "d = ", d)
x += d
return x
}
}
package main
import "fmt"
func getSequence() func() int {
i := 0
return func() int {
i++
return i
}
}
func main() {
// nextNumber 为一个函数,函数i为0
nextNumber := getSequence()
// 调用nextNumber函数,i变量自增1并返回
fmt.Printf("%d ", nextNumber()) // 1
fmt.Printf("%d ", nextNumber()) // 2
fmt.Printf("%d ", nextNumber()) // 3
// 创建新的函数nextNumber1,并查看结果
nextNumber1 := getSequence()
fmt.Printf("%d ", nextNumber1()) // 1
fmt.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 main
import "fmt"
/*
传指针的好处:
1. 传指针使得多个函数能操作同一个对象
2. 传指针比较轻量级(8B),毕竟只是传内存地址,可以用指针传递体积大的结构体
函数调用时,像切片slice、字典map、接口interface、通道channel这样的引用类型都是默认使用引用传递的(即使没有显式地指出指针)
channel、slice、map这三种类型的实现机制类似指针可直接传递而不用传地址。不过若函数需要改变slice的长度,则仍需要取址传递指针
3. 传指针赋予了函数直接修改外部变量的能力,所以被修改的变量不再需要使用return返回
*/
func main() {
x := 3
fmt.Println("x =", x, "&x =", &x) // x = 3 &x = 0xc000014098
y := add(x)
fmt.Println("x =", x, "&y =", &y) // x = 3 &y = 0xc0000140b8
z := addP(&x)
fmt.Println("x =", x, "&z =", &z) // x = 4 &z = 0xc0000140e0
fmt.Println("&x =", &x) // &x = 0xc000014098
}
func add(a int) int {
a++
return a
}
func addP(a *int) int {
*a++
return *a
}
package main
import "fmt"
// 传指针直接修改外部变量,不需要return
func main() {
n := 0
res := &n
multiply(2, 4, res)
fmt.Println("Result:", *res) // Result: 8
}
func multiply(a, b int, res *int) {
*res = a * b
}
defer与跟踪
用途:IO操作、错误处理
package main
import "fmt"
func main() {
fmt.Println("return:", a())
}
func a() int {
var i int
defer func() {
i++
fmt.Println("defer2:", i) // 顺序 2
}()
defer func() {
i++
fmt.Println("defer1:", i) // 顺序 1
}()
return i // 准备返回i(0),但是需要等到defer执行完 顺序3
}
/*
defer1: 1
defer2: 2
return: 0
解释:返回0而不是2,这是因为返回值没有被声明,所以函数a()的返回值还是0
*/
package main
import "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: 1
defer2: 2
return: 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的初始化还是受到外部影响的。