Go语言基础——03 函数
函数
计算机的函数,是一个固定的一个程序段,或称其为一个子程序,它在可以实现固定运算功能的同时,还带有一个入口和一个出口,所谓的入口,就是函数所带的各个参数,我们可以通过这个入口,把函数的参数值代入子程序,供计算机处理;所谓出口,就是指函数的函数值,在计算机求得之后,由此口带回给调用它的程序。
- 函数是基本的代码块。
- Go语言中至少要有一个 main 函数。
函数的基本定义格式:
func functionName([parameter list]) [returnTypes] {函数体}
函数定义解析:
func
:函数由func
开始声明functionName
:函数名称parameter list
:参数列表returnTypes
:返回类型函数体
:一段代码的集合
注意:函数和方法是完全不一样的,只有面向对象里才有方法。而Go是一门静态强类型、编译型语言。
函数签名
函数参数、返回值以及它们的类型被统称为函数签名
函数的调用
函数被调用的基本格式如下:
packageName.Function(arg1, arg2, ..., argn)
Function
是 packageName
包里面的一个函数,括号里的是被调用函数的实参(argument):这些值被传递给被调用函数的形参(parameter)。
函数的使用
无参无返回值
的函数有一个参数
的函数有两个参数或多个参数
的函数有一个返回值
的函数有两个或多个返回值
的函数
package main
import "fmt"
func main() {
outputStatement()
outputParameter("这是一个参数的函数")
outputParameters("ZhangSan", 18)
fmt.Println(addSum(1, 2))
fmt.Println(returnValues("返回值1", "返回值2"))
}
// 无参无返回值
func outputStatement() {
fmt.Println("这是一个无参无返回值的函数")
}
// 有一个参数
func outputParameter(str string) {
fmt.Println(str)
}
// 有两个或多个参数
func outputParameters(name string, age int) {
fmt.Println("输出参数1:", name, "输出参数2:", age)
}
// 有一个返回值
func addSum(num1 int, num2 int) int {
return num1 + num2
}
// 有两个或多个返回值
func returnValues(value1 string, value2 string) (string, string) {
return value1, value2
}
- 可变参数(长参数)
在参数类型确定,但参数个数不确定时,我们可以使用可变参数,可变参数如果有多个参数时,必须放在最后一个参数
package main
import "fmt"
func main() {
sum := getSum(1, 2, 3, 4, 5, 6)
fmt.Println(sum)
}
func getSum(nums ...int) int {
sum := 0
for _, num := range nums {
sum += num
}
return sum
}
注意:
在Go语言中没有函数重载(函数重载:指函数名相同,参数或返回值不同的函数),在Go语言中函数的重载是不允许的,会导致编译错误,Go不支持函数重载的主要原因是:函数重载需要进行多余的类型匹配,会影响性能。
- 命名的返回值
package main
import "fmt"
func main() {
x1, x2 := MultReturnVal(1, 2)
fmt.Printf("和: %d, 积: %d", x1, x2)
}
func MultReturnVal(a int, b int) (x1 int, x2 int) {
x1 = a + b
x2 = a * b
return
}
命名返回值作为结果形参被初始化为相应类型的零值,当需要返回的时候,我们只需要不带参数的 return 语句即可。需要注意,即使只有一个命名返回值,也需要用()
括起来。
- 改变外部变量
传递指针给函数不但可以节省内存(因为没有赋值变量的值),而且赋予了函数直接修改外部变量的能力,所以被修改的变量不再需要使用return
返回。
当需要在函数内改变一个占用内存比较大的变量时,性能优势就会非常明显。在使用可以改变外部变量的函数时,需要添加注释,便于他人能了解此函数。
package main
import "fmt"
func main() {
n := 0
// 类型: int, 地址: 0xc00000e0c8, 值: 0
fmt.Printf("类型: %T, 地址: %p, 值: %d\n", n, &n, n)
reVal := &n
// 类型: *int, 地址: 0xc00000e0c8, 值: 0
fmt.Printf("类型: %T, 地址: %p, 值: %d\n", reVal, reVal, *reVal)
returnVal(2, 3, reVal)
// 类型: *int, 地址: 0xc00000e0c8, 值: 6
fmt.Printf("类型: %T, 地址: %p, 值: %d\n", reVal, reVal, *reVal)
}
func returnVal(a, b int, reVal *int) {
*reVal = a * b
}
递归函数
一个函数在其函数体内调用自身,称为递归。
注意:
递归函数需要有一个出口,逐渐向出口靠近,没有出口就会形成死循环。
经典的例子:斐波那契数列
即:前两个数为1,从第三个数开始每个数均为前两个数之和
package main
import "fmt"
func main() {
result := 0
for i := 1; i <= 10; i++ {
result = fibonacciSequence(i)
fmt.Printf("斐波那契数列(%d): %d\n", i, result)
}
}
func fibonacciSequence(n int) (returnVal int) {
if n <= 2 {
returnVal = 1
} else {
returnVal = fibonacciSequence(n-1) + fibonacciSequence(n-2)
}
return
}
/*
结果:
斐波那契数列(1): 1
斐波那契数列(2): 1
斐波那契数列(3): 2
斐波那契数列(4): 3
斐波那契数列(5): 5
斐波那契数列(6): 8
斐波那契数列(7): 13
斐波那契数列(8): 21
斐波那契数列(9): 34
斐波那契数列(10): 55
*/
求和
package main
import "fmt"
func main() {
val := summation(5)
fmt.Println(val) // 15
}
/*
summation(5) = summation(4) + 5 = 15
summation(4) = summation(3) + 4 = 10
summation(3) = summation(2) + 3 = 6
summation(2) = summation(1) + 2 = 3
summation(1) = 1
*/
func summation(num int) (returnVal int) {
if num == 1 {
returnVal = 1
} else {
returnVal = summation(num-1) + num
}
return
}
defer
关键字 defer
,允许推迟到函数返回之前一刻才执行某个语句或函数。
defer
的用法类似面向对象编程语言的Java和C#的finally
语句块,它一般用于释放某些已分配的资源。
package main
import "fmt"
/*
执行 -- fun1 -- start
执行 -- fun1 -- end
执行 -- fun2
*/
func main() {
fun1()
}
func fun1() {
fmt.Println("执行 -- fun1 -- start")
defer fun2()
fmt.Println("执行 -- fun1 -- end")
}
func fun2() {
fmt.Println("执行 -- fun2")
}
package main
import "fmt"
/* 结果:
1
3
4
6
7
8
10
9
5
2
*/
func main() {
n := 2
fmt.Println("1")
defer fmt.Println(n)
fmt.Println("3")
fmt.Println("4")
defer fmt.Println("5")
fmt.Println("6")
fun(7)
fmt.Println("8")
defer fun(9)
fmt.Println("10")
}
func fun(a int) {
fmt.Println(a)
}
- 在函数中可以添加 多个defer语句,这些defer语句会按照逆序执行(类似栈,即后进先出)
- defer存在参数传递
回调函数
函数作为其它函数的参数进行传递,然后在其它函数内调用执行,称为回调函数。
package main
import "fmt"
func main() {
resultAdd := oper(1, 2, add)
resultSub := oper(3, 2, sub)
fmt.Println(resultAdd)
fmt.Println(resultSub)
}
func oper(a, b int, fun func(int, int) int) (result int) {
result = fun(a, b)
return
}
func add(a, b int) (result int) {
result = a + b
return
}
func sub(a, b int) (result int) {
result = a - b
return
}
匿名函数
匿名函数:顾名思义,没有名字的函数。
package main
import "fmt"
// Fun 定义一个全局匿名函数
var Fun = func(a, b int) (res int) {
res = a * b
return
}
func main() {
// 定义一个匿名函数并调用
func(s string) (str string) {
str = s
fmt.Println(str)
return
}("这是一个匿名函数")
// 将一个匿名函数赋值给一个变量,通过变量来调用匿名函数
result := func(a, b int) (res int) {
res = a + b
return
}
fmt.Println(result(1, 2))
// 调用全局匿名函数
res := Fun(2, 3)
fmt.Println(res)
}
闭包
一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量,并且该外层函数的返回值就是这个内层函数。这个内层函数和外层函数的局部变量,统称为闭包结构。
package main
import "fmt"
// 用 闭包 生成 斐波那契数列
/* 结果
1
1
2
3
5
8
13
21
34
55
*/
func main() {
f := fibonacci()
for i := 1; i <= 10; i++ {
fmt.Println(f())
}
}
func fibonacci() func() (res int) {
var a, b int = 1, 1
return func() (res int) {
res = a
a, b = b, a+b
return
}
}
注意:
- 在闭包结构中,局部变量的生命周期会发生改变,正常局部变量会随函数的调用而创建,随函数的结束而销毁,但在闭包结构中,因为内层函数还在使用外层函数的局部变量,所以外层函数的局部变量不会随着外层函数的结束而销毁。
- 闭包结构可能会造成内存泄漏,因为垃圾回收不会将闭包结构中的变量销毁。
- 通过闭包结构创建的函数内部变量,只在这个函数中作用,不会和其它函数冲突。
- 闭包结构的返回值是一个函数,这个函数可以调用闭包结构中的变量。
- 闭包结构可以解决全局变量污染问题。
计算函数执行时间
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
result := funTime(10000)
fmt.Println(result)
end := time.Now()
delta := end.Sub(start)
fmt.Printf("函数执行需要的时间: %s\n", delta)
}
func funTime(a int) (result []int) {
for i := 0; i < a; i++ {
result = append(result, i)
}
return
}