go中,函数分为 自定义函数 和 系统函数
func 函数名(形参列表) (返回值类型列表) {
执行语句...
return 返回值列表
}
package main import "fmt" func cal(n1 int,n2 int,str string) int { var res int switch str { case "+": res = n1 + n2 case "-": res = n1 - n2 case "*": res = n1 * n2 case "/": res = n1 / n2 default: fmt.Println("操作符错误") } return res } func main() { n1 := 10 n2 := 2 var string = "/" result := cal(n1,n2,string) fmt.Println("result=",result) }
函数的调用过程:
调用函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其他栈的空间区分开来
在每个函数对应的栈中,数据空间是独立的,不会混淆
当一个函数执行完毕后,程序会销毁这个函数对应的栈空间
return
go函数支持返回多个值
如果返回多个值,在接收时,希望忽略某个返回值,则使用_符号表示占位忽略
如果返回值只有一个,(返回值类型列表) 可以不写 ()
递归函数:一个函数在函数体内又调用了本身
package main import "fmt" func test(n int) { if n > 2 { n -- test(n) } fmt.Printf("n=",n) } func main() { test(4) }
执行一个函数时,就会创建一个新的受保护的独立空间(新函数栈)
函数的局部变量是独立的,不会相互影响
递归必须有明确的结束条件,并向结束条件逼近,否则会无限循环
当一个函数执行完毕时/或者遇到return,就会返回,遵循谁调用返回给谁,同时函数本身也会被销毁
*** 函数使用的注意事项与细节
函数的形参列表可以是多个,返回值列表也可以是多个
形参列表和返回值列表的数据类型,可以是值类型和引用类型
函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写可以被本包和其它包文件使用,首字母小写只能被本包文件使用
函数内的变量是局部的,函数外不生效
基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值
func test(n int) { n = n + 10 fmt.Printf("test_n=",n) } func main() { n := 20 test(n) fmt.Printf("main_n=",n) }
如果希望在函数内修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。
package main import "fmt" func test(n *int) { *n = *n + 10 fmt.Println("test_n=",*n) } func main() { n1 := 20 test(&n1) fmt.Println("main_n1=",n1) }
go函数不支持函数重载(即函数重新定义)
在go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量,通过该变量,可以对函数进行调用
package main import "fmt" func test() { fmt.Println(111111) } func main() { a := test a() fmt.Printf("a的数据类型是%T,test的数据类型是%T",a,test) // 数据类型都是 func() }
函数是一种数据类型,在go中,函数可以作为形参,并且调用
package main import "fmt" func getSum(n1 int,n2 int) int { return n1 + n2 } func myFun(funvar func(int,int) int,num1 int,num2 int) int { return funvar(num1,num2) } func main() { res2 := myFun(getSum,50,60) fmt.Println("res2=",res2) }
go支持自定义数据类型:
type 自定义数据类型名字 数据类型 (相当于取别名)
type myInt int (myInt 等价于 int)
package main import "fmt" func main() { type myInt int var num1 myInt var num2 int num1 = 40 num2 = int(num1) // go认为myInt与int是两个类型,所以需要转换 fmt.Println("num1=",num1) fmt.Println("num2=",num2) }
支持对函数返回值命名
package main import "fmt" func test() (a int,b int) { return 1,2 } func main() { n1,n2 := test() fmt.Println(n1) fmt.Println(n2) }
使用_标识符,忽略返回值
package main import "fmt" func test() (a int,b int) { return 1,2 } func main() { n1,_ := test() fmt.Println(n1) }
支持可变参数 args
package main import "fmt" // func sum(args... int) int { // 0到多个参数 func sum(n1 int,args... int) int { // 1到多个参数,args是slice切片,通过 args[index] 访问每一个值 sum := n1 for i := 0 ; i < len(args) ; i++ { sum += args[i] } return sum } func main() { res := sum(1,2,3,4,5,6) fmt.Println(res) }
init函数
每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被go运行框架调用,也就是说,init会在main函数前被调用
如果文件同时包含 全局变量,main,init,执行流程是 全局变量 > init > main
init函数作用:完成一些初始化的工作
package main import "fmt" var n = test() func test() string { fmt.Println("--test--") return "无所谓" } func init() { fmt.Println("--init--") } func main() { fmt.Println("--main--") fmt.Println(n) }
匿名函数
没有名字的函数,如果函数只使用一次,可以使用匿名函数
package main import "fmt" func main() { n := func (n1 int,n2 int) int { return n1 + n2 }(10,20) fmt.Println(n) }
也可以实现多次调用 (吧函数赋值给变量)
package main import "fmt" func main() { a := func (n1 int,n2 int) int { return n1 + n2 } p := a(10,20) q := a(20,30) fmt.Println(p,q) }
全局匿名函数
如果将匿名函数赋值给一个全局变量,那么这个匿名函数,就成为全局匿名函数,可以在程序中有效
package main import "fmt" var ( Fun1 = func (n1 int,n2 int) int { return n1 + n2 } ) func main() { n := Fun1(10,20) fmt.Println(n) }
闭包
内部函数引用了外部函数的变量,被内部函数引用的变量,不会因为外部函数结束而被释放掉,而是一直存在内存中,直到内部函数被调用结束。
package main import ( "fmt" ) func a() func() int { // 函数a 的返回值是func,func的返回值是int i := 0 fmt.Println("a",i) b := func() int { i++ fmt.Println("func",i) return i } return b } func main() { a() // a 0 c := a() // a 0 c() // func 1 c() // func 2 c() // func 3 a() // a 0 }
函数的defer
在函数中,程序员需要创建资源(比如数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,引入了 defer(延时机制)
package main import "fmt" func sum(n1 int,n2 int) int { // 当执行到defer时,本行暂不执行,会将defer后面的语句压入到独立的栈中(defer栈) // 当函数执行完毕后,再从defer栈,按照后入先出的方式出栈,执行 defer fmt.Println("3",n1) defer fmt.Println("2",n2) res := n1 + n2 defer fmt.Println("1",res) return res } func main() { n := sum(10,20) fmt.Println("4",n) }
注意事项与细节
package main import "fmt" func sum(n1 int,n2 int) int { // 当执行到defer时,本行暂不执行,会将defer后面的语句压入到独立的栈中(defer栈) // 当函数执行完毕后,再从defer栈,按照后入先出的方式出栈,执行 defer fmt.Println("3,n1",n1) defer fmt.Println("2,n2",n2) n1 ++ n2 ++ res := n1 + n2 defer fmt.Println("1",res) // 32 return res } func main() { n := sum(10,20) fmt.Println("4",n) }
函数参数传递方式
值传递与引用传递,值类型参数默认是值传递,引用类型参数默认是引用传递
不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝。一般来说,地址拷贝效率高,因为数据量小,而值拷贝效率决定于数据量,数据量越大,效率越低。
值类型:int,float,bool,string,数组,结构体
引用类型:指针,slice切片,map,管道chan,interface 等
值传递与引用传递的特点:
- 值传递:值类型默认是值传递,变量直接存储值,内存通常在栈中分配。
- 引用传递:引用类型默认是引用传递,变量存储的是一个地址,这个地址对应的空间才真正存储数据,内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。
- 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。
变量的作用域
函数内声明的变量叫局部变量,作用域仅限于函数内部
函数外部声明的变量叫全局变量,作用域在整个包都有效,如果首字母大写,则作用域在整个程序有效
如果作用域在一个代码块中,比如if/for,那么作用域仅在该代码块中有效