package main
import (
"fmt"
"sort"
)
//函数
//func name(parameter-list) (result-list) {
// body
//}
//如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的
//如果一组形参或返回值有相同的类型,我们不必为每个形参都写出参数类型
// _符号 可以强调某个参数未被使用。
func aad(x int, y int) int { return x + y }
func sub(x, y int) (z int) { z = x - y; return }
func first(x int, _ int) int { return x }
func zero(int, int) int { return 0 }
//函数的类型被称为函数的签名
//如果两个函数形式参数列表和返回值列表中的变量类型一一对应,那么这两个函数被认为有相同的类型或签名
//Go语言没有默认参数值,也没有任何方法可以通过参数名指定形参
//每一次函数调用都必须按照声明顺序为所有参数提供实参
// 多返回值
// 一个函数可以返回多个值
// 当你调用接受多参数的函数时,可以将一个返回多参数的函数调用作为该函数的参数
func exchange(a int, b int) (int, int) {
a, b = b, a
return b, a
}
func doubleexchange(a int, b int) (int, int) {
exchange(exchange(a, b)) //将一个返回多参数的函数调用作为该函数的参数
return a, b
}
// 如果一个函数所有的返回值都有显式的变量名,那么该函数的return语句可以省略操作数
func plus(c int) (a, b int) {
a = c
b = 0
return //a,b
}
//开始处定义a,最后返回a
//按照返回值列表的次序,返回所有的返回值,
//error
//对于大部分函数而言,永远无法确保能否成功运行。这是因为错误的原因超出了程序员的控制
//对于那些将运行失败看作是预期结果的函数,它们会返回一个额外的返回值,通常是最后一个,来传递错误信息。如果导致失败的原因只有一个,额外的返回值可以是一个布尔值,通常被命名为ok
//需要了解更多的错误信息。因此,额外的返回值不再是简单的布尔类型,而是error类型。
//error类型可能是nil或者non-nil。nil意味着函数运行成功,non-nil表示失败
//对于non-nil的error类型,我们可以通过调用error的Error函数或者输出函数获得字符串类型的错误信息。
//通常,当函数返回non-nil的error时,其他的返回值是未定义 a,这些未定义的返回值应该被忽略。
//当一次函数调用返回错误时,调用者应该选择合适的方式处理错误。根据情况的不同,有很多处理方式,让我们来看看常用的五种方式。
//传播错误
//函数中某个子程序的失败,会变成该函数的失败
//resp, err := http.Get(url)
//if err != nil{
// return nil, err
//}
//重新尝试
//需要限制重试的时间间隔或重试的次数
//for 循环
//输出错误信息并结束程序
//只需要输出错误信息
//我们可以直接忽略掉错误
//文件结尾错误(EOF)
// 函数值
// 函数被看作第一类值,像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回。
func square(a int) (s int) {
s = a * a
return
}
func borrow() {
f := square
fmt.Println(f(3))
}
//函数类型的零值是nil
//函数值可以与nil比较
//但是函数值之间是不可比较的,也不能用函数值作为map的key
//不仅仅可以通过数据来参数化函数,亦可通过行为
//匿名函数
//拥有函数名的函数只能在包级语法块中被声明,通过函数字面量我们可绕过这一限制,在任何表达式中表示一个函数值
//函数值字面量是一种表达式
//func关键字后没有函数名
func lambda() {
a := func(x, y int) int { c := x * y; return c }
fmt.Println(a)
} //通过这种方式定义的函数可以访问完整的词法环境,在函数中定义的内部函数可以引用该函数的变量
// squares返回一个匿名函数。
// 该匿名函数每次被调用时都会返回下一个数的平方。
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
func mul() {
f := squares()
fmt.Println(f()) // "1"
fmt.Println(f()) // "4"
fmt.Println(f()) // "9"
fmt.Println(f()) // "16"
}
//函数值不仅仅是一串代码,还记录了状态
//在squares中定义的匿名内部函数可以访问和更新squares中的局部变量
//Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包
func topoSort(m map[string][]string) []string { //参数是【string】[string]类型
var order []string //定义一个[string]数组
seen := make(map[string]bool) //创建一个map
var visitAll func(items []string) //函数变量
visitAll = func(items []string) { //匿名函数
for _, item := range items { //遍历val
if !seen[item] { //如果没有这个建
seen[item] = true //这个建存在
visitAll(m[item]) //以这个值为建,看看能不能找到下一位
order = append(order, item) //插入键值对
}
}
}
var keys []string
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
visitAll(keys)
return order
}
//捕获迭代变量
// 可变参数
// 参数数量可变的函数称为可变参数函数。典型的例子就是fmt.Printf和类似函数
// 需要在参数列表的最后一个参数类型之前加上省略符号“...”,表示该函数会接收任意数量的该类型参数
func p(vals ...int) { //vals被看作是类型为[] int的切片
for i, j := range vals { //
//少写j会导致只能拿到index
fmt.Println(i, j)
}
}
//,调用者隐式的创建一个数组,并将原始参数复制到数组中,再把数组的一个切片作为参数传给被调用函数
//如果原始参数已经是切片类型,我们该如何传递给sum?只需在最后一个参数后加上省略符
//在最后一个参数后加上省略符
//注意是实参处
//可变参数函数和以切片作为参数的函数是不同的
//interface{}表示函数的最后一个参数可以接收任意类型
// 你只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。当执行到该条语句时,函数和参数表达式得到计算,但直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行
// 可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。
func deftest() int {
defer func() { fmt.Println("1") }()
defer func() { fmt.Println("2") }()
defer func() { fmt.Println("3") }()
defer func() { fmt.Println("4") }()
//输出4,3,2,1
return 5
}
//延迟函数
//defer语句中的函数会在return语句更新返回值变量后再执行
//又因为在函数中定义的匿名函数可以访问该函数包括返回值变量在内的所有变量,所以,对匿名函数采用defer机制,可以使其观察函数的返回值。
func doub(x int) (y int) {
defer func() {
fmt.Println(y)
}()
return x * x
}
//Panic异常
//有些错误只能在运行时检查,编译时无法检查出,这些运行时错误会引起painc异常。
//一般而言,当panic异常发生时,程序会中断运行,并立即执行在该goroutine(可以先理解成线程,在第8章会详细介绍)中被延迟的函数(defer 机制)
//随后,程序崩溃并输出日志信息。
//通常,我们不需要再次运行程序去定位问题,日志信息已经提供了足够的诊断依据。
//不是所有的panic异常都来自运行时,直接调用内置的panic函数也会引发panic异常;panic函数接受任何值作为参数。当某些不应该发生的场景发生时,我们就应该调用panic。
//手动阻止
//Recover捕获异常