GO语言入门 - (六)函数和方法

是时候开始研究函数和方法了。。。
函数
通过函数,可以把开发任务分解成一个个小的单元,这些小单元可以被其他单元复用,进而提高开发效率、降低代码重合度。
1. 函数声明
func funcName(params) result { body }

  • 关键字 func
  • 函数名字 funcName
  • 函数的参数 params,用来定义形参的变量名和类型
  • result 是返回的函数值,用于定义返回值的类型,如果没有可以省略
  • body 就是函数体,可以在这里写函数的代码逻辑
写一个计算两数相加的函数:
package mainimport ( "fmt" )// 计算两值之和 // 变量名称在前,变量类型在后 // 变量名称叫做参数名称,也就是函数的形参 func addTwoNum(a int, b int) int{ return a + b }func main(){ fmt.Println(addTwoNum(12, 21)) }

运行结果为:
GO语言入门 - (六)函数和方法
文章图片

2. 多值返回 Go 语言的函数可以返回多个值,也就是多值返回
第一个值返回函数的结果,第二个值返回函数出错的信息
package mainimport ( "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) } }

运行结果为:
GO语言入门 - (六)函数和方法
文章图片

3.命名返回参数 函数的返回值也可以有变量名称,这个并不常用,了解一下
改造函数为:
package mainimport ( "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) } }

运行结果为:
GO语言入门 - (六)函数和方法
文章图片

4. 可变参数 可变参数,就是函数的参数数量是可变的
如果函数中既有普通参数又有可变参数,那么可变参数一定要放到参数列表的末尾
主要就是在参数类型前面添加三个点
例如:
// 没有参数 fmt.Println() // 一个参数 fmt.Println("zhouzhaodong") // 两个参数 fmt.Println("zhouzhaodong","xiaoqiang")

我们写一个计算所有数字之和的函数:
package mainimport ( "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)) }

运行结果为:
GO语言入门 - (六)函数和方法
文章图片

5. 包级函数 不管是自定义的函数
还是我们使用到的函数,都会从属一个包
也就是 package
不同包的函数要被调用,那么函数的作用域必须是公有的,也就是函数名称的首字母要大写
  • 函数名称首字母小写代表私有函数,只有在同一个包中才可以被调用
  • 函数名称首字母大写代表公有函数,不同的包也可以调用
  • 任何一个函数都会从属于一个包
6. 匿名函数和闭包 匿名函数就是没有名字的函数
package mainimport ( "fmt" )func main() { sum := func(a, b int) int { return a + b } fmt.Println("计算结果为:", sum(1, 2)) }

运行结果为:
GO语言入门 - (六)函数和方法
文章图片

在函数中再定义函数(函数嵌套),定义的这个匿名函数,也可以称为内部函数
更重要的是,在函数内定义的内部函数,可以使用外部函数的变量等,这种方式也称为闭包
方法
方法必须要有一个接收者,这个接收者是一个类型
这样方法就和这个类型绑定在一起,成为这个类型的方法
接收者的定义和普通变量、函数参数等一样
前面是变量名,后面是接收者类型
package mainimport ( "fmt" )// 定义一个新的类型,该类型等价于 uint // 可以理解为 uint 的重命名 type Age uint// 定义一个方法,参数就是Age func (age Age) String() { fmt.Println("the age is", age) }func main() { age := Age(25) age.String() }

运行结果为:
GO语言入门 - (六)函数和方法
文章图片

接收者就是函数和方法最大的不同
1. 值类型接收者和指针类型接收者 定义的方法的接收者类型是指针,所以我们对指针的修改是有效的,如果不是指针,修改就没有效果,如下所示:
package mainimport ( "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() }

运行结果为:
GO语言入门 - (六)函数和方法
文章图片

提示:在调用方法的时候,传递的接收者本质上都是副本,只不过一个是这个值副本,一是指向这个值指针的副本。指针具有指向原有值的特性,所以修改了指针指向的值,也就修改了原有的值。我们可以简单地理解为值接收者使用的是值的副本来调用方法,而指针接收者使用实际的值来调用方法。
这就是 Go 语言编译器帮我们自动做的事情:
  • 如果使用一个值类型变量调用指针类型接收者的方法,Go 语言编译器会自动帮我们取指针调用,以满足指针接收者的要求。
  • 如果使用一个指针类型变量调用值类型接收者的方法,Go 语言编译器会自动帮我们解引用调用,以满足值类型接收者的要求。
总之,方法的调用者,既可以是值也可以是指针,不用太关注这些,Go 语言会帮我们自动转义,大大提高开发效率,同时避免因不小心造成的 Bug。
不管是使用值类型接收者,还是指针类型接收者,要先确定你的需求:在对类型进行操作的时候是要改变当前接收者的值,还是要创建一个新值进行返回?这些就可以决定使用哪种接收者。
个人博客地址:
【GO语言入门 - (六)函数和方法】http://www.zhouzhaodong.xyz/

    推荐阅读