是时候开始研究函数和方法了。。。

函数

通过函数,可以把开发任务分解成一个个小的单元,这些小单元可以被其他单元复用,进而提高开发效率、降低代码重合度。

1. 函数声明

func funcName(params) result {
	body
}
  • 关键字 func
  • 函数名字 funcName
  • 函数的参数 params,用来定义形参的变量名和类型
  • result 是返回的函数值,用于定义返回值的类型,如果没有可以省略
  • body 就是函数体,可以在这里写函数的代码逻辑

写一个计算两数相加的函数:

package main

import (
	"fmt"
)

// 计算两值之和
// 变量名称在前,变量类型在后
// 变量名称叫做参数名称,也就是函数的形参
func addTwoNum(a int, b int) int{
	return a + b
}

func main(){
	fmt.Println(addTwoNum(12, 21))
}

运行结果为:
image.png

2. 多值返回

Go 语言的函数可以返回多个值,也就是多值返回
第一个值返回函数的结果,第二个值返回函数出错的信息

package main

import (
	"errors"
	"fmt"
)

// 计算两值之和,如果为负数就返回错误
func addTwoNum(a int, b int) (int, error) {
	if a < 0 || b < 0 {
		return 0, errors.New("a或者b不能为负数")
	}
	return a + b, nil
}

func main() {
	// 获取结果和错误信息
	a, err := addTwoNum(-12, 21)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("计算结果为:", a)
	}
}

运行结果为:
image.png

3.命名返回参数

函数的返回值也可以有变量名称,这个并不常用,了解一下
改造函数为:

package main

import (
	"errors"
	"fmt"
)

// 计算两值之和,如果为负数就返回错误
func addTwoNum(a int, b int) (sum int, err error) {
	if a < 0 || b < 0 {
		// 这里按照正常进行返回
		return 0, errors.New("a或者b不能为负数")
	}
	// 这里按照返回值给相关参数赋值,return后面不需要任何参数
	sum = a + b
	err = nil
	return
}

func main() {
	// 获取结果和错误信息
	a, err := addTwoNum(-12, 21)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("计算结果为:", a)
	}
}

运行结果为:
image.png

4. 可变参数

可变参数,就是函数的参数数量是可变的
如果函数中既有普通参数又有可变参数,那么可变参数一定要放到参数列表的末尾
主要就是在参数类型前面添加三个点
例如:

// 没有参数
fmt.Println()
// 一个参数
fmt.Println("zhouzhaodong")
// 两个参数
fmt.Println("zhouzhaodong","xiaoqiang")

我们写一个计算所有数字之和的函数:

package main

import (
	"fmt"
)

// 计算所有值之和
func addAllNum(params ...int) int {
	sum := 0
	for _, i := range params {
		sum += i
	}
	return sum
}

func main() {
	fmt.Println("计算结果为:", addAllNum(1, 2, 3, 3, 3, 4, 5, 9))
}

运行结果为:
image.png

5. 包级函数

不管是自定义的函数
还是我们使用到的函数,都会从属一个包
也就是 package
不同包的函数要被调用,那么函数的作用域必须是公有的,也就是函数名称的首字母要大写

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

6. 匿名函数和闭包

匿名函数就是没有名字的函数

package main

import (
	"fmt"
)

func main() {
	sum := func(a, b int) int {
		return a + b
	}
	fmt.Println("计算结果为:", sum(1, 2))
}

运行结果为:
image.png

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

方法

方法必须要有一个接收者,这个接收者是一个类型
这样方法就和这个类型绑定在一起,成为这个类型的方法

接收者的定义和普通变量、函数参数等一样
前面是变量名,后面是接收者类型

package main

import (
	"fmt"
)

// 定义一个新的类型,该类型等价于 uint
// 可以理解为 uint 的重命名
type Age uint

// 定义一个方法,参数就是Age
func (age Age) String() {
	fmt.Println("the age is", age)
}

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

运行结果为:
image.png

接收者就是函数和方法最大的不同

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

定义的方法的接收者类型是指针,所以我们对指针的修改是有效的,如果不是指针,修改就没有效果,如下所示:

package main

import (
	"fmt"
)

// 定义一个新的类型,该类型等价于 uint
// 可以理解为 uint 的重命名
type Age uint

// 定义一个方法,参数就是Age
func (age Age) String() {
	fmt.Println("the age is", age)
}

// 定义一个方法,参数就是Age指针
func (age *Age) Modify() {
	*age = Age(30)
}

func main() {
	age := Age(25)
	age.String()
	// 修改age的值
	age.Modify()
	age.String()
}

运行结果为:
image.png

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

这就是 Go 语言编译器帮我们自动做的事情:

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

总之,方法的调用者,既可以是值也可以是指针,不用太关注这些,Go 语言会帮我们自动转义,大大提高开发效率,同时避免因不小心造成的 Bug。

不管是使用值类型接收者,还是指针类型接收者,要先确定你的需求:在对类型进行操作的时候是要改变当前接收者的值,还是要创建一个新值进行返回?这些就可以决定使用哪种接收者。

上一篇 下一篇