go语言之函数及闭包

一:函数

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 语句放在错误检查语句之后。
//2
defer 也有明显的副作用: 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))
}
原文地址:https://www.cnblogs.com/liucsxiaoxiaobai/p/10801361.html