当前位置:   article > 正文

Go (二) 函数部分1 -- 函数定义,传参,返回值,作用域,函数类型,defer语句,匿名函数和闭包,panic_go func () 函数名称()()

go func () 函数名称()()

一、函数

1.1、函数的定义

Go语言中定义函数使用func关键字,具体格式如下:

参数返回值时:
func 函数名(参数 type) (返回值type){
    函数体代码

    return
}

参数无返回值时:

func 函数名(参数 type){
    函数体代码

}

无参数无返回值时:

func 函数名(){
    函数体代码

}

  • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
  • 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
  • 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
  • 函数体:实现指定功能的代码块。

1.2、函数的使用(汇总)

  1. // 定义一个求和函数(有参数),有return返回值时(需要给返回值定义一个类型)
  2. func s1(x int, y int) int {
  3. return x + y
  4. }
  5. // 定义一个函数(无参数),有return返回值时(需要给返回值定义一个类型)
  6. func s4() string {
  7. return "sudada"
  8. }
  9. // 定义一个求和函数(有参数),没有return返回值时
  10. func s2(x int, y int) {
  11. fmt.Println(x + y)
  12. }
  13. // 定义一个函数(无参数),没有return返回值时
  14. func s3() {
  15. fmt.Println("szq")
  16. }
  17. // return返回值 可以命名(命名后可以直接使用,因为已经做了声明),也可以不命名
  18. // return返回值"szq"在已经声明好的情况下,return后可以什么都不写,默认返回的就是"szq"
  19. func s5(x int, y int) (szq int) {
  20. szq = x + y
  21. return // 这里就可以不写szq,写上也没关系
  22. }
  23. // 多个返回值
  24. func s6() (int, string) {
  25. return 1, "2"
  26. }
  27. // 参数类型的简写(2个参数连续且类型一致时,可以这么写)
  28. func s7(x, y int) int {
  29. return x + y
  30. }
  31. // 可变长参数(...)必须放在函数参数的最后, y可以不传参,可以传一个参数,也可以传多个参数(拿到的就是一个切片类型的值)
  32. func s8(x string, y ...int) {
  33. fmt.Println(x)
  34. fmt.Println(y) // [1 2 3 4] 拿到的是一个int类型的切片
  35. }
  36. // go语音中函数没有默认参数的概念
  37. func main() {
  38. fmt.Println(s1(1, 2))
  39. s2(2, 3)
  40. s3()
  41. fmt.Println(s4())
  42. fmt.Println(s5(2, 2))
  43. fmt.Println(s6())
  44. s8("szq")
  45. }

1.3、函数传参

1.3.1、形参类型可以简写

函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:

  1. // 参数类型的简写(2个参数连续且类型一致时,可以这么写)
  2. func s7(x, y int) int {
  3. return x + y
  4. }

上面的代码中,s7函数有两个参数,这两个参数的类型均为int,因此可以省略x的类型,因为y后面有类型说明,x参数也是该类型。

1.3.2、可变长参数

可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加...来标识
注意:可变参数通常要作为函数的最后一个参数。

  1. // 可变长参数(...)必须放在函数参数的最后, y可以不传参,可以传一个参数,也可以传多个参数(拿到的就是一个切片类型的值)
  2. func s8(x string, y ...int) {
  3. fmt.Println(x)
  4. fmt.Println(y) // [1 2 3 4] 拿到的是一个int类型的切片
  5. }

可变长参数使用:

  1. func intSum2(x ...int) int {
  2. fmt.Println(x) // x是一个切片
  3. sum := 0
  4. // for range循环时,key是索引,value是值
  5. for _, v := range x {
  6. sum = sum + v
  7. }
  8. return sum
  9. }
  10. func main() {
  11. ret1 := intSum2()
  12. ret2 := intSum2(10)
  13. ret3 := intSum2(10, 20)
  14. ret4 := intSum2(10, 20, 30)
  15. fmt.Println(ret1, ret2, ret3, ret4) //0 10 30 60
  16. }

固定参数搭配可变参数使用时,可变参数放在固定参数后面,示例代码如下:

  1. func intSum3(x int, y ...int) int {
  2. fmt.Println(x, y)
  3. sum := x
  4. for _, v := range y {
  5. sum = sum + v
  6. }
  7. return sum
  8. }
  9. func main() {
  10. ret5 := intSum3(100)
  11. ret6 := intSum3(100, 10)
  12. ret7 := intSum3(100, 10, 20)
  13. ret8 := intSum3(100, 10, 20, 30)
  14. fmt.Println(ret5, ret6, ret7, ret8) //100 110 130 160
  15. }

本质上,函数的可变参数是通过切片来实现的。

1.4、函数 返回值

Go语言中通过return关键字向外输出返回值。

1.4.1、多返回值
Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。

  1. func calc(x, y int) (int, int) {
  2. sum := x + y
  3. sub := x - y
  4. return sum, sub
  5. }
  6. func main() {
  7. a, b := calc(1, 2)
  8. fmt.Println(a)
  9. fmt.Println(b)
  10. }

1.4.1、多返回值命名
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。

  1. // return返回值 可以命名(命名后可以直接使用,因为已经做了声明),也可以不命名
  2. // return返回值"szq"在已经声明好的情况下,return后可以什么都不写,默认返回的就是"szq"
  3. func s5(x int, y int) (szq int) {
  4. szq = x + y
  5. return // 这里就可以不写szq,写上也没关系
  6. }
  7. // 例子2
  8. func calc(x, y int) (sum, sub int) {
  9. sum = x + y
  10. sub = x - y
  11. return
  12. }
  13. func main() {
  14. a, b := calc(1, 2)
  15. fmt.Println(a)
  16. fmt.Println(b)
  17. }

1.5、函数的递归调用(函数体内调用了函数本身)

函数递归需要遵守的重要原则:

1.执行一个函数时,就创建一个新的受保护的独立空间。

2.函数的局部变量是独立的,不会相互影响。

3.递归必须向退出递归的条件逼近(例子里面的"x--"),否则就是死循环。

4.当一个函数执行完毕,或者遇到return,就会返回,遵循谁调用,就将返回结果给谁。同时函数执行完毕或被返回时,该函数本身也会被回收。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func test(x int) {
  6. if x > 2 {
  7. // 递归必须向退出递归的条件逼近
  8. x--
  9. test(x)
  10. }
  11. fmt.Println(x)
  12. }
  13. func test1(x int) {
  14. if x > 2 {
  15. // 递归必须向退出递归的条件逼近
  16. x--
  17. test1(x)
  18. } else {
  19. fmt.Println(x)
  20. }
  21. }
  22. func main() {
  23. test(4) // 返回值:2,2,3
  24. test1(4) // 返回值:2
  25. }

1.6、init函数

init函数会在main函数执行之前调用,通常在init函数中完成初始化的工作

  1. package main
  2. import "fmt"
  3. func init() {
  4. fmt.Println("init")
  5. }
  6. func main() {
  7. fmt.Println("main")
  8. }
  9. // 返回值: init main

细节:

1、一个文件同时包含全局变量定义,init函数main函数,则执行的顺序是:全局变量定义 -> init函数 -> main函数

  1. package main
  2. import "fmt"
  3. var age = test()
  4. func test() int {
  5. fmt.Println("test")
  6. return 90
  7. }
  8. func init() {
  9. fmt.Println("init")
  10. }
  11. func main() {
  12. fmt.Println("main")
  13. }
  14. // 返回值:test init main

二、包

包的基本语法:package "包名"

引入包:import "包的路径"

使用包内的函数:"包名".xxx

2.1、包的简单使用例子

  1. // 外部的包(utils.go)
  2. package utils
  3. // 这个函数是给外部使用(该函数可以导出),所以函数首字母大写
  4. func Calc(x, y int) (sum , sub int) {
  5. sum = x + y
  6. sub = x - y
  7. return
  8. }
  9. // 引用包(main.go)
  10. package main
  11. import (
  12. "fmt"
  13. "Study/go006/utils"
  14. // abc "Study/go006/utils" 还可以写成别名的方式,使用的使用就是abc.xxx
  15. )
  16. func main() {
  17. a, b := utils.Calc(20, 10)
  18. fmt.Println(a, b) // 返回值:30,10
  19. }

2.2、包的注意事项

1.文件的包名(package utils)通常和文件(utils.go)所在的文件夹名(utils)一致,一般为小写字母。

2.import "PATH/包名(文件夹名)"

2.1.在import包时,路径从$GOPATH的src目录下开始(不包含src),编译器会自动从src目录下开始引用。

3.使用包的数据utils.xxx时,这里的utils就是"包名(package utils)"如果包名改为"(package abc)",那么使用时就要写成abc.xxx

4.包内(可以导出的)函数、变量,首字母必须大写

5.如果给包(utils)起了别名为(abc),那么在使用包时,使用别名即可abc.xxx

6.同一个包下,不能有重复函数名,全局变量名

7.如果要编译一个可执行的文件,就需要将这个包声明为main (main包只能有一个),即package main,如果是一个库文件,则包名可以自定义。(编译时需要编译main包所在的文件夹)

三、函数作用域

3.1、全局变量

全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效。 在函数中可以访问到全局变量。

函数中查找变量的顺序:
   1.先在函数的内部查找
   2.找不到就往函数的外面查找,一直找到全局,如果找不到就报错。

  1. var szq = 123
  2. func s0() {
  3. fmt.Println(szq)
  4. }
  5. func main() {
  6. s0() // 123
  7. }

3.2、局部变量

1.局部变量又分为两种: 函数内定义的变量无法在该函数外使用

  1. func testLocalVar() {
  2. //定义一个函数局部变量x,仅在该函数内生效
  3. var x int64 = 100
  4. fmt.Printf("x=%d\n", x)
  5. }
  6. func main() {
  7. testLocalVar()
  8. fmt.Println(x) // 此时无法使用变量x
  9. }

2.如果局部变量和全局变量重名,优先访问局部变量

  1. //定义全局变量num
  2. var num int64 = 10
  3. func testNum() {
  4. num := 100
  5. fmt.Printf("num=%d\n", num) // 函数中优先使用局部变量
  6. }
  7. func main() {
  8. testNum() // num=100
  9. }

3.3、语句块定义的变量

1.通常我们会在if条件判断、for循环、switch语句上使用这种定义变量的方式。

  1. func testLocalVar2(x, y int) {
  2. fmt.Println(x, y) //函数的参数也是只在本函数中生效
  3. if x > 0 {
  4. z := 100 //变量z只在if语句块生效
  5. fmt.Println(z)
  6. }
  7. fmt.Println(z) //此处无法使用变量z
  8. }
  9. func main() {
  10. testLocalVar2(1,2)
  11. }

2.还有for循环语句中定义的变量,也是只在for语句块中生效

  1. func testLocalVar3() {
  2. for i := 0; i < 10; i++ {
  3. fmt.Println(i) //变量i只在当前for语句块中生效
  4. }
  5. fmt.Println(i) //此处无法使用变量i
  6. }
  7. func main() {
  8. testLocalVar3()
  9. }

四、函数类型与变量

4.1、定义函数类型

4.1.1.什么是函数类型,举个例子:

  1. // 函数作为参数赋值给变量时,有哪些类型
  2. func s1(){
  3. fmt.Println("szq")
  4. }
  5. func s2() int{
  6. return 18
  7. }
  8. func s3(x int) int{
  9. return x+1
  10. }
  11. func main() {
  12. a := s1
  13. // 打印变量a的类型
  14. fmt.Printf("%T\n",a) //func():这是一个普通的函数类型
  15. a() // 此时a就等于函数s1,加()即可执行该函数,返回值"szq"
  16. b := s2
  17. // 打印变量b的类型
  18. fmt.Printf("%T\n",b) //func() int :这是一个函数类型(包括函数的返回值类型)
  19. fmt.Println(b()) // 此时b就等于函数s2,加()即可执行该函数,返回值18
  20. c := s3
  21. // 打印变量c的类型
  22. fmt.Printf("%T\n",c) //func(int) int :这是一个函数类型(包括函数的参数类型和函数的返回值类型)
  23. fmt.Println(c(1)) // 此时c就等于函数s3,加()即可执行该函数,返回值2
  24. }

4.1.2.函数作为一个参数

  1. // 一、函数作为参数时,无形参
  2. // 定义函数s1
  3. func s1() int {
  4. return 18
  5. }
  6. // s1函数作为参数传递给s2函数
  7. // x func() int 说明:
  8. // "x"(形参的名称)
  9. // "func() int"形参x的类型是一个"函数类型",该函数有一个返回值,返回值的类型是int
  10. func s2(x func() int){
  11. res := x()
  12. fmt.Println(res)
  13. }
  14. func main() {
  15. re1 := s1 // 将函数赋值给变量re1
  16. fmt.Printf("%T\n",re1) // func() int
  17. s2(s1) // 函数s1作为参数传递给函数s2(s1函数的类型必须满足s2的接收参数类型,即:func() int)
  18. s2(re1) // 变量re1作为参数传递给函数s2(变量re1的类型必须满足s2的接收参数类型,即:func() int)
  19. }
  20. // 二、函数作为参数时,有形参
  21. func s3(x int) int{
  22. return x+1
  23. }
  24. // funvar func(int) int 说明:
  25. // "funvar"(形参的名称)
  26. // "func(int) int"形参funvar的类型是一个"函数类型",该函数会接收一个参数,参数类型是int,并且有一个返回值,返回值的类型是int
  27. func szq(funvar func(int) int, num int) int {
  28. res := funvar(20) // 这里funvar(20)就等于s3(20),拿到的是函数s3的执行结果
  29. return res + num
  30. }
  31. func main() {
  32. fmt.Println(szq(s3,10)) // 返回值:31
  33. }

4.1.3.函数作为一个返回值

  1. // 定义函数s1
  2. func s1() int {
  3. return 18
  4. }
  5. func ff(a int,b int)int{
  6. return a+b
  7. }
  8. // 函数作为一个返回值返回
  9. // x func() int :形参x的类型是一个"函数类型",该函数有一个返回值,返回值的类型是int
  10. // y func(int,int)int :"函数s3的返回值类型是一个"函数类型",该函数会接收2个参数,参数类型是int,并且有一个返回值,返回值的类型是int
  11. func s3(x func() int)(y func(int,int) int){
  12. fmt.Println(x())
  13. res := func(a int,b int)int{
  14. return a+b
  15. }
  16. //res := ff // 这里也可以换一种写法,ff函数的格式需要满足y即可
  17. return res
  18. }
  19. func main() {
  20. szq := s3(s1) // 把函数s1做为参数传入到函数s3中,执行函数s3的代码后会打印18,并且拿到一个返回值res,此时的res是一个函数类型。
  21. fmt.Println(szq(1,2)) // 拿到的返回值res类型为"函数类型:func(int,int)int",那么就可以正常的传值,查看执行结果
  22. }
  23. //返回结果:
  24. //18
  25. //3

4.2、函数类型的变量 (变量的类型为函数类型,然后为该变量赋值)

了解以上函数类型之后,我们可以使用type关键字来定义一个函数类型的变量,具体格式如下:
type myFun func(int, int) int
上面语句定义了一个变量myFun,是一个函数类型这种函数接收两个int类型的参数并且返回一个int类型的返回值。

  1. package main
  2. import "fmt"
  3. // 定义一个变量"myFunc",类型为函数类型(函数需要接收2个int类型的参数,并有一个返回值也是int类型)
  4. type myFunc func(int, int) int
  5. // 定义一个全局变量c,c的类型myFunc
  6. var c myFunc
  7. // 定义一个普通函数,类型为:add(x, y int) int
  8. func add(x, y int) int {
  9. return x + y
  10. }
  11. // 形参 n func(int, int) int 可以写成 n myFunc (变量myFunc的类型就是函数类型)
  12. func szq(a,b int, n myFunc) int {
  13. res := n(a,b)
  14. return res
  15. }
  16. func main() {
  17. c = add // 给变量c做赋值操作(变量c = add函数)
  18. fmt.Println(add(1,2)) // 返回值为:3
  19. fmt.Printf("%T\n",c) // main.myFunc
  20. fmt.Println(c(1,2)) // 像调用add一样调用c,返回值为:3
  21. fmt.Println(szq(1,2, add)) // 返回值为:3
  22. }

五、defer语句 (延时机制)

5.1、Go语言中的defer语句会将其后面跟随的语句进行延迟处理

defer归属的函数即将返回时(简单理解为函数内的逻辑执行完毕后,在执行defer语句),将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行(先入后出)

简单例子:

  1. func main() {
  2. fmt.Println("start")
  3. defer fmt.Println(1)
  4. defer fmt.Println(2)
  5. defer fmt.Println(3)
  6. fmt.Println("end")
  7. }
  8. //输出结果:
  9. //start
  10. //end
  11. //3
  12. //2
  13. //1

由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。

5.2、defer的执行时机(先给返回值赋值,然后执行defer,最后执行return)

在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值RET(return)指令两步。defer语句执行的时机就在返回值赋值操作后,RET(return)指令执行前具体如下图所示:

defer执行时机

简单例子:

  1. package main
  2. import "fmt"
  3. func sum(a,b int) int {
  4. defer fmt.Println("3")
  5. defer fmt.Println("2")
  6. defer fmt.Println("1")
  7. res := a+b
  8. fmt.Println("sum", res)
  9. return res
  10. }
  11. func main() {
  12. fmt.Println("main")
  13. res := sum(2,3)
  14. fmt.Println("mian-res", res)
  15. }
  16. // 返回值
  17. // main
  18. // sum 5
  19. // 1
  20. // 2
  21. // 3
  22. // mian-res 5

5.3、defer经典案例

  1. // 第一步:返回值赋值
  2. //defer
  3. //第二步:真的的return,返回一个值
  4. func f1() int {
  5. x := 5
  6. defer func() {
  7. x++ // 修改的是x,不是返回值
  8. }()
  9. return x
  10. }
  11. func f2() (x int) {
  12. defer func() {
  13. x++
  14. }()
  15. return 5 // 返回值为x
  16. }
  17. func f3() (y int) {
  18. x := 5
  19. defer func() {
  20. x++ // 修改的是x
  21. }()
  22. return x // 返回值为y,y的值=x=5
  23. }
  24. func f4() (x int) {
  25. defer func(x int) {
  26. x++ // 改变的是函数中的副本
  27. }(x)
  28. return 5 // 返回值=x=5
  29. }
  30. func main() {
  31. fmt.Println(f1()) //5
  32. fmt.Println(f2()) //6
  33. fmt.Println(f3()) //5
  34. fmt.Println(f4()) //5
  35. }

5.4、defer面试题

  1. func calc(index string, a, b int) int {
  2. ret := a + b
  3. fmt.Println(index, a, b, ret)
  4. return ret
  5. }
  6. func main() {
  7. // var x int = 1
  8. x := 1
  9. // var y int = 2
  10. y := 2
  11. // 函数内套用函数调用时,代码走到这一层就会先执行函数内的函数,会把对应变量的值传过去。 即:defer calc("AA", 1, calc("A", 1, 2)) == defer calc("AA", 1, 3),详见下面的分析结果:
  12. defer calc("AA", x, calc("A", x, y))
  13. // 这里的x因为在前面已经做了定义,所以可以直接用"="号赋值
  14. x = 10
  15. // 函数内套用函数调用时,代码走到这一层就会先执行函数内的函数,会把对应变量的值传过去即:defer calc("BB", 10, calc("B", 10, 2)) == defer calc("BB", 10, 12),详见下面的分析结果:
  16. defer calc("BB", x, calc("B", x, y))
  17. // 这里的y因为在前面已经做了定义,所以可以直接用"="号赋值
  18. y = 20
  19. }
  20. // 分析以上代码的执行过程:
  21. // 1)x := 1
  22. // 2)y := 2
  23. // 3)defer calc("AA", x, calc("A", x, y)) == defer calc("AA", 1, calc("A", 1, 2)) == defer calc("AA", 1, 3)
  24. // 4)x = 10
  25. // 5)defer calc("BB", x, calc("B", x, y)) == defer calc("BB", 10, calc("B", 10, 2)) == defer calc("BB", 10, 12)
  26. // 6)y = 20
  27. // 7)defer calc("BB", 10, 12) == BB 10 12 22
  28. // 8)defer calc("AA", 1, 3) == AA 1 3 4
  29. //输出结果:
  30. //A 1 2 3 先执行函数内的函数
  31. //B 10 2 12 先执行函数内的函数
  32. //BB 10 12 22 在按照defer的逻辑,最后定义的最先执行
  33. //AA 1 3 4 按照defer的逻辑,最先定义的最后执行

六、匿名函数和闭包

6.1、匿名函数 (匿名函数多用于实现回调函数和闭包,匿名函数一般都用在函数内部)

函数当然还可以作为返回值,但是在Go语言中函数内部不能再像之前那样定义函数了,只能定义匿名函数。匿名函数就是没有函数名的函数,匿名函数的定义格式如下:
func (参数) (返回值){
    函数体
}

1.把匿名函数赋值给一个变量

  1. // 匿名函数使用1:将匿名函数赋值给一个"全局"变量,该匿名函数可以任意地方使用
  2. var f1 = func(x,y int){
  3. fmt.Println(x+y)
  4. }
  5. func main() {
  6. f1(1,2) // 调用变量f1就是调用匿名函数,返回值为:3
  7. // 匿名函数使用2:在函数内部将匿名函数赋值给一个变量,该匿名函数只能在函数内部使用
  8. f2 := func(x,y int){
  9. fmt.Println(x+y)
  10. }
  11. f2(10,20) // 调用匿名函数,返回值为:3
  12. // 如果只是调用一次的函数,可以简写成"立即执行函数"
  13. func(x,y int){
  14. fmt.Println(x+y)
  15. }(2,3) // 调用"立即执行函数",返回值为:5
  16. }

2.匿名函数作为立即执行函数(只是一次调用的函数)

  1. func main() {
  2. // 如果只是调用一次的函数,可以简写成"立即执行函数"
  3. func(x,y int){
  4. fmt.Println(x+y)
  5. }(2,3) // 调用"立即执行函数",返回值为:5
  6. }

6.2、闭包 (一个函数既"引用了函数内的变量",又"引用了函数外的变量")

闭包的底层原理:
  1.函数可以作为返回值,
  2.函数内部查找顺序:先在函数内找,然后在到函数外部找。

闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境。 首先我们来看一个例子:

  1. // 例子1:
  2. // 闭包的实际应用,要求:s1(s2) 把s2作为参数传入到s1
  3. func s1(f func()) {
  4. fmt.Println("s1")
  5. f()
  6. }
  7. func s2(x, y int) {
  8. fmt.Println("s2")
  9. fmt.Println(x + y)
  10. }
  11. //s3函数要接收s2函数的传参,并且返回值的类型要符合s1函数参数类型
  12. //f func(int, int), x, y int 这些都是给s2函数使用的
  13. //func() 这个是给s1函数使用的
  14. func s3(f func(int, int), x, y int) func() {
  15. res := func() {
  16. f(x, y)
  17. }
  18. return res // 这里的返回值res是一个func()类型,符合s1函数的接收类型
  19. }
  20. func main() {
  21. ret := s3(s2, 100, 300) // 这里ret也就等于s3函数内的res
  22. s1(ret) // s1函数传入ret,本质上执行的是s2函数的代码。
  23. }
  24. //输出结果
  25. //s1
  26. //s2
  27. //400
  28. // 例子2:
  29. import "fmt"
  30. // 函数的返回值是一个匿名函数,但这个匿名函数引用到函数外的变量n,因此这个匿名函数就和变量n形成一个整体,构成闭包。
  31. // 当反复调用f函数时,因为n只初始化一次,因此每调用一次就进行累计。
  32. func addUpper() func(int) int {
  33. var n int = 10
  34. return func(x int) int {
  35. n = n +x
  36. return n
  37. }
  38. }
  39. func main() {
  40. f1 := addUpper()
  41. fmt.Println(f1(1)) // 返回值:11
  42. fmt.Println(f1(2)) // 返回值:13
  43. fmt.Println(f1(3)) // 返回值:16
  44. }

例子2:在函数内修改函数的参数值

  1. func calc(base int) (func(int) int, func(int) int) {
  2. add := func(i int) int {
  3. base += i
  4. return base
  5. }
  6. sub := func(i int) int {
  7. base -= i
  8. return base
  9. }
  10. return add, sub
  11. }
  12. func main() {
  13. f1, f2 := calc(10)
  14. fmt.Println(f1(1), f2(2)) //11 9,这里每次改的都是calc函数内的base,也就是说随着每次的改修,base的值会发生改变。
  15. fmt.Println(f1(3), f2(4)) //12 8
  16. fmt.Println(f1(5), f2(6)) //13 7
  17. }

七、内置函数

7.1、内置函数介绍

内置函数介绍
close主要用来关闭channel
len用来求长度,比如string、array、slice、map、channel
new用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
make用来分配内存,主要用来分配引用类型,比如channel、map、slice
append用来追加元素到数组、slice中
panic和recover用来做错误处理

7.2、panic 主动抛出异常

  1. func funcA() {
  2. fmt.Println("func A")
  3. }
  4. func funcB() {
  5. panic("出现了严重的错误!") // 主动报错
  6. }
  7. func funcC() {
  8. fmt.Println("func C")
  9. }
  10. func main() {
  11. funcA()
  12. funcB()
  13. funcC()
  14. }
  15. // 输出结果:
  16. // func A // 报错之前执行的代码
  17. // panic: 出现了严重的错误! // 报错的输出
  18. // goroutine 1 [running]:
  19. // main.funcB(...) // 报错的代码位置点
  20. // /Users/suzhaoqiang/Desktop/Go/Goland_Document/学习过程/2021-8-16/05.go:10
  21. // main.main() // 报错代码的执行点
  22. // /Users/suzhaoqiang/Desktop/Go/Goland_Document/学习过程/2021-8-16/05.go:18 +0x96

7.3、panic + recover + defer 接收错误信息后,抛出异常提示,同时代码继续执行

Go中可以抛出一个panic的异常,然后再defer中通过recover捕获这个异常,然后正常执行代码。

注意:
recover()必须搭配defer使用。
defer一定要在可能引发panic的语句之前定义。

例子1:使用panic抛出异常,然后使用recover + defer捕捉异常

  1. func funcA() {
  2. fmt.Println("func A")
  3. }
  4. func funcB() {
  5. defer func() {
  6. err := recover() // recover()拿到的就是panic的输出。使用recover()后程序不会崩溃退出,继续往下执行。
  7. fmt.Println(err)
  8. }()
  9. panic("出现了严重的错误!") // defer要在panic之前定义,否则panic程序崩溃,没法执行defer语句了。
  10. }
  11. func funcC() {
  12. fmt.Println("func C")
  13. }
  14. func main() {
  15. funcA()
  16. funcB()
  17. funcC()
  18. }
  19. //输出结果:
  20. //func A
  21. //出现了严重的错误!
  22. //func C

例子2:使用recover + defer 捕捉异常

  1. import "fmt"
  2. // 使用defer + recover来捕获和处理异常
  3. func test() {
  4. defer func() {
  5. err := recover() // "recover()"是一个内置函数,可以捕捉到异常
  6. if err != nil { // 如果错误不为空,说明捕捉到了异常
  7. fmt.Println(err) // 输出异常信息
  8. }
  9. }()
  10. a := 10
  11. b := 0
  12. res := a / b
  13. fmt.Println(res)
  14. }
  15. func main() {
  16. test() // 抛出异常信息:runtime error: integer divide by zero
  17. fmt.Println("main代码逻辑")
  18. }

例子3:使用自定义错误:errors.New("读取文件失败")

  1. import (
  2. "errors"
  3. "fmt"
  4. )
  5. func test(name string) (err error) {
  6. if name == "config.ini" {
  7. return nil
  8. } else {
  9. // 返回一个自定义错误
  10. return errors.New("读取文件失败")
  11. }
  12. }
  13. func test02() {
  14. err := test("config.123")
  15. if err != nil {
  16. // 读取文件发生错误时,输出自定义错误,并终止程序
  17. panic(any(err))
  18. // Golang 1.18 版本开始引入any类型可以替代空接口 interface{} ,直接写成panic(err)会有错误提示,但是执行不会报错
  19. }
  20. fmt.Println("test02...")
  21. }
  22. func main() {
  23. test02()
  24. fmt.Println("main...")
  25. }
  26. // 返回值:panic: 读取文件失败

7.4、内置函数(rune,strconv,byte)例子

  1. package main
  2. import (
  3. "fmt"
  4. "strconv"
  5. )
  6. func main() {
  7. str2 := "hello上海"
  8. // 字符串便利时,处理有中文的场景,使用:"[]rune(xxx)"
  9. str3 := []rune(str2)
  10. for n := 0; n < len(str3); n++ {
  11. fmt.Printf("%c\n",str3[n]) // 返回值:"hello上海"
  12. }
  13. // 字符串转为整数
  14. num, err := strconv.Atoi("123")
  15. // err == nil时,说明无报错
  16. if err != nil {
  17. fmt.Println("error: ",err)
  18. } else {
  19. fmt.Println("value: ",num)
  20. }
  21. // 整数转化为字符串
  22. str := strconv.Itoa(12345)
  23. fmt.Println("str value: ",str) // 返回值:str value: 12345
  24. // 字符串转[]byte
  25. var bytes = []byte("hello go")
  26. fmt.Printf("%v\n",bytes) // 返回值:[104 101 108 108 111 32 103 111]
  27. // byte转字符串
  28. str1 := string([]byte{97,98,99})
  29. fmt.Printf("%v\n",str1) // 返回值:abc
  30. }

7.5、内置函数 new(用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针)

  1. import "fmt"
  2. func main() {
  3. var num int = 100
  4. fmt.Printf("%T %v %v\n",num,num,&num)
  5. // 返回值:int 100 0xc00000a0e8
  6. num2 := new(int)
  7. *num2 = 100
  8. fmt.Printf("%T %v %v %v\n",num2,num2,&num2,*num2)
  9. // 返回值:*int 0xc00000a108 0xc00006a028 100
  10. }

7.6、内置函数 make(用来分配内存,主要用来分配引用类型,比如channel、map、slice)

八、常用模块

8.1、strings模块的常用方法

  1. import (
  2. "fmt"
  3. "strings"
  4. )
  5. func main() {
  6. // 查找指定字符串
  7. // 判断字符串"food"是否存在于"seafood"中,存在返回true不存在返回false
  8. b := strings.Contains("seafood","food")
  9. fmt.Printf("%v\n",b) // 返回值:true
  10. // 判断字符串"asd"是否存在于"seafood"中,存在返回true不存在返回false
  11. c := strings.Contains("seafood","asd")
  12. fmt.Printf("%v\n",c) // 返回值:false
  13. // 统计字符串的数量
  14. // 字符串"e"在"cehese"出现的次数
  15. num1 := strings.Count("cehese","e")
  16. fmt.Printf("%v\n",num1) // 返回值:false
  17. // 字符串替换,1指的是替换1次,为-1表示替换全部
  18. // 把"go"替换为"go语言",针对"go go hello"做替换
  19. str4 := strings.Replace("go go hello","go","go语言",-1)
  20. fmt.Println(str4) // 返回值:"go语言 go语言 hello"
  21. // 字符串指定分隔符,得到一个数组
  22. fmt.Println(strings.Split("hello,world",",")) // 返回值:[hello world]
  23. // 字母大小写转换
  24. fmt.Println(strings.ToLower("Go")) // 返回值:gp
  25. fmt.Println(strings.ToUpper("Go")) // 返回值:GO
  26. // 字符串去掉两边的空格
  27. fmt.Println(strings.TrimSpace(" is shanghai ")) // 返回值:"is shanghai"
  28. // 字符串去掉左右两边指定的字符串
  29. fmt.Println(strings.Trim("! hello !","!")) // 返回值: " hello "
  30. // 去掉字符串左边指定的字符
  31. fmt.Println(strings.TrimLeft("to shanghai","to")) // 返回值: " shanghai"
  32. // 去掉字符串右边指定的字符
  33. fmt.Println(strings.TrimRight("to shanghai gogo","gogo")) // 返回值:"to shanghai"
  34. // 字符串是否以指定的字符串开头
  35. fmt.Println(strings.HasPrefix("www.baidu.com","www")) // 返回值:true
  36. // 字符串是否以指定的字符串结尾
  37. fmt.Println(strings.HasSuffix("www.baidu.com","com")) // 返回值:true
  38. }

8.2、time模块

8.2.1、常用方法

  1. import (
  2. "fmt"
  3. "time"
  4. )
  5. func main() {
  6. now := time.Now()
  7. fmt.Printf("%v \n",now) // 返回值:2023-10-22 19:49:50.0430013 +0800 CST m=+0.001257301
  8. fmt.Println(now.Year()) // 返回值:年
  9. fmt.Println(now.Month()) // 返回值:月(英文月份)
  10. fmt.Println(int(now.Month())) // 返回值:月(数字)
  11. fmt.Println(now.Day()) // 返回值:日
  12. fmt.Println(now.Hour()) // 返回值:时
  13. fmt.Println(now.Minute()) // 返回值:分
  14. fmt.Println(now.Second()) // 返回值:秒
  15. // 格式化时间,方式1:
  16. dateStr := fmt.Sprintf("当前时间: %d/%d/%d %d:%d:%d",now.Year(),
  17. now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second(),
  18. )
  19. fmt.Printf("%v\n",dateStr) // 返回值:当前时间: 2023/10/22 20:2:38
  20. // 格式化时间,方式2:
  21. fmt.Println(now.Format("2006-01-02 15:04:05")) // 返回值:当前时间的"年-月-日 时:分:秒"
  22. fmt.Println(now.Format("2006-01-02")) // 返回值:当前时间的"年-月-日"
  23. fmt.Println(now.Format("15:04:05")) // 返回值:当前时间的"时:分:秒"
  24. fmt.Println(now.Format("2006")) // 返回值:当前时间的"年"
  25. fmt.Println(now.Format("01")) // 返回值:当前时间的"月"
  26. fmt.Println(now.Format("02")) // 返回值:当前时间的"日"
  27. fmt.Println(now.Format("15")) // 返回值:当前时间的"时"
  28. fmt.Println(now.Format("04")) // 返回值:当前时间的"分"
  29. fmt.Println(now.Format("05")) // 返回值:当前时间的"秒"
  30. }

8.2.2、时间常量

    time.Microsecond:1微妙
    time.Millisecond:1毫秒
    time.Second:1秒
    time.Minute:1分钟
    time.Hour:1小时

  1. import (
  2. "fmt"
  3. "time"
  4. )
  5. func main() {
  6. // 时间常量
  7. // time.Microsecond:1微妙
  8. // time.Millisecond:1毫秒
  9. // time.Second:1秒
  10. // time.Minute:1分钟
  11. // time.Hour:1小时
  12. // 等待1秒
  13. time.Sleep(time.Second)
  14. // 等待10秒
  15. time.Sleep(10 * time.Second)
  16. // 获取时间戳
  17. now := time.Now()
  18. fmt.Println(now.Unix()) // 返回值:1697989319
  19. }

8.2.3、获取函数的执行时间小例子

  1. import (
  2. "fmt"
  3. "strconv"
  4. "time"
  5. )
  6. func test() {
  7. str := ""
  8. for i:=0;i<=100000;i++{
  9. str += "hello" + strconv.Itoa(i)
  10. }
  11. }
  12. func main() {
  13. now_time := time.Now().Unix()
  14. test()
  15. end_time := time.Now().Unix()
  16. res := end_time-now_time
  17. fmt.Println(res) // 返回值:5
  18. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/624854
推荐阅读
相关标签
  

闽ICP备14008679号