函数定义

  1. 函数基本组成:关键字func函数名参数列表返回值列表函数体返回语句
1
2
3
4
func 函数名(参数列表) (返回值列表) {
    // 函数体
    return // 返回语句
}
  1. 除了main()init()函数外,其他所有类型的函数都可以有【参数】与【返回值】。
  2. 函数一般也可以这么写:func FunctionName Signature [FunctionBody]
    • func定义函数关键字。FunctionName函数名。
    • Signature函数签名,包括函数参数和函数返回值,函数签名是识别一个函数的依据。
    • FunctionBody函数体。
func FunctionName (a typea, b typed) (t1 type1, t2 type2)
  1. 函数签名由函数参数、返回值以及它们的类型组成。
(a typea, b typed) (t1 type1, t2 type2)
  1. 如果两个函数的参数列表和返回值列表的变量类型能一一对应,那么这两个函数就有相同得签名。
  2. 下面testatestb具有相同得函数签名。
func testa (a, b int, z float32) bool
func testb (a, b int, z float32) (bool)
  1. 函数调用传入的参数必须按照参数声明的顺序。Go语言【没有默认参数值】。
  2. 函数签名中的最后传入参数可以具有前缀为...的类型(...int),这样的参数称为【可变参数】。
    • 在接收这种(...)参数的时候,当做【切片】处理即可。
    • 注意(s…)这种形式的s只能是切片或者是字符串(只能在append()函数或可变参数函数中使用)【不能是数组】。
  3. 可以使用零个多个参数来调用该函数,这样的函数称为【变参函数】。
// 其实values就是 []int 切片
func doFix(prefix string, values ...int)    

使用

  1. 函数的参数和返回值列表始终带括号,但只有一个未命名的参数值,可以将其写为未加括号的类型。
  2. 一个函数也可以拥有多个返回值,返回类型之间需要使用逗号分隔,并使用小括号()将它们括起来。
func testa (a, b int, z float32) bool
func swap (a int, b int) (t1 int, t2 int)
  1. 在函数体中,参数是局部变量,被初始化为调用者传入的值。
  2. 函数的参数和命名返回值是函数最外层的局部变量,它们的作用域就是整个函数。
  3. 如果函数的签名声明了返回值,则函数体的语句列表必须以终止语句结束。
  4. 但是如果函数没有声明返回值也是可以使用return结束函数后面代码。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func IndexRune(s string,r rune) int {
    for i, c := range s {
        if c == r {
            return i
        }
    }
    
    //必须有终止语句return,
    // 否则会发生编译错误 missing return at end of function
    return 
}

// 没有指定返回值的函数,也是可以使用return结束整个函数运行的
func Show() {
    fmt.Println(123)
    return
}
  1. Go语言函数重载是不被允许的。

    • 函数重载:可以编写多个同名函数,只要它们拥有不同的形参或者不同的返回值。
    • 官方不支持重载原因,让Go保存简单
  2. 函数可以作为函数类型被使用。函数类型就是函数签名。函数类型的未初始化变量的值为nil(函数是引用类型)。

  3. 函数作为参数被使用,这种是回调。其实就是funcval指针。

  4. 函数作为返回值被使用,这种是闭包。其实就是funcval指针。

1
2
3
4
5
// 通过type关键字,定义一个新的函数类型 funcType
type funcType func(int, int) int

// 通过var关键字,创建函数变量
var f func() int
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
    "fmt"
    "unsafe"
)

type funcType func(int, int) int

func main() {
    var f funcType = func(a, b int) int {
        return a + b
    }

    fmt.Println(unsafe.Sizeof(f))

    // Output:
    // 8
}
  1. 函数可以在表达式中赋值给变量,这样作为表达式中的右值出现,称为函数值字面量。
  2. 函数值字面量是一种表达式,它的值被称为匿名函数
1
2
3
f := func() int {
    return 7
}
 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
// 定义函数类型 funcType
type funcType func(time.Time)

func main() {
    // 直接赋值给变量
    f := func(t time.Time) time.Time {
        return t
    }

    fmt.Println("一:", f( time.Now() ))

    // 定义函数类型 funcType 变量 timer
    var timer funcType = CurrentTime

    timer( time.Now() )

    // 先把CurrentTime函数转为funcType类型,然后传入参数调用
    // funcType(CurrentTime) CurrentTime转换为funcType类型
    funcType(CurrentTime)(time.Now())
    
    // Output:
    // 一: 2021-04-11 12:25:15.8479173 +0800 CST m=+0.001997401
    // 二: 2021-04-11 12:25:15.8589326 +0800 CST m=+0.013013701
    // 二: 2021-04-11 12:25:15.8599231 +0800 CST m=+0.014004301
}

func CurrentTime (start time.Time) {
    fmt.Println("二:", start)
}

...int

  1. 函数只能是最后一个参数是 ...int 形式。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

func main() {
    show("aaa")
}

//go:noinline
func show(name string, params ...int) {
    // params默认值[]int(nil)
    // params == nil
    println(name, params)    
}
  1. main.main相关汇编:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
  0x4551e0      493b6610        CMPQ 0x10(R14), SP      # 1. 栈增长判断
  0x4551e4      7630            JBE 0x455216
  0x4551e6      4883ec30        SUBQ $0x30, SP          # 2. 设置栈大小
  0x4551ea      48896c2428      MOVQ BP, 0x28(SP)       # 3. 保存rbp寄存器值
  0x4551ef      488d6c2428      LEAQ 0x28(SP), BP       # 4. 设置rbp新值
    show("aaa")
  # AX和BX寄存器用于传递参数 【name string】
  0x4551f4      488d05fdc70000  LEAQ 0xc7fd(IP), AX     # name.data = 0xc7fd(IP)
  0x4551fb      bb03000000      MOVL $0x3, BX           # name.len = 0x3
  # params == nil   【params ...int】
  # CX、DI、SI 用于传递 ..int 参数,是 []int
  0x455200      31c9            XORL CX, CX             # params.data = 0
  0x455202      31ff            XORL DI, DI             # params.len = 0
  0x455204      4889fe          MOVQ DI, SI             # params.cap = 0
  0x455207      e814000000      ALL main.show(SB)       # 调用 main.show 函数
}
  0x45520c      488b6c2428      MOVQ 0x28(SP), BP
  0x455211      4883c430        ADDQ $0x30, SP
  0x455215      c3              RET
func main() {
  0x455216      e845cdffff      CALL runtime.morestack_noctxt.abi0(SB)
  0x45521b      ebc3            JMP main.main(SB)
  1. ...int传递参数:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

func main() {
    //var s []int = []int{1, 2}
    X(1, 2, 3, 4) // []int  24字节
}

func X(ss ...int) int {
    // 可能ss==nil,因此编译器要做检查
    return ss[0] 
}
  1. main.main相关汇编:
 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
33
34
35
36
37
38
39
40
$ go build -gcflags="-N -l" -o ./h1 heliu.site/helium
$ go tool objdump -S -s '^main.main$' ./h1
TEXT main.main(SB) /mnt/hgfs/workspace/helium/main.go
func main() {
  0x4551e0      493b6610            CMPQ 0x10(R14), SP      # 栈增长判断
  0x4551e4      0f8687000000        JBE 0x455271        
  0x4551ea      4883ec60            SUBQ $0x60, SP        
  0x4551ee      48896c2458          MOVQ BP, 0x58(SP)    
  0x4551f3      488d6c2458          LEAQ 0x58(SP), BP    
    X(1, 2, 3, 4) # []int   24字节
    # 0x18-0x38 分别作为参数1,2,3,4
  0x4551f8      440f117c2418        MOVUPS X15, 0x18(SP)    # 0x18-0x28 清零
  0x4551fe      440f117c2428        MOVUPS X15, 0x28(SP)    # 0x28-0x38 清零
  0x455204      488d542418          LEAQ 0x18(SP), DX       # DX=0x18(SP)
  0x455209      4889542438          MOVQ DX, 0x38(SP)       # 0x38(SP)=0x18(SP)
  0x45520e      8402                TESTB AL, 0(DX)        
  0x455210      48c744241801000000  MOVQ $0x1, 0x18(SP)     # 1    
  0x455219      8402                TESTB AL, 0(DX)        
  0x45521b      48c744242002000000  MOVQ $0x2, 0x20(SP)     # 2
  0x455224      8402                TESTB AL, 0(DX)        
  0x455226      48c744242803000000  MOVQ $0x3, 0x28(SP)     # 3
  0x45522f      8402                TESTB AL, 0(DX)        
  0x455231      48c744243004000000  MOVQ $0x4, 0x30(SP)     # 4
  0x45523a      488b442438          MOVQ 0x38(SP), AX       # AX=0x38(SP)
  0x45523f      8400                TESTB AL, 0(AX)        
  0x455241      eb00                JMP 0x455243        
  0x455243      4889442440          MOVQ AX, 0x40(SP)       # ss.data=0x38(SP)
  0x455248      48c744244804000000  MOVQ $0x4, 0x48(SP)     # ss.len=4
  0x455251      48c744245004000000  MOVQ $0x4, 0x50(SP)     # ss.cap=4
  0x45525a      bb04000000          MOVL $0x4, BX           # BX=4
  0x45525f      4889d9              MOVQ BX, CX             # CX=4
  # AX=0x38(SP), BX=4, CX=4; 作为 main.X(SB) 的调用参数
  0x455262      e819000000          CALL main.X(SB)        
}
  0x455267      488b6c2458          MOVQ 0x58(SP), BP    
  0x45526c      4883c460            ADDQ $0x60, SP        
  0x455270      c3                  RET            
func main() {
  0x455271      e8eaccffff          CALL runtime.morestack_noctxt.abi0(SB)    
  0x455276      e965ffffff          JMP main.main(SB)
  1. main.X相关汇编:
 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
$ go tool objdump -S -s '^main.X$' ./h1
TEXT main.X(SB) /mnt/hgfs/workspace/helium/main.go
func X(ss ...int) int {
  0x455280      4883ec20            SUBQ $0x20, SP        
  0x455284      48896c2418          MOVQ BP, 0x18(SP)    
  0x455289      488d6c2418          LEAQ 0x18(SP), BP    
  # ss ...int
  0x45528e      4889442428          MOVQ AX, 0x28(SP)   # slice.data
  0x455293      48895c2430          MOVQ BX, 0x30(SP)   # slice.len
  0x455298      48894c2438          MOVQ CX, 0x38(SP)   # slice.cap
  0x45529d      48c744241000000000  MOVQ $0x0, 0x10(SP) # main.X 返回值 0
    return ss[0]
  0x4552a6      488b4c2430          MOVQ 0x30(SP), CX   # CX=4
  0x4552ab      488b542428          MOVQ 0x28(SP), DX   # DX=slice.data
  # TEST 逻辑与运算,因为ss[0]取第一个下标,因此CX=0是不能满足的,直接panic
  0x4552b0      4885c9              TESTQ CX, CX        # CX & CX; 这里做了一次越界检查    
  0x4552b3      7702                JA 0x4552b7            
  0x4552b5      eb12                JMP 0x4552c9            
  0x4552b7      488b02              MOVQ 0(DX), AX      # AX=ss[0]
  0x4552ba      4889442410          MOVQ AX, 0x10(SP)   # main.X 返回值 1
  0x4552bf      488b6c2418          MOVQ 0x18(SP), BP        
  0x4552c4      4883c420            ADDQ $0x20, SP            
  0x4552c8      c3                  RET                
  0x4552c9      31c0                XORL AX, AX            
  0x4552cb      e8f0d3ffff          CALL runtime.panicIndex(SB)    # panic,无效的索引
  0x4552d0      90                  NOPL
  1. main.mainmain.X 函数的栈布局情况:
 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
33
34
35
36
// +88  runtime.main back
// -------------------------------- ---------------------------------------
// +80  BP of runtime.main              +58
// -------------------------------- rbp     <-------    ------------------
// +78  4                               +50             sliceStruct.cap
// --------------------------------         <-------
// +70  4                               +48             sliceStruct.len   []int切片传参前
// --------------------------------         <-------
// +68  0x18(rsp)                       +40             sliceStruct.data
// --------------------------------         <-------    ------------------
// +60  0x18(rsp)                       +38             关联数组开始位置
// --------------------------------         <-------    ------------------
// +58          4                       +30
// --------------------------------
// +50          3                       +28
// --------------------------------                     传参变量
// +48          2                       +20
// --------------------------------
// +40          1                       +18
// --------------------------------         <-------    ------------------
// +38  4                               +10
// --------------------------------
// +30  4                               +08             X函数的参数 []int
// --------------------------------
// +28  0x18(rsp)                       +00
// -------------------------------- rsp     <-------    ------------------
// +20  main.main back
// -------------------------------- ---------------------------------------
// +18  BP of main.main
// -------------------------------- rbp
// +10  1               X函数的返回值
// --------------------------------                     X函数栈
// +08
// --------------------------------
// +00
// -------------------------------- rsp -----------------------------------
  1. s...形式传参:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

func main() {
    var s []int = []int{1, 2}
    X(s...) // []int    24字节
}

func X(ss ...int) int {
    // if len(ss) >= 2 {
    //        return ss[1]
    // } else {
    //        return 0
    // }

    return ss[1]    // 这里会进行一次越界检查
}
  1. main.main相关汇编代码:
 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
33
34
35
$ go tool objdump -S -s '^main.main$' ./h1
TEXT main.main(SB) /mnt/hgfs/workspace/helium/main.go
func main() {
  0x4551e0      493b6610            CMPQ 0x10(R14), SP      # 栈增长判断
  0x4551e4      7669                JBE 0x45524f        
  0x4551e6      4883ec50            SUBQ $0x50, SP        
  0x4551ea      48896c2448          MOVQ BP, 0x48(SP)    
  0x4551ef      488d6c2448          LEAQ 0x48(SP), BP    
    var s []int = []int{1, 2}
    # 0x18(SP)-0x28(SP) 用于 1,2 参数的存放
  0x4551f4      440f117c2418        MOVUPS X15, 0x18(SP)    # 0x18(SP)-0x28(SP) 清零
  0x4551fa      488d442418          LEAQ 0x18(SP), AX       # AX=0x18(SP)
  0x4551ff      4889442428          MOVQ AX, 0x28(SP)       # 0x28(SP)=AX=0x18(SP)
  0x455204      8400                TESTB AL, 0(AX)        
  0x455206      48c744241801000000  MOVQ $0x1, 0x18(SP)     # 0x18(SP)=1
  0x45520f      8400                TESTB AL, 0(AX)        
  0x455211      48c744242002000000  MOVQ $0x2, 0x20(SP)     # 0x20(SP)=2
  0x45521a      8400                TESTB AL, 0(AX)        
  0x45521c      eb00                JMP 0x45521e        
  0x45521e      4889442430          MOVQ AX, 0x30(SP)       # ss.data=0x18(SP)
  0x455223      48c744243802000000  MOVQ $0x2, 0x38(SP)     # ss.len=2
  0x45522c      48c744244002000000  MOVQ $0x2, 0x40(SP)     # ss.cap=2
    X(s...) # []int    24字节
  0x455235      bb02000000          MOVL $0x2, BX           # BX=2
  0x45523a      4889d9              MOVQ BX, CX             # CX=2
  0x45523d      0f1f00              NOPL 0(AX)        
  # AX=0x18(SP); BX=2; CX=2; 用作 main.X 函数的传参
  0x455240      e81b000000          CALL main.X(SB)        
}
  0x455245      488b6c2448          MOVQ 0x48(SP), BP    
  0x45524a      4883c450            ADDQ $0x50, SP        
  0x45524e      c3                  RET            
func main() {
  0x45524f      e80ccdffff          CALL runtime.morestack_noctxt.abi0(SB)    
  0x455254      eb8a                JMP main.main(SB)
  1. main.X函数相关汇编:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ go tool objdump -S -s '^main.X$' ./h1
TEXT main.X(SB) /mnt/hgfs/workspace/helium/main.go
func X(ss ...int) int {
  0x455260      4883ec20        SUBQ $0x20, SP        
  0x455264      48896c2418      MOVQ BP, 0x18(SP)    
  0x455269      488d6c2418      LEAQ 0x18(SP), BP    
  0x45526e      4889442428      MOVQ AX, 0x28(SP)   # ss.data
  0x455273      48895c2430      MOVQ BX, 0x30(SP)   # ss.len
  0x455278        48894c2438        MOVQ CX, 0x38(SP)   # ss.cap
  0x45527d      48c744241000000000  MOVQ $0x0, 0x10(SP) # 返回值临时内存 0
    return ss[1] # 这里会进行一次越界检查
  0x455286      488b4c2430      MOVQ 0x30(SP), CX   # CX=2
  0x45528b      488b542428      MOVQ 0x28(SP), DX   # DX=2
  0x455290      4883f901        CMPQ $0x1, CX        # 这里进行越界检查
  0x455294      7702            JA 0x455298            
  0x455296      eb13            JMP 0x4552ab            
  0x455298      488b4208        MOVQ 0x8(DX), AX    # AX=ss[1]
  0x45529c      4889442410      MOVQ AX, 0x10(SP)   # 返回值临时内存 ss[1]
  0x4552a1      488b6c2418      MOVQ 0x18(SP), BP        
  0x4552a6      4883c420        ADDQ $0x20, SP            
  0x4552aa      c3              RET                
  0x4552ab      b801000000      MOVL $0x1, AX            
  0x4552b0      e80bd4ffff      CALL runtime.panicIndex(SB)    
  0x4552b5      90              NOPL
  1. main.mainmain.X函数的栈布局情况:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// +50  runtime.main back
// ------------------------------------- ----------------------------------------------
// +48  BP of runtime.main
// ------------------------------------- rbp    <----------------   -------   ---------
// +40  2                                       sliceStruct.cap
// -------------------------------------        <----------------
// +38  2                                       sliceStruct.len     []int
// -------------------------------------        <----------------
// +30  0x18(rsp)                               sliceStruct.data
// -------------------------------------        <----------------   -------   初始化s变量
// +28  0x18(rsp)                               底层数组首地址
// -------------------------------------
// +20  2
// -------------------------------------
// +18  1
// -------------------------------------        <----------------   -------   ---------
// +10
// -------------------------------------        <----------------
// +08                                                                  X函数参数[]int
// -------------------------------------        <----------------
// +00
// ------------------------------------- rsp    <----------------   -------   ---------

函数调用

  1. Go语言中函数默认使用按值传递来传递参数,也就是传递参数的副本。函数接收参数副本之后,在使用变量的过程中可能对副本的值进行更改,但不会影响原来的变量。
  2. 如果希望函数可以直接修改参数的值,而不是对参数的副本进行操作。需要将参数的地址传递给函数,这就是按引用传值。如Function(&arg1),此时传递给函数的是一个指针。如果传递给函数的是一个指针,则可以通过这个指针来修改对应地址上的变量值。
  3. 在进行函数调用时,像切片(slice)、字典(map)、函数(func)、通道(channel)等这样的引用类型都是默认使用引用传递
  4. 命名返回值被初始化为相应类型的零值,当需要返回的时候,只需要一条简单的不带参数的return语句。即使只有一个命名返回值,也需要使用()括起来。如type funcType func() (b bool)type funcType1 func() bool
 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
func main() {
    // 常规调用,参数可以是多个
    list(1, 2, 3, 4, 5, 6)  // []int{1, 2, 3, 4, 5, 6} []int

    // 在参数同类型时,可以组成slice使用 params... 进行参数传递
    numbers := []int{1, 2, 3, 4, 5, 6, 7}
    // numbers... 语法只对slice和string使用。
    list(numbers...)        // []int{1, 2, 3, 4, 5, 6, 7} []int
    fmt.Println(numbers)    // [2 2 3 4 5 6 7]

    // Output:
    // []int{1, 2, 3, 4, 5, 6} []int
    // []int{1, 2, 3, 4, 5, 6, 7} []int
    // [2 2 3 4 5 6 7]
}

// 变长参数,参数不定长
func list(nums ...int) {
    if nums == nil {
        painc("切片未初始化 nil")
    }
    
    fmt.Printf("%#v %T\n", nums, nums)

    nums[0] += 1
}

内置函数

  1. 内置函数是预先声明的,它们像任何其他函数一样被调用。
  2. 内置函数没有标准的类型,因此只能出现在调用表达式中,不能用作函数值。
  3. 它们有时候可以针对不同的类型进行操作。
  4. 内置函数make()new()都和内存分配相关,但也有差异。
内置函数 说明
make(T) make只用于slicemap以及channel这三种引用数据的内存分配初始化make(T)返回类型T的值(不是*T)
new(T) new用于值类型的内存分配,并且置为零值new(T)分配类型T的零值并返回其地址,也就是指向类型T的指针
  1. 内置函数make()用作于slicemapchannel三种数据类型时,参数及作用有些区别。
  2. make 函数原型:func make(t Type, size ...IntegerType) Type
  3. new 函数原型:func new(Type) *Type
T 的类型 参数 说明
slice make(T, n) T为切片类型,长度和容量都为n
slice make(T, n, m) T为切片类型,长度n,容量mn <=m,否则错误 )
map make(T) T为字典类型
map make(T, n) T为字典类型,分配n个元素的空间
channel make(T) T为通道类型,无缓冲区
channel make(T, n) T为通道类型,缓冲区容量为n
  1. 内置函数make()的实际使用举例
1
2
3
4
5
6
s := make([]int, 10, 100)        // 切片,len(s) == 10, cap(s) == 100
s := make([]int, 1e3)            // 切片,len(s) == cap(s) == 1000
s := make([]int, 1 << 63)        // 非法,int类型的大小*len(s) 以造成内存不足,所以导致非法 int*cap(s)如果溢出也是一样的
s := make([]int, 10, 0)          // 非法,len(s) > cap(s)
c := make(chan int, 10)          // 通道缓冲区有10个元素
m := make(map[string]int, 100)   // map的初始空间有大约100个元素
  1. new(T) 内置函数在运行时为该类型的变量分配内存,返回指向它的类型 *T 的值,并对变量初始化
1
2
3
4
5
type S struct {
    a int
    b float64
}
new(S)  // &S{0,0.0}
  1. new(S)S类型的变量分配内存,并初始化(a = 0, b = 0.0),返回包含该位置地址的类型 *S 的值。
  2. slicemapchannel这三种数据类型声明时,可设置长度或容量,所以通过内置函数len()cap()得到对应长度与容量。
  3. len 函数原型:func len(v Type) int
  4. cap 函数原型:func cap(v Type) int
内置函数 参数s的类型 结果说明
len(s) string string类型s的长度(按照字节计算)
len(s) [n]T*[n]T 数组类型s的长度([n]T[n]*T*[n]T)数组指针*[n]T支持语法糖
len(s) []T 切片类型s的长度
len(s) map[K]T 字典类型s的长度
len(s) chan T 通道类型s的缓冲区排队的元素数量
cap(s) []T 切片类型s的容量
cap(s) chan T 通道类型s的缓冲区容量
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import "fmt"

func main() {
    var a = new([2]int) // *[2]int

    // len(a) 语法糖
    fmt.Printf("%#v %T %d", a, a, len(a))

    // Output:
    // &[2]int{0, 0} *[2]int 2
}
  1. 对于len(s)cap(s),如果snil值,则两个函数的取值都是0,此外:
1
0 <= len(s) <= cap(s)
  1. Go语言中,常量在某些计算条件下也可以通过表达式计算得到。
  2. 假如s是字符串常量,则表达式len(s)是常量。
  3. s的类型是数组或指向数组的指针而表达式不包含通道接收或(非常量)函数调用,则表达式len(s)cap(s)是常量,否则lencap的调用不是常量。
1
2
3
4
5
6
7
8
9
const (
    c1 = imag(2i)                   // 2.0  imag(2i) == 2.0 是常量
    c2 = len([10]float64{2})        // 10   [10]float64{2} 无函数调用
    c3 = len([10]float64{c1})       // 10   [10]float64{c1} 无函数调用  
    c4 = len([10]float64{imag(2i)}) // 10   imag(2i)常量无函数调用
    c5 = len([10]float64{imag(z)})  // 无效 imag(z) 非常量函数调用
)

var z complex128

递归与回调

  1. 递归函数:函数直接或间接调用函数本身。使用递归函数时经常会遇到栈溢出。
 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
func main() {
    var i uint64 = 7
    fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(i)) // 7 的阶乘是 5040
    fmt.Printf("%d 的阶乘是 %d\n", i, Fac2(i))      // 7 的阶乘是 5040

    // Output:
    // 7 的阶乘是 5040
    // 7 的阶乘是 5040
}

// Factorial 函数递归调用   n! = n*(n-1)...*1
func Factorial(n uint64) (result uint64) {
    if n > 0 {
        result = n * Factorial(n - 1)

        return result
    }

    return 1
}

// Fac2 循环形式实现 n! = n*(n-1)...*1
func Fac2(n uint64) (result uint64) {
    result = 1
    var un uint64 = 1
    for i := un; i <= n; i++ {
        result *= i
    }

    return
}
  1. Go语言中也可以使用相互调用的递归函数,多个函数之间相互调用形成闭环。
  2. 回调:Go语言函数可以作为其他函数的参数进行传递,然后在其他函数内调用执行。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func main() {
    callback(1, Add)
    
    // Output:
    // 1 + 2 = 3
}

func Add(a, b int) {
    fmt.Printf("%d + %d = %d", a, b, a + b) // 1 + 2 = 3
}

func callback(y int, f func(int, int)) {
    f(y, 2) // 回调函数f
}

匿名函数

  1. 匿名函数:函数值字面量是一种表达式。不给函数起名字的时候,可以使用匿名函数。
  2. 这样的函数不能独立存在,但是可以被赋值于某个变量,即保存函数的地址到变量中。
1
2
3
4
5
6
fplus := func(x, y int) int {
    return x + y
}

// 然后通过变量名对函数进行调用
fplus(3, 4)
  1. 当然,也可以直接对匿名函数进行调用。注意匿名函数的最后加上括号并填入参数值,如果没有参数,也需要加上括号,代表直接调用。
1
2
3
func(x, y int) int {
    return x + y
}(3, 4)
  1. 计算0 ~ 100万整数的总和的匿名函数。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
    fn := func() {
        fmt.Println("hello")
    }

    fn()

    fmt.Println("匿名函数加法求集:", func(x, y int) int {return x + y}(3,4))

    func() {
        sum := 0
        for i := 1; i <= 1e6; i++ {
            sum += i
        }
        fmt.Println("匿名函数加法循环求和:", sum)
    }()
    
    // Output:
    // hello
    // 匿名函数加法求集: 7
    // 匿名函数加法循环求和: 500000500000
}

变参函数

  1. 可变参数就是不定长参数,支持可变参数列表的函数可以支持任意个传入参数。如:fmt.Println 函数就是一个支持可变长参数列表的函数。
 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
package main

import "fmt"

func main() {
    s := []string{"hello", "world"}

    // 注意这里的切片 s... 把切片打撒传入,与s具有相同底层数组的值
    Greeting(s...)

    fmt.Println(s)

    // Output:
    // 0 hello
    // 1 world
    // [hello 123456]
}

// 这里的who参数其实就是切片[]string类型,也就是所谓的可变参数
func Greeting(who ...string){
    for k, v := range who{
        fmt.Println(k, v)
    }

    who[1] = "123456"
}