Golang 函数及相关特性

一、函数基础

1. 函数定义

(1)基本语法

Go 语言中函数定义的基本语法如下:

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

(2)三种函数类型

  • 普通函数:有明确名称的函数,可直接调用。
  • 匿名函数:无名称的函数,可在函数内部定义或作为值使用。
  • 方法:定义在 struct 内的函数,与特定结构体关联。

2. 函数参数

(1)参数特点

  • 可包含多个参数,每个参数需指定类型。
  • 允许参数无名称(类似 C++ 占位参数)。

(2)传参方式

  • 值传递:传递参数的拷贝副本,函数内修改不影响外部。
  • 特殊类型:map、slice、interface、channel 本身为指针类型,拷贝后的指针仍指向底层数据,修改可能影响外部。

(3)变长参数

格式为 ARGS...TYPE,将参数保存到名为 ARGS 的 slice 中(元素类型均为 TYPE)。

3. 函数返回值

(1)返回值特点

  • 支持多个返回值,需指定类型。
  • return 可带参数或省略,省略时返回值必须有名称。
  • 返回值有名称时需用括号包围(即使单个),且名称不能与参数重名。
  • return 中可含表达式,但不能是赋值表达式(如 return c = a + b 错误)。
  • 命名返回值在函数内可直接使用,无需再次声明;return 中指定参数时,优先级更高。

(2)使用习惯

  • 常用一个返回值表示执行结果(如 value, err value, ok)。
  • 返回值超过 4 个时,建议放入容器(同类型用 slice,不同类型用 map)。
  • 可用下划线 _ 丢弃不需要的返回值。

4. 函数类型与函数变量

通过类型别名定义函数类型,类似函数指针,可实现类似重载的效果:

type fun func(int, int) int // 定义函数类型 fun

5. 函数特性

  • 不支持函数重载(overload)。
  • 不能嵌套函数,但可嵌套匿名函数。
  • 函数是值,可赋值给变量、作为参数传递或作为返回值。
  • 调用时参数使用值传递(先拷贝副本再传递)。

6. 函数示例

package main

import "fmt"

// 无返回值函数
func test01() {
    fmt.Println("not return a value")
}

// 函数类型别名
type fun func(int, int) int

// 求和函数(有返回值)
func sum(a int, b int) (ret int) {
    ret = a + b
    return ret
}

// 比较大小函数
func compare(a int, b int) (max int) {
    if a > b {
        max = a
    } else {
        max = b
    }
    return max
}

// 多返回值(可省略 return 参数)
func test02() (name string, age int) {
    name = "tom"
    age = 20
    return // 等价于 return name, age
}

// return 覆盖命名返回值
func test03() (name string, age int) {
    a := "jim"
    b := 30
    return a, b
}

// 值传递示例
func test04(a int) {
    a = 200
    fmt.Printf("a = %d\n", a)
}

// 引用类型修改示例(slice)
func test05(a []int) {
    a[0] = 10
}

// 变长参数示例 1
func printArgs1(args ...int) {
    for _, v := range args {
        fmt.Printf("%v ", v)
    }
    fmt.Println()
}

// 变长参数示例 2(含固定参数)
func printArgs2(name string, age int, args ...int) {
    fmt.Printf("name: %v, age: %v, args: ", name, age)
    for _, v := range args {
        fmt.Printf("%v ", v)
    }
    fmt.Println()
}

func main() {
    ret := sum(1, 2)
    fmt.Printf("ret = %d\n", ret) // 输出:ret = 3

    max := compare(1, 3)
    fmt.Printf("max = %d\n", max) // 输出:max = 3
    fmt.Println("------------------------")

    test01() // 输出:not return a value

    name, age := test02()
    fmt.Printf("name: %v, age: %v\n", name, age) // 输出:name: tom, age: 20

    name, _ = test03() // 丢弃 age 返回值
    fmt.Printf("name: %v, age: %v\n", name, age) // 输出:name: jim, age: 20

    // 值传递演示
    a := 100
    test04(a)                 // 输出:a = 200
    fmt.Printf("a = %d\n", a) // 输出:a = 100(外部值未变)
    fmt.Println("------------------------")

    // 引用类型修改演示
    s := []int{1, 2, 3}
    test05(s)
    fmt.Printf("s: %v\n", s) // 输出:s: [10 2 3](内部修改影响外部)
    fmt.Println("------------------------")

    // 变长参数调用
    printArgs1(1, 2, 3, 4, 5)            // 输出:1 2 3 4 5
    printArgs2("tom", 20, 1, 2, 3, 4, 5) // 输出:name: tom, age: 20, args: 1 2 3 4 5
    fmt.Println("------------------------")

    // 函数变量使用
    var f fun
    f = sum
    result := f(1, 3)
    fmt.Printf("sum = %v\n", result) // 输出:sum = 4
    f = compare
    result = f(1, 3)
    fmt.Printf("max = %v\n", result) // 输出:max = 3
}

二、defer 语句

1. 作用与执行顺序

defer 用于延迟执行语句,在所属函数即将返回时,按 defer 定义的逆序(先进后出,类似栈)执行。

2. 特性

  • 注册的延迟调用在 return 前执行。
  • 多个 defer 按“先进后出”顺序执行。
  • defer 语句中的变量值在声明时确定。
  • 若遇 panic,会先执行所有 defer 函数再 panic。

3. 主要用途(资源释放)

  • 关闭文件句柄。
  • 释放锁资源。
  • 释放数据库连接。

三、指针

1. 基本概念

指针变量指向值的内存地址,通过指针可修改函数外部变量,避免数据拷贝。

2. 指针类型与声明

  • 指针类型格式:*Type(如 *int *string)。

  • 声明语法:

    var ip *int    // 声明 int 类型指针
    var fp *float32 // 声明 float32 类型指针
    

3. 指针数组

数组元素均为指针,声明示例:

const MAX int = 3
var ptr [MAX]*int // 包含 3 个 int 指针的数组

4. 指针使用限制

Go 指针仅支持取地址(&)和根据地址取值(*),不支持偏移和运算。

5. 指针示例

package main

import "fmt"

const MAX int = 3

func main() {
    // 指针基本使用(& 取地址,* 取值)
    var a int = 20
    var ip *int
    ip = &a
    fmt.Printf("%v\n", &a)       // 输出:a 的内存地址(如 0xc000016098)
    fmt.Printf("%v\n", ip)       // 输出:同 &a(指针存储的地址)
    fmt.Printf("ip = %v\n", *ip) // 输出:ip = 20(指针指向的值)
    fmt.Println()

    // 指针数组示例
    arr1 := []int{1, 2, 3}
    var arr2 [MAX]*int
    fmt.Println(arr1) // 输出:[1 2 3]
    fmt.Println(arr2) // 输出:[<nil> <nil> <nil>]

    for i := 0; i < MAX; i++ {
        arr2[i] = &arr1[i]
        fmt.Printf("%v ", *arr2[i]) // 输出:1 2 3
    }
    fmt.Println()
    fmt.Println(arr2) // 输出:arr2 中存储的 arr1 元素地址
}

四、高阶函数

1. 匿名函数

(1)定义与特点

  • 无名称的函数,格式:func (参数列表) (返回值)
  • 可在函数内部定义(Go 不允许嵌套普通函数,但允许嵌套匿名函数)。
  • 可直接赋值给变量或立即执行。

(2)示例

package main

import "fmt"

// 函数作为参数(类似回调)
func sayHello(name string) {
    fmt.Printf("hello, %s\n", name)
}

func f1(name string, f func(string)) {
    f(name)
}

// 函数作为返回值
func add(x, y int) int {
    return x + y
}

func sub(x, y int) int {
    return x - y
}

func cal(s string) func(int, int) int {
    switch s {
    case "+":
        return add
    case "-":
        return sub
    default:
        return nil
    }
}

func main() {
    f1("tom", sayHello) // 输出:hello, tom

    addFunc := cal("+")
    ret := addFunc(1, 3)
    fmt.Printf("ret = %v\n", ret) // 输出:ret = 4
    subFunc := cal("-")
    ret = subFunc(3, 1)
    fmt.Printf("ret = %v\n", ret) // 输出:ret = 2
    fmt.Println("---------------------")

    // 匿名函数赋值给变量
    max := func(a int, b int) int {
        if a > b {
            return a
        } else {
            return b
        }
    }
    i := max(3, 1)
    fmt.Printf("max = %v\n", i) // 输出:max = 3

    // 匿名函数立即执行
    result := func(a int, b int) int {
        if a > b {
            return a
        } else {
            return b
        }
    }(3, 1)
    fmt.Printf("立即执行结果:%v\n", result) // 输出:立即执行结果:3
}

2. 闭包

(1)概念

闭包 = 函数 + 引用环境,即外层函数中定义的内层函数,操作外层函数的局部变量(参数或内部变量),且外层函数返回该内层函数。

(2)特性

外层函数的局部变量生命周期延长,不会随外层函数结束而销毁(因内层函数仍需使用)。

(3)示例

package main

import (
    "fmt"
    "strings"
)

// 闭包基础示例
func add1() func(int) int {
    var x int // 外层函数局部变量
    return func(y int) int {
        x += y // 内层函数操作外层变量
        return x
    }
}

// 带参数的闭包
func add2(x int) func(int) int {
    return func(y int) int {
        x += y
        return x
    }
}

// 为文件名添加后缀的闭包
func makeSuffix(suffix string) func(string) string {
    return func(name string) string {
        if !strings.HasSuffix(name, suffix) {
            return name + suffix
        }
        return name
    }
}

// 返回多个闭包
func cal(base int) (func(int) int, func(int) int) {
    add := func(x int) int {
        base += x
        return base
    }
    sub := func(x int) int {
        base -= x
        return base
    }
    return add, sub
}

func main() {
    // 基础闭包演示
    f1 := add1()
    fmt.Println(f1(10)) // 输出:10(x=10)
    fmt.Println(f1(20)) // 输出:30(x=10+20)
    fmt.Println(f1(30)) // 输出:60(x=30+30)
    f2 := add1() // 新闭包,独立 x
    fmt.Println(f2(40)) // 输出:40
    fmt.Println(f2(50)) // 输出:90
    fmt.Println("--------------")

    // 带参数闭包演示
    f3 := add2(10) // 初始 x=10
    fmt.Println(f3(10)) // 输出:20(10+10)
    fmt.Println(f3(20)) // 输出:40(20+20)
    fmt.Println(f3(30)) // 输出:70(40+30)
    f4 := add2(20) // 初始 x=20
    fmt.Println(f4(40)) // 输出:60(20+40)
    fmt.Println(f4(50)) // 输出:110(60+50)
    fmt.Println("--------------")

    // 文件名后缀闭包
    jpgFunc := makeSuffix(".jpg")
    txtFunc := makeSuffix(".txt")
    fmt.Println(jpgFunc("test")) // 输出:test.jpg
    fmt.Println(txtFunc("test")) // 输出:test.txt
    fmt.Println("--------------")

    // 多闭包共享基础值
    f5, f6 := cal(10) // 初始 base=10
    fmt.Println(f5(2), f6(3)) // 输出:12(10+2) 9(12-3)
    fmt.Println(f5(3), f6(4)) // 输出:12(9+3) 8(12-4)
    fmt.Println(f5(4), f6(5)) // 输出:12(8+4) 7(12-5)
}

3. 递归函数

(1)注意事项

  • 必须定义退出条件,避免死循环。
  • 可能产生大量 goroutine 或导致栈内存溢出。

(2)示例

package main

import "fmt"

// 阶乘(n! = n × (n-1) × ... × 1)
func fac(n int) int {
    if n == 1 { // 退出条件
        return 1
    }
    result := n * fac(n-1)
    fmt.Println(n, result)
    return result
}

// 斐波那契数列(第 n 项 = 第 n-1 项 + 第 n-2 项,前两项为 1)
func fib(n int) int {
    if n == 1 || n == 2 { // 退出条件
        return 1
    }
    return fib(n-1) + fib(n-2)
}

func main() {
    fac(5) // 输出:2 2;3 6;4 24;5 120

    for i := 1; i < 10; i++ {
        fmt.Printf("%d ", fib(i)) // 输出:1 1 2 3 5 8 13 21 34
    }
    fmt.Println()
}

五、init 函数

1. 作用与特点

  • 特有的初始化函数,先于 main 执行,用于包级初始化(类似 C++ 构造函数)。
  • 无参数、无返回值,自动执行,不能被调用。
  • 每个包、每个源文件可包含多个 init 函数。

2. 执行顺序

  • 初始化顺序:变量初始化 → init() → main()。
  • 同一文件:按 init 定义顺序从上到下执行。
  • 同一包不同文件:按文件名字符串升序执行各文件的 init。
  • 不同包:若无依赖,按 main 包 import 顺序执行;若有依赖,最后被依赖的包先初始化(如 import 顺序 main-A-B-C,初始化顺序 C-B-A-main)。

3. 与 main 函数的对比

相同点 不同点
均为 Go 保留函数;无参数、无返回值;仅由 Go 自动调用 init 可在任意包中定义,可多个;main 仅在 main 包中,仅一个;init 先于 main 执行

4. 注意事项

  • 避免循环 import(如 A-B-C-A)。
  • 一个包被多次 import 仅初始化一次。
  • 建议使用主动初始化,避免依赖 init 执行顺序。

5. 示例

package main

import "fmt"

// 一个包中可以多个 init 函数,但是 init 的执行顺序没有明确定义
// 变量初始化(先于 init)
var a int = initVar()

// 第一个 init 函数
func init() {
    fmt.Println("init1")
}

// 第二个 init 函数
func init() {
    fmt.Println("init2")
}

// 变量初始化函数
func initVar() int {
    fmt.Println("init var")
    return 10
}

func main() {
    fmt.Println("main...")
}

/* 输出:
init var
init1
init2
main...
*/

六、builtin 包(内置标识符与函数)

builtin 并非实际包,包含 Go 预定义的标识符和函数。官方文档:https://pkg.go.dev/builtin@go1.19.1

1. nil 变量

nil 是指针、通道、函数、接口、map、切片的零值,声明格式:

var nil Type // Type 必须是指针、通道、函数、接口、map 或切片类型

2. 常用内置函数

(1)append

  • 作用:向切片追加元素。

  • 原型:

    func append(slice []Type, elems ...Type) []Type
    

(2)cap

  • 作用:返回容量(切片、数组、通道的最大存储能力)。

  • 原型:

    func cap(v Type) int
    

(3)close

  • 作用:关闭通道(仅用于发送通道 chan<- Type)。

  • 原型:

    func close(c chan<- Type)
    

(4)copy

  • 作用:拷贝切片内容(将 src 拷贝到 dst)。

  • 原型:

    func copy(dst, src []Type) int // 返回实际拷贝的元素数
    

(5)delete

  • 作用:删除 map 中的键值对。

  • 原型:

    func delete(m map[Type]Type1, key Type)
    

(6)len

  • 作用:返回长度(字符串、数组、切片、map、通道的当前元素数)。

  • 原型:

    func len(v Type) int
    

(7)make

  • 作用:为 slice、map、chan 分配空间并初始化。

  • 原型:

    func make(t Type, size ...IntegerType) Type
    

(8)new

  • 作用:分配内存(初始化为零值),返回指向该内存的指针。

  • 原型:

    func new(Type) *Type
    

(9)print 与 println

  • 作用:打印内容(简单输出,不支持格式化)。

  • 原型:

    func print(args ...Type)
    func println(args ...Type) // 打印后换行
    

(10)panic 与 recover

  • panic:引发恐慌,立即停止当前调用并抛出异常。

    func panic(v any)
    
  • recover:在延迟函数中执行,用于恢复恐慌,返回 panic 传递的值;若在延迟函数外调用或无恐慌,返回 nil。

    func recover() any