- 函数:在Go中,函数分为:自定义函数、系统函数
func 函数名 (形参列表)(返回值列表){
执行语句…
return 返回值列表
}
(1)形参列表:表示函数的输入;形参列表和返回值列表的数据类型可以是值类型也可以是引用类型;
(2)函数可以有返回值,也可以没有,如果返回多个值时,在接收时,希望忽略某个返回值,则使用_符号表示占位忽略
(3)如果返回值只有一个,(返回值类型列表)可以不写()。
(4)基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值.
package main
import (
"fmt"
)
func test(n1 int){
n1=n1+10
fmt.Println("n1=",n1)
}
func main() {
n1:=20
test(n1)
fmt.Println("main n1=",n1)
}
n1= 30
main n1= 20
(5)如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。
package main
import (
"fmt"
)
func test(n1 *int){
*n1=*n1+10
fmt.Println("n1=",*n1)
}
func main() {
n1:=20
test(&n1)
fmt.Println("main n1=",n1)
}
n1= 30
main n1= 30
(6)go函数不支持重载。
(7)在go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量,通过该变量可以对函数调用。
package main
import (
"fmt"
)
func getsum(n1 int,n2 int) (sum int,sub int){
sum=n1+n2
sub=n1-n2
return
}
func main() {
a:=getsum
fmt.Printf("a的类型%T,getsum类型%T\n",a,getsum)
res,ress:=a(20,30)
fmt.Println("res= ress=",res,ress)
}
a的类型func(int, int) (int, int),getsum类型func(int, int) (int, int)
res= ress= 50 -10
(8)在go中,函数可以作为形参,并且调用。
package main
import (
"fmt"
)
func getsum(n1 int,n2 int) int{
sum:=n1+n2
return sum
}
func myfun (funvar func(int,int) int,num1 int,num2 int) int{
return funvar(num1,num2)
}
func main() {
res:=myfun(getsum,20,10)
fmt.Println("res= ",res)
}
res= 30
(9)为了简化数据类型定义,go支持自定义数据类型。
type myint int //myint等价于int
type mysum func(int,int) int //mysun等价于函数类型func(int,int) int
给int取了别名,在go中myint和int虽然都是int类型,但是go认为myint和int是两个类型。
(10)支持对函数返回值命名
func getsum(n1 int,n2 int) (sum int,sub int){
sum=n1+n2
sub=n1-n2
return
}
(11)go支持可变参数
//支持0到多个参数
func sum(args… int) sum int{
}
//支持1到多个参数
func sum(n1 int,args… int) sum int{
}
说明:args是slice切片,通过args[index]可以访问到各个值;args放后面
package main
import (
"fmt"
)
func getsum(n1 int,args... int) int{
sum:=n1
for i:=0;i<len(args);i++{
sum+=args[i]
}
return sum
}
func main() {
res:=getsum(10,0,-1,90,10,100)
fmt.Println("res= ",res)
}
(12)函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效。
- 包
包的本质实际上是创建不同的文件夹,来存放程序文件;
(1)go的每一个文件都是属于一个包的,也就是说go是以包的形式来管理文件和项目目录结构的;
(2)包可以控制函数、变量等访问范围,即作用域。
(3)打包基本语法:package 包名
引入包的基本语法:import “包的路径”
package util
import "fmt"
//将计算的功能放到一个函数中,然后在使用使用时,调用即可
//为了让其他包的文件使用Cal函数,需要将C大写,类似其他语言的public
func Cal(n1 float64,n2 float64,operator byte) float64{
var res float64
switch operator{
case '+':
res=n1+n2
case '-':
res=n1-n2
case '*':
res=n1*n2
case '/':
res=n1/n2
default:
fmt.Println("操作符号错误")
}
return res
}
package main
import (
"fmt"
"go_code/project01/util"//导入包
)
func main() {
n1:=4.5
n2:=6.7
var operator byte='*'
result:=util.Cal(n1,n2,operator) //引用包,包名.函数名
fmt.Println("result=",result)
}
(4)在给一个文件打包时,该包对应一个文件夹,文件的包名通常和文件所在的文件夹名一致,一般为小写字母。
(5)在import 包时,路径从src下开始,不用带src,编译器会自动从src下开始引入。
(6)如果包名较长,Go支持给包取别名,取别名后,原来的包名不能使用
package main
import (
"fmt"
u "go_code/project01/util"//取别名
)
func main() {
n1:=4.5
n2:=6.7
var operator byte='*'
result:=u.Cal(n1,n2,operator) //引用包
fmt.Println("result=",result)
}
(7)在同一包下,不能有相同的函数名,也不能有相同的全局变量名,否则报重复定义。(同一个文件夹下两个go文件也不可以)
(8)如果要编译一个可执行程序文件,就需要将这个包声明为main,即package main.但是写一个库,包名可以自定义。
编译的指令,在项目目录下,编译路径也不需要带src,编译器会自动带。此外,可执行文件还可以指定名字和目录。
go build -o src/my.exe go_code/project01/main
-
递归函数:一个函数在函数体内又调用了本身,我们称为递归调用。
(1)执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
(2)函数的局部变量是独立的,不会相互影响。
(3)当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁;同时当函数执行完毕或者返回时,该函数本身也会被系统销毁。 -
init函数
每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被go运行框架调用,也就是说init会在main函数前被调用。
(1)如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程是变量定义->init函数->main 函数
(2)init函数最主要的作用,就是完成一些初始化的工作。
(3)如果main.go和util.go都含有变量定义,init函数时,执行的流程 -
匿名函数
go支持匿名函数,如果某个函数只是希望使用一次,可以考虑使用匿名函数。匿名函数也可以实现多次调用。
匿名函数使用方式1:再定义匿名函数时就直接调用。
匿名函数使用方式2:将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数。
package main
import (
"fmt"
)
func main() {
res1:=func(n1 int,n2 int) int{
return n1+n2
}(10,20)
fmt.Println("res1= ",res1)
a:=func(n1 int,n2 int) int{
return n1-n2
}
res2:=a(10,30)
fmt.Println("res2= ",res2)
}
res1= 30
res2= -20
全局匿名函数:如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以再程序有效。
package main
import (
"fmt"
)
var (
//fun1是一个全局匿名函数
fun1=func(n1 int,n2 int) int{
return n1*n2
}
)
func main() {
res:=fun1(4,9)
fmt.Println("res= ",res)
}
res= 36
- 闭包
闭包就是一个函数和与其相关的应用环境组合的一个整体(实体)
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)) //11
fmt.Println(f(2)) //13
fmt.Println(f(3)) //16
}
(1)AddUpper()是一个函数,返回的数据类型是func (int) int
(2)AddUpper()中,返回的是一个匿名函数,但是这个函数引用到函数外的n,因此这个匿名函数就和n形成一个整体,构成闭包。
(3)可以理解为闭包是类,函数是操作,n是字段。函数和它使用到n构成闭包。
(4)当反复调用f函数时,因为n是初始化一次,因此每调用一次就进行累计。
(5)搞清楚闭包的关键,就是要分析出返回的函数使用(引用)到哪些变量,因为函数和它引用的变量共同构成闭包。
(6)闭包的使用
package main
import (
"fmt"
"strings"
)
//makeSuffix(suffix string)可以接收一个文件后缀名(.jpg),并返回一个闭包
//调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg),则返回 文件名.jpg,如果已经有后缀,则返回原文件名
func makeSuffix(suffix string) func (string) string{
return func(name string) string{
//如果没有指定后缀则加上,否则返回原来的名字
if !strings.HasSuffix(name,suffix){
return name+suffix
}
return name
}
}
func main() {
f2:=makeSuffix(".jpg")
fmt.Println("文件名处理后=",f2("winter"))
fmt.Println("文件名处理后=",f2("bird.jpg"))
}
文件名处理后= winter.jpg
文件名处理后= bird.jpg
- defer
在函数中,需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时释放资源,go提供defer(延时机制)
package main
import (
"fmt"
)
func sum(n1 int,n2 int) int{
defer fmt.Println("ok1 n1=",n1)//3
defer fmt.Println("ok2 n2=",n2)//2
res:=n1+n2
fmt.Println("ok3 res=",res)//1
return res
}
func main() {
res:=sum(10,20)
fmt.Println("res=",res) //4
}
ok3 res= 30
ok2 n2= 20
ok1 n1= 10
res= 30
(1)当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈)
(2)当函数执行完毕后,再从defer 栈,按照先入后出的方式出栈,执行
(3)在defer将语句放入到栈时,也会将相关的值拷贝同时入栈。
package main
import (
"fmt"
)
func sum(n1 int,n2 int) int{
defer fmt.Println("ok1 n1=",n1)//3
defer fmt.Println("ok2 n2=",n2)//2
n1++
n2++
res:=n1+n2
fmt.Println("ok3 res=",res)//1
return res
}
func main() {
res:=sum(10,20)
fmt.Println("res=",res) //4
}
ok3 res= 32
ok2 n2= 20
ok1 n1= 10
res= 32
(4)当函数执行完毕后,defer可以及时的释放函数创建的资源
//关闭文件资源
func test(){
file=openfile(文件名)
//当函数结束时,执行file.close()
defer file.close()
//...
}
package main
import (
"fmt"
)
//释放数据库资源
func test(){
connect=openDatabse()
//当函数结束时,执行connect.close()
defer connect.close()
//...
}
在go中,创建资源后,比如打开了文件、获取了数据库的链接,或者是锁资源,可以执行defer connect.close()等。在defer后,可以继续使用创建的资源。当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源。