李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
GO
正文
12.Golang函数
Leefs
2022-07-03 PM
706℃
0条
[TOC] ### 前言 ![12.Golang函数01.jpeg](https://lilinchao.com/usr/uploads/2022/07/1358395649.jpeg) ### 一、概述 **函数** 是基于功能或逻辑进行封装的可复用的代码结构。将一段功能复杂、很长的一段代码封装成多个代码片段(即函数),有助于提高代码可读性和可维护性。由于 Go 语言是编译型语言,所以函数编写的顺序是无关紧要的。 #### 特点 + 无需声明原型 + 支持不定变参 + 支持多返回值 + 支持命名返回参数 + 支持匿名函数和闭包 + 函数也是一种类型,一个函数可以赋值给变量 + 不支持函数嵌套 (nested) ,但可以嵌套匿名函数。 + 不支持重载 (overload) ,一个包不能有两个名字一样的函数。 + 不支持默认参数 (default parameter) ### 二、函数的声明 Go语言中声明函数使用`func`关键字,具体格式如下: ```go func 函数名(参数名 类型,参数名 类型)(返回值1类型,返回值2类型){ 函数体 return 返回值1,返回值2 } ``` **说明** + **函数名**:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名不能重名。 + **参数**:参数由参数变量和参数变量的类型组成,多个参数之间使用`,`分隔。 + **返回值**:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用`()`包裹,并用`,`分隔。 + **函数体**:实现指定功能的代码块。 > 示例 ```go //定义一个函数,求两数之和 //函数返回一个无名变量,返回值列表的括号省略 func sum(x int,y int) int{ return x + y } // 参数的类型一致,只在最后一个参数后添加该类型 func sub(x , y int) int { return x - y } //调用该函数打印出Hello GO //函数的参数和返回值都是可选的,下方函数既不需要参数也没有返回值 func hello(){ fmt.Println("Hello GO") } func main() { //调用函数 hello() s := sum(10,20) b := sub(20,10) fmt.Println(s) fmt.Println(b) } ``` **说明** - **形式参数列表**:函数的参数名以及参数类型,这些参数作为局部变量,其值由参数调用者提供,函数中的参数列表和返回值并非是必须的。 - **返回值列表**:函数返回值的变量名以及类型,如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的。 - 如果有连续若干个参数的类型一致,那么只需在最后一个参数后添加该类型。 + 定义了函数之后,可以通过`函数名()`的方式调用函数。 *注意:调用有返回值的函数时,可以不接收其返回值。* ### 三、可变参数 #### 3.1 多个类型一致的参数 可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加`...`来标识。 **示例** ```go func sum2and(x ...int) int{ fmt.Println(x) //x是一个切片 sum := 0 for _,v := range x{ sum = sum + v } return sum } func main() { ret1 := sum2and() ret2 := sum2and(10) ret3 := sum2and(10, 20) ret4 := sum2and(10, 20, 30) fmt.Println(ret1, ret2, ret3, ret4) //0 10 30 60 } ``` **运行结果** ``` [] [10] [10 20] [10 20 30] 0 10 30 60 ``` 固定参数搭配可变参数使用时,可变参数要放在固定参数的后面 **示例代码** ```go func sum3and(x int, y ...int) int { fmt.Println(x, y) //y是一个切片 sum := x for _, v := range y { sum = sum + v } return sum } func main() { ret5 := sum3and(100) ret6 := sum3and(100, 10) ret7 := sum3and(100, 10, 20) ret8 := sum3and(100, 10, 20, 30) fmt.Println(ret5, ret6, ret7, ret8) //100 110 130 160 } ``` **运行结果** ``` 100 [] 100 [10] 100 [10 20] 100 [10 20 30] 100 110 130 160 ``` 本质上,函数的可变参数是通过切片来实现的。 *注意:如果该函数下有其他类型的参数,这些其他参数必须放在参数列表的前面,切片必须放在最后。* #### 3.2 多个类型不一致的参数 如果传多个参数的类型都不一样,可以指定类型为 `...interface{}` ,然后再遍历。 ```go func printType(args ...interface{}) { for _, arg := range args { switch arg.(type) { case int: fmt.Println(arg, "type is int.") case string: fmt.Println(arg, "type is string.") case float64: fmt.Println(arg, "type is float64.") case bool: fmt.Println(arg, "type is boole.") default: fmt.Println(arg, "is an unknown type.") } } } func main() { printType(10, 3.14, "李林超博客",true) } ``` **运行结果** ``` 10 type is int. 2.16 type is float64. 李林超博客 type is string. true type is boole. ``` ### 四、返回值 #### 4.1 定义 函数可以有0或多个返回值,返回值需要指定数据类型,返回值通过 `return`关键字来指定。 1. `return`可以有参数,也可以没有参数,这些返回值可以有名称,也可以没有名称。go中的函数可以有多个返回值。 2. `return`关键字中指定了参数时,返回值可以不用名称。如果`return`省略参数,则返回值部分必须带名称。 3. 当返回值有名称时,必须使用括号包围,逗号分隔,即使只有一个返回值。 4. 但即使返回值命名了,`return`中也可以强制指定其它返回值的名称,也就是说`return`的优先级更高 5. 命名的返回值是预先声明好的,在函数内部可以直接使用,无需再次声明。命名返回值的名称不能和函数参数名称相同,否则报错提示变量重复定义 6. `return`中可以有表达式,但不能出现赋值表达式,这和其它语言可能有所不同。例如`return a+b`是正确的,但`return c=a+b`是错误的。 #### 4.2 实例 **(1)没有返回值** ```go func hello() { fmt.Printf("Hello GO") } ``` **(2)有一个返回值** ```go func sum(x int, y int) (ret int) { ret = x + y return ret } ``` **(3)多个返回值,且在return中指定返回的内容** ```go func person() (name string, age int) { name = "Leefs" age = 20 return name, age } ``` **(4)多个返回值,返回值名称没有被使用** ``` func person2and() (name string, age int) { name = "Leefs" age = 20 return // 等价于return name, age } ``` **(5)return覆盖命名返回值,返回值名称没有被使用** ``` func person3and() (name string, age int) { n := "Leefs" a := 20 return n, a } ``` Go中经常会使用其中一个返回值作为函数是否执行成功、是否有错误信息的判断条件。例如 `return value,exists`、`return value,ok`、`return value,err`等。 当函数有多个返回值时,如果其中某个或某几个返回值不想使用,可以通过下划线 `_`来丢弃这些返回值。 ### 五、参数传递 #### 5.1 值传递 ```go func changeA(a int) { a = 200 fmt.Printf("a1: %v\n", a) //a1: 200 } func main() { a := 100 changeA(a) fmt.Printf("a: %v\n", a) //a: 100 } ``` 从运行结果可以看到,调用函数`changeA`后,a的值并没有被改变,说明参数传递是拷贝了一个副本,也就是拷贝了一份新的内容进行运算。 #### 5.2 引用传递 **引用传递本质上也是值传递,只不过这份值是一个指针(地址)。** 所以我们在函数内对这份值的修改,其实不是改这个值,而是去修改这个值所指向的数据,从而会影响到函数外部的值的。 ```go func changeA(a *int) { *a = 200 fmt.Printf("a1: %v\n", *a) //a1: 200 } func main() { a := 100 changeA(&a) fmt.Printf("a: %v\n", a) //a: 200 } ``` 传指针使得多个函数能操作同一个对象。 传指针比较轻量级(8bytes),只是传内存地址,可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次 copy 上面就会花费相对较多的系统开销(内存和时间)。所以当需要传递大的结构体的时候,用指针是一个明智的选择。 > `map`、`slice`、`interface`、`channel`这些数据类型本身就是**指针** 类型的,所以就算是拷贝传值也是拷贝的指针,拷贝后的参数仍然指向底层数据结构,所以修改它们**可能** 会影响外部数据结构的值 ```go func changeSlice(a []int) { a[0] = 100 } func main() { a := []int{1, 2} changeSlice(a) fmt.Printf("a: %v\n", a) //a: [100 2] } ``` 从运行结果发现,调用函数后,slice内容被改变了。 ### 六、高阶函数 高阶函数分为函数作为参数和函数作为返回值两部分。 #### 6.1 函数作为参数 ```go func sayHello(name string) { fmt.Printf("Hello,%s", name) } func f1(name string, f func(string)) { f(name) } func main() { f1("Leefs", sayHello) } ``` **运行结果** ``` Hello,Leefs ``` #### 6.2 函数作为返回值 ```go func add(x, y int) int { return x + y } func sub(x, y int) int { return x - y } func cal(s string) func(int, int) int { switch s { case "+": return add case "-": return sub default: return nil } } func main() { add := cal("+") r := add(1, 2) fmt.Printf("r: %v\n", r) fmt.Println("-----------") sub := cal("-") r = sub(100, 50) fmt.Printf("r: %v\n", r) } ``` **运行结果** ``` r: 3 ----------- r: 50 ``` ### 七、匿名函数 Go语言函数不能嵌套,但是在函数内部可以定义匿名函数,实现一下简单功能调用。 匿名函数就是没有函数名的函数,匿名函数的定义格式如下: ```go func(参数)(返回值){ 函数体 } ``` 匿名函数因为没有函数名,所以没办法像普通函数那样调用,所以匿名函数需要保存到某个变量或者作为立即执行函数: ```go func main() { // 将匿名函数保存到变量 add := func(x, y int) { fmt.Println(x + y) } add(10, 20) // 通过变量调用匿名函数 //自执行函数:匿名函数定义完加()直接执行 func(x, y int) { fmt.Println(x + y) }(10, 20) } ``` 匿名函数多用于实现回调函数和闭包。 ### 八、闭包 Go语言中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量,因此,简单的说: > 函数 + 引用环境 = 闭包 同一个函数与不同引用环境组合,可以形成不同的实例,如下图所示。 ![12.Golang函数02.jpg](https://lilinchao.com/usr/uploads/2022/07/2385499325.jpg) 一个函数类型就像结构体一样,可以被实例化,函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有“记忆性”,函数是编译期静态的概念,而闭包是运行期动态的概念。 **示例** ```go // 返回一个函数 func add() func(int) int { var x int return func(y int) int { x += y return x } } func main() { var f = add() fmt.Println(f(10)) fmt.Println(f(20)) fmt.Println(f(30)) fmt.Println("-----------") f1 := add() fmt.Println(f1(40)) fmt.Println(f1(50)) } ``` **运行结果** ``` 10 30 60 ----------- 40 90 ``` 变量 `f`是一个函数并且它引用了其外部作用域中的 `x`变量,此时 `f`就是一个闭包。 在 `f`的生命周期内,**变量 `x`也一直有效。** **闭包进阶示例1:** ```go func add(x int) func(int) int { return func(y int) int { x += y return x } } func main() { var f = add(10) fmt.Println(f(10)) fmt.Println(f(20)) fmt.Println(f(30)) fmt.Println("----------") f1 := add(20) fmt.Println(f1(40)) fmt.Println(f1(50)) } ``` **运行结果** ``` 20 40 70 ---------- 60 110 ``` **闭包进阶示例2:** ```go func makeSuffixFunc(suffix string) func(string) string { return func(name string) string { if !strings.HasSuffix(name, suffix) { return name + suffix } return name } } func main() { jpgFunc := makeSuffixFunc(".jpg") txtFunc := makeSuffixFunc(".txt") fmt.Println(jpgFunc("test")) fmt.Println(txtFunc("test")) } ``` **运行结果** ``` test.jpg test.txt ``` **闭包进阶示例3:** ```go func calc(base int) (func(int) int, func(int) int) { add := func(i int) int { base += i return base } sub := func(i int) int { base -= i return base } return add, sub } func main() { f1, f2 := calc(10) fmt.Println(f1(1), f2(2)) fmt.Println(f1(3), f2(4)) fmt.Println(f1(5), f2(6)) } ``` **运行结果** ``` 11 9 12 8 13 7 ``` 闭包其实并不复杂,只要牢记 `闭包=函数+引用环境`。 ### 九、defer语句 go语言中的 `defer`语句会将其后面跟随的语句进行**延迟** 处理。在 `defer`归属的函数即将返回时,将延迟处理的语句按 `defer`定义的**逆序** 进行执行,也就是说,先被 `defer`的语句最后被执行,最后被 `defer`的语句,最先被执行。 **示例** ```go func main() { fmt.Println("start") defer fmt.Println(1) defer fmt.Println(2) defer fmt.Println(3) fmt.Println("end") } ``` **运行结果** ``` start end 3 2 1 ``` 由于`defer`语句延迟调用的特性,所以`defer`语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。 #### defer执行时机 在Go语言的函数中`return`语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而`defer`语句执行的时机就在返回值赋值操作后,RET指令执行前。 具体如下图所示: ![12.Golang函数03.png](https://lilinchao.com/usr/uploads/2022/07/2691527252.png) ### 十、内置函数介绍 | 内置函数 | 介绍 | | :------------: | :----------------------------------------------------------: | | close | 主要用来关闭channel | | len | 用来求长度,比如string、array、slice、map、channel | | new | 用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针 | | make | 用来分配内存,主要用来分配引用类型,比如chan、map、slice | | append | 用来追加元素到数组、slice中 | | panic和recover | 用来做错误处理 | *附参考文章链接* *https://www.liwenzhou.com/posts/Go/09_function/#autoid-2-2-2* *http://www.go-edu.cn/2022/05/16/go-09-%E5%87%BD%E6%95%B0/*
标签:
Golang基础
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2218.html
上一篇
11.Golang结构体(二)
下一篇
13.Golang包介绍
取消回复
评论啦~
提交评论
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
标签云
排序
Kibana
Yarn
HDFS
CentOS
微服务
GET和POST
Beego
Stream流
MyBatis
国产数据库改造
DataX
二叉树
序列化和反序列化
Redis
数学
DataWarehouse
Hadoop
Http
数据结构
设计模式
正则表达式
数据结构和算法
工具
JVM
Quartz
SpringCloudAlibaba
随笔
Spark Core
Hbase
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞