【Go函数和方法】

函数和方法是迈向代码复用、多人协作开发的第一步。通过函数,可以把开发任务分解成一个个小的单元,这些小单元可以被其他单元复用,进而提高开发效率、降低代码重合度。在Go语言中有函数和方法两种概念,但是他们相似度非常高,只是所属对象不同函数属于一个包,方法属于一个类型,所以方法也可以简单的理解为和一个类型关联的函数。

函数

函数声明

​ 函数的声明格式如下, 它由几部分组成:

func funcName(params) result {
    body
}
  • 关键字func,任何一个函数的定义,都有一个func关键字,用于声明一个函数,就像使用var关键字声明一个变量一样;
  • 函数名字 funcName,命名符合Go语言的规范即可,不能以数字开头,函数名字后面的一对括号()是不能省略的;
  • 函数的参数params,用来定义形参的变量名和雷新,可以有一个参数,也可以有多个,也可以没有;
  • result是返回的函数值,用于定义返回值的类型,如果没有返回值,省略即可,也可以有多个返回值;
  • body 就是函数体,可以在这里写函数的代码逻辑

​ 函数中形参的定义和我们定义变量是一样的,都是变量名在前,变量类型在后,只不过在函数里,变量名称叫做参数名称,也就是函数的形参,形参只能在该函数体内使用。函数形参的值由调用者提供,这个值也称为函数的实参。如果函数有返回值,使用return关键字返回,如果没有,可以不使用return关键字。

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

// 如果多个相同类型的变量可以这样声明,和变量的声明是一样的
func sum2(a,b int) int {
    return a+b
}

多值返回

​ 和其他编程语言不一样, Go语言的函数可以支持返回多个值,即多值返回。在Go语言标准库中,有很多这样的函数:第一个值返回函数的结果,第二个值返回函数出错的信息,这种就是多值返回的经典应用需要注意的是:如果函数由多个返回值,返回值部分的类型定义需要使用小括号括起来,也就是(int, error)。这代表函数有两个返回值,第一个是 int 类型,第二个是 error 类型,。我们在函数体中使用return返回结果的时候,也要符合这个类型顺序。

​ 在函数体中,可以使用return返回多个值,返回的多个值通过逗号分隔即可,返回多个值得类型顺序要和函数声明的返回顺序一致。

func sun1(a, b int) (int, error) {
    if a < 0 || b < 0 {
        // 提示: 这里的error是Go语言内置的一个接口,用于表示程序的错误信息。
        return 0, errors.New("a或者b不能为负数")
    }
    return a + b, nil
}

命名返回参数

​ 不止函数的参数可以有变量名称,函数的返回值也可以,即我们可以为每一个返回值都起一个名字,这个名字可以向参数一样在函数体内使用。 返回值的命名和参数、变量是一样的,名称在前,类型在后。虽然Go语言支持函数返回值命名,但是并不是太常用,根据自己的需求,酌情选择是否对函数返回值命名

func sum1(a, b int) (res int, err error) {
    if a < 0 || b < 0 {
        return 0, errors.New("a或者b不能为负数")
    }
    // 为函数返回参数赋值,就等于函数有了返回值,所以可以忽略return的返回值了
    res = a + b
    err = nil
    // return 可以不跟返回值
    return 
}

通过明明返回参数的赋值方式,和直接使用return返回值的方式结果是一样的。

可变参数

​ 可变参数,就是函数的参数数量是可变的,比如最常见的fmt.Println函数。同样一个函数,可以不传参数,可以传一个参数,也可以传两个参数,也可以传多个等等,这种函数就是就有可变参数的函数。

可变参数的定义

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


func sum1(parmas ...int) int {
    sum := 0
    for _,v := range parmas {
        sum += v
    }
    return sum
}

可变参数的类型其实就是切片,所以可以通过for range循环遍历。函数有了可变参数,就可以灵活的使用了。注意: 如果定义的函数中既有普通参数,又有可变参数,那么可变参数一定要放在参数列表的最后一个。

包级函数

​ 不管是自定义函数,还是系统函数,都会从属于一个包。即package,自定义函数sun属于main包,Println函数属于fmt包。

​ 同一个包中的函数即使是私有的(函数名称首字母小写)也可以相互调用。如果不同包的函数要被调用,那么函数的作用域必须是公开的,即函数名称的首字母要大写

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

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

匿名函数和闭包

​ 和正常函数的主要区别是:匿名函数没有函数名。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

func main() {
    // 变量sum所对应的值就是一个匿名函数,需要注意的是,这里的sum只是一个函数类型的变量,并不是函数的名字
    sum := func(a,b int) int {
        retun a + b
    }
    // 	有了匿名函数,我们可以对匿名函数进行调用,
    fmt.Println(sum(1,2))
}

​ 有了匿名函数,可以在函数中在定义函数(函数嵌套),定义的这个匿名函数,可以称为内部函数。更重要的是,在函数呢一顶一的内部函数,可以使用外部函数的变量,这种方式也称为闭包

提示:在Go语言中,函数也是一种类型,它可以被用来声明函数类型的变量、参数或者作为另一个函数的返回值类型。

func incr() func() int {
    x := 0
    return func() int {
        x++
        return x
    }
}

/** 调用这个函数会返回一个函数变量。
	i := incr() 通过把这个函数变量赋值给i,i就形成了一个闭包
	所以i保存着对x的引用,可以想象i中有一个指针指向x或者i中有x的地址
	由于i有着指向x的指针,所以可以修改想,且保持着状态。也就是说x逃逸了,它的生命周期没有随着它的作用域结束而结束。
*/

println(i()) // 1
println(i()) // 2
println(i()) // 3


// 但是这段代码却不会递增
println(incr()) // 1
println(incr()) // 1
println(incr()) // 1

// 因为这里调用了三次incr(),反悔了三个闭包,这三个闭包引用着三个不同的x,它们的状态是各自独立的。

方法

不同于函数的方法

​ 在Go语言中,方法和函数是两个概念,但又非常相似,不同点在于方法必须要有一个接收者,这个接收者是一个类型,这样方法就和这个类型绑定在一起,称为这个类型的方法

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

n := Age(18)
n.String()

​ type Age unit表示定义了一个新类型Name,该类型等价于uint,可以理解我类型uint的重命名。其中type是Go语言的关键字,表示定义一个类型。示例中,方法String()就是类型Age的方法,类型Age就是方法String()的接收者。

​ 和函数不同,定义方法是会在关键字func 和方法名之间加一个接收者,接收者使用小括号包围。现在方法String()就和类型Age绑定在一起了,String()是类型Age的方法。定义了接收者的方法后,可以通过点操作符调用方法。

​ 接收者就是函数和方法的最大不同,此外,函数具备的能力,方法同样具备。

值类型接收者和指针类型接收者

​ 方法的接收者除了可以是值类型,也可以是指针类型。

​ 定义的方法的接收者类型是指针,所以我们对指针的修改是有效的,如果不是指针,修改就是无效的。

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

age := Age(18)
// 值是18
age.String()
age.Modify()
// 值已经修改为45
age.String()

提示:在调用方法的时候,传递的接收者本质上都是副本,只不过一个是这个值副本,一个是指向这个值指针的副本。指针具有指向原有值的特性,所以修改了指针指向的值,也就修改了原有的值。我们可以简单的理解为值接收者使用的的是值得副本来调用方法,而指针接收者使用实际的值来调用方法。

​ Go语言编译期帮我们自动做的事情

  • 如果使用一个值类型变量调用指针类型接收者的方法,Go语言编译器会自动帮我们取指针调用,以满足指针接收者的要求
  • 同样, 如果使用一个指针类型变量调用值类型接收者的方法,Go语言编译器会自动帮我们解引用调用,以满足值类型接收者的要求

​ 总之,方法的调用者,既可以是值也可以是指针。不用太关注这些,Go语言会自动转义。大大提高开发效率,同时避免因不小心造成的bug。不管是值类型接收者,还是指针类型接收者,要确定需求:对类型进行操作的时候要改变当前接收者的值,还是要创建一个新值返回。

​ 方法也可以赋值给变量,将方法赋值给变量称为方法表达式

age := Age(25)
// 方法赋值给变量,方法表达式
sm := Age.String
// 通过变量,要传一个接收者进行调用,即age
sm(age)

// 方法String是没有参数的,但是通过方法表达式赋值后,在调用的时候必须要产一个接收者,这样变量才知道如何调用
// 小提示: 不管方法是否有参数,通过方法表达式调用,第一个参数必须是接收者,然后才是方法自身的参数。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值