part 1 概要

基本概念 1. 函数的签名=?
2. 函数的组成、各个特点
3. 形参、返回值解析
4. 值传递与引用传递的区别是什么?
函数类型 1. 如何运用函数类型?
2. 函数签名vs函数类型vs匿名函数
应用 1. 匿名函数,
1. 怎么用?
1. 可传递给变量、可做参数列表、可做返回值
2. 可即使使用
3. 可做闭包
2. 匿名函数vs普通函数???
2. 函数递归
3. defer函数—延迟调用
4. 获取命令行参数

part 2 练习

  1. 对比

函数语法:func behavior(x,y int, s string)result int{...<函数内容>}

函数类型:func(int, int, string)int(去掉所有名字(函数名、形参名、返回值名)名即可,func保留

匿名函数:func (x,y int, s string)result int{...<函数内容>}除没有名字外,匿名函数和普通函数完全相同)

part 3 局部分析

函数组成、特点

  1. func function_name( [parameter list] ) [return_types] {
  2. 函数体
  3. return xxx
  4. }
是什么 解析/特点
func 关键字 函数由 func 开始声明
function_name 函数名称 1. 函数签名= <font style="color:rgb(51, 51, 51);">function_name (parameter_list) return_types</font>
2. 函数名可大写(包内外用),可小写(包内用)
3. 不支持重载(overload)
1. 什么叫重载? —同一个函数名用不同的函数内容去定义,那么如何区分呢?通过输入参数类型、个数和返回值类型的不同来实现
2. go语言如何考虑 —函数重载可能会导致代码难以阅读和理解,因为不同的函数定义之间的区别可能非常微小,此外,函数重载也可能导致函数名的混淆,因为同一个函数名可以代表多个不同的函数,这会给代码的维护和调试带来一定的困难。 —go语言不支持函数重载,但支持定义不同receiver的但可同名的方法
parameter list 参数列表 1. 参数列表(形参)规定了输入的实参的参数类型、顺序、及参数个数。
2. 可输入0或多个参数,但是括号必须有
3. 分为
1. 固定参数列表 (x,y int)
2. 不定参数列表 <font style="color:#AD1A2B;">(x...int)</font>
4. 不支持默认参数如 a int = 1
return_types 返回类型 1. 无返回值无return;有返回值的函数体内,必须有return,遇到return就回中止函数进程,进行返回
2. 应用
1. 可无返回值,整个省略
2. 一个返回值/多个返回值;带变量名/不带变量名
函数体 函数体 1. 不支持嵌套函数(函数内再定义一个函数),但支持嵌套匿名函数

形参、返回值解析

无参无返回值

保证语法格式即可

函数 - 图1

形参解析(值传递)

普通参数列表

函数 - 图2

  1. 形参语法
    实际就是,变量声明删去关键字
    var x, y int ➡ (x,y int)
    var x int; var y string ➡ (x int, y string)
  2. 实参的值,按序,不缺地,单方向传给形参,不能形参的值传给实参
  3. 作用域:仅限于函数体内

不定参数列表

和传入列表有什么差别?

  1. func myfunc(args...int){
  2. x, y, z := args
  3. fmt.Println(args)
  4. fmt.Println("++++++++++++++++++++++++")
  5. for k,v := range args{
  6. fmt.Println("%d个参数是%d", k, v)
  7. }
  8. }
  1. 语法
    1. 什么叫不定参数列表?——传递地实参可以是0或多个(并没有定义容量
    2. 形参语法(args...type)
    3. 接受什么?——0或多个同类型的参数
      返回什么?——若要想返回args,那么返回的是切片
      函数 - 图3
  2. 要求
    1. 固定参数一定要传参,但不定参数根据需求传递参数,可以是0或多个
      实例:如a一定要赋值,但arg不一定
      函数 - 图4**
    2. 不定参数可以和固定参数混搭,但一定放在参数最后,且不定参数只能有一个
  3. 本质:args就是一个slice
    1. 类型上,要求必须是同类型的参数;若需要不同类型,则定义args...interface{}用interface{}传递任意类型数据是Go语言的惯例用法,而且interface{}是类型安全的。
    2. args在函数中如何使用?→切片怎么用args就怎么用

返回值解析

  1. 变量名
不带变量名的 带变量名的
一个返回值 函数 - 图5 可省略括号 函数 - 图6
多个返回值 函数 - 图7 函数 - 图8 ➡同样可以采取省略写法 函数 - 图9
优缺点 可以返回不同变量的值,只是不够直观 只能返回开头声明的变量的值,有约束性,但直观

最终在main函数体内调用的时候a, b := MyFunc(1,2)

  1. 编程习惯
    1. Go中经常会使用一个返回值作为函数是否执行成功、是否有错误信息的判断,比如,return value, existsreturn value,okreturn value, err
    2. 返回值过多,如超过四个的时候,应收集到相应容器中,同类型放slice、不同类型放map
    3. _来丢弃返回值

与c语言的区别

  1. 语法上
    1. go中参数和返回值类型都不是必须的,c语言中你不需要至少要写个void为返回值类型
    2. 格式不同,go需要关键字func声明
      函数 - 图10函数 - 图11
  2. 返回值
    go语言返回值可返回多个(注意,要在开头声明出来),且返回值可以有变量名,而c语言无法在开头就告知返回值变量名,只能在函数体定义
  3. go无函数声明,只要是包内,都可以调用,也就是对自定义函数的位置,不关心

!!函数参数(值传递/引用传递)

调用函数,可以通过两种方式来传递参数:
函数 - 图12 1. 值传递 1. 默认情况值传递 2. 形参的作用域和c语言一样,调用实参过程中不会影响到实际参数。 3. 具体解析见上文 2. 引用传递 1. 形参是带指针 2. map、slice、chan、指针、interface默认以引用的方式传递,或者含有这些引用类型的符合数据类型,比如结构体指针 3. 无论是值传递,还是引用传递,传递给函数的都是变量的副本****不过,值传递是值的拷贝。引用传递是地址的拷贝一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。 ## 函数是一种类型 函数也是一种数据类型,和int,float一样,可以通过type给一个函数类型起别名,之所以提出函数类型,是为了让函数和基本变量一样,可以进行传递 ### 函数变量的声明与调用 > 其流程与结构体一致 > 1. 什么是函数类型?
func Myfunc (a, b int)int<font style="color:#AD1A2B;">func (int, int)int</font>(去掉所有名字(函数名、形参名、返回值名)名即可,func保留) 2. 如何使用? 1. 用type关键字,给函数类型其个别名
type 类型别名 函数类型比如 type Func_type func(a, b int) 2. 声明一个函数变量
var Mytest Func_type 3. 赋值
给一个函数变量赋什么值?当然是函数啦
Mytest = Swap(Swap是某个已定义的函数) 4. 调用
Mytest(1,2)与基本函数调用完全一致 3. 作用:可以创建一个变量,赋予该变量同一类型但不同内容的函数,实现<font style="color:#AD1A2B;">多态</font>的理念 ### 函数作为参数/返回值 1. key:如何获得函数类型?
func Myfunc (a, b int)int<font style="color:#AD1A2B;">func (int, int)int</font>(去掉函数名和形参名即可,func保留) 2. 应用—实现封装与多态
实例: go package main import "fmt" //函数作为形参 func sayHello(name string){ fmt.Printf("Hello,%s\n", name) } func test(name string, f func(string2 string)){ f(name) } //函数作为返回值 func add(a int, b int)int { return a+b } func sub(a int, b int)int { return a-b } func cal(operator string) func(int, int)int { switch operator { case "+": return add case "-": return sub default: return nil } } func main() { //函数作为形参 test("ray", sayHello) //函数作为返回值 f1 := cal("+") r1:= f1(1,2) fmt.Println(r1) f2 := cal("-") r2 := f2(3,5) fmt.Println(r2) } go package main import "fmt" type oneall_type func(x,y int)int func test(x,y int, oneall oneall_type)int{ return oneall(x,y) } func add(x,y int)int{ return x+y } func cut(x,y int)int{ return x-y } func mul(x,y int)int{ return x*y } func main() { a := test(2,3,add) b := test(9,3,cut) c := test(3,4,mul) fmt.Println(a,b,c) } 函数 - 图13 结果:函数 - 图14 ## 应用 ### ### 匿名函数 由于,在Go里面,函数可以像普通变量一样被传递或使用,故,产生了匿名函数
  1. 语法:func(参数列表)返回值{...除没有名字外,匿名函数和普通函数完全相同)
  2. 优点 在于可以直接使用函数内的变量,不必申明。
  3. 应用
    1. 可赋值给变量
      函数 - 图15=函数 - 图16
    2. 可作为参数
      函数 - 图17
    3. 可作为返回值
      函数 - 图18
    4. 即时调用 (定义完后立即运行)
      函数 - 图19
      若有返回值,返回给变量(和第一个不一样,x和y得到的是max和min的值
      函数 - 图20
    5. 普通函数和匿名函数都可作为结构体的元素类型,或经通道传递(函数类型把形参的标识符也去了)
      函数 - 图21
      函数 - 图22
    6. 除闭包因素外,匿名函数也是一种常见重构手段。可将大函数分解成多个相对独立的匿名函数块,然后用相对简洁的调用完成逻辑流程,以实现框架和细节分离。

:::info 注意点

:::

  1. 将匿名函数赋值给变量,与为普通函数提供名字标识符有着根本的区别。(**???不知道?)**当然,编译器会为匿名函数生成一个“随机”符号名。
  2. 未被使用的匿名函数会被编译器当成错误
  3. 闭包与匿名函数的差别???
    相比语句块,匿名函数的作用域被隔离( 不使用闭包),不会引发外部污染,更加灵活。没有定义顺序限制,必要时可抽离,便于实现干净、清晰的代码层次。

?闭包

什么是闭包?

—闭包 = 嵌套函数(闭包是一般通过匿名函数来实现的,但也有在函数内调用其他函数实现的) + 引用环境

特点

闭包捕获的变量,具有记忆性,可迭代,那么什么叫记忆性?

—闭包不关心这些捕获了的变量或者常量是否超出了作用域,所以只有闭包还在使用它,这些变量就还会存在(即,闭包内,变量的生命周期不由它的作用域决定)

条件

—外部函数A,包含着函数B,函数B;函数B引用了外部的(外部函数A内或者全局)变量C,且不会被垃圾回收机制回收。以下是理解:

  1. 如何理解外部的?
    1. C是在外部函数A外定义的,从作用域是全局理解即可
    2. C是在外部函数A内定义的,看下方原理分析
  2. 如何保证不会被垃圾机制回收?
    前言:闭包捕获的变量C的作用域=闭包的声明周期
    →闭包生命周期的结束取决于闭包本身是否还被其它代码引用。具体:
    1. 执行了包含闭包的函数并返回了闭包,但是没有将闭包赋值给任何变量或者将闭包从变量中移除了,从而使闭包无法再被访问。
    2. 将包含闭包的变量从内存中删除或销毁,例如通过垃圾回收器清除了不再被使用的变量。

需要注意的是,在闭包中引用的外部变量如果是指针类型的话,需要注意内存泄漏的问题。如果闭包中引用的外部变量是一个指针,并且在闭包的生命周期中没有被正确释放,那么就可能会导致内存泄漏,从而影响程序的性能和稳定性。因此,在使用闭包时需要注意避免这种情况的发生。

  1. 函数B作为输入参数或者返回值
    1. 输入参数实例,实例如下
    2. 返回值实例
  2. 函数B通常是匿名函数,但也可以是引用的普通函数,实例如下

原理分析

闭包引用的变量具有记忆性,因为它们的生命周期不仅仅是由外部函数的生命周期所决定,而是由闭包本身的生命周期所决定。

当一个函数返回一个闭包时,该闭包会捕获其所引用的外部变量,并将它们保存在一个内部的数据结构中。这些外部变量可以是局部变量、函数参数或者全局变量。当闭包被调用时,它可以访问这些外部变量,并且可以修改它们的值。

闭包的记忆性是因为闭包本身保存了对外部变量的引用,并且在闭包的生命周期中,这些外部变量的值可以随着闭包的调用而改变。这意味着,闭包可以“记住”它们在上一次调用时所引用的变量值,并且可以在下一次调用时使用这些值。这种记忆性可以使闭包在特定场景下非常有用,例如实现缓存、迭代器和状态机等。

  1. func fibonacci() func() int {
  2. a, b := 0, 1
  3. return func() int {
  4. result := a
  5. a, b = b, a+b
  6. return result
  7. }
  8. }
  9. func main() {
  10. f := fibonacci()
  11. for i := 0; i < 10; i++ {
  12. fmt.Println(f())
  13. }
  14. }

实例分析

  1. 实例1——在add的生命周期内,变量base一直有效
    函数 - 图23
  2. 实例2
    函数 - 图24
  3. 实例3
    函数 - 图25
    结果
    函数 - 图26
    注意:
    1. 当闭包重新赋予新的变量f1的时候,原闭包的生命周期依然存在,因为并未销毁f
      闭包→初始函数
      闭包+base=100→f1函数
      闭包+base=200→f2函数
    2. 当我们谈论闭包生命周期的时候,指的是定义闭包的函数块内,定义的变量的生命周期
  1. package main
  2. import "fmt"
  3. func add()func (y int)int{ //返回匿名函数也可以写func(int)int
  4. var x int //x最终做返回值
  5. return func(y int) int {
  6. x += y
  7. return x
  8. }
  9. }
  10. func main() {
  11. var f = add()
  12. fmt.Println(f(10))
  13. fmt.Println(f(20))
  14. fmt.Println(f(30))
  15. fmt.Println("----------")
  16. f1 := add()
  17. fmt.Println(f1(40))
  18. fmt.Println(f1(50))
  19. }
  1. 实例4——返回两个闭包
    函数 - 图27
    函数 - 图28
    理解:f1与f2是相关的,从始至终都是一个base变量

函数递归

函数调用流程:先调用后返回,先进后,要deep到顶点之后,才从顶点返回

具体流程以前在c语言分析过,笔记作用不明显,要看往回看视频黑马go语言第49个视频去看,这里讲结果怎么用

:::info 原理

:::

递归 = 递推 + 回归

递推是往下挖掘,不计算;回归才是真正的计算,返回一个关系,

:::info 应用

:::

  1. 返回什么关系?
    类似于数列上的f(n) 与f(n-1)的关系,比如函数是Myfunc(x),那么就 return g{Myfunc(x-1)}g(x)描述 Myfunc(x)和Myfunc(x-1)的关系,比如
    1. 累加就是return Myfunc(x-1) +x
    2. 累乘就是return Myfunc(x-1) *x
    3. 如果是奇数项彼此相加,那就是 return Myfunc(x-2) *x(初始为奇数,末端为0)
  2. 终止项
    1. 必须夹在函数开头与返回循环关系之间
    2. 一般是
      函数 - 图29
  1. package main
  2. import "fmt"
  3. func main() {
  4. sum := Myfunc(100)
  5. fmt.Println(sum)
  6. }
  7. func Myfunc(x int)( int){
  8. if x==1{
  9. return 1
  10. }
  11. return Myfunc(x-1)+x
  12. }

defer延迟调用

我们要延迟执行一个命令,怎么办?——defer关键字

  1. 基本语法:
    defer ...(语句/函数/方法) defer是一个关键字
    函数 - 图30
  2. 作用
    延迟调用一个语句/函数/方法,延迟到什么时候呢?——函数结束的时候,常用于网络编译,比如关闭连接的这个命令要在客服选择挂机后才执行
  3. 要求
    只能用于函数内部,即花括号内
  4. 多个执行语句怎么办?
    后进先出(越后头,越先出来)
    函数 - 图31
  5. defer语句中一旦出错了
    函数 - 图32
  6. defer和匿名函数结合使用
    为什么刚刚强调先编译,再提取,最后按序执行呢?——我们来看两个例子
    1. 实例1
      函数 - 图33
    2. 实例2
      函数 - 图34
    3. 分析
      观察两个例子,结果截然不同,差别就在于实例2是输入参数的,实例1不输入参数,这就表示,即使某段代码加入defer关键字,后面执行,那也是先进行了从例子中输入了参数,只是执行,放到了后面

:::info 核心步骤

:::

  1. 在多个含有defer的语句中,编译器会把含有defer的命令,先运行,该接收的参数接收它,没接收参数,需要调用的,再函数结束时再调用
  2. 在函数结束的时候,所有带defer的语法块提取出来,按后进先出的顺序,进行执行
    1. 若此时需要调用参数,则以函数结束时的参数的值为准
    2. 此时,即使其中有一个出错了,那其他也会按序,执行出来,不会终止