函数-递归调用
一个函数在函数体内又调用了本身,称为递归调用
package main
import "fmt"
func test(n int) {
if n > 2 {
n--
test(n)
}
fmt.Println("n=", n)
}
func main() {
test(4)
}
递归练习
斐波那契数
用递归方式,求出斐波那契数列1,1,2,3,5,8,13...
给出一个整数,求出它的斐波那契数
package main
import "fmt"
//斐波那契数
//用递归方式,求出斐波那契数列1,1,2,3,5,8,13...
//给出一个整数,求出它的斐波那契数
//1、当n==1||n==2,返回1
//2、当n>2时,返回前两个数之和
func fbn(n int) int {
if n == 1 || n == 2 {
return 1
} else {
return fbn(n-1) + fbn(n-2)
}
}
func main() {
var res int
fmt.Scanln(&res)
fmt.Println("res的斐波那契数是=", fbn(res))
}
已知f(1)=3;f(n)=2*f(n-1)+1;求函数值f(n)
package main
import "fmt"
//已知f(1)=3;f(n)=2*f(n-1)+1;求函数值f(n)
func f(n int) int {
if n == 1 {
return 3
} else {
return 2*f(n-1) + 1
}
}
func main() {
//测试
fmt.Println("f(1)=", f(1))
fmt.Println("f(5)=", f(5))
}
函数注意事项和细节
函数的形参列表可以是多个,返回值列表可以是多个
形参列表和返回值了表的数据类型可以是值类型,也可以是引用类型。
函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其他包文件使用,类似public,首字母小写,只能被本包文件使用,其他包文件不能使用,类似private
函数中的变量是局部的,函数内不能生效
基本数据类型和数组都是值传递的,即进行值传递,即进行拷贝,在函数内修改,函数外不生效
func test02(n1 int) {
n1 = n1 + 10
}
func main() {
n1 := 20
test02(n1)
fmt.Println("n1=", n1)
}
如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针方式操作变量,从效果上来看类似引用
//n1是*int类型
func test03(n1 *int) {
*n1 = *n1 + 100
}
func main() {
n1 := 20
test03(&n1)
fmt.Println("n1=", n1)
}
Go函数不支持重载
在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用
func getsum(n1 int, n2 int) int {
return n1 + n2
}
func main() {
a := getsum
fmt.Printf("a的数据类型是%T,getsum的数据类型是%T\n", a, getsum)
res := getsum(1, 1)
fmt.Println(res)
}
函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用
func getsum(n1 int, n2 int) int {
return n1 + n2
}
func myFun(fanvar func(int, int) int, num1 int, num2 int) int {
return fanvar(num1, num2)
}
func main() {
a := getsum
fmt.Printf("a的数据类型是%T,getsum的数据类型是%T\n", a, getsum)
res := getsum(1, 1)
fmt.Println(res)
res2 := myFun(getsum, 50, 50)
fmt.Println("res2=", res2)
}
为了简化数据类型定义,go支持自定义数据类型
基本语法:type 自定义数据类型名 数据类型
var num1 myInt
var num2 int
num1 = 40
num2 = int(num1)
fmt.Println("num1=",num1)
fmt.Println("num2=",num2)
package main
import "fmt"
func getsum(n1 int, n2 int) int {
return n1 + n2
}
type myFunType func(int, int) int
func myFun(fanvar myFunType, num1 int, num2 int) int {
return fanvar(num1, num2)
}
func main() {
type myInt int
var num1 myInt
var num2 int
num1 = 40
num2 = int(num1)
fmt.Println("num1=", num1)
fmt.Println("num2=", num2)
res3 := myFun(getsum, 50, 50)
fmt.Println("res2=", res3)
}
支持对函数返回值名
package main
import "fmt"
func getsum(n1 int, n2 int) (sum int) {
sum = n1 + n2
return
}
func main() {
var n1 int = 12
var n2 int = 11
fmt.Println(getsum(n1, n2))
}
使用_标识符,忽略返回值
Go中的可变参数
func sum(args...int) sum int{
}
func sum(n1 int,args...int) sum int{
}
args是slice切片,通过args[index]可以访问到各个值
package main
import "fmt"
func sum(n1 int, args ...int) int {
sum := n1
for i := 0; i < len(args); i++ {
sum += args[i]
}
return sum
}
func main() {
res4 := sum(10, 1, 4, 5, 6, 4)
fmt.Println("res4=", res4)
}
如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后
Init函数
每一个源文件都可以包含一个Init函数,该函数会在main函数执行钱,被Go运行框架调用,也就是说Init会在main函数前被调用
package main
import "fmt"
//Init函数,通常可以在Init函数中完成初始化操作
func init() {
fmt.Println("Init()...")
}
func main() {
fmt.Println("main()...")
}
细节
如果一个文件同时包含全局变量定义,Init函数和main函数,则执行的流程是:
全局变量定义->Init函数->main函数
var age = test()
func test() int {
fmt.Println("test()")
return 90
}
//Init函数,通常可以在Init函数中完成初始化操作
func init() {
fmt.Println("Init()...")
}
func main() {
fmt.Println("main()...")
}
Init函数最主要的作用,就是完成一些初始化工作
package utils
var Age int
var Name string
func init() {
Age = 55
Name = "Tom"
}
package main
import (
"fmt"
utils "funcinit/utils"
)
var age = test()
func test() int {
fmt.Println("test()")
return 90
}
// Init函数,通常可以在Init函数中完成初始化操作
func init() {
fmt.Println("Init()...")
}
func main() {
fmt.Println("main()...")
fmt.Println("Age=", utils.Age)
fmt.Println("Name=", utils.Name)
}
匿名函数
Go支持匿名函数,如果某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用
使用方式
package main
import "fmt"
var (
fun1 = func(n1 int, n2 int) int {
return n1 + n2
}
)
func main() {
//方式1
res1 := func(n1 int, n2 int) int {
return n1 + n2
}(10, 20)
fmt.Println("res1=", res1)
//方式2
a := func(n1 int, n2 int) int {
return n1 - n2
}
res2 := a(10, 30)
fmt.Println("res2=", res2)
//方式3
//全局匿名函数的使用
res3 := fun1(4, 9)
fmt.Println("res3=", res3)
}
闭包
闭包就是一个函数与其相关的引用环境组合的一个整体
package main
import "fmt"
func AddUpper() func(int) int {
var n int = 10
return func(x int) int {
n = n + x
return n
}
}
func main() {
f := AddUpper()
fmt.Println(f(1))
}
AddUpper是一个函数,返回的数据类型是func(int)int
返回的是一个匿名函数,但是这个匿名函数引用到函数外的你,因此这个匿名函数就和n形成一个整体,构成闭包
可以这样理解:闭包是类,函数是操作,n是字段,函数和它使用到n构成闭包
当我们反复的调用f函数时,因为n时初始化一次,因此每调用一次就进行累计
我们要搞清楚闭包的关键,就是要分析出返回的函数它使用到哪些变量,因为函数和它引用到的变量共同构成闭包。
实践
编写一个函数makeSuffix(suffix string)可以接收一个文件后缀名,并返回一个闭包
调用闭包,可以传入一个文件名,如果该文件没有指定的后缀,则返回文件名.jpg,如果已经有了.jpg后缀,则返回原文件名
要求使用闭包的方式完成
string.HasSuffix,该函数可以判断某个字符是否有指定后缀
package main
import (
"fmt"
"strings"
)
func makeSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main() {
//返回一个闭包
f := makeSuffix(".jpg")
f2 := makeSuffix(".jpg")
fmt.Println("文件名处理后=", f("winter"))
fmt.Println("文件名处理后=", f2("bird.jpg"))
}
返回的匿名函数和makeSuffix(Suffix string)的suffix变量组成一个闭包,因为返回的函数引用到suffix这个变量
如果使用传统的方法,需要每次都传入后缀名,比如jpg,二闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用
函数中-defer
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,即使的释放资源,Go的设计者提供defer(延时机制)。
package main
import "fmt"
func sum(n1 int, n2 int) int {
//当执行到defer时,会将defer后面的语句压入到独立的栈
//当函数执行完毕后,再从defer栈,按照先入后出的方式出栈
defer fmt.Println("ok1,n1=", n1)
defer fmt.Println("ok2,n2=", n2)
res := n1 + n2
fmt.Println("ok3,res=", res)
return res
}
func main() {
sum(10, 20)
}
细节
当go执行到一个defer时,不会立即执行defer后面的语句,而是将defer后的语句压入到一个栈中,然后继续执行下一个语句
但函数执行完毕后,在从defer栈中,一次从栈顶取出语句执行
在defer将语句放入到栈式,也会将相关的值拷贝同时入栈。