王清欢Randy 王清欢Randy
首页
  • 编程语言

    • C/C++ 学习笔记
    • Golang 学习笔记
  • 算法分析

    • LeetCode 刷题笔记
  • 操作系统

    • Linux 基础
    • Vim 实用技巧
    • Shell 脚本编程
    • GDB 学习笔记
  • 开发工具

    • Git 学习笔记
  • 分布式理论

    • 共识算法
    • 分布式事务
  • 数据库内核

    • PostgreSQL
    • Postgres-XL
  • hidb
  • pgproxy
  • 实用技巧
  • 学习方法
  • 资源分享
GitHub (opens new window)
首页
  • 编程语言

    • C/C++ 学习笔记
    • Golang 学习笔记
  • 算法分析

    • LeetCode 刷题笔记
  • 操作系统

    • Linux 基础
    • Vim 实用技巧
    • Shell 脚本编程
    • GDB 学习笔记
  • 开发工具

    • Git 学习笔记
  • 分布式理论

    • 共识算法
    • 分布式事务
  • 数据库内核

    • PostgreSQL
    • Postgres-XL
  • hidb
  • pgproxy
  • 实用技巧
  • 学习方法
  • 资源分享
GitHub (opens new window)
  • Golang基础

    • 数据类型

      • 变量与常量
      • 基础数据类型之值类型
      • 基础数据类型之引用类型
    • 流程控制

      • 条件判断
      • 循环控制
    • 函数

      • 函数基础
        • 01 函数声明
        • 02 更多样的参数列表
        • 03 更灵活的返回值列表
        • 参考资料
      • 匿名函数与闭包
      • 延迟调用
  • Golang学习笔记
  • Golang基础
  • 函数
王清欢
2023-03-24
目录

函数基础

# 函数基础

函数是一个固定的、可重复使用的程序段(子程序),它在实现单一或相关联功能的同时,还可以带有入口和出口,所谓的入口,就是函数参数即形参,通过这个入口把函数的参数值传递给子程序;所谓出口,就是指函数的返回值,通过这个出口可以获取子程序的计算结果。

# 01 函数声明

Golang 中使用 func 关键字进行函数声明,声明语句中包含函数名,参数列表, 返回值列表和函数体四个部分,其中:

  • 函数名:由字母、数字、下画线组成,和其他语言一样函数名的首字母不能为数字;
  • 参数列表:由一个或多个形式参数和参数类型组成,同类型形参可以进行合并,参数列表中的形参作用域仅限于该函数体内。(支持不定变参 interface{}/any)
  • 返回值列表:由一个或多个变量类型类型组成,返回值列表可以省略,但是一旦声明在函数体中必须使用 return 语句提供返回值。函数从第一条语句开始执行,直到执行 return 语句或者执行函数的最后一条语句。(不同于C/C++,Golang 函数支持多个返回值类型)
  • 函数体:实现函数功能逻辑的代码片段。
// 声明语法
func函数名(参数列表)(返回值列表){
    函数体
}

// example:
func func1(a, b int, s string) (int, string) {
    res := a + b
    return res, fmt.Sprintf("%s:%d\n", s, res)
}

func main() {
    res, str := func1(2, 3, "res of func1")
    fmt.Printf("result:%d\n%s", res, str)
}

/* output:
result:5
res of func1:5
*/

# 02 更多样的参数列表

函数定义时有参数,该变量可称为函数的形参,形参就像定义在函数体内的局部变量;而在外部调用函数时传递的变量就是函数的实参。

值传递与引用传递:值传递和引用传递都是传递的实参的副本,但值传递是值的拷贝,而引用传递则是地址的拷贝。所以值传递中对参数修改并不会影响外部的实际参数,因为它们的值存储在不同的内存空间中;但引用传递传递的是实际值所存储的内存空间地址,当实参和形参指向相同的内存空间,所以当对形式参数修改将会影响外部的实际参数。

一般来说,地址拷贝更为高效,特别是传入对象很大时;而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。

切片slice、键值对map、通道chan、指针、接口interface 这几种类型默认以引用的方式传递。

func func2(a int, b *int, sl []int) {
    a++
    *b--
    if len(sl) != 0 {
        sl[0] = -1
    }
    fmt.Printf("inner: a:%d, b:%d, ", a, *b)
    fmt.Println("sl:", sl)
}

func main() {
    s1 := []int{1, 2, 3}
    a := 1
    b := 1
    fmt.Printf("outer(before): a:%d, b:%d, ", a, b)
    fmt.Println("sl:", s1)
    func2(a, &b, s1)
    fmt.Printf("outer(after): a:%d, b:%d, ", a, b)
    fmt.Println("sl:", s1)
}

/* output:
outer(before): a:1, b:1, sl: [1 2 3]
inner: a:2, b:0, sl: [-1 2 3]
outer(after): a:1, b:0, sl: [-1 2 3]
*/

不定类型参数:Golang 中在 1.18 版本之前使用 interface{} 作为泛型,后又引入了 any 关键字作为空接口 interface{} 的别名,在泛型应用场景下使用 interface{}/any 可以表示任意类型。

func func3(a any) {
    switch t := a.(type) {
    case nil:
        fmt.Printf("变量的类型 :%T\r\n", t)
    case int:
        fmt.Println("变量是 int 型")
    case float64:
        fmt.Println("变量是 float64 型")
    case func(int) float64:
        fmt.Println("变量是 func(int) 型")
    case bool, string:
        fmt.Println("变量是 bool 或 string 型")
    default:
        fmt.Println("未知型")
    }
}

func main() {
    var i int = 1
    func3(i)
    var s string = "hello"
    func3(s)
}

/* output:
变量是 int 型
变量是 bool 或 string 型
*/

数量可变参数:相较于 C/C++,Golang 支持可变参数,即函数在声明和调用时没有固定数量的参数。

  • 可变参数通常只能有一个且被放在参数列表的末尾,如果有固定参数放在其前面,没有固定参数时则所有变量都是可变参数。
  • 可变参数的声明方式是 args ...Type,其本质就是一个切片,可以通过 args[index]依次访问每个参数。
  • 任意类型可变参数:当声明中参数类型为 interface{}/any 时,可变参数中每个参数的类型都可以是不固定的。
func func4(str string, hash map[string]int, args ...any) {
    fmt.Println("(Fixed) first param:", str)
    fmt.Println("(Fixed) second param:", hash)
    fmt.Println("(Variable) args:")
    for _, arg := range args {
        func3(arg)
    }
}

func main() {
    var i int = 1
    var s string = "hello"
    hash := map[string]int{
        "one": 1,
        "two": 2,
    }
    func4("testString", hash, i, s, 3.14, false)
}

/* output:
(Fixed) first param: testString
(Fixed) second param: map[one:1 two:2]
(Variable) args:
变量是 int 型
变量是 bool 或 string 型
变量是 float64 型
变量是 bool 或 string 型
*/

# 03 更灵活的返回值列表

Golang 支持多返回值,能方便地获得函数执行后的多个返回结果;由于 Golang 在错误处理上的不足,经常会使用多返回值中的最后一个返回参数返回函数执行中可能发生的错误。

func func5(a, b int) (int, int) {
    sum := a + b
    sub := a - b
    return sum, sub
}

func main() {
    sum5, sub5 := func5(1, 1)
    fmt.Printf("func5 sum:%d, sub:%d\n", sum5, sub5)
}

返回值作为函数实参:如果某个函数的参数列表与另一个函数的返回值列表匹配,那么可以直接将后者的多个返回值作为前者的实参。

func func6(a, b int) float32 {
    res := float32(a / b)
    return res
}

func func7(args ...int) int {
    res := 1
    for _, arg := range args {
        res *= arg
    }
    return res
}

func main() {
    res6 := func6(func5(2, 1))
    res7 := func7(func5(4, 2))
    fmt.Printf("func6:%f, func7:%d\n", res6, res7)
}

/* output:
func6:3.000000, func7:12
*/

命名函数返回参数:返回值列表中还可以对返回值进行命名,这样返回值就和参数一样拥有参数变量名和类型,这种被命名的返回参数和函数的局部变量类似,他们具备类型对应的默认值,最后由 return 语句隐式返回。(这种方式允许 defer 延迟调用通过闭包读取和修改)

func func8(a, b int) (res int, test int) {
    res = a + b
    return
}

func main() {
    res8, test8 := func8(1, 1)
    fmt.Printf("res:%d, test:%d\n", res8, test8)
}

/* output:
res:2, test:0
*/

# 参考资料

函数定义 · Go语言中文文档 (opens new window)

参数 · Go语言中文文档 (opens new window)

返回值 · Go语言中文文档 (opens new window)

上次更新: 2023/11/19, 12:55:48
循环控制
匿名函数与闭包

← 循环控制 匿名函数与闭包→

Theme by Vdoing | Copyright © 2023-2024 Wang Qinghuan | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式