Go-Functions

概念

在Go中,所谓的函数(function)、过程(procedure)、subroutine都统称为function。关键字只有func。

前面已经多次自定义过func了,包括有返回值或没有返回值。没有返回值的函数,在诸如VB的语言中,就成为precedure/subroutine。

示例再现

这个例子包括了两种形式的函数:有返回值、无返回值。

package main
import "fmt"

/*
D:\examples>go run helloworld.go
1       2       3       4       5
average: 3

D:\examples>
*/
func main() {
    x := [5]int {1,2,3,4,5}

    debug_array(x)
    fmt.Println("average:", get_average(x))
}

/*
cannot use x (type [5]int) as type []int in argument to debug_array
func debug_array(a[]int) {
    ...
}*/

func debug_array(a[5]int) {
    for _, item := range a {
        fmt.Print(item, "\t")
    }
    fmt.Println()
}

func get_average(a[5]int) int {
    total := 0
    for _, item := range a {
        total += item
    }
    return total / len(a)
}

named return value

函数的返回值,除了指定数据类型,还可以指定一个名字,可视为变量名,且在函数体中可以使用。——这可以看做Go的一个特色。

注意如下例子中的get_min_max()的两个版本:函数声明中的返回值部分,以及函数体的return语句。

package main
import "fmt"

/*
D:\examples>go run helloworld.go
1       2       3       4       5
average: 3
min:  1 , max:  5
min:  1 , max:  5

D:\examples>
*/
func main() {
    x := [5]int {1,2,3,4,5}

    debug_array(x)
    fmt.Println("average:", get_average(x))

    the_min, the_max := get_min_max(x)
    fmt.Println("min: ", the_min, ", max: ", the_max)

    the_min, the_max = get_min_max_v2(x)
    fmt.Println("min: ", the_min, ", max: ", the_max)
}

func debug_array(a[5]int) {
    for _, item := range a {
        fmt.Print(item, "\t")
    }
    fmt.Println()
}

func get_average(a[5]int) (average int) {
    total := 0
    for _, item := range a {
        total += item
    }
    average = total / len(a)
    return
}

func get_min_max(a[5]int) (min int, max int) {
    min = a[0]
    max = a[0]

    for _, item := range a {
        if (item < min) { min = item }
        if (item > max) { max = item }
    }

    // the 'return' is required, or 'missing return at end of function'
    return
}

func get_min_max_v2(a[5]int) (int, int) {
    min := a[0]
    max := a[0]

    for _, item := range a {
        if (item < min) { min = item }
        if (item > max) { max = item }
    }

    // the 'return' is required, or 'missing return at end of function'
    return min, max
}

通过这个例子可以看到,命名返回值在某些场合下还是很有用。其他编程语言是在函数体中定义临时变量,然后返回这个临时变量。而Go直接在函数声明中指定返回值的变量名,这样子在函数体中使用的时候,就具有更加明显的含义,特指其是要返回的数据。

顺便提及,Go的函数可以返回多个数值的用法和Python脚本语言一样,而和C/C++/Java不一样。后者需要打包到一个(比如struct)对象中,即只能返回至多一个对象。

另外,在前面描述Go-Maps的时候也用到了返回多个数值的场景。——value,ok:=the_map[the_key].

变参/可变长度参数列表/Variadic Functions

注意观察示例中的两种传参方式。

package main
import "fmt"

/*
D:\examples>go run helloworld.go
6
15

D:\examples>
*/
func main() {
    //x := [5]int {1,2,3,4,5}
    y := [ ]int {1,2,3,4,5}

    fmt.Println(add(1,2,3))

    //cannot use x (type [5]int) as type []int in argument to add
    //fmt.Println(add(x...))

    fmt.Println(add(y...))
}

func add(args ... int) int {
    total := 0
    for _, item := range args {
        total += item
    }
    return total
}

Closure / 内部函数

所谓的函数式编程,中文通常译closure为闭包。closure涉及到内部函数(inner function),但closure又不仅仅是内部函数,它还包括了使用的上下文(如变量)。

比如下面的示例代码中,add_square和square_total构成了closure。而add则既为inner function,又为closure,此时它没有用到任何上下文。

package main
import "fmt"

/*
D:\examples>go run helloworld.go
total: 15
square total: 55

D:\examples>
*/
func main() {
    x := [5]int {1,2,3,4,5}
    test_total(x)
    test_square_total(x)
}

func test_total(x [5]int) {
    //Ooh, attention pls the puzzling 'x'.
    add := func(x int, y int) int {
        return x + y
    }

    total := 0
    for _,item := range x {
        total = add(total, item)
    }

    fmt.Println("total:", total)
}

func test_square_total(x [5]int) {
    square_total := 0
    add_squre := func(n int) {
        square_total += n * n 
    }

    for _,item := range x {
        add_squre(item)
    }

    fmt.Println("square total:", square_total)
}

Closure与状态

下面是Introducing Go中的例子:

package main
import "fmt"

/*
D:\examples>go run helloworld.go
0
2
4
6

D:\examples>
*/
func main() {
    nextEven := makeEvenGenerator() 
    fmt.Println(nextEven())
    fmt.Println(nextEven())
    fmt.Println(nextEven())
    fmt.Println(nextEven())
}

func makeEvenGenerator() func() uint {
    i := uint(0)

    return func() (ret uint) {
        ret = i 
        i += 2 
        return 
    }
}

这个例子较好地阐述了closure所谓状态的概念。也可以把closure看做functor(中文译为仿函数/函子/etc)或函数对象。即closure不仅仅具备函数的特征,而且具有状态。所谓状态,就是具有内部存储的数据。

在这个例子中,makeEvenGenerator()用来生成偶数数列。初始化的时候,其内部状态(类似数据成员)i初始化为0。因为makeEvenGenerator()的返回值是一个函数func() uint,所以每次调用nextEven()的时候,就是执行func() uint这个内部函数。第一次调用的时候,ret取i,故ret为0,即nextEven()返回0。在这个调用过程中,i得到了更新,变成接下来的一个偶数。。。如此,nextEven()的不断调用,就形成了一个偶数序列。

作为对比,下面是C++代码的例子:

#include <stdio.h>

class MakeEvenGenerator {
public:
    MakeEvenGenerator() {
        i = 0;
    }

    int operator()() {
        int ret = i;
        i += 2;
        return ret;
    }

private:
    int i;
};

int main() 
{
    MakeEvenGenerator nextEven;

    for (int i = 0; i < 5; i++) {
        printf("%d\t", nextEven());
    }
    printf("\n");
}

运行结果:

0       2       4       6       8
请按任意键继续. . .

递归函数/Recursion

递归函数比较常见。同为函数式编程的内容,显然递归函数比闭包Closure更为大众所知。——因为即便thq版《C语言编程》都会讲递归,任何一本数据结构也会讲递归;但却没有多少学校的教科书会讲到Closure(集合论中的闭包就不在讨论范围了)。

当然,一旦了解了函数式编程,那么就会有使用的冲动。事实上,用函数式编程实现功能,往往更为简洁和清晰。

接下来给一个传统&经典的阶乘的例子结束本节。

package main
import "fmt"

func main() {
    fmt.Println(factorial(5)) //120
}

func factorial(x uint) uint {
    if (x == 0) {
        return 1
    }

    return x * factorial(x-1)
}

函数参数列表

最后再补充描述Go函数参数列表。下面的示例代码会有编译错误,如注释部分。

package main
import "fmt"

func main() {
    var x, y int 

    // cannot use "hello" (type string) as type int in assignment
    x = "hello"

    y = 3

    foo(2, 3)

    //cannot use "hello" (type string) as type int in argument to bar
    bar("hello", 3)
}

func foo(x int, y int) int {
    return x + y;
}

func bar(x, y int) {
    // cannot use "hello" (type string) as type int in assignment
    x = "hello"

    fmt.Println(y)
}

在Introducing Go和The Go Programming Language中都没有明确描述这种规则。但后者给出了一个示例,拷贝如下(P120):

Here are four ways to declare a function wit h two parameters and one result, all of typ e int. The blank identifier can be used to emp hasize that a parameter is unu sed.

func add(x int, y int) int { return x + y }
func sub(x, y int) (z int) { z = x - y; return }
func first(x int, _ int) int { return x }
func zero(int, int) int { return 0 }
fmt.Printf("%T\n", add) // "func(int, int) int"
fmt.Printf("%T\n", sub) // "func(int, int) int"
fmt.Printf("%T\n", first) // "func(int, int) int"
fmt.Printf("%T\n", zero) // "func(int, int) int"

另Page30:

It is possible to declare and optionally initialize a set of variables in a single declaration, with a matching list of expressions. Omitting the type allows declaration of multiple variables of different types:

var i, j, k int // int, int, int
var b, f, s = true, 2.3, "four" // bool, float64, string

通过这里的说明,以及前面有编译错误的示例代码,可以得出结论:如果相邻的变量为同一种数据类型,只需要在最后一个变量后面写上类型,之前的都可以省略。——好的语言就是能够简化就简化,让coder把精力集中于业务领域。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值