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

2023-08-14  本文已影响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 率。

上一篇下一篇

猜你喜欢

热点阅读