20 函数基础

函数基础

一 函数介绍

1.1 什么是函数

函数就是盛放一组代码块的”容器”,把实现某个特定功能一组代码块组织到一个函数内,相当于制作了一个工具,可以重复调用

1.2 为何要用函数

不同于Python,在go语言中,非声明语句只能在函数内,比如fmt.Println("啊p啊")写到函数外则会报语法错误。

syntax error: non-declaration statement outside function body

也就是说go强制我们把功能代码组织到函数内,而使用函数至少解决了以下3种问题

(1)代码的组织结构不清晰,可读性差

(2)遇到重复的功能只能重复编写实现代码,代码冗余

(3)功能需要扩展时,需要找出所有实现该功能的地方修改之,无法统一管理且维护难度极大

1.3 函数分类

#1、内置函数
为了方便我们的开发,针对一些简单的功能,go已经为我们定义好了的函数即内置函数。对于内置函数,我们可以拿来就用而无需事先定义,例如len、cap、append、make等

#2、自定义函数
很明显内置函数所能提供的功能是有限的,这就需要我们自己根据需求,事先定制好我们自己的函数来实现某种功能,以后,在遇到应用场景时,调用自定义的函数即可。
此外,我们早已知晓,go程序中必须自定义一个main函数,main函数是整个程序的入口

Go语言中还支持匿名函数和闭包,并且函数在Go语言中属于“一等公民”。

二 定义与调用

1.1 定义

语法

func 函数名(参数1 类型,参数2 类型,...)(返回参数1 类型,返回参数2 类型,...){
    函数体
}

解释:

- 1、函数名:函数名要能反映其意义,命名规范同变量名,同一个包内不能重复
- 2、参数与返回值都是可选的
  - 2.1 参数由参数名和类型组成,可以有多个,用逗号分隔开即可
  - 2.2 返回值由参数名和类型组成,可以只写返回值的类型,也可以有多个,但在定义函数时,如果有多个返回值,则必须用逗号分隔开并且用()包裹
- 3、函数体:具体的功能实现

定义函数的三种形式

1、无参

func test() {
    fmt.Println("老妹真白")
}

2、有参

// 例1
func add(x int, y int) {
    fmt.Println(x + y)
}

// 例2
func add(x int,y int) int{
    return x + y
}

3、空函数

// 可以声明参数但不能声明返回值,函数体为空即可
func sayHi(name string) {
}

注意:在查看源码时,我们偶尔会遇到不仅没有函数体、甚至连花括号都没有的函数声明,这表示该函数不是以Go实现的,如下

package math

func Sin(x float64) float // 采用汇编语实现

1.2 调用

调用函数语法如下

函数名(值1,值2,值3,...)

因为在go中非声明语句必须在函数内,所以函数调用这种非声明语句必须在某一函数内,具体来说,调用函数也有三种形式

1、语句形式:test()

// 定义函数
func test() {
    fmt.Println("老妹真白")
}

// 调用函数
func main()  {
    test()  
}

2、表达式形式:3*add(1,2),本质是在利用函数的返回值

// 定义函数
func add(x int, y int) int {
    return x + y
}

// 调用函数
func main()  {
    res := 3 * add(1, 2)
    fmt.Println(res) 
}

3、当做参数传给另外一个函数,本质也是在利用函数的返回值

// 定义函数
func add(x int, y int) int {
    return x + y
}

// 调用函数
func main()  {
    res := 3 * add(1, add(2,3))
    fmt.Println(res)
}

强调:在调用函数时,小括号换行需在末尾加逗号

与大括号{}一样,调用函数的小括弧()也可以另起一行缩进,但必须在末尾显式加逗号,这是Go编译器的一个特性,用于防止编译器在行尾自动插入分号而导致的编译错误

package main

import "fmt"

func main() {
    var x, y = 1, 2

    // 错误写法
    fmt.Println(x,
        y
    )
    // 改进:
    fmt.Println(x,
        y, // 默认加逗号
    )      // 小括弧另起一行缩进,和大括弧的风格保持一致
}

三 参数

参数分为形参与实参,实参指的是调用函数时传入的值(相当于变量值),形参指的是定义函数是指定参数(相当于变量名),调用函数时,会将实参值赋值给形参

强调:

1、每一次函数调用都必须按照声明顺序为所有形参数提供实参(参数值)。

2、在函数调用时,Go语言没有默认参数值,也没有任何方法可以通过参数名指定形参,因此形参和返回值的变量名对于函数调用者而言没有意义。

3.1 类型简写

形参中若相邻变量的类型相同,则可以省略类型

func add(x,y int) int{
    return x + y
}

3.2 可变长参数

可变长指的就是调用函数时,传入的实参值个数不固定,我们可以在形参名or其类型前加…,该形参就可以用来接收溢出的实参值,并把它们存成一个切片,称之为可变长参数,本质上,函数的可变参数是通过切片来实现的。

func add(x ...int) int {
    fmt.Println(x)  // x是一个切片
    sum := 0
    for _, v := range x {
        sum += v
    }
    return sum
}

调用

func main() {
    res1 := add(1, 2, 3)
    res2 := add(1, 2, 3, 4, 5)
    res3 := add(1, 2, 3, 4, 5, 6)
    fmt.Println(res1, res2, res3)  // 6 15 21

}

注意:固定参数搭配可变参数使用时,可变参数要放在固定参数的后面

func add(x int,y ...int) int {
    fmt.Println(x,y)
    sum := 0
    for _, v := range y {
        sum += v
    }
    return sum
}

四 返回值

返回值是函数的运行结果,函数体一旦执行到return就会立即结束并且返回值

强调:

如果一个函数在声明时,包含返回值列表,该函数必须以 return语句结尾,除非函数明显无法运行到结尾处。例如函数在结尾时调用了panic异常或函数中存在无限循环。

4.1 多个返回值

Go语言中函数支持多返回值,函数如果需要有多个返回值,我们在定义函数的返回值时,必须用()将所有返回值包裹起来。

调用多返回值函数时,返回给调用者的是一组值,调用者必须显式的将这些值分配给变量:

func divmod(x, y int) (int, int, int) {
    res1 := x / y
    res2 := x % y
    res3 := x + y
    return res1, res2, res3
}

func main() {
    m, n, _ := divmod(1, 2)
    fmt.Println(m, n)
}

4.2 命名返回值

返回值也可以像形参一样被命名,有名返回值与函数的形参会被当作函数最外层的局部变量,

func add(x int, y int) (z int) {
    // 相当于在函数内最开头/最外层声明了变量
    //var x int
    //var y int
    //var z int
}

例1

// 声明了返回值,名字为res1、res2,在函数内直接使用即可,无需声明
func divmod(x, y int) (res1,res2 int) {
    fmt.Println(res1,res2)  
    return
}
func main() {
    divmod(1,2) // 0,0
}

例2:

func divmod(x, y int) (res1,res2 int) {
    res1 = x / y  // 此处用的是赋值操作,而非声明res1的操作,因为已经声明过了
    res2 = x % y
    return // 无需跟返回值,默认返回res1与res2
}

func main() {
    fmt.Println(divmod(10,3))  // 3 1
}

4.3 返回空

若函数返回值类型为slice,想要返回空切片,直接返回nil即可,没必要显示返回一个长度为0的切片,也就是说nil可以看做是一个有效的slice。

func someFunc(x string) []int {
    if x == "" {
        return nil // 没必要返回[]int{}
    }
    ...
}

了解:

许多标准库中的函数返回2个值,一个是期望得到的函数处理结果,另一个是函数出错时的错误信息

func findLinks() ([]int, error) {
    return 正确结果,nil
    // return nil, 错误对象
}

五 练习题

  1. 分金币
/*
你有50枚金币,需要分配给以下几个人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。
分配规则如下:
a. 名字中每包含1个'e'或'E'分1枚金币
b. 名字中每包含1个'i'或'I'分2枚金币
c. 名字中每包含1个'o'或'O'分3枚金币
d: 名字中每包含1个'u'或'U'分4枚金币
写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
程序结构如下,请实现 ‘dispatchCoin’ 函数
*/
var (
    coins = 50
    users = []string{
        "Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
    }
    distribution = make(map[string]int, len(users))
)

func main() {
    left := dispatchCoin()
    fmt.Println("剩下:", left)
}

答案

package main

import "fmt"

var (
    coins = 50
    users = []string{
        "Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
    }
    distribution = make(map[string]int, len(users))
)

func dispatchCoin() int {
    for _, name := range users {
        for _, c := range name {
            switch c {
            case 'e', 'E':
                distribution[name]++
                coins--
            case 'i', 'I':
                distribution[name] += 2
                coins -= 2
            case 'o', 'O':
                distribution[name] += 3
                coins -= 3
            case 'u', 'U':
                distribution[name] += 4
                coins -= 4
            }
        }
    }
    return coins
}
func main() {
    left := dispatchCoin()
    fmt.Println("剩下:", left)
    fmt.Println(distribution)

}
上一篇
下一篇
Copyright © 2022 Egon的技术星球 egonlin.com 版权所有 帮助IT小伙伴学到真正的技术