Go 语言编程 — 函数

目录

定义一个函数

函数声明需要指定:

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

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

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

示例:

func max(num1, num2 int) int {
   var result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result
}

形参列表

值传递

值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。默认情况下,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)
}

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

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

引用传递

引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。类似于 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)
}

结果:

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

返回值

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)
}

结果:

world hello

初始化函数

构造函数

析构函数

回调函数

回调函数,一个函数作为另外一个函数的实参,即:向函数传递一个函数的引用。显然,这是一个引用传递的场景。这与 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))
}

结果:

3

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

package main

import (
    "fmt"
    "math"
)

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

    fmt.Println(get_square_root(9))
}

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

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)
}

更好的一种写法:

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)
}

再看一种写法:

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
    })
}

反射函数

闭包(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
1
2
3

方法函数

在常规的面向对象编程语言(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]{
   /* 函数体*/
}

实际上,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())
}

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

递归函数

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

格式:

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

func main() {
   recursion()
}

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

  • 阶乘:
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)))
}
  • 斐波那契数列:
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))
    }
}

猜你喜欢

转载自blog.csdn.net/Jmilk/article/details/107140665