golang基础之函数
1、为什么需要函数?
有些相同的代码可能出现多次,如果不进行封装,那么多次写入到程序中,会造成程序冗余,并且可读性降低
2、什么是函数
为完成某些特定功能的程序指令集合称为函数
3、函数分类
内建函数
自定义函数
4、函数的基本用法
基本语法
func 函数名(形参列表) (返回值列表) {
//执行语句
return 返回值列表
}
形参列表
表示函数的输入
执行语句
表示函数执行的某功能代码
返回值列表
函数可以有返回值也可以没有返回值,可以返回多个,也可以一个也不返回
使用注意
golang中的函数可以返回多个值,如果需要返回多个值,需要在自定义函数时,声明的返回值列表必须是多个,否则报错
案例演示
package main import ( “fmt” ) func main(){ key := 12 var a int = 9 if key >= 10 { k,b := test(a) //调用函数 fmt.Println(k,b) fmt.Printf("%T",k) } } func test(a int) (float32, int){ //声明函数 var t int = 3 b := float32(a + t) c := a + t return b,c } 运行结果: 12 12 float32
5、函数参数的传递方式
基本介绍
函数参数传递分为值传递和引用传递
分类
值传递
基本数据类型、数组和结构体等都是值类型,值类型采用的是值传递
引用传递
指针、slice切片、map、管道chan、接口等都是引用类型,引用类型采用的是引用传递
使用说明
其实不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是值传递是值的拷贝,引用传递的是地址的拷贝,一般来说,地址的拷贝效率比较高,因数据量小,而值拷贝拷贝的效率取决于数据的大小,数据越大,效率越低
6、变量作用域
基本介绍
函数内部声明或者定义的变量叫做局部变量,作用域仅限于函数内部
函数外部声明的或者定义的变量叫做全局变量,作用域在整个包都有效,全局变量如果在整个程序有效的前提是首字母必须大写
注意事项
如果变量被定义在一个代码块中,比如if或者for循环中,那么这个变量的作用域就在该代码块中
变量的赋值不能再全局作用域进行,否则会报错。全局作用域只能声明一些变量或者函数,不能进行赋值操作
案例演示
package main import ( "fmt" "strings" ) var name int name = 123 //错误的做法 name1 := 123 //错误的做法 func main(){ fmt.Print(name) } 案例演示 package main import “fmt” name := “zhangsan” func test(){ fmt.Print(name) //zhangsan } func test1(){ name := “wangwu” fmt.Print(name) //wangwu } func main(){ test() test1() } 运行结果 shangsan wangwu
7、函数使用的注意事项和细节讨论
(1) 函数的形参列表可以是多个,返回值列表也可以是多个,返回值列表,每个值用逗号隔开
(2) 形参列表和返回值列表的数据类型可以是值类型和引用类型
(3) 函数命名规范遵守变量命名规范,不能以数字开头,或者包含特殊字符,首字母大写,被其他包导入时,具有可访问性。如果是小写只能被本包使用
(4) 变量分为全局变量和局部变量,全局变量是声明在main函数中,局部变量可以声明在任何一个块结构中,比如if语句中,循环体中,自定义函数中等
(5) 基本数据类型的和数组在传递时默认都是值传递,即进行值的拷贝,在函数内部修改,不会影响原值
(6) 如果希望函数内部的变量修改函数外的变量,可以传入变量的地址&,函数内部已指针的方式操作变量,从效果看类似引用
案例演示
package main import “fmt” func test(n *int){ *n = *n + 1 fmt.Print(*n) } func main(){ num := 123 test(&num) fmt.Print(num) }
(7) go语言不支持传统的重载(也就是通过参数列表的不同来实现的重载),go语言是用另一种方式实现的重载,而不是通过参数列表的不同来实现重载
(8) 在Go中,函数也是一个数据类型,可以赋值给一个变量,则该变量就是函数类型的变量,通过该变量可以对函数调用
举例说明
package main import “fmt” func main(){ a := yanshi a() //通过把函数赋值给一个变量,变量加个括号来调用函数 fmt.Printf("%T %T",a,yanshi) } func yanshi(n int){ fmt.Print("案例演示") }
(9) 函数既然是一种数据类型,因此Go中,函数可以作为参数传递给另一个函数
举例说明
package main import “fmt” func main(){ a := myfunc(yanshi,12) fmt.Printf("%T %T ",a,myfunc) fmt.Println(a) } func myfunc(funcvar func(int) int ,num int) { //函数作为参数时,必须把函数的参数列表和函数的返回值类型的列表补全 return funcvar(num) } func yanshi(n int){ fmt.Print("案例演示") }
(10) 函数作为参数传递时,必须把函数的声明和返回值的类型列表写全
(11) 为了简化数据类型的定义,Go支持自定义数据类型
基本语法
type 自定义数据类型名 数据类型
演示自定义数据类型
第一个演示: type myint int //相当于给int数据类型取了别名,但是这两个是独立,是不同的数据类型
第二个演示: type mysum func(int,int) int
关于自定义类型后与原类型的问题
使用注意
关于子自定义函数类型作用,能够简化函数类型作为形参的定义,但是自定义函数类型的定义必须在使用这个自定义函数类型的前面
举例说明
不使用自定义函数
func myfunc(funcvar func(int,int) int ,num int) int { //没有使用自定义函数类型时,函数类型作为形参在参数列表时需要写很长 return funcvar(num) }
使用自定义函数类型
type myfunctype func(int,int) int func myfunc(funcvar myfunctype ,num int) int { //自定义函数类型后,函数作为形参,在参数列表不用写很长 return funcvar(num) }
(12) Go语言支持对函数返回值命名
优点:不用一个一个在return中对应的写返回的值
举例说明
package main import “fmt” func main(){ n := 12 m := 4 sum ,sub :=myfunc(n,m) } func myfunc(n int , m int)(sum int,sub int) { //把返回的值定义好 sum = n + m //直接使用=等号赋值就行 sub = n-m return //返回时不用填任何返回数据,并且返回时不用担心对应关系,只需在接收时对应好就行 }
(13) 使用_下划线标识符忽略返回值
package main import “fmt” func main(){ n := 12 m := 4 sum ,_:=myfunc(n,m) //忽略返回值sub } func myfunc(n int , m int)(sum int,sub int) { sum = n + m sub = n-m return } (14) Go语言函数支持可变参数 基本语法: func sum(args... int) sum int { //代码 } func sum(n1 int,args... int) sum int { //代码 }
使用注意
args其实是一个slice切片,通过args[index]可以访问到各个值,如果一个函数形参列表有可变参数,和其他参数,那么可变参数要放在最后,否则报错
案例演示
package main import “fmt” func main(){ n := sum(3,1,2,3,4,5,6) fmt.Println(n) } func sum(n int , args... int ) int { s := 0 for i := 0 ; i < len(args) ; i++ { if args[i] == n { break } s += args[i] } return s } 运行结果 3
(15) 如果在函数的形参列表中,两个或者多个形参的数据类型一样,那么可以省略前面几个,最后写一个就行
func sum(n , m , b ,c int) int { //代码 }
8、包的使用和使用原理
包的使用原理
包的本质其实就是创建不同的文件夹来存放程序文件
包的相关说明
使用注意
导入包时,路径必须从src下的某个目录开始算,比如,src文件夹下有一个utils文件,utils文件下有一个utils.go文件,那么,在main.go中导入utils包下的utils.go的某个变量或者函数的路径是:import “utils”
9、使用包的注意事项
1)在给一个文件打包时,该报对应一个文件夹。文件的包通常和文件所在的文件夹名是一直的,一般为小写
2)当一个文件要使用其他包的函数或者变量时,需要先引入对应的包
引包方式1:import 包名
引包方式2:import (
“包名1”
“包名2”
)
3)package指令在文件第一行,然后接着是import导包命令
4)在import包时,路径从计算机环境变量$GOPATH下的src目录开始算,不用带src目录,编译器会自动从src开始引入
5)为了让其他包可以访问到本包的函数或者变量,则该函数或者变量的首字母必须大写
6)在访问其他包的函数或者变量时,语法是:包名.函数名或者包.变量
7)如果包名过长,Go语言支持跟包名取别名,如果取了别名,那么原来的包名就不能用
8)在同一个包下面不能有重复的变量名,否则包重复定义
说明:如果包取了别名,则必须使用别名来访问包中的函数或者变量
9)如果两个.go文件打了同一个包,并且这两个文件都有同一个名字的函数或者变量,这样都会报错,因为同一个包下只能有一个函数名或者变量名,即使这个两个函数名被定义在两个不同的但是打了同一个包的文件
10)在一个go程序中,main包只能有一个
函数中return语句使用注意
注意:对于函数的返回值,如果函数返回多个值,但是你只需要某个值,那么不需要的值可以使用下划线接受,例如: _ , a := getSum(a,b)。
10、函数之递归调用
基本介绍
一个函数体内调用了本身,我们称为递归调用
案例演示
func test( a int ) { if a > 2 { a-- test(a) } fmt.Println(a) } func main(){ test(4) } 运行结果: 2 2 3
执行流程
使用注意
① 递归每执行一个函数时,就创建一个新的受保护的独立的空间,也可以说是一个新的函数栈
② 函数的局部变量是独立的,不会相互影响
③ 递归必须向退出递归的条件逼近,否则就是无限递归
④ 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁。执行完毕时,或者返回时,该函数栈会被销毁
(1) 书写递归的重要思想就是:寻找出路
11、递归之解决斐波那契数列
(2) 比如有一个数列:1 1 2 3 5 7 12 19.......
(3) 实现思路
1、当数列在1和2位置时值为1
2、数列的前一个数和后一个数相加等于第三个数
(4) 实现代码
package main import “fmt” func main{ //1 1 2 3 5 .... n := 0 fmt.Print("请输入你要查找的数:") fmt.Scanln(&n) a := fibonaqi(n) fmt.Println(a) }
//求斐波那契第n个位置的值
func fibonaqi(n int) int { if n == 1 || n == 2 { return 1 } return fibonaqi(n-1)+fibonaqi(n-2) }
运行结果:
12、求函数值
已知f(1)=3,f(n)=2*f(n-1)+1,请使用递归求出n为任意数的值
解题思路
寻找递归出口,这里是f(1)=3
实现代码
package main import “fmt” func main{ //1 1 2 3 5 .... n := 0 fmt.Print("请输入你要查找的数:") fmt.Scanln(&n) a := hs(n) fmt.Println(a) } func hs( n int) int { if n == 1 { return 3 } return 2 * hs(n-1) + 1 }
13、递归练习之猴子吃桃子
问题描述
问题解剖
1、当到10天时,只剩下一个
2、前一天的个数为后一天个数加1乘以2
实现代码
package main import “fmt” func main{ //1 1 2 3 5 .... n := 0 fmt.Print("请输入你要查找的数:") fmt.Scanln(&n) a := chi(n) fmt.Println(a) } func chi(d int ) int { //设第d天还剩下n个 if d == 10 { return 1 } else if d > 10 { return 1 } return ( chi(d + 1) + 1 ) * 2 }
运行结果:
14、Go语言中的init函数
基本介绍
每个源文件中都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用,也就是说init会在main函数执行前执行
案例演示
package main import "fmt" func init(){ fmt.Println("我先被执行了?") } func main(){ fmt.Println("我后被执行?") } 打印: PS F:goProjectssrc est> go run .utils.go 我先被执行了? 我后被执行?
init函数的使用细节
如果一个文件中同时包含全局变量定义,init函数和main函数,则执行先后流程是:先执行全局变量定义---->init函数------>main函数最后执行
案例演示
package main import "fmt" var num = test() func test() int { fmt.Print(“我最先被执行?”) return 100 } func init(){ fmt.Println("") } func main(){ fmt.Printf("我最后被执行?%d",num) } 运行结果: 我最先被执行? 我第二个被执行了? 我最后被执行?100
使用注意
如果一包里边包含一个init函数,当我们把这个包导入main.go文件中,当执行main.go时,先执行导入包中的init函数,为什么呢?因为程序运行时,代码从上往下开始执行,当执行到导入包时,如果导入的包有init函数,就会去执行导入包的init函数
15、Go语言的匿名函数
基本介绍
Go语言支持匿名函数,如果某个函数只希望被使用一次,可以考虑使用匿名函数。匿名函数也可以实现多次调用
匿名函数的使用
方式1:将匿名函数赋值给一个变量(函数变量),在通过该变量来来调用匿名函数
案例演示 package main import "fmt" func main(){ //求两个数的和,使用匿名函数来完成 functions := func ( n1 ,n2 int ) int { return n1 + n2 } sum := functions(10,20) //可以通过变量多次调用匿名函数 fmt.Print(sum) } 方式2:在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次 案例演示 package main import "fmt" func main(){ //求两个数的和,使用匿名函数来完成 sum := func ( n1 ,n2 int ) int { //匿名函数没有函数名,结果直接用一个变量接受 return n1 + n2 }(10,20) //调用 fmt.Print(sum) }
全局匿名函数
基本介绍
如果将匿名函数赋值一个全局变量,那么这个匿名函数就称为全局匿名函数,可以在程序中有效
案例演示
package main import "fmt" var ( Fun1 = func (n , m int ) int { return n + m } //全局匿名函数的定义 ) var func2 = func (n int ) int { return 1 } //这样定以也是可以 func main(){ sum := Fun1(1,2) fmt.Print(sum) }
16、defer
基本介绍
延迟执行某些代码
使用案例
package main import ( "fmt" "strings" ) func sum(n , m int ) { defer fmt.Println(n) defer fmt.Println(m) n = n+m fmt.Println(n) } func main(){ sum(1,2) fmt.Println(“执行”) }
使用注意
当编译器执行到defer语句时,不会马上执行defer后边的语句,他会将defer后的语句压入到一个derfer栈(为理解取的名字)中。当函数执行完毕后,再冲defer栈中将原来压入站的语句拿出来执行
defer后面的语句被压入栈中是,也会将在这个语句前定义的变量拷贝到这个defer栈中
注意:defer栈的取名是为了理解而取的
defer使用价值
defer最主要的价值在于但函数执行完毕后可以及时的是仿函数创建的资源。比如:打开一个文件的句柄,释放一个数据库资源等都可以使用defer在函数调用完毕后关闭