Go语言函数

什么是函数

  1. 函数是组织好的、可重复使⽤的执⾏特定任务的代码块。它可以提⾼应⽤程序的模块性和代码的重复利⽤率。
  2. Go语⾔⽀持普通函数、匿名函数和闭包,从设计上对函数进⾏了优化和改进,让函数使⽤起来更加⽅便。
  3. Go语⾔的函数属于⼀等公⺠(first-class):
  • 函数本身可以作为值进⾏传递;
  • ⽀持匿名函数和闭包(closure);
  • 函数可以满⾜接⼝。

声明函数

  1. 函数声明的作⽤
  • 普通函数需要先声明才能调⽤,⼀个函数的声明包括参数和函数名等。编译器通过声明才能了解函数应该怎样在调⽤代码和函数体之间传递参数和返
    回参数。
  1. 语法格式:
func 函数名(参数列表)(返回参数列表){
//函数体
}
func funcName (parametername type1, parametername type2...) 
(output1 type1, output2 type2...) {
 //逻辑代码
 //返回多个值
 return value1, value2...
 }
  1. 函数定义解析:
  • func:函数关键字。
    • 函数由 func 开始声明
  • funcName:函数名。
    • 函数名和参数列表⼀起构成了函数签名。
    • 函数名由字⺟、数字和下划线组成。函数名的第⼀个字⺟不能为数字。在同⼀个包内,函数名称不能重名。
  • parametername type:参数列表。
    • 参数就像⼀个占位符,定义函数时的参数叫做形式参数,形参变量是函数的局部变量;当函数被调⽤时,你可以将值传递给参数,这个值被称为实际参数。
    • 参数列表指定的是参数类型、顺序、及参数个数。
    • 参数是可选的,也就是说函数也可以不包含参数。
    • 参数类型的简写
      • 在参数列表中,如果有多个参数变量,则以逗号分隔;如果相邻变量是同类型,则可以将类型省略。
      • 例如:func add (a , b int) {}
    • Go语⾔的函数⽀持可变参数。接受变参的函数是有着不定数量的
      参数的。
      func myfunc(arg …int) {}
      arg …int告诉Go这个函数接受不定数量的参数。注意,这
      些参数的类型全部是int。在函数体中,变量arg是⼀个int的
      slice。
  • output1 type1, output2 type2:返回值列表。
    • 返回值返回函数的结果,结束函数的执⾏。
    • Go语⾔的函数可以返回多个值。
    • 返回值可以是:返回数据的数据类型,或者是:变量名+变量类型的组合。
    • 函数声明时有返回值,必须在函数体中使⽤return语句提供返回值列表。
    • 如果只有⼀个返回值且不声明返回值变量,那么可以省略包括返回值的括号。
    • return后的数据,要保持和声明的返回值类型、数量、顺序⼀致。
    • 如果函数没有声明返回值,函数中也可以使⽤return关键字,⽤于强制结束函数。
  • 函数体:函数定义的代码集合,是能够被重复调⽤的代码⽚段。

变量作⽤域

  1. 概述
  • 作⽤域是变量、常量、类型、函数的作⽤范围。
  • Go 语⾔中变量可以在三个地⽅声明:
    • 函数内定义的变量称为局部变量
    • 函数外定义的变量称为全局变量
    • 函数中定义的参数称为形式参数
  1. 局部变量
  • 在函数体内声明的变量称之为局部变量,它们的作⽤域只在函数体内,参
    数和返回值变量也是局部变量。
  1. 全局变量
  • 在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚⾄外部
    包(被导出后)使⽤。

全局变量可以在任何函数中使⽤。Go 语⾔程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。

  1. 形式参数
  • 形式参数会作为函数的局部变量来使⽤。
  1. 案例分析
package main
import "fmt"
/* 声明全局变量 */
var a1 int = 7
var b1 int = 9
func main() {
 /* main 函数中声明局部变量 */
 a1, b1, c1 := 10, 20, 0
 fmt.Printf("main()函数中 a1 = %d\n", a1) //10
 fmt.Printf("main()函数中 b1 = %d\n", b1) //20
 fmt.Printf("main()函数中 c1 = %d\n", c1) //0
 c1 = sum(a1, b1)
 fmt.Printf("main()函数中 c1 = %d\n", c1) //33
}
/* 函数定义-两数相加 */
func sum(a1, b1 int) (c1 int) {
 a1++
 b1 += 2
 c1 = a1 + b1
 fmt.Printf("sum() 函数中 a1 = %d\n", a1) //11
 fmt.Printf("sum() 函数中 b1 = %d\n", b1) //22
 fmt.Printf("sum() 函数中 c1 = %d\n", c1) //33
 return c1
}
输出结果:
main()函数中 a1 = 10
main()函数中 b1 = 20
main()函数中 c1 = 0
sum() 函数中 a1 = 11
sum() 函数中 b1 = 22
sum() 函数中 c1 = 33
main()函数中 c1 = 33

函数变量(函数作为值)

在Go语⾔中,函数也是⼀种类型,可以和其它类型⼀样被保存在变量中。
可以通过type来定义⼀个⾃定义类型。函数的参数完全相同(包括:参数类型、个数、顺序),函数返回值相同。

  1. 案例代码⼀:
package main
import (
 "fmt"
 "strings"
)
func main() {
 result := StringToLower("AbcdefGHijklMNOPqrstUVWxyz", processCase)
 fmt.Println(result)
 result = StringToLower2("AbcdefGHijklMNOPqrstUVWxyz", processCase)
 fmt.Println(result) }
//处理字符串,奇数偶数依次显示为⼤⼩写
func processCase(str string) string {
 result := ""
 for i, value := range str {
 if i%2 == 0 {
 result += strings.ToUpper(string(value))
 } else {
 result += strings.ToLower(string(value))
 }
 }
 return result
}
func StringToLower(str string, f func(string) string) string {
 fmt.Printf("%T \n", f)
 return f(str) }
type caseFunc func(string) string // 声明了⼀个函数类型。通过type关键字,
caseFunc会形成⼀种新的类型。
func StringToLower2(str string, f caseFunc) string {
 fmt.Printf("%T \n", f)
 return f(str) 
 }
  1. 案例代码⼆:
package main
import "fmt"
type processFunc func(int) bool // 声明了⼀个函数类型
func main() {
slice := []int{1, 2, 3, 4, 5, 7}
fmt.Println("slice = ", slice)
odd := filter(slice, isOdd) // 函数当做值来传递
fmt.Println("奇数元素: ", odd)
even := filter(slice, isEven) // 函数当做值来传递
fmt.Println("偶数元素: ", even) }
//判断元素是否是偶数
func isEven(integer int) bool {
if integer%2 == 0 {
return true
}
return false
}
//判断元素是否是奇数
func isOdd(integer int) bool {
if integer%2 == 0 {
return false
}
return true
}
//根据函数来处理切⽚,根据元素奇数偶数分组,返回新的切⽚
func filter(slice []int, f processFunc) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
return result
}

3、函数变量的使⽤步骤及意义:

  1. 定义⼀个函数类型
  2. 实现定义的函数类型
  3. 作为参数调⽤
    函数变量的⽤法类似接⼝的⽤法。
    函数当做值和类型在写⼀些通⽤接⼝的时候⾮常有⽤,通过上⾯例⼦可以
    看到processFunc这个类型是⼀个函数类型,然后两个filter函数的参数和返
    回值与processFunc类型是⼀样的。⽤户可以实现很多种的逻辑,这样使得
    程序变得⾮常的灵活。

匿名函数

  1. 概念
    Go语⾔⽀持匿名函数,即在需要使⽤函数时,再定义函数,匿名函数没有函数名,只有函数体,函数可以被作为⼀种类型被赋值给变量,匿名函数也往往以变量⽅式被传递。
  • 匿名函数经常被⽤于实现回调函数、闭包等。
  1. 定义格式
    func(参数列表) (返回参数列表) {
    //函数体
    }
  2. 定义匿名函数
  • 在定义时调⽤匿名函数
package main
import "fmt"
func main() {
 func(data int) {
 fmt.Println("hello" , data)
 }(100)
 }
  • 将匿名函数赋值给变量
package main
import "fmt"
func main() {
 f:= func(data string) {
 fmt.Println(data)
 }
 f("欢迎学习Go语⾔!") 
 }
  • 匿名函数的⽤法——作回调函数
package main
import (
"fmt"
"math"
)
func main() {
//调⽤函数,对每个元素进⾏求平⽅根操作
arr := []float64{1, 9, 16, 25, 30}
visit(arr, func(v float64) {
v = math.Sqrt(v)
fmt.Printf("%.2f \n", v)
})
//调⽤函数,对每个元素进⾏求平⽅操作
visit(arr, func(v float64) {
v = math.Pow(v , 2)
fmt.Printf("%.0f \n", v)
}) }
//定义⼀个函数,遍历切⽚元素,对每个元素进⾏处理
func visit(list []float64, f func(float64)) {
for _, value := range list {
f(value)
}
}

闭包

  1. 概念:
  • 闭包并不是什么新奇的概念,它早在⾼级语⾔开始发展的年代就产⽣了。闭包(Closure)是词法闭包(Lexical Closure)的简称。对闭包的具体定义有很多种说法,⼤体可以分为两类:
    • ⼀种说法认为闭包是符合⼀定条件的函数,⽐如这样定义闭包:闭包是在其词法上下⽂中引⽤了⾃由变量的函数。
    • 另⼀种说法认为闭包是由函数和与其相关的引⽤环境组合⽽成的实体。⽐如这样的定义:在实现深约束时,需要创建⼀个能显式表示引⽤环境的东⻄,并将它与相关的⼦程序捆绑在⼀起,这样捆绑起来的整体被称为闭包。函数 + 引⽤环境 = 闭包
    • 上⾯的定义,⼀个认为闭包是函数,另⼀个认为闭包是函数和引⽤环境组成的整体。显然第⼆种说法更确切。闭包只是在形式和表现上像函数,但实际上不是函数。
      • 函数是⼀些可执⾏的代码,这些代码在函数被定义后就确定了,不会在执⾏时发⽣变化,所以⼀个函数只有⼀个实例。
      • 闭包在运⾏时可以有多个实例,不同的引⽤环境和相同的函数组合可以产⽣不同的实例。
    • 闭包在某些编程语⾔中被称为Lambda表达式。
    • 函数本身不存储任何信息,只有与引⽤环境结合后形成的闭包才具有“记忆性”。函数是编译器静态的概念,⽽闭包是运⾏期动态的概念。
    • 对象是附有⾏为的数据,⽽闭包是附有数据的⾏为。
  1. 闭包的价值
  • 加强模块化
    • 闭包有益于模块化编程,它能以简单的⽅式开发较⼩的模块,从⽽提⾼开发速度和程序的可复⽤性。和没有使⽤闭包的程序相⽐,使⽤闭包可将模块划分得更⼩。
    • ⽐如我们要计算⼀个数组中所有数字的和,这只需要循环遍历数组,把遍历到的数字加起来就⾏了。如果现在要计算所有元素的积呢?要打印所有的元素呢?解决这些问题都要对数组进⾏遍历,如果是在不⽀持闭包的语⾔中,我们不得不⼀次⼜⼀次重复地写循环语句。⽽这在⽀持闭包的语⾔中是不必要的。这种处理⽅法多少有点像回调函数,不过要⽐回调函数写法更简单,功能更强⼤。
  • 抽象
    闭包是数据和⾏为的组合,这使得闭包具有较好抽象能⼒。
  • 简化代码
  1. ⼀个编程语⾔需要哪些特性来⽀持闭包呢?
  • 函数是⼀阶值(First-class value,⼀等公⺠),即函数可以作为另⼀个
  • 函数的返回值或参数,还可以作为⼀个变量的值。
  • 函数可以嵌套定义,即在⼀个函数内部可以定义另⼀个函数。允许定义匿名函数。
  • 可以捕获引⽤环境,并把引⽤环境和函数代码组成⼀个可调⽤的实体;
  1. 案例代码⼀
  • 没有使⽤闭包进⾏计数的代码
package main
import "fmt"
func main() {
 for i := 0; i < 5; i++ {
 fmt.Printf("i=%d \t", i)
 fmt.Println(add2(i))
 }
}
func add2(x int) int {
 sum := 0
 sum += x
 return sum
}

运⾏结果:
i=0 0
i=1 1
i=2 2
i=3 3
i=4 4
for循环每执⾏⼀次,sum都会清零,没有实现sum累加计数。

  • 使⽤闭包函数实现计数器:
package main
import "fmt"
func main() {
 pos := adder()
 for i := 0; i < 10; i++ {
 fmt.Printf("i=%d \t", i)
 fmt.Println(pos(i))
 }
 fmt.Println("---------------------")
 for i := 0; i < 10; i++ {
 fmt.Printf("i=%d \t", i)
 fmt.Println(pos(i))
 }
}
func adder() func(int) int {
 sum := 0
 return func(x int) int {
 fmt.Printf("sum1=%d \t", sum)
 sum += x
 fmt.Printf("sum2=%d \t", sum)
 return sum
 }
}

运⾏结果为:

i=0 sum1=0 sum2=0 0
i=1 sum1=0 sum2=1 1
i=2 sum1=1 sum2=3 3
i=3 sum1=3 sum2=6 6
i=4 sum1=6 sum2=10 10
---------------------
i=0 sum1=10 sum2=10 10
i=1 sum1=10 sum2=11 11
i=2 sum1=11 sum2=13 13
i=3 sum1=13 sum2=16 16
i=4 sum1=16 sum2=20 20
  1. 案例代码⼆
package main
import "fmt"
func main() {
 myfunc := Counter()
  //fmt.Printf("%T\n", myfunc)
 fmt.Println("myfunc", myfunc)
 /* 调⽤ myfunc 函数,i 变量⾃增 1 并返回 */
 fmt.Println(myfunc())
 fmt.Println(myfunc())
 fmt.Println(myfunc())
 /* 创建新的函数 nextNumber1,并查看结果 */
 myfunc1 := Counter()
 fmt.Println("myfunc1", myfunc1)
 fmt.Println(myfunc1())
 fmt.Println(myfunc1())
}
//计数器.闭包函数
func Counter() func() int {
 i := 0
 res := func() int {
 i += 1
 return i
 }
 //fmt.Printf("%T , %v \n" , res , res) //func() int , 0x1095af0
 fmt.Println("Counter中的内部函数:", res) //0x1095af0
 return res
}

可变参数

  1. 如果⼀个函数的参数,类型⼀致,但个数不定,可以使⽤函数的可变参数。
  2. 语法格式:
    func 函数名(参数名 …类型) [(返回值列表)] {
    //函数体
    }
  • 该语法格式定义了⼀个接受任何数⽬、任何类型参数的函数。这⾥特殊的语法是三个点“…”,在⼀个变量后⾯加上三个点后,表示从该处开始接受不
    定参数。
    当要传递若⼲个值到不定参数函数中得时候,可以⼿动书写每个参数,也
    可以将⼀个slice传递给该函数,通过"…"可以将slice中的参数对应的传递给
    函数。
  1. 案例代码:计算学员考试总成绩及平均成绩
package main
import (
 "fmt"
)
func main() {
 //1、传进n个参数
 sum, avg, count := GetScore(90, 82.5, 73, 64.8)
 fmt.Printf("学员共有%d⻔成绩,总成绩为:%.2f,平均成绩为:%.2f",
count, sum, avg)
 fmt.Println()
 // 2、传切⽚作为参数
 scores := []float64{92, 72.5, 93, 74.5, 89, 87, 74}
 sum, avg, count = GetScore(scores...)
 fmt.Printf("学员共有%d⻔成绩,总成绩为:%.2f,平均成绩为:%.2f",
count, sum, avg) }
//累加求和,参数个数不定,参数个数从0-n
func GetScore(scores ...float64) (sum, avg float64, count int) {
 for _, value := range scores {
 sum += value
 count++
 }
 avg = sum / float64(count)
 return
}
  1. 可变参数注意细节:
    ⼀个函数最多只能有⼀个可变参数
    参数列表中还有其它类型参数,则可变参数写在所有参数的最后

递归函数

  1. 在函数内部,可以调⽤其他函数。如果⼀个函数在内部调⽤⾃身本身,这个函数就是递归函数。
    递归函数必须满⾜以下两个条件:
    1) 在每⼀次调⽤⾃⼰时,必须是(在某种意义上)更接近于解;
    2) 必须有⼀个终⽌处理或计算的准则。
  2. 案例代码:求阶乘
    计算阶乘n! = 1 x 2 x 3 x … x n,⽤函数fact(n)表示,可以看出:fact(n) =n! = 1 x 2 x 3 x … x (n-1) x n = (n-1)! x n = fact(n-1) x n。所以,fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。
package main
import "fmt"
func main() {
 fmt.Println(factorial(5))
}
//通过递归实现阶乘
func factorial(n int) int {
 if n == 0 {
 return 1
 }
 return n * factorial(n-1) }
//通过循环实现阶乘
func getMultiple(num int) (result int) {
 result = 1
 for i:=1; i<= num; i++ {
 result *= i
 }
 return
}3、使⽤递归的注意事项
递归的计算过程
===> factorial(5)
===> 5 * factorial(4)
===> 5 * (4 * factorial(3))
===> 5 * (4 * (3 * factorial(2)))
===> 5 * (4 * (3 * (2 * factorial(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120
  • 递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以⽤循环的⽅式实现,但循环的逻辑不如递归清晰。
  • 使⽤递归函数需要注意防⽌栈溢出。在计算机中,函数调⽤是通过栈(stack)这种数据结构实现的,每当进⼊⼀个函数调⽤,栈就会加⼀层栈,每当函数返回,栈就会减⼀层。由于栈的⼤⼩不是⽆限的,所以,递归调⽤的次数过多,会导致栈溢出。
  • 使⽤递归函数的优点是逻辑简单清晰,缺点是过深的调⽤会导致栈溢出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

中亿丰数字科技集团有限公司

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值