写点什么

Go 学习笔记——函数篇一

作者:为自己带盐
  • 2022 年 5 月 12 日
  • 本文字数:3675 字

    阅读完需:约 12 分钟

Go 学习笔记——函数篇一

书接上回《Go 学习笔记——Switch(我不是游戏机)》

先自嘲

转眼有 20 多天过去了,终于又来写东西了。。不知道是上了年纪还是还没从近期加班的焦虑中摆脱出来,再加上前阵子封控在家办公,最近总是禁不住的犯懒,代码也不想好好写,能复制粘贴就绝不动脑子,说到写博客也是,又要看TonyBai老师的课,还要在自己的环境上试一下,然后在总结沉淀,这一套流程下来,我感觉我的脑子都不够用了~~

但也正是因为年纪大了,所以认识也就成熟了点,这种状态,可不能让它形成常态,还是得需要做点什么来打破这个状态。

虽然近期项目也很紧张,但大多是些无聊透顶的业务编码,提不起兴致,前阵子本来还想自己写一个有趣的控制台工具,用来做一些自动化运维之类的工作,但也是写到一半就停工了,主要还是因为项目时间紧,没办法集中起来,所以还是厚着脸来博客更博吧。为什么说厚着脸,因为距离上篇实在太久了,5 月份的 21 天活动我也凑不够了,而且,我实在是想换换口味,先写点别的然后在继续 Go 的学习笔记,但又颓了这么久,一时间也不知道写啥~~~ ̄□ ̄||


言归正传

好了,还是继续记笔记吧。对了,再提一句,现在互联网行业不景气,贸然的把主要语言技能转到别的语言有一定风险,但 Go,Python 等这类语言不一样,他们本身的特色就是容易上手,加上社区比较成熟热闹,我个人还是建议多了解一下,先作为辅助技能傍身,后期看情况随时可以扶正上位!而且,在了解一门新的语言的同时,可以接触到相关的生态环境,比如容器编排啊,比如某个新的微服务框架啊(微软新出的 dapr 不就是 go 开发的),再比如 Python 现在作为很多机器学习,大数据相关的接口对接语言,可以借机了解很多语言之外的知识,虽是蜻蜓点水,但也能积少成多,聚沙成塔。


函数概念

在 Go 语言中,函数是唯一一种基于特定输入,实现特定任务并可返回任务执行结果的代码块

Go 函数与函数声明

一个 Go 函数的声明由五部分组成


  • 第一部分是关键字 func,Go 函数声明必须以关键字 func 开始。

  • 第二部分是函数名。函数名是指代函数定义的标识符

ps.函数名应该是唯一的,并且它也遵守 Go 标识符的导出规则,也就是首字母大写的函数名指代的函数是可以在包外使用的,小写的就只在包内可见。

  • 第三部分是参数列表。参数列表中声明了我们将要在函数体中使用的各个参数。

ps.Go 函数支持变长参数,也就是一个形式参数可以对应数量不定的实际参数

  • 第四部分是返回值列表。返回值承载了函数执行后要返回给调用者的结果,返回值列表声明了这些返回值的类型,返回值列表的位置紧接在参数列表后面,两者之间用一个空格隔开。

ps.上图 go 标准库里的 Fprintf 函数除了声明了返回值类型,还声明了返回值名称,这种返回值被称为“具名返回值”,这并不常见。

  • 第五部分是放在一对大括号内的是函数体,函数的具体实现都放在这里。

ps.注意,函数生命中的函数体是可选的。

特点


由这个图的可知,

  • 函数声明中的函数名其实就是变量名,函数声明中的 func 关键字、参数列表和返回值列表共同构成了函数类型

  • 参数列表与返回值列表的组合也被称为函数签名,它是决定两个函数类型是否相同的决定因素。

  • 通常,在表述函数类型时,我们会省略函数签名参数列表中的参数名,以及返回值列表中的返回值变量名,比如上图的 Fprintf 函数的函数类型是

func(io.Writer, string, ...interface{}) (int, error)
复制代码

注意,函数签名(参数列表和返回值列表)是决定两个函数类型是否相同的因素,也就是即便参数名和返回值变量名不一样,他们也是同类型的函数类型!

func (a int, b string) (results []string, err error)func (c int, d string) (sl []string, err error)
复制代码

上面两个函数把参数名和变量名省掉后,就是都是 func (int, string) ([]string, error)类型,也就是相同的函数类型。

  • 每个函数声明所定义的函数,仅仅是对应的函数类型的一个实例,就像 var a int = 13 这个变量声明语句中 a 是 int 类型的一个实例一样


函数的参数

  • 函数声明阶段,参数列表中的参数叫形参,被调用是传入的参数是实参,这个应该不用多说了吧~

参数传递

  • 函数传递参数采用的是值传递的方式。

  • 关于值传递,基本有两种拷贝形式;

  • 一种是“逐位拷贝”,对于像整型、数组、结构体这类类型,它们的内存表示就是它们自身的数据内容,因此当这些类型作为实参类型时,值传递拷贝的就是它们自身,传递的开销也与它们自身的大小成正比。

  • 一种是“浅拷贝”,像 string、切片、map 这些类型,它们的内存表示对应的是它们数据内容的“描述符”。当这些类型作为实参类型时,值传递拷贝的也是它们数据内容的“描述符”,不包括数据内容本身,所以这些类型传递的开销是固定的,与数据内容大小无关。

  • 例外形式:形参为接口类型和变长参数时,简单的值传递就不在满足需求,Go 编译器会介入:对于类型为接口类型的形参,Go 编译器会把传递的实参赋值给对应的接口类型形参;对于为变长参数的形参,Go 编译器会将零个或多个实参按一定形式转换为对应的变长形参。

函数返回值

Go 函数支持多返回值

func foo()                       // 无返回值func foo() error                 // 仅有一个返回值func foo() (int, string, error)  // 有2或2个以上返回值
复制代码

函数返回值还可以为每个返回值声明变量名,像开头 go 标准库里的 Fprintf 函数,这类带名字的返回值被称为“具名返回值”。


函数是“一等公民”

“如果一门编程语言对某种语言元素的创建和使用没有限制,我们可以像对待值(value)一样对待这种语法元素,那么我们就称这种语法元素是这门编程语言的“一等公民”。拥有“一等公民”待遇的语法元素可以存储在变量中,可以作为参数传递给函数,可以在函数内部创建并可以作为返回值从函数返回。”

  • 特征一:Go 函数可以存储在变量中。

var (    myFprintf = func(w io.Writer, format string, a ...interface{}) (int, error) {        return fmt.Fprintf(w, format, a...)    })
func main() { fmt.Printf("%T\n", myFprintf) // func(io.Writer, string, ...interface {}) (int, error) myFprintf(os.Stdout, "%s\n", "Hello, Go") // 输出Hello,Go}
复制代码
  • 特征二:支持在函数内创建并通过返回值返回。

  • Go 函数不仅可以在函数外创建,还可以在函数内创建。而且由于函数可以存储在变量中,所以函数也可以在创建后,作为函数返回值返回。

  • 下面这个例子可能有点绕,截个图后续帮助了解一下

func setup(task string) func() {    println("do some setup stuff for", task)    return func() {        println("do some teardown stuff for", task)    }}
func main() { teardown := setup("demo") defer teardown() println("do some bussiness stuff")}
复制代码



在列举一个利用闭包简化函数调用的案例

func times(x, y int) int {  return x * y}//在上面的代码中,times 函数用来进行两个整型数的乘法。我们使用 times 函数的时候需要传入两个实参times(2, 5) // 计算2 x 5times(3, 5) // 计算3 x 5times(4, 5) // 计算4 x 5//----------------------////为了解决某些场景传入高频乘数,利用闭包原则创建新函数
func partialTimes(x int) func(int) int { return func(y int) int { return times(x, y) }}//这里,partialTimes 的返回值是一个接受单一参数的函数,这个由 partialTimes 函数生成的匿名函数,使用了 partialTimes 函数的参数 x。按照前面的定义,这个匿名函数就是一个闭包。partialTimes 实质上就是用来生成以 x 为固定乘数的、接受另外一个乘数作为参数的、闭包函数的函数。//当程序调用 partialTimes(2) 时,partialTimes 实际上返回了一个调用 times(2,y) 的函数,这个过程的逻辑类似于下面代码
timesTwo = func(y int) int { return times(2, y)}
//这个时候,我们再看看如何使用 partialTimes,分别生成以 2、3、4 为固定高频乘数的乘法函数,以及这些生成的乘法函数的使用方法
func main() { timesTwo := partialTimes(2) // 以高频乘数2为固定乘数的乘法函数 timesThree := partialTimes(3) // 以高频乘数3为固定乘数的乘法函数 timesFour := partialTimes(4) // 以高频乘数4为固定乘数的乘法函数 fmt.Println(timesTwo(5)) // 10,等价于times(2, 5) fmt.Println(timesTwo(6)) // 12,等价于times(2, 6) fmt.Println(timesThree(5)) // 15,等价于times(3, 5) fmt.Println(timesThree(6)) // 18,等价于times(3, 6) fmt.Println(timesFour(5)) // 20,等价于times(4, 5) fmt.Println(timesFour(6)) // 24,等价于times(4, 6)}
复制代码


  • 特征三:作为参数传入函数。

time.AfterFunc(time.Second*2, func() { println("timer fired") })
复制代码
  • 特征四:拥有自己的类型。

// $GOROOT/src/net/http/server.gotype HandlerFunc func(ResponseWriter, *Request)
// $GOROOT/src/sort/genzfunc.gotype visitFunc func(ast.Node) ast.Visitor
复制代码


关于函数的概念,我就不过多记录了,大家有了解需求的话,可以还是去看一下TonyBai老师的课,这节偏概念性的东西还是多一些,比较干,所以能总结的东西也就少了,主要是梳理了一下知识点。


发布于: 刚刚阅读数: 3
用户头像

学着码代码,学着码人生。 2019.04.11 加入

狂奔的小码农

评论

发布
暂无评论
Go 学习笔记——函数篇一_Go_为自己带盐_InfoQ写作社区