Go入门16:函数 func

一、函数定义

函数是结构化编程中最小的模块单元,日常开发过程中,将复杂的算法过程分解为若干个小任务(代码块),使程序的结构性更清晰,程序可读性提升,易于后期维护和让别人读懂你的代码。

编写多个函数的主要目的是将一个需要很多行代码的复杂问题分解为一系列简单的任务(那就是函数)来解决。在实际编程中,我们把重复性的任务抽象成一个函数。

和所有的编程语言一样,Go语言支持各种风格的函数。在Go语言中,当函数执行到代码块最后一行 } 之前或者 return 语句的时候会退出,其中 return 语句可以带有零个或多个参数;这些参数将作为返回值供调用者使用。简单的 return 语句也可以用来结束 for 死循环,或者结束一个Go协程(goroutine)。

二、定义语法

func 函数名(参数列表) (返回值列表) {

    // 函数体

}

func funcName (input1 type1, input2 type2)  (output1 type1, output2 type2) {

    // 逻辑代码

    // 返回多个值

    return value1, value2

}

上面的代码我们可以看出:

1)关键字 func 用来声明一个函数 funcName;

2)函数可以有一个或者多个参数,每个参数后面带有类型,多个参数之间通过“,”分割;

3)函数可以返回多个值;

4)上面返回值声明了两个变量 output1 和 output2,如果你不想声明也可以,直接就两个类型;

5)如果只有一个返回值且不声明返回值变量,那么你可以省略,包括返回值的括号;

6)如果没有返回值,那么就直接省略最后的返回信息;

7)如果有返回值, 那么必须在函数的外层添加return语句;

这里需要强调的是,Go语言函数的返回值类型和变量定义的数据类型一样,都要遵循Go语言的“后置原则”放在后面,这一点和C语言函数定义有显著不同。

另外,Go语言函数定义中如果参数列表中若干个相邻参数的类型相同,则可以在参数列表中省略前面的变量类型声明。

func Add(a, b int) int {        //这里a和b都是int类型

    // 函数体

}

最后,Go语言函数定义中左花括号的位置被强制规范,如果左花括号放置不规范,golang编译器会报编译错误。

//错误!!!左括号必须紧跟在括号后面

func hello()

{              //左括号不能另起一行

}

三、综合示例代码1

// 返回a、b中最大值

func max(a, b int) int {

    if a > b {

        return a

    }

    return b

}

func main() {

    x := 3

    y := 4

    z := 5

    max_xy := max(x, y)    // 调用函数max(x, y)

    max_xz := max(x, z)    // 调用函数max(x, z)

    fmt.Printf("max(%d, %d) = %d\n", y, z, max(y, z))    // 也可在这直接调用

}

上面这个里面我们可以看到max函数有两个参数,它们的类型都是int,那么第一个变量的类型可以省略(即 a,b int,而非 a int, b int),默认为离它最近的类型,同理多于2个同类型的变量或者返回值。同时我们注意到它的返回值就是一个类型,这个就是省略写法。

四、支持多返回值、支持命名返回值

Go语言的函数可以返回不止一个结果,即支持“多值返回”。

Go语言函数多值返回一般用于处理错误。比如在IO操作时候,不一定每次都能成功:可能文件不存在或者磁盘损坏无法读取文件。所以一般在函数调用发生错误时返回一个附加的结果作为错误值,习惯上将错误值作为最后一个结果返回

func sumProductDiff(i, j int) (int, int, int) {    // 多返回值

    return i+j, i*j, i-j

}

func Split(sum int) (x, y int) {    // 命名返回

    x = sum * 4 / 9

    y = sum - x

    return

}

如果是命名返回值,则返回的时候不用带上变量名,因为直接在函数里面初始化了。

如果方法是公开访问的,建议最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档可读性差。

五、不定参数

Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参:

func myfunc (arg ...int) {}

arg ...int告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是int。

调用该函数的时候可以传递该类型任意数目的参数。在函数体中,变量arg是一个int的slice。

for _, n := range arg {

    fmt.Printf("And the number is: %d\n", n)

}

六、综合示例代码2

我们在这里写三个函数,依次展示出函数最常态模样,多值返回和不定参数。

func Add(i int, j int) (int) {  //常规函数

    return i+j

}

func Add_Multi_Sub(i, j int) (int, int, int) {  //多值返回

    return i+j, i*j, i-j

}

func sum(nums ...int) { //变参函数

    total := 0

    for _, num := range nums {

        total += num

    }

    fmt.Println(total)

}

func main(){

    a, b := 2,3

    arr := []int{1, 2, 3}

    var c int = Add(a,b)

    d,e,f := Add_Multi_Sub(a,b)

    fmt.Println(c,d,e,f)    // 5 5 6 -1

    sum(arr...)  //注意传参形式 6

}

七、传值与传指针

当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。

// 简单的一个函数,实现了函数+1的操作

func add1(a int) int {

    a = a + 1    // 改变了a值

    return a    // 返回新值

}

func main () {

    x := 3

    fmt.Println("x=", x)    // 输出"x=3"

    x1 := add1(x)

    fmt.Println("x+1=", x1)    // 输出"x+1=4"

    fmt.Println("x=", x)    // 输出"x=3"

}

虽然我们调用了add1函数,并且在add1中执行a = a+1操作,但是上面例子中x变量的值没有发生变化。

理由很简单:因为当我们调用add1的时候,add1接收的参数其实是x的copy,而不是x本身。

如果真的需要传这个x本身,该怎么办呢?

我们知道,变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。只有add1函数知道x变量所在的地址,才能修改x变量的值。所以我们需要将x所在地址&x传入函数,并将函数的参数的类型由int改为*int,即改为指针类型,才能在函数中修改x变量的值。此时参数仍然是按copy传递的,只是copy的是一个指针。

// 简单的一个函数,实现了函数+1的操作

func add1(a *int) int {

    *a = *a + 1    // 改变了a值

    return *a    // 返回新值

}

func main () {

    x := 3

    fmt.Println("x=", x)    // 输出"x=3"

    x1 := add1(&x)

    fmt.Println("x+1=", x1)    // 输出"x+1=4"

    fmt.Println("x=", x)    // 输出"x=4"

}

这样,我们就达到了修改x的目的。

那么到底传指针有什么好处呢?

1)传指针使得多个函数能操作同一个对象;

2)传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当要传递大的结构体的时候,用指针是一个明智的选择。

3)Go语言中channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)

八、函数作为值、类型

在Go中函数也是一种变量,我们可以通过type来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型。

type typeName func(input1 inputType1, input2 inputType2 [, ...]) (result1 resultType1 [, ...])

函数作为类型到底有什么好处呢?那就是可以把这个类型的函数当做值来传递。

type testInt func(int) bool    // 声明了一个函数类型

func isOdd(integer int) bool {

    if integer%2 == 0 {

        return false

    }

    return true

}

func isEven(integer int) bool {

    if integer%2 == 0 {

        return true

    }

    return false

}

// 声明的函数类型作为一个参数

func filter(slice []int, f testInt) []int {

    var result []int

    for _, value := range slice {

        if f(value) {

            result = append(result, value)

        }

    return result

}

func main() {

    slice := []int {1,2,3,4,5,6,7}

    fmt.Println("slice=", slice)

    odd := filter(slice, isOdd)    // 函数作为值传递

    fmt.Println("odd elements of slice are:", odd)

    even := filter(slice, isEven)    // 函数作为值传递

    fmt.Println("even elements of slice are:", even)

}

函数当做值和类型在我们写一些通用接口的时候非常有用,通过上面例子我们看到testInt这个类型是一个函数类型,然后两个filter函数的参数和返回值与testInt类型是一样的,但是我们可以实现很多种的逻辑,这样使得我们的程序变得非常的灵活。

九、函数只能判断是否为nil

什么是nil?

In Go, nil is the zero value for pointers, interfaces, maps, slices, channels and function types, representing an uninitialized value.

func add(a, b int) int {

    return a + b

}

func main() {

    fmt.Println(add == nil)

    //fmt.Println(add == 1)  //错误 mismatched types func(int, int) int and int)

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容