一:函数
1 概述:
函数是 Go 程序源代码的基本构造单位,一个函数的定义包括如下几个部分,函数声明关键字 也町、 函数名、参数列表、返回列表和函数体。
函数名遵循标识符的命名规则, 首字母的大小写决定该函数在其他包的可见性:大写时其他包可见,小写时只有相同的包可以访问;
func 函数名 (参数列表) (返回值列表) { 函数体 }
2 特点
函数声明的格式
方法名首字母大写是public,方法名首字母小写private私有方法
1)函数类型
package main import ( "fmt" ) //1 无参无返回值 func test(){ fmt.Println("三无产品") } //2 有参无返回值 func test1(v1 int,v2 int){ fmt.Println(v1,v2) } //3 有不定参数无返回值 func test2(args ...int){ for _,n :=range args{ fmt.Println(n) } } //调用以上函数 func main(){ test() test1(1,2) test2(1,2,3,4) }
package main
import "fmt"
//无参有返回值
func test4(a int ,str string){
a = 666
str= "我是沙雕"
return
}
func test5() (int,string){
return 250,"字符串"
}
func main(){
a,str := test4() //test4没有返回值,运行会报错
fmt.Println(a,str)
_,s :=test5()
fmt.Println(s)
}
package main import ( "fmt" ) //有参有返回值 //求两个数的最大值和最小值 func test1(num1 int,num2 int) (min int,max int){ if num1>num2{ min=num2 max=num1 }else { max=num2 min=num1 } return } func main(){ min,max := test1(33,22) fmt.Println(min,max) }
求1~100的和代码实现两种方法
package main import "fmt" //循环实现1到100累加 func test01() int { sum :=0 for i :=1;i<=100;i++{ sum +=i } return sum } //递归实现1到100的累加 func test02(num int)int{ if num==1{ return 1 } return num +test02(num-1) } func main() { fmt.Println(test01()) fmt.Println(test02(100)) }
2)注意:
(1) 有参无返回值 ,参数名就相当于函数体内层的局部变量,命名返回值变量会被初始化类型零值 (2)不支持默认值参数。 (3)不支持函数重载。 (4)不支持函数嵌套,严格地说是不支持命名函数的嵌套定义,但支持嵌套匿名函数
3 多值返回
func swap(a,b int) (int,int){ return b,a }
4 实参到形参的传递
package main import "fmt" func chvalue(a int) int{ a=a+1 return a } func chpointer(a *int){ *a = *a +1 return } func main() { a :=10 chvalue(a) //实参传递给形参是值拷贝 fmt.Println(a) chpointer(&a) //实参传递给形参然仍是值拷贝,只不过复制的是a的地址值 fmt.Println(a) }
5 不定参数
不定参数声明使用 (参数 .. . type) 的语法格式
特点:
(1) 所有的不定参数类型必须是相同的 (2)不定参数必须是函数的最后一个参数。 (3)不定参数名在函数体 内相当于切片,对切片的操作同样适合对不定参数的操作
package main func sum(arr ...int)(sum int){ for _, v := range arr{ //arr相当于切片 sum += v } return }
6 切片可以作为参数传递给不定参数,切片名后要加上 ”...“
package main //import "go/types" func sum(arr ...int)(sum int){ for _,v :=range arr{ sum += v } return } func main() { slice := []int{1,2,3,4} array := [...]int{1,2,3,4} //数组不可以作为实参传递给不定参数的函数 所以sum(array...)会报错 cannot use array (type [4]int) as type []int in argument to sum sum(slice...) }
7 形参为不定参数的函数的函数和形参为切片的函数类型不相同
package main import ( "fmt" ) func suma(arr ...int)(sum int){ for v := range arr{ sum += v } return } func sumb(arr []int) (sum int) { for v := range arr{ sum += v } return } func main() { //suma 和sumb的类型不一样 fmt.Printf("%T ",suma) fmt.Printf("%T ",sumb) }
8 函数签名
概述:
函数类型又 函数签名 个函 类型就是函数定义首行去掉函数名、参数名和{,可以
使用台nt.Printf 的”%T”格式化参数打印函数的类型。
package main import "fmt" func add(a,b int) int { return a + b } func main() { fmt.Printf("%T ",add) //打印效果 func(int, int) int }
两个函数类型相同的条件是:拥有相同的形参列表和返回值列表(列表元素的次序、个数和类型相同)形参名可以不同
func add(a,b int) int { return a+b) func sub (x int, y int) (c int) { c=x- y ; return c )
可以使用 type 定义函数类型,函数类型变量可以作为函数的参数或返
package main import "fmt" func add(a,b int) int { return a + b } func sub(a,b int) int { return a - b } type Op func(int,int) int //定义一个函数类型,输入的是两个int类型 //返回值是一个int类型 func do(f Op,a,b int) int { //定义一个函数,第一个参数是函数类型Op return f(a,b) //函数类型变量可以直接用来进行函数调用 } func main(){ a := do(add,1,2) //函数名add可以当作相同函数类型的形参 fmt.Println(a) //3 s := do(sub,1,2) fmt.Println(s) //-1 }
总结:
1 实际函数类型变 和函数名都可以当作指针变量,该指针指向函数代码开始位置 通常说函数类型变量是一 种引用类型,未初始化的函数类型变量的默认值是nil 2 有名函数的函数名可以看作函数类型的常 ,可以 直接使用函数名调用函数,也可以直接赋值给函数类型变量,
package main func sum(a,b int) int{ return a + b } func main(){ sum(3,4) //直接调用 f := sum //有名函数可以直接赋值给变脸 f(1,2) }
9 匿名函数
Go 提供两种函数 有名函数和匿名函数。匿名函数可以看作函数字面量 所有直接使用函
数类型变量的地方都可以由匿名函数代替。医名函数可以直接赋值给函数变量,可以当作实参,
也可以作为返回值,还可以直接被调用
package main import "fmt" //匿名函数被直接赋值函数变量 var sum = func(a,b int) int { return a + b } func doinput(f func(int,int) int,a,b int) { return } //匿名函数作为返回值 func wrap(op string) func(int, int) int{ switch op { case "add": return func(a int, b int) int { return a +b } case "sub": return func(a int, b int) int { return a + b } default: return nil } } func main() { //匿名函数被直接调用 defer func() { if err :=recover();err !=nil{ fmt.Println(err) } }() sum(1,2) //匿名函数作为实参 doinput(func(x , y int) int { return x + y },1,2) opFunc :=wrap("add") re := opFunc(2,3) fmt.Printf("%d ",re) fmt.Println(f) }
二 : defer关键字
使用
Go 函数里提供了 defe 关键字,可以注册多个延迟调用,这些调用以先进后出( FILO )的
顺序在函数返回前被执行
package main import "fmt" func test(x int){ fmt.Println(100/x) } func main() { //defer 是延迟操作 defer fmt.Println("aaa") defer fmt.Println("bbb") //报错并不影响程序的运行 defer test(0) defer fmt.Println("ccc") }
注意 :
1 defer 后面必须是函数或方法的调用,不能是语句,否则会报 express on in defer must be function call 错误。 2 defer 函数的实参在注册时通过值拷贝传递进去。
package main func f() int{ a := 0 defer func(i int) { println("defer i=",i) //defer i= 0 }(a) a++ return a } func main() { f() }
//注:实参a 的值在defer注册时通过值拷贝传递进去,后续语句a++不会影响defer语句最后输出结果
3 defer语句必须先注册后才能执行,如果defer位于return之后,则defer因为没有注册,不会执行
package main func main(){ defer func() { println("first") }() a := 0 println(a) //0 return defer func() { println("second") //first }() }
4 defer的好处是可以在一定程度上避免资源泄漏,特别是在有很多return语句,有多个资源需要关闭的场景, 很容易漏掉资源关闭操作
func CopyFile (dst , src string) (w int64 , err error) { src , err := os.Open(src) if err != nil { return } dst, e rr := os . Create(dst) if err != nil { //src 很容易忘记关闭 src.Close () returη } w, err =工 Copy dst src ) dst.Close() src.Close () return }
在打开资源无报错后直接调用 defer 关闭资源
func CopyFile (dst , src string) (w nt64 err error) { src , err := os.Open (src ) if err != nil { return } defer src . Close() dst , err := os . Create(dst) if err != nil { return } defer dst. Close () w, err =工 Copy(dst src) return }
//总结 1defer 语句的位置不当,有可能导致 panic 一般 def1 语句放在错误检查语句之后。
//2defer 也有明显的副作用: defer 会推迟资源的释放, defer 尽量不要放到循环语句里面,将大函数内部的defer语句单独拆分成
//一个小函数是一种很好的实践方式。另外, defer 相对于普通的函数调用需要间接的数据结构的支持,相对于普通函数调用有一定的性能损耗
//3 defer r 中最好不要对有名返回值参数进行操作
四 :闭包
1 概述: 闭包是由函数及其相关引用环境组合而成的实体,一般通过在匿 名函数中引用外部函数的局部变量或包全局变构成。 2 引用:闭包=函数+引用环境 详解: 闭包对闭包外的环境引入是直接引用,编译器检测到闭包,会将闭包引用的外部变量分配到堆上 如果函数返回的闭包引用了该函数的局部变量( 参数或函数内部变量〉 (1)多次调用该函数,返回的多个闭包所引用的外部变量是多个副本,原因是每次调用函 数都会为局部变量分配内存 (2)用一个闭包函数多次,如果该闭包修改了其引用的外部变量,则每一次调用该闭包对 该外部变量都有影响,因为闭包函数共享外部引用。
示例:
package main func fa(a int) func(i int) int{ return func(i int) int { println(&a,a) a= a+i return a } } func main(){ f :=fa(1) //f 引用的外部的闭包环境包括本次函数调用的形参a的值1 g := fa(1) //g 引用的外部的闭包环境包括本次函数调用的形参a的值1 //此时f、g引用的闭包环境中的a的值并不是同一个,而是两次函数调用产生的副本 println(f(1)) //0xc00006c000 1 2 //多次调用f引用的同一个副本a println(f(1)) //0xc00006c000 2 3 //g中a的值然仍是1 println(g(1)) //0xc00006c008 1 2 println(f(1)) //0xc00006c000 3 4 }
//f和g引用的是不同的a
(3)如果函数返回的闭包引用的是全局变量 ,则多次调用该函数返回的多个闭包引用的都是同一个a。
同理,调用 个闭包多次引用的也是同一个 。此时如果闭包中修改了a 值的逻辑,
每次闭包调用都会影响全局变量 的值。
示例:
package main var( a=0 ) func fa() func(i int) int { return func(i int) int { println(&a,a) a = a +i return a } } func main() { f :=fa() //f 引用的外部的闭包环境包括全局交量a g :=fa() //f 引用的外部的闭包环境包括全局变量a ///此时f、g 引用的闭包环境中的a 的值是同一个 println(f(1)) //0x4d68b8 0 1 println(g(1)) //0x4d68b8 0 2 println(g(1)) //0x4d68b8 0 3 println(g(1)) //0x4d68b8 0 4 }
(4)同一个函数返回的多个闭包共享该函数的局部
package main func fa(base int) (func(int) int,func(int) int) { print(&base,base) //0xc00006c00000xc00006c00800xc00006c000 1 add := func(i int) int { base += i println(&base,base) return base } sub := func(i int) int{ base -= i println(&base,base) return base } return add,sub } func main() { //f、g 闭包引用的 base 是同一个,是fa函数调用传递过来的实参值 f,g := fa(0) //s、k 包引用的base是同一个是fa函数调用传递过来的实参值 s,k := fa(0) //f、g和s、k 引用不同的闭包交量,这是由于fa每次调用都妥重新分配形参 println(f(1)) println(g(2)) println(s(1)) println(k(2)) }