Go 编程 | 连载 15 - Go 语言的函数

简介: Go 编程 | 连载 15 - Go 语言的函数

一、函数的定义

Go 语言中 函数 也是一种数据类型,有内存地址,且未被初始化的函数的值为 nil

函数由函数名、参数和返回值组成,使用 func 关键字定义,{} 中定义函数体。

func funcName(arg1 argType, arg2 argType) (returnVar1 varType, returnVar2 varType) {
    //funcBody
}
复制代码

Go 中函数的参数可以是不定长的,并且可以有多个返回值。

返回值的定义

函数的返回值第一种定义方式

Go 函数只有一个返回值是可以将括号省略,有多个返回值时需要使用括号报告,并且括号中的数据类型与 函数体中 return 关键字返回的变量一一对应。参数类型如果相同,可以只在最后一个参数后面跟上参数类型,其他可以省略。

func main(){
   sum := zuluAdd(1,4)
   fmt.Println(sum)
   sum1, isTrue := zuluAddwithTwoReturn(2, 6)
   fmt.Println(sum1, isTrue)
}
func zuluAdd(x, y int) int {
   return x + y
}
func zuluAddwithTwoReturn(x, y int) (int, bool) {
   return x + y, true
}
复制代码

执行上述代码,输出结果如下:

5
8 true
复制代码

函数的返回值第二种定义方式

返回值第二种定义方式,将返回值在函数定义的时候初始化,而不是在函数体内初始化(函数体中不要再使用 := 或者 var 定义返回变量),并且可以省略 return 关键字后的返回值变量名。

func main(){
   sum := yankeeAdd(1,4)
   fmt.Println(sum)
   sum1, isTrue := yankeeAddwithTwoReturn(2, 6)
   fmt.Println(sum1, isTrue)
}
func yankeeAdd(x, y int) (sum int) {
   sum = x + y
   return
}
func yankeeAddwithTwoReturn(x, y int) (sum int, isTrue bool) {
   sum = x + y
   isTrue = true
   return
}
复制代码

执行上述代码,输出结果如下:

5
8 true
复制代码

第二种返回值的定义方式是最常用的

通过省略号设置不定长参数

Go 中的函数可以设置不定长参数,不定长参数有两种表示方式,第一种时使用 ... 表示

func main(){
   argsSum1, argsLen1 := xrayAddwithArgsByDots(2, 6)
   fmt.Println(argsSum1, argsLen1)
   argsSum2, argsLen2 := xrayAddwithArgsByDots(2, 6, 7, 9)
   fmt.Println(argsSum2, argsLen2)
}
func xrayAddwithArgsByDots(args ...int) (argsSum, argsLen int) {
   for _, val := range args {
      argsSum += val
   }
   argsLen = len(args)
   fmt.Printf("%T\n", args)
   return
}
复制代码

执行上述代码,输出结果如下:

[]int
8 2
[]int
24 4
复制代码

根据输出结果可以确定,不定长参数的类型是切片类型。

所以第二种表示不定长参数的表示方式就是可以直接放一个切片作为参数,因为切片时可以自动扩容的。

func main(){
   argsSum1, argsLen1 := xrayAddwithArgsBySlice([]int{2, 6})
   fmt.Println(argsSum1, argsLen1)
   argsSum2, argsLen2 := xrayAddwithArgsBySlice([]int{2, 6, 7, 9})
   fmt.Println(argsSum2, argsLen2)
}
func xrayAddwithArgsBySlice(args []int) (argsSum, argsLen int) {
   for _, val := range args {
      argsSum += val
   }
   argsLen = len(args)
   fmt.Printf("%T\n", args)
   return
}
复制代码

这两种不定长参数的定义有什么区别?

首先可以确定的是切片是引用传递,那么 ... 传递是值传递还是引用传递?可以通过代码来进行验证。

func main(){
   args := []int{1, 3, 5}
   xrayAddwithArgsByDots(args...)
   fmt.Println(args)
   fmt.Printf("%v\n", &args[0])
   fmt.Println("传递单独赋值的变量")
   x := args[0]
   y := args[1]
   xrayAddwithArgsByDots(x, y)
   fmt.Println(args)
   fmt.Printf("%v\n", &args[0])
}
func xrayAddwithArgsByDots(args ...int)  {
   fmt.Printf("%v\n", &args[0])
   fmt.Println(args)
   args[0] = 100
   fmt.Println(args)
}
复制代码

执行上述代码,输出结果如下:

0xc00001a078
[1 3 5]
[100 3 5]
[100 3 5]
0xc00001a078
传递单独赋值的变量
0xc00001c0e0
[100 3]
[100 3]
[100 3 5]
0xc00001a078
复制代码

... 定义不定长参数传入一个切片进行拆解时,是引用传递,但是如果将切片拆解并单独赋值给变量,再讲赋值的变量传入不定长参数的函数,这种是值传递(本质上传递的还是基本数据类型)。

... 在 append 添加一个slice 的时候也会用到。

匿名函数

当函数只在某一处会使用,不会在其他地方使用时,为了避免函数名泛滥,可以定义匿名函数。匿名函数调用时参数在 {} 后面传递,使用 () 包裹。

func main(){
   res := func(x, y int) int {
      return x + y
   }(1, 2)
   fmt.Println(res)
   // 匿名函数的调用
   fmt.Println(func(x, y int) int {
      return x * y
   }(2, 3))
}
复制代码

执行上述函数,输出代码如下:

3
6
复制代码

二、函数的 ”一等公民(FirstClass)“ 特性

Go 中函数的 “一等公民” 特性是指函数可以存储在变量中,可以作为参数传递给函数,可以在函数中创建并作为返回值从函数返回。

函数存储在变量中

将函数赋值给一个变量,并查看变量的类型。

func main(){
   // 第一种定义方式,函数类型为 func(int, int) int, 类似其他如 float63, int64等
   var whiskeyFunc1 func(int, int) int = whiskeyAdd
   fmt.Printf("%T\n", whiskeyFunc1)
   // 直接使用 := 简写方式
   whiskeyFunc := whiskeyAdd
   fmt.Printf("%T\n", whiskeyFunc)
   res := whiskeyFunc(1, 2)
   fmt.Println(res)
}
func whiskeyAdd(x, y int) (sum int) {
   sum = x + y
   return
}
复制代码

执行上述代码,输出结果如下:

func(int, int) int
func(int, int) int
3
复制代码

要注意区分函数名和保存函数的变量名。

当然匿名函数也可以赋值给一个变量。

func main(){
   funcVal := func(x, y int) int {
      return x + y
   }
   fmt.Println(funcVal(1, 2))
}
复制代码

函数作为参数传递

使用 type 关键字自定义函数类型

首先自己定义出来一个类型,这个类型是函数类型,就跟 int 类型 map 类型是一样的

func main(){
   // 定义一个函数类型(自定义的函数类型)的变量,将victoryImpl函数赋值给函数变量
   var myVictor victor = victorImpl
   res := myVictor(7, 2)
   fmt.Println(res)
   // 也可以直接将匿名函数赋值给自定义的函数类型的变量
   var myVictor2 victor = func(x, y int) int {
      return x - y
   }
   fmt.Println(myVictor2(3, 2))
}
// 定义一个函数类型
type victor func (x, y int) int
// 定义一个函数实现
func victorImpl(x, y int) int {
   return x % y
}
复制代码

执行上述代码,输出结果如下:

1
1
复制代码

将函数作为另一个函数的参数

以一个过滤列表中的元素为例

func main(){
   balances := []int{10, 15, 13, 20, 22}
   fmt.Println(filter(balances))
}
func filter(balances []int) (greaterThanTen []int){
   greaterThanTen = make([]int, 0)
   for _, balance := range balances {
      if balance >= 10 {
         greaterThanTen = append(greaterThanTen, balance)
      }
   }
   return
}
复制代码

执行上述代码,输出结果如下:

[10 15 13 20 22]
复制代码

如果筛选条件变化了,那么可以将筛选条件定义为一个函数 greater,该函数返回布尔值,在 filter 函数中传入 greater 函数作为参数

func main(){
   balances := []int{10, 15, 13, 30, 22}
   // greater 函数赋值给一个变量
   greateFunuc := greater
   fmt.Println(filter(balances, greateFunuc))
}
func filter(balances []int, funcParam func(int, int) bool) (greaterThanTen []int){
   greaterThanTen = make([]int, 0)
   for _, balance := range balances {
      if greater(balance, 20) {
         greaterThanTen = append(greaterThanTen, balance)
      }
   }
   return
}
func greater(item, val int) (isGreater bool) {
   if item > val {
      return true
   }
   return false
}
复制代码

执行上述代码,输出结果如下:

[30 22]
复制代码

函数作为参数在链式处理类函数中应用也很广泛。


相关文章
|
9天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
30 2
|
8天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
19 2
|
8天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
21 2
|
12天前
|
Ubuntu 编译器 Linux
go语言中SQLite3驱动安装
【11月更文挑战第2天】
36 7
|
13天前
|
关系型数据库 Go 网络安全
go语言中PostgreSQL驱动安装
【11月更文挑战第2天】
47 5
|
12天前
|
安全 Go
用 Zap 轻松搞定 Go 语言中的结构化日志
在现代应用程序开发中,日志记录至关重要。Go 语言中有许多日志库,而 Zap 因其高性能和灵活性脱颖而出。本文详细介绍如何在 Go 项目中使用 Zap 进行结构化日志记录,并展示如何定制日志输出,满足生产环境需求。通过基础示例、SugaredLogger 的便捷使用以及自定义日志配置,帮助你在实际开发中高效管理日志。
32 1
|
11天前
|
程序员 Go
go语言中的控制结构
【11月更文挑战第3天】
87 58
|
6天前
|
Go
go语言中的continue 语句
go语言中的continue 语句
16 3
|
10天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
6天前
|
安全 Go 调度
探索Go语言的并发模型:goroutine与channel
在这个快节奏的技术世界中,Go语言以其简洁的并发模型脱颖而出。本文将带你深入了解Go语言的goroutine和channel,这两个核心特性如何协同工作,以实现高效、简洁的并发编程。