Go[语言基础]-函数

1. 函数

  • 函数对应操作序列,是程序的基本组成元素
  • Go语言中的函数有具名和匿名之分
  • Go语言通过隐式接口机制实现了鸭子面向对象模型

在Go语言中,函数是第一类对象,我们可以将函数保持到变量中。
函数主要有具名和匿名之分,包级函数一般都是具名函数,具名函数是匿名函数的一种特例。
Go语言中每个类型还可以有自己的方法,方法其实也是函数的一种。

package main

import "fmt"

// 具名函数
func Add(a, b int) int {
	return a + b
}

// 匿名函数
var Add2 = func(a, b int) int {
	return a + b
}

func main() {
	fmt.Println(Add(1, 2)) // 3
	fmt.Println(Add2(2, 3)) // 5
}

Go语言中的函数可以有多个参数和多个返回值
参数和返回值都是以传值的方式和被调用者交换数据
支持可变数量的参数,可变数量的参数必须是最后出现的参数
可变数量的参数其实是一个切片类型的参数。

// 多个参数和多个返回值
func Swap(a, b int) (int, int) {
    return b, a
}

// 可变数量的参数
// more 对应 []int 切片类型
func Sum(a int, more ...int) int {
    for _, v := range more {
        a += v
    }
    return a
}

不仅函数的参数可以有名字,也可以给函数的返回值命名:

func Find(m map[int]int, key int) (value int, ok bool) {
    value, ok = m[key]
    return
}

如果返回值命名了,可以通过名字来修改返回值,也可以通过defer语句在return语句之后修改返回值:

func Inc() (v int) {
    defer func(){ v++ } ()
    return 42
}

其中defer语句延迟执行了一个匿名函数,因为这个匿名函数捕获了外部函数的局部变量v,这种函数我们一般叫闭包。
闭包对捕获的外部变量并不是传值方式访问,而是以引用的方式访问。

闭包的这种引用方式访问外部变量的行为可能会导致一些隐含的问题:

func main() {
    for i := 0; i < 3; i++ {
        defer func(){ println(i) } ()
    }
}
// Output:
// 3
// 3
// 3

因为是闭包,在for迭代语句中,每个defer语句延迟执行的函数引用的都是同一个i迭代变量,在循环结束后这个变量的值为3,因此最终输出的都是3。

修复的思路是在每轮迭代中为每个defer函数生成独有的变量。可以用下面两种方式:

func main() {
    for i := 0; i < 3; i++ {
        i := i // 定义一个循环体内局部变量i
        defer func(){ println(i) } ()
    }
}

func main() {
    for i := 0; i < 3; i++ {
        // 通过函数传入i
        // defer 语句会马上对调用参数求值
        defer func(i int){ println(i) } (i)
    }
}

Go语言中,如果以切片为参数调用函数时,有时候会给人一种参数采用了传引用的方式的假象:因为在被调用函数内部可以修改传入的切片的元素。其实,任何可以通过函数参数修改调用参数的情形,都是因为函数参数中显式或隐式传入了指针参数。函数参数传值的规范更准确说是只针对数据结构中固定的部分传值,例如字符串或切片对应结构体中的指针和字符串长度结构体传值,但是并不包含指针间接指向的内容。将切片类型的参数替换为类似reflect.SliceHeader结构体就很好理解切片传值的含义了:

func twice(x []int) {
    for i := range x {
        x[i] *= 2
    }
}

type IntSliceHeader struct {
    Data []int
    Len  int
    Cap  int
}

func twice(x IntSliceHeader) {
    for i := 0; i < x.Len; i++ {
        x.Data[i] *= 2
    }
}

因为切片中的底层数组部分是通过隐式指针传递(指针本身依然是传值的,但是指针指向的却是同一份的数据),所以被调用函数是可以通过指针修改掉调用参数切片中的数据。除了数据之外,切片结构还包含了切片长度和切片容量信息,这2个信息也是传值的。如果被调用函数中修改了Len或Cap信息的话,就无法反映到调用参数的切片中,这时候我们一般会通过返回修改后的切片来更新之前的切片。
这也是为何内置的append必须要返回一个切片的原因。


Git https://github.com/aloneliu/learngo/tree/master/gobase 觉得帮助到你请star

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值