Go 语言编程 — 函数

举报
云物互联 发表于 2021/08/06 01:21:30 2021/08/06
【摘要】 目录 文章目录 目录定义一个函数形参列表值传递引用传递 返回值 初始化函数构造函数析构函数回调函数闭包(Closure)函数方法函数递归函数 定义一个函数 函数声明需要指定: 函数的名称形参列表返回值列表 函数名和形参列表一起构成函数签名。格式: func function_name([parameter list]) [return_typ...

目录

定义一个函数

函数声明需要指定:

  • 函数的名称
  • 形参列表
  • 返回值列表

函数名和形参列表一起构成函数签名。格式:

func function_name([parameter list]) [return_types] { 函数体
}

  
 
  • 1
  • 2
  • 3

示例:

func max(num1, num2 int) int { var result int if (num1 > num2) { result = num1 } else { result = num2 } return result
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

形参列表

值传递

值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。默认情况下,Golang 使用的是值传递,即在调用过程中不会影响到实际参数。

示例:

package main

import "fmt"

func swap(x, y int) int { var temp int temp = x x = y y = temp return temp;
}

func main() { var a int = 100 var b int = 200 fmt.Printf("交换前 a 的值为 : %d\n", a) fmt.Printf("交换前 b 的值为 : %d\n", b) swap(a, b) fmt.Printf("交换后 a 的值 : %d\n", a) fmt.Printf("交换后 b 的值 : %d\n", b)
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

结果:可见,值传递的方式不会影响到实参变量的原数值

交换前 a 的值为 : 100
交换前 b 的值为 : 200
交换后 a 的值 : 100
交换后 b 的值 : 200

  
 
  • 1
  • 2
  • 3
  • 4

引用传递

引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。类似于 C 语言函数定义中的指针类型形参。

引用传递方式,将指针参数传递到函数内,以下是交换函数 swap() 使用了引用传递:

package main

import "fmt"


func swap(x, y *int) { *x, *y = *y, *x
}

func main() { var a int = 100 var b int= 200 fmt.Printf("交换前,a 的值 : %d\n", a) fmt.Printf("交换前,b 的值 : %d\n", b) swap(&a, &b) fmt.Printf("交换后,a 的值 : %d\n", a) fmt.Printf("交换后,b 的值 : %d\n", b)
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

结果:

交换前,a 的值 : 100
交换前,b 的值 : 200
交换后,a 的值 : 200
交换后,b 的值 : 100

  
 
  • 1
  • 2
  • 3
  • 4

返回值

Go 函数可以返回多个值,例如:

package main

import "fmt"

func swap(x, y string) (string, string) { return y, x
}

func main() { a, b := swap("hello", "world") fmt.Println(a, b)
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

结果:

world hello

  
 
  • 1

初始化函数

Golang 程序的初始化流程:

  1. 初始化导入的包,包的初始化顺序由 runtime 需要解析包依赖关系决定,没有依赖的包最先被初始化。
  2. 初始化包作用域的变量,该作用域的变量初始化也并非按照 “从上到下、从左到右” 的顺序,同样由 runtime 解析变量依赖关系,没有依赖的变量最先初始化。
  3. 执行包的 init 函数;

Golang 提供了一个特殊的初始化函数 init(),会先于 main() 函数执行,通常用于实现包级别的一些初始化操作。例如:在 ORM database 包中的 init() 通常用于实现 AutoMigrate(数据库初始化)。

init() 的主要特点:

  • init 函数先于 main 函数执行,且不能被其他函数调用;
  • init 函数没有输入参数,也没有返回值;
  • 每个包可以有多个 init 函数;
  • 包的每个源文件也可以有多个 init 函数,这点比较特殊;
  • Golang 没有明确定义同一个包中多个 init 函数的执行顺序,编程时要注意程序不要依赖 init 函数的执行顺序。
  • 不同包之间的 init 函数按照包导入的依赖关系决定执行顺序。

构造函数

用于构造一个实例对象的函数,build-in 的构造函数有 new() 用于构造自建数据类型的实例。如果是自定义派生数据类型的构造函数通常是开发者自己实现的。

析构函数

Golang 原生没有析构函数,需要开发者额外的人工处理资源清理的工作,通常会使用 defer 语句来完成这些工作。

回调函数

回调函数,一个函数作为另外一个函数的实参,即:向函数传递一个函数的引用。显然,这是一个引用传递的场景。这与 C 语言中的函数指针(指向函数的指针)类似。

所以,在传递一个函数之前,首先需要创建一个函数变量,函数变量的名称就是函数自身的一个引用。

package main

import ( "fmt" "math"
)

func get_square_root(x float64) float64 { return math.Sqrt(x)
}

func main() { fmt.Println(get_square_root(9))
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

结果:

3

  
 
  • 1

或者,可以直接在一个函数体中定义一个新的函数:

package main

import ( "fmt" "math"
)

func main() { /* 定义函数变量。 */ get_square_root := func(x float64) float64 { return math.Sqrt(x) } fmt.Println(get_square_root(9))
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

实际上,当我们定义一个接受函数实参的函数时,首先需要声明函数形参的类型:

package main

import "fmt"


func test_call_back(x int, f func(int) int) { f(x)
}

func call_back(x int) int { fmt.Printf("我是回调,x:%d\n", x) return x
}

func main() { test_call_back(1, call_back)
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

更好的一种写法:

package main

import "fmt"


type call_back_func_t func(int) int

func test_call_back(x int, f call_back_func_t) { f(x)
}

func call_back(x int) int { fmt.Printf("我是回调,x:%d\n", x) return x
}

func main() { test_call_back(1, call_back)
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

再看一种写法:

package main

import "fmt"


type call_back_func_t func(int) int

func test_call_back(x int, f call_back_func_t) { f(x)
}

func main() { test_call_back(1, func(x int) int { fmt.Printf("我是回调,x:%d\n", x) return x })
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

闭包(Closure)函数

闭包(Closure):如果内层函数引用了外层函数的局部变量,并且在外层函数中 return 内层函数,这种关系就称之为闭包。

可见,闭包的特点是发生在函数嵌套的基础上实现。外层函数返回的内层函数还引用了外层函数的局部变量,所以要想正确的使用闭包,那么就要确保这个被内层函数引用的局部变量是不变的。Python 中使用闭包函数来实现了装饰器机制。

Golang 支持匿名函数,可用于实现闭包。匿名函数是一个表达式吗,其优越性在于可以直接使用函数内的变量,而不必声明函数名。以下示例中,定义了函数 getSequence() 并返回了另外一个函数。getSequence() 函数的目的是在闭包中递增 i 变量。

package main

import "fmt"

/**
 * get_sequence 函数作为外层函数,返回匿名函数 func() int,
 * 匿名函数 func() int 作为内层函数,并且直接引用了外层函数的局部变量 i,
 * 如此的,就形成了一个闭包。
 */
func get_sequence() func() int { i := 0 return func() int { i += 1 return i }
}

func main() { /* 定义一个闭包函数变量,当前 i 变量为 0。 */ next_num := get_sequence() /* 每调用一次闭包函数,变量 i += 1 并返回。 */ fmt.Println(next_num()) fmt.Println(next_num()) fmt.Println(next_num()) /* 定义另一个闭包函数变量。 */ next_num1 := get_sequence() fmt.Println(next_num1()) fmt.Println(next_num1()) fmt.Println(next_num1())
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

结果:

1
2
3
1
2
3

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

方法函数

在常规的面向对象编程语言(OOP)中,比如 Python,函数(Function)和方法(Method)是属于两个不同的术语:

  • 在类中定义的称为方法,称为类对象的成员方法。
  • 不在类中定义的称为独立函数。

虽然 Golang 不是一种 OOP 类型编程语言,没有类的概念,但它也同样支持为数据类型定义相应的 Method。所谓的 Method 其实就是函数,只不过与普通函数相比,这类函数是作用在某个数据类型上的。

所以,在函数签名中,会有个 Receiver(接收器)来表明当前定义的函数会作用在该 Receiver 上。一个 Method 就是一个包含了 Receiver 的函数,接受者可以是:“命名(type)类型” 或者 “结构体类型” 的一个值或一个指针。例如下述的 (variable_name variable_data_type)

func (variable_name variable_data_type) function_name() [return_type]{ /* 函数体*/
}

  
 
  • 1
  • 2
  • 3

实际上,Golang 支持对除 Interface 类型外的任何数据类型定义其 Method,只不过实际编程中,Method 多定义在结构体上而已。

从这点上,我们可以简易的将结构体类型变量理解为 Python 中对象的概念,而对象可以调用属于他自身的方法。这是 Golang 对 OOP 编程机制的一种补充。

示例:

package main

import "fmt"

/* 自定义结构体类型 */
type circle_t struct { radius float64
}

// 定义属于 circle 类型对象的 Method
func (c circle_t) get_area() float64 { // c.radius 即为 circle 类型对象中的属性 return 3.14 * c.radius * c.radius
}

func main() { // 定义一个结构体类型对象 c1 var c1 circle_t c1.radius = 10.00 fmt.Println("圆的面积 = ", c1.get_area())
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

从上述例子可见,方法 get_area 就像是变量 c1 的成员方法一般。

递归函数

递归,就是在运行的过程中调用自己。Golang 支持递归调用,但我们在使用递归时,需要合理设置退出条件,否则递归将陷入无限循环中。

格式:

func recursion() { recursion() /* 函数调用自身 */
}

func main() { recursion()
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

递归函数对于解决数学上的问题是非常有用的,就像计算阶乘,生成斐波那契数列等。

  • 阶乘:
package main

import "fmt"

func Factorial(n uint64) (result uint64) { if (n > 0) { result = n * Factorial(n-1) return result } return 1
}

func main() { var i int = 15 fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(uint64(i)))
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 斐波那契数列:
package main

import "fmt"

func fibonacci(n int) int { if n < 2 { return n } return fibonacci(n-2) + fibonacci(n-1)
}

func main() { var i int for i = 0; i < 10; i++ { fmt.Printf("%d\t", fibonacci(i)) }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

文章来源: is-cloud.blog.csdn.net,作者:范桂飓,版权归原作者所有,如需转载,请联系作者。

原文链接:is-cloud.blog.csdn.net/article/details/107140665

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。