李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
GO
正文
12.Golang函数
Leefs
2022-07-03 PM
1197℃
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
NLP
4
标签云
Spark Streaming
数学
递归
ajax
Linux
LeetCode刷题
Java
Sentinel
高并发
哈希表
Spark RDD
并发编程
BurpSuite
Map
国产数据库改造
栈
Filter
SpringCloudAlibaba
Kibana
Spark
Hive
序列化和反序列化
JavaWeb
稀疏数组
线程池
JVM
Typora
JavaScript
算法
Shiro
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭