函数
函数的基本形式
- 形参可以有 0 个或 N 个
- 参数类型相同时可以只写一次,比如 argf(a,b int)
go
//a,b是形参,形参是函数内部的局部变量,实参的值会拷贝给形参
func argf(a,b int){
a = a+b //在函数内部修改形参的值,实参的值不受影响
}
func main(){
var x,y int = 3,6
argf(x,y) //x,y是实参
}
参数传指针
- 如果想通过函数修改实参,就需要指针类型
go
func argf(a,b *int){
*a = *a + *b
*b = 888
}
func main(){
var x,y int = 3,6
argf(&x,&y)
}
传引用和传引用的指针
- slice、map、channel 都是引用类型,它们作为函数参数时其实跟普通 struct 没什么区别,都是对 struct 内部的各个字段做一次拷贝传到函数内部
go
func sliceArg(arr []int){ //slice作为参数,实际上是把slice的arrayPointer、len、cap拷贝了一份传进来
arr[0] = 1 //修改底层数据里的首元素,即arr的值发生了变化
arr = append(arr,1) //arr的len和cap发生了变化,但不会影响实参
}
函数返回值
- 可以返回 0 个或这 N 个
- 可以在 func 行直接声明要返回的变量
- return 后面的语句不会执行
- 无返回参数时 return 可以不写
go
func returnf(a,b int) (c int){ //返回变量c在这里就声明好了
a = a+b
c = a //直接使用c
return //由于函数要求有返回值,即使给c赋过值,也需要显示写return
}
不定长参数
go
//不定长参数实际上时slice类型
func variableEngthArg(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
}
func main(){
variableEngthArg(1) //len 0 cap 0
variableEngthArg(1,3,5,7,9) //len 4 cap 4
brr := []int{1, 2, 3}
variableEngthArg(3, brr...)//传不定长参数的另类写法
}
匿名函数
在 Go 语言(Golang)中,匿名函数是没有名字的函数。它们通常用于实现闭包和即时函数调用。匿名函数可以定义在任何地方,并且可以直接调用。
go
//f参数是一种函数类型
func functionArg1(f func(a,b int) int,b int) int{
a := 2 * b
return f(a,b)
}
//foo是一种函数类型
type foo func(a,b int) int
//参数类型看上去简洁多了
func functionArg2(f foo,b int) int{
a := 2 * b
return f(a,b)
}
type User struct{
Name string
bye foo //bye的类型是foo,而foo代表一种函数类型
hello func(name string) string //使用匿名函数来声明struct字段的类型
}
ch := make(chan func(string) string,10) //用匿名函数
ch <- func(name string) string{
return "hello " + name
}
递归函数
简单来说,递归就是函数自己调用自己,有 2 种思想方式:一种是直接在自己函数中调用自己,一种是间接在自己函数中调用的其它函数中调用了自己.
- 递归函数需要有边界条件、递归前进段、递归返回段
- 递归一定要有边界条件
- 当边界条件不满足时,递归前进
- 当边界条件满足时,递归返回
闭包
- 闭包(Closure)是引用了自由变量的函数
- 自由变量将和函数一同存在,即使已经离开了创造它的环境
- 闭包复制的是原对象的指针(浅拷贝)
go
func sub() func() {
i := 10
fmt.Printf("%p\n", &i) //0xc00000a0d8
b := func() { //闭包
fmt.Printf("i address %p\n", &i)
i--
fmt.Println(i)
}
return b
}
func main() {
f := sub()
f() //f就是b,i没有被回收,i address 0xc00000a0d8,9
f() //i address 0xc00000a0d8,8
fmt.Println()
}
延迟调用 defer
- defer 用于注册一个延迟调用(在函数返回之前调用)
- defer 典型的应用场景是释放资源,比如关闭文件句柄,释放数据库连接等
- 如果同一个函数里有多个 defer,则后注册的先执行
- defer 后可以跟一个 func,func 内部如果发生 panic,会把 panic 暂时搁置,当把其它 defer 执行完之后再执行这个 panic
- defer 后不是跟 func,而直接跟一条执行语句,则相关变量在注册 defer 时被拷贝或计算
- 遇到 os.Exit() 直接结束,不执行 defer 语句
go
func basic(){
fmt.Println("A")
defer fmt.Println(1) fmt.Println("B")
//如果同一个函数里有多个defer,则后注册的先执行
defer fmt.Println(2)
fmt.Println("C")
}
func main(){
basic() //输出顺序:A C 2 1 B
}
- 函数调用、注册、注册的计算、defer 注册时必须把实参固定下来
go
// 运行结果: start end 3 2 1
func main(){
count := 1
fmt.Println("start")
defer fmt.Println(count)
count++
defer fmt.Println(count)
count++
defer fmt.Println(count)
fmt.Println("end")
}
go
// 运行结果: start end 3 2 3
func main(){
count := 1
fmt.Println("start")
// 因为这是一个无参函数,没有实参,所以里面的 count 不需要固定下来
defer func(){
fmt.Println(count)
}()
count++
defer fmt.Println(count)
count++
defer fmt.Println(count)
fmt.Println("end")
}
defer func
go
func deferExeTime() (i int){
i = 9
//defer后可以跟一个func
defer func(){
fmt.Printf("i=%d\n",i) //打印5,而非9
}()
defer fmt.Printf("i=%d\n",i) //变量在注册defer时被拷贝或计算
return 5
}
- 函数定义: 这个函数 deferExeTime 定义了一个返回类型为 int 的命名返回值 i。函数开始时将 i 赋值为 9。
- 第一个 defer 语句: 这里使用了一个匿名函数,它在 deferExeTime 函数返回时执行。由于 defer 的特性,它将在函数返回之前的最后执行,而且此时使用的 i 值是函数作用域内的最终值,也就是在 return 语句之后的值。
- 第二个 defer 语句: 这个 defer 语句将直接打印 i 的值。不过,需要注意的是,这里的 i 的值是在 defer 语句被执行时确定的,即为 9。这是因为在 Go 语言中,defer 语句中的参数在声明时就会被计算和保存。
- 返回语句: 函数返回时,命名返回值 i 被设置为 5。这个值将在函数完全返回到调用者之前由第一个 defer 语句打印。
综上所述,当调用 deferExeTime 函数时,以下事件将发生:
- i 被设置为 9。
- 第二个 defer 语句登记,将在函数结束时打印 i=9。
- 第一个 defer 语句登记,将在函数结束时打印 i 的当前值,这将是返回语句之后的值。
- i 被设置为 5 并准备返回。
- 第一个 defer 语句执行,打印 i=5。
- 第二个 defer 语句执行,打印 i=9。
- 函数返回 i 的值 5。
- 因此,最终的输出将是:i=5;i=9
defer panic
go
defer fmt.Println("111") //这个最后输出了
n := 0
defer func(){
fmt.Println(2/n) //发生panic事件,func 内部如果发生 panic,会把panic暂时搁置,当把其它defer执行完之后再执行这个panic
defer fmt.Println("222") //这个defer不执行
}()
defer fmt.Println("333") //这个先输出
异常处理
go
//go语言没有try catch,它提倡返回error
func divide(a,b int) (int,error){
if b==0{
return -1,errors.New("divide by zero")
}
return a / b,nil
}
func main(){
//函数调用方判断error是否为nil
if _,err := divide(3,0);err != nil{
fmt.Println(err.Error()) //divide by zero
}
}
自定义 error
go
//自定义error
type PathError struct{
path string
op string
createTime string
message string
}
//error接口要求实现Error() string方法
func (err PathError) Error() string{
return err.createTime + ":" + err.op + " " + err.path + " " + err.message
}
panic
- 何时会发生 panic
- 运行时错误会导致 panic,比如数组越界、除 0
- 程序主动调用 panic(error)
- panic 会执行什么:
- 逆序执行当前 goroutine 的 defer 链(recover 从这里介入)
- 打印错误信息和调用堆栈
- 调用 exit(2)结束整个进程
recover
- recover 会阻断 panic 的执行
go
func soo(a,b int){
defer func(){
//recover必须在defer中才能生效
if err := recover();err != nil{
fmt.Printf("soo函数中发生了panic:%s\n",err)
}
}()
panic(errors.New("my error"))
}
系统函数
fmt
- Scan: 控制台输入
go
func main{
var (
name string
age int
)
n, err := fmt.Scan(&name, &age) // 必须传入指针才能获取输入的值,返回值n为输入的个数,err为错误信息
if err != nil {
panic(err)
} else {
fmt.Println(n, name, age)
}
}