美文网首页
8 函数和方法: Go语言中的函数和方法

8 函数和方法: Go语言中的函数和方法

作者: 滔滔逐浪 | 来源:发表于2023-08-14 14:35 被阅读0次
    func main(){}
    

    他有几部分构成
    1,任何一个函数的定义,都有一个func关键字,用于声明一个函数,就像使用var关键字声明一个变量一样;
    2,然后紧跟的main是函数的名字,命名符合Go语言的规范即可,比如不能以数字开头;
    3,main函数名字后面的一对括号()是不能省略的,括号里可以定义函数使用的参数,这里的main函数没有参数,所以空括号();
    4括号()后还可以有函数的返回值,因为main函数没有返回值,所以这里没定义;
    5,最后是大括号{}函数体,你可以在函数体里书写代码,写该函数自己的业务逻辑。

    函数声明

    func funcName(params) result{
        body
    }
    
    

    这就是一个函数的签名定义,他包含一下几个部分
    1,关键字 func
    2, 函数名字 funcName
    3 函数的参数params,用来定义形参的变量名和类型,可以有一个参数,也可以有多个,也可以没有
    4,result是返回的函数值,用于定义返回值的类型,如果没有返回值,省略即可,也可以有多个返回值
    5,body就是函数体,可以在这里写函数的代码;逻辑

    现在,自定义一个函数

    func sum(a int, b int) int {
        return a + b
    }
    
    
    

    这是一个计算2数之和的函数,函数的名字是sum,他有2个参数a,b 参数的类型都是int.sum函数的返回值也是int类型,函数体部分就是把a+b通过关键字返回,如果函数没有返回值,可以不用使用return 关键字。

    函数中形参的定义和我们定义变量是一样的,都是变量名称在前,变量类型在后,只不过在函数里变量名称叫做参数名称,也就是函数的形参,形参只能在改函数体内使用,函数形参的值由调用者提供,,这个值也称为函数的实参,现在我们传递实参给sum函数,演示函数的调用,如下面的代码所示:

    func main() {
        result:=sum(1,2)
        fmt.Println(result)
    }
    
    

    我们自定义的sum函数,在main函数中直接调用,调用的时候需要提供真是的参数,也就是实参1和2
    函数的返回值被赋值给变量result,然后把这个结果打印出来,你可以自己运行下,能看到结果3,这样我们就通过函数sum达到了2数相加的目的如果其他业务逻辑也需要两数相加,那么就可以直接使用这个 sum 函数,不用再定义了。
    在以上函数定义中,a 和 b 形参的类型是一样的,这个时候我们可以省略其中一个类型的声明,如下所示:

    func sum(a, b int) int {
    
        return a + b
    
    }
    

    像这样使用逗号分割变量,后面统一使用int类型,这和变量的声明是一样的,多个相同类型的变量都可以这么声明。
    多值返回
    同有的编程语言不一样,Go语言的函数可以返回多个值,也就是多值返回,在Go语言的标准库中,你可以看到很多这样的函数:第一个值返回函数的结果,第二个值返回函数出错的信息,这种就是多值返回的经典应用。

    对于sum函数,假设我们不允许提供的实参是负数,可以这样改造: 在实参是负数的时候,通过多值返回,返回函数的错误信息,

    func sum(a,b int) (int,error){
        if a<0 || b<0{
            return 0,errors.New("a或者b不能是负数")
        }
        return  a+b,nil
    }
    
    
    

    这里需要注意的是,如果函数有多个返回值,返回值的类型定义需要使用小括号括起来,也就是(int,error),这代表函数sum有2个返回值,第一个是int类型,第二个是error类型,我们在函数体重使用return 返回结果的时候,也要符合这个类型顺序
    在函数体重,可以使用return返回多个值,返回的多个值通过逗号分割即可,返回多个值的类型顺序要和函数声明的返回类型顺序一致,比如下面的例子

    return 0,errors.New("a或者b不能是负数")
    

    返回的第一个值0 是int类型,第二个值是error类型,和函数定义的返回类型完全一致
    定义好了多值返回的函数,现在我们用如下代码尝试调用:

    package main
    
    import (
        "errors"
        "fmt"
    )
    
    func main() {
        result, err := sum(1, 2)
        if err != nil {
            fmt.Println(err)
        } else {
            fmt.Println(result)
        }
    }
    
    func sum(a, b int) (int, error) {
        if a < 0 || b < 0 {
            return 0, errors.New("a或者b不能是负数")
        }
        return a + b, nil
    }
    
    

    函数有多值返回的时候,需要有多个变量接收它的值,示例中使用result和err变量,使用逗号分开。
    如果有的函数的返回值不需要,可以使用下划线_丢弃它。如下所示’

     result,_:=sum(1,2)
    

    这样即可忽略函数sum函数返回的错误信息,也不用做判断
    提示: 这里使用的error 是Go语言内置的一个接口,用于表示程序的错误信息。
    命名返回参数

    不止函数的参数可以有变量名称,函数的返回值也可以,也就是说你可以为每个返回值都起一个名字,这个名字可以像参数一样在函数体内使用
    现在我们继续对sum函数的例子进行改造,为其返回值命名,

    func sum(a, b int) (sum int,err error){
    
        if a<0 || b<0 {
    
            return 0,errors.New("a或者b不能是负数")
    
        }
    
        sum=a+b
    
        err=nil
    
        return
    
    }
    
    

    示例中,命名的两个返回值名称,一个是 sum,一个是 err,这样就可以在函数体中使用它们了。

    通过下面示例中的这种方式直接为命名返回参数赋值,也就等于函数有了返回值,所以就可以忽略 return 的返回值了,也就是说,示例中只有一个 return,return 后没有要返回的值。

    通过命名返回参数的赋值和直接使用return 返回值的方式结果是一样的,所以调用以上sum函数,返回的结果也一样
    可变参数
    可变参数,就是函数的参数数量是可变的,比如常见的fmt.PrintIn函数。
    同样一个函数,可以不传参数,也可以传递一个参数,也可以2个参数,也可以是多个等等,这种函数就是具有可变参数的函数,

    fmt.Println()
    fmt.Println("飞雪")
    fmt.Println("飞雪","无情")
    

    下面所演示的是 Println 函数的声明,从中可以看到,定义可变参数,只要在参数类型前加三个点 … 即可:

    func Println(a ...interface{}) (n int, err error)
    

    func sum1(params ...int) int {
    sum := 0
    for _, i := range params {
    sum += i
    }
    return sum
    }
    为了便于和 sum 函数区分,我定义了函数 sum1,该函数的参数是一个可变参数,然后通过 for range 循环来计算这些参数之和。

    讲到这里,相信你也看明白了,可变参数的类型其实就是切片,比如示例中 params 参数的类型是 []int,所以可以使用 for range 进行循环。

    函数有了可变参数,就可以灵活地进行使用了。

    如下面的调用者示例,传递几个参数都可以,非常方便,也更灵活:
    ch05/main.go

    fmt.Println(sum1(1,2))
    fmt.Println(sum1(1,2,3))
    fmt.Println(sum1(1,2,3,4))
    这里需要注意,如果你定义的函数中既有普通参数,又有可变参数,那么可变参数一定要放在参数列表的最后一个,比如 sum1(tip string,params …int) ,params 可变参数一定要放在最末尾。

    包级函数
    不管是自定义的函数sum,sum1还是我们使用到的函数PrintIn,都会从属于一个包,也就是package.sum函数属于main包,printIn函数属于fmt包
    同一个包中的函数哪怕是私有的也可以被调用,如果不同包的函数要被调用,那么函数的作用域必须是共有的,也就是函数名称的首字母要大写,比如Printin

    1,函数名称首字母小写代表私有函数,只有在一个包中才可以被调用
    2,函数名称首字母大写代表公有函数,不同的包也可以被调用
    3,任何一个函数都会从属于一个包

    Go语言没有用Public,private这样的修饰符来修饰函数是共有还是私有,而是通过函数名称的大小写来代表,这样省略了繁琐的修饰符,更简洁。

    匿名函数和闭包
    顾名思义,匿名函数就是没有名字的函数,这就是他和正常函数的主要区别。
    在下面的示例中,变量sum2所对应的值就是一个匿名函数,需要注意的是,这里的sum2只是一个函数类型的变量,并不是函数的名字

    sum2:= func(a,b int)int{
            return a+b
        }
        fmt.Println(sum2(1,2))
    

    通过sum2 ,我们可以对匿名函数进行调用,以上示例算出的结果是3,和使用正常的函数一样

    有了匿名函数,就可以在函数中在定义函数(函数嵌套),定义的这个匿名函数,也可以称为内部函数。更重要的是,在函数内定义的内部函数,也可以使用外部函数的变量等,这种方式也称为闭包。
    我们用下面的代码进行演示;

    func main() {
        cl:=colsure()
        fmt.Println(cl())
        fmt.Println(cl())
        fmt.Println(cl())
    }
    
    func colsure() func() int {
        i:=0
        return func() int {
            i++
            return i
        }
    }
    

    运行这个代码,你会看到输出打印的结果是:

    1
    2
    3

    这个得益于匿名函数闭包的能力,让我们定义的colsure函数,可以返回一个匿名函数,并且支持外部函数colsure的变量i,因而在main函数中,每调用一次cl(),i的值就会加1
    在Go语言中,函数也是一种类型,他可以被用来声明函数类型的变量,参数或者作为另一个函数的返回值类型

    方法

    不同于函数的方法
    在Go语言中,方法和函数是2个概念,但又非常类似,不同点在于方法必须要有一个接收者,这个接收者是一个类型,这样方法就和这个类型绑在一起,称为这个类型的方法。
    在下面的示例中,type Age unit 表示定义一个新类型Age,该类型等价于unit,可以理解为类型unit的重命名,其中type 是Go语言关键字,表示定义一个类型,

    type Age uint

    func (age Age) String() {
    fmt.Println("the age is", age)
    }

    示例中方法 String() 就是类型Age的方法,类型Age是方法String()的接受者
    和函数不同,定义方法时会在关键字func 和方法名String 之间加一个接收者(age Age),接收者使用小括号包围
    接收着的定义和普通变量,函数参数一样,前面是变量名,后面是接收者类型。
    现在方法String()就和类型Age绑在一起了,String()是类型AGe的方法;
    定义接收者的方法后,就可以通过操作符调用的方法,如下代码所示;

    func main() {
        age:=Age(25)
        age.String()
    }
    
    

    运行这段代码,可以看出如下结果

    the age is 25
    
    

    接收者就是函数和方法的最大不同,此外,上面所讲到的函数具备的能力,方法也都具备

    提示:因为 25 也是 unit 类型,unit 类型等价于我定义的 Age 类型,所以 25 可以强制转换为 Age 类型。
    值类型接收者和指针类型接收者
    方法的接收者除了可以是值类型,也可以是指针类型
    定义的方法的接收者类型是指针,所以我们对指针的修改是有效的,如果不是指针,修改就没有效果。

    func (age*Age) Modify(){
        *age=Age(30)
    }
    
    

    调用一次modify 方法后,在调用String方法查看结果,会发现已经变成了30,说明基于指针的修改有效,如图所示:
    ···
    age := Age(25)
    age.String()
    age.Modify()
    age.String()
    ···
    提示在调用方法的时候,传递的接收者本质上都是副本,之不过一个是这个值副本,一个指向这个值指针的副本,指针具有指向原有值的属性,所以修改了指针指向的值,也就修改了原有的值,我们可以简单的理解为值接收者使用的是值的副本来调用方法,而指针接收者使用实际的值来调用方法
    示例中调用指针接收者方法的时候,使用的是一个值类型的变量,并不是一个指针类型,其实这里使用指针变量调用也是可以的,如下代码所示

        (&age).Modify()
        age.String()
    

    这就是Go语言编译器帮我们自动做的事情:
    如果使用一个值类型变量调用指针类型接收者的方法,Go语言编译器会自动帮我们取指针调用,以满足指针接收者的要求
    同样的原理,如果使用一个指针类型变量调用值类型接收者的方法,Go语言编译器会自动帮我们解引用调用,以满足值类型接收者的要求。
    总之,方法的调用者,既可以是值也可以是指针,不用太过关注这些,Go语言会帮助我们自动转意,大大提高开发效率,同时避免不小心早造成的bug.

    不管是使用值类型接收者,还是指针类型接收者,要先确定你的要求,在对类型进行操作的时候是要改变当前接收者的值,还是要创建一个新值进行返回,这些就可以决定使用哪种接收者
    总结
    在Go语言中,虽然存在函数和方法2个概念,但是他们基本相同,不同的是所属的对象。函数属于一个包,方法属于一个类型,所以方法也可以简单的理解为何一个类型关联的函数

    不管是函数还是方法,它们都是代码复用的第一步,也是代码职责分离的基础。掌握好函数和方法,可以让你写出职责清晰、任务明确、可复用的代码,提高开发效率、降低 Bug 率。

    相关文章

      网友评论

          本文标题:8 函数和方法: Go语言中的函数和方法

          本文链接:https://www.haomeiwen.com/subject/vixcmdtx.html