Go——函数

一. 函数定义

        1.1 特点

  • 无需声明原型
  • 支持不定变参
  • 支持多返回值
  • 支持命名返回参数
  • 支持匿名函数和闭包
  • 函数也是一种类型,一种函数可以赋值给变量
  • 不支持嵌套,一个包不能有两个名字一样的函数
  • 不支持重载
  • 不支持默认参数

        1.2 函数声明

        函数声明包含一个函数名,参数列表,返回值列表和函数体。如果没有返回值,则返回值列表可以省略。函数从第一条语句开始执行,直到执行return语句或者函数体的最后一条语句。

        函数可以没有参数或者接受多个参数。注意:类型在变量名后。

        当两个或者两个以上的函数命名参数是同一类型,则除最后一个类型之外,其它可以省略。

        函数可以返回任意数量的返回值。

        使用关键字func定义函数,左括号不能另起一行。

func test(s string, x, y int) (int, string) {
	n := x + y
	return n, fmt.Sprintf(s, n)
}

        函数是第一类对象,可作为参数传递。建议将复杂签名定义为函数类型,以便于阅读。 

        有返回值的函数,必须有明确的终止语句,否则会引发编译错误。

        你可能会偶尔遇到没有函数体的函数声明,这表示该函数不是以Go实现的。这样的声明定义了函数标识符。

package math

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

        1.3 参数

        函数定义时指出,函数定义时有参数,该变量可以称为函数形参。形参就像定义在函数体内的局部变量。

        但调用函数时,传递过来的变量就是函数的实参。函数可以通过两种方式来传递参数:

        值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响实际参数。

        引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数进行修改,将影响实际参数。

        在默认情况下,Go语言是值传递,即在调用过程中,不会影响实际参数。

        注意:

  • 无论是值传递还是引用传递,传递给函数的都是变量的副本。不过,值传递是值的拷贝,引用传递是指针的拷贝(解引用后就是实际的值)。
  • 一般来说,地址拷贝更加高效(指针在32位下占4字节,在64位下占8字节)。而值拷贝取决于拷贝对象的大小,对象越大性能越低。
  • map,slice,chan,指针,interface默认以引用传递

不定参数传递:

        不定参数传值,就是函数的参数不是固定的,后面的类型是固定的。(可变参数)

        Golang可变参数的本质上就是一个切片slice。只能有一个,且必须是最后一个。

        在参数赋值时,可以不用一个个的赋值,可以直接传递一个切片。注意传递切片时在可变参数后需要加"..."。

func myFunc1(args ...int) {//0个或多个参数

}

func myFunc2(a int, args ...int){//1个或多个参数

}

func myFunc3(a int, b int, args ...int){ //2个或多个参数

}

注意:其中args是一个slice,我们可以通过arg[index]依次访问所有的参数,通过len(args)来判断参数的个数。

任意类型的不定参数: 

         任意类型的不定参数,就是函数参数个数和每个参数的类型都是不固定的。

        用户interface{}传递任意类型数据是Go语言的常用惯例,而且interface{}是类型安全的。

func myFunc(args ...interface{}) {

}

        1.4 返回值 

特点

  • "_"标识符,用来忽略函数的返回值
  • Go的返回值可以被命名,并且就像函数开头声明的变量那样使用
  • 返回值的名称应当具有一定的意义,可以作为文档使用
  • 没有参数的return语句返回各个返回变量的当前值(用在返回值有名称的情况)。这种用法被称作"裸"返回
  • 直接返回语句仅应当用在短函数中。在长函数中它们会影响代码的可读性。
  • 命名返回值参数可看做与形参类似的局部变量,最后由return隐式返回 

  • Golang返回值不能用容器对象接收多返回值。只能用多个变量,或者"_"忽略返回值。 即有几个返回值,就需要几个变量接收

  • 多返回值可以直接作为其它函数调用的实参 

 

  • 命名返回参数可被同名局部变量遮掩,此时需要显示返回

  • 命名返回参数允许defer延迟调用通过闭包读取和修改 

  • 显示return返回前,会先修改命名返回参数 

        1.5 匿名函数

        匿名函数是指不需要定义函数名的一种函数实现方式。 

        在Go语言中,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。

        匿名函数由一个不带函数名的函数声明和函数体组成。匿名函数的优点在于可以直接使用函数内的变量,不必声明。

         对比C/C++,函数虽然也可以作为变量或参数,但是函数需要有名字。

        Golang匿名函数可以赋值给变量,作为结构体字段,或者在channel里传送。

        1.6 闭包

        闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。

        官方的解释是:所谓闭包,指的是一个拥有许多变量和绑定了这些变量的环境表达式,通常是一个函数,因而这些变量也是表达式的一部分。

        维基百科讲,闭包是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,由另外一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有很多实例,不同的引用环境和相同的函数组合可以参数不同的实例。

        看着上面的描述,会发现闭包和匿名函数很像。下面来一个例子。

  • 定义 

         函数b嵌套在函数a内部,函数a返回函数b,这样执行完var c=a()后,实际c指向了函数b(),再执行c()会执行函数b打印变量i,第一次为1,第二次为2,第三次为3,以此类推。其实,这段代码就创建了一个闭包。因为函数a()外的变量c引用了函数a()内的函数b()。也就是说:当函数a()的内部函数b()被函数a()外的一个变量引用的时候,就创建了一个闭包。

        在上面例子中,由于闭包的存在使得函数a返回后,a中的变量i始终存在,这样每次执行c(),i都是自加1后的值。闭包使得Go的垃圾回收机制GC不会回收a()占用的资源。

  • 作用

        在给定函数被多次调用的过程中,这些私有变量能够保持其持久性但是变量的作用域仅限于包含它们的函数,所以在其它程序代码中无法进行访问。变量的生存周期变长,在一次函数调用期间所创建生成的值在下次函数调用时仍然存在(所以上面打印的值为1,2,3)。正因为上面的特点,闭包可以用来完成信息隐藏,并进而应用于需要状态表达的某些编程范型中。

        对于上面的例子,如果函数a返回的不是函数b,情况就完全不同了。因为函数a执行完后,函数b没有被返回给函数a的外界,只是被函数a引用了,函数a被函数b引用。所以函数a和函数b互相引用不被外界引用。函数a和b会被垃圾回收机制GC回收。

  • 引用环境

        上面的例子,c()跟c2()引用的是不同的环境,在调用i++时修改的不是同一个i,因此开始时输出的都是1。函数a()每进入一次,就形成了新的环境,对应的闭包中,函数都是同一个函数,环境确是引用不同的环境。这和c()和c1()的调用顺序无关。

        闭包进入一次,引用环境不同。

  • 闭包复制的是原对象指针,这就很容易解释延时引用现象

        匿名函数变量i和test()函数变量i是同一个。

        在汇编层,test实际返回的是FuncVal对象,其中包含匿名函数地址,闭包对象指针。当调用匿名函数时,只需以某个寄存器传递该对象即可。

FuncVal { func_address, closure_var_pointer ... }
  •  外部引用函数参数的局部变量

  • 返回两个闭包

         1.7 递归

        递归,就是在运行的过程中调用自己。一个函数调用自己,就叫做递归函数。

        构成递归需具备的条件:

  1. 子问题须与原始问题为同样的事情,且更为简单
  2. 不能无限制地调用本身,必须有个出口,化简为非递归状况处理
  • 数字阶乘

        一个正整数的阶乘是所有小于等于该数地正整数地积,并且0的阶乘为1。

package main

import "fmt"

func factorial(x int) int {
	if x <= 1 {
		return 1
	}

	return x * factorial(x-1) //自己调用自己
}

func main() {
	res := factorial(5)
	fmt.Println(res)
}
  • 斐波那契数列

        1.8 延时调用(defer)

        defer特性:

  • 关键字defer用于注册延时调用
  • 这些调用直到return前才被执行。因此,可以用来做资源清理
  • 多个defer语句,按先进后出的方式执行。因为后面的语句会依赖前面的资源,因此如果前面的资源释放了,后面的语句就没法执行了。
  • defer语句中的变量,在defer声明前就决定了。

        defer用途:

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

        defer触发时机:

  • 包裹着defer语句的函数返回时
  • 包裹着defer语句的函数执行完时
  • 当前goroutine发送Panic时

        实例:

        defer先进后出:

  • 当go遇到一个defer语句时,不会立即执行,而是将defer后面的语句压入到一个栈中,然后继续执行函数下的语句。
  • 当函数或方法执行完毕,再从栈中依次从栈顶取出执行(先入后出)。

         i是从0到4,但是由于defer先进后出,所以是4到0。

        defer遇上闭包:

  • 在Go语言中,对外部作用域中变量访问的方式是"引用",捕获的是变量的地址。当外部变量发生改变时,闭包中的变量也会发生改变。

  • 多个defer语句注册,按先入后出次序执行。哪怕函数或某个延时调用发生错误,这些调用依旧会被执行。 

  •  延迟调用参数在注册时求值或复制,可以用指针或闭包"延时读取"

  • 滥用defer可能导致性能问题,尤其是在一个大循环里面 

defer陷阱: 

  • defer与闭包:

        由于闭包使用的外部变量保存的是地址。修改外部变量可以得到最新的值。

  • defer与return

  • defer nil函数 

        值得注意的是run在声明是不会报错,而是在使用是报错。

  •  在错误位置使用defer

        当http.Get失败时会抛异常。

        修改:

        获取返回错误:

  • 释放相同资源 

         解决方案:不建立闭包,将变量传进来,这样是值拷贝。

         1.9 异常处理

        Golang没有结构化异常,使用panic抛出错误,recover捕获错误。

        使用场景:Go中可以抛出一个panic异常,然后在defer中通过recover捕获这个异常,然后正常处理。

        panic:

  • 内置函数
  • 假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
  • 返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行
  • 直到goroutine整个退出,并报告错误

        recover:

  • 内置函数
  • 用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为。 
  • 一般的调用建议:
    • 在defer函数中,通过recever来终止一个goroutine的panicking过程,从而恢复正常代码的执行。
    • 可以通过panic传递错误

        注意:

  • 利用recover处理panic指令,defer必须放在panic之前定义,另外recover只有在defer调用函数中才有效。否则当panic时,recover无法捕获到panic,无法防止panic扩散。
  • recover处理异常后,逻辑并不会恢复到panic那个点去,函数跑到defer之后的那个点
  • 多个defer会形成defer栈,后定义的defer语句会被最先调用

 由于panic和recover参数类型为interface{},因此可以抛出任何类型的对象

func panic(v interface{})
func recover()interface{}
  •  向已关闭的通道发送数据会引发panic

  • 延时调用中引发错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获

 

  • 当异常panic被回收recover后,就没了,后面的recover回收不到该异常

  • 捕获函数recover只有在延时调用内直接调用才会终止错误,否则捕获不到异常(返回nil),任何未捕获的的错误都会沿调用堆栈向外传递。

         也可以不使用匿名函数,而是使用函数,也可以回收异常。

  • 如果需要保护代码段,可将代码重构成匿名函数,如此可确保后续代码被执行

  • 除了用panic来引发中断性错误外,还可返回error类型错误对象来表示函数调用状态
type error interface{
    Error() string
}

        标志库errors.New和fmt.Errorf函数用于创建实现error接口的错误对象。通过判断错误对象实例来确定具体错误类型。

        panic用来返回error接口对象:

  • Go实现类似try catch的异常处理

如何区分使用panic和error两种方式?

        导致关键流程出现不可修复性错误的使用panic,其它使用error(返回error)。 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/513107.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

谈谈考研数学几个常见误区

25考研数学&#xff0c;一定一定要吃透基础&#xff0c;练好计算 我之所以要强调这个&#xff0c;是因为现在的考研数学&#xff0c;越来越重视基础和计算的考察&#xff0c;题海战术已经过时&#xff0c;如果想要有效的提升自己&#xff0c;要进行针对性的学习。我去年考研的…

【.NET全栈】.NET全栈学习路线

一、微软官方C#学习 https://learn.microsoft.com/zh-cn/dotnet/csharp/tour-of-csharp/ C#中的数据类型 二、2021 ASP.NET Core 开发者路线图 GitHub地址&#xff1a;https://github.com/MoienTajik/AspNetCore-Developer-Roadmap/blob/master/ReadMe.zh-Hans.md 三、路线…

常州威雅:「西陵氏奖学金」项目,只为寻找优秀的你!

栀子花开&#xff0c;凤凰花落&#xff0c;又是一年中考季。站在深耕“全人教育”的第十年&#xff0c;常州威雅现今面向全社会的优秀初三学子&#xff0c;首次推出「西陵氏奖学金」项目&#xff0c;助力梦想起航。 西陵氏女嫘祖&#xff0c;华夏文明的奠基人。她所发现、发明的…

Flutter iOS上架指南

本文探讨了使用Flutter开发的iOS应用能否上架&#xff0c;以及上架的具体流程。苹果提供了App Store作为正式上架渠道&#xff0c;同时也有TestFlight供开发者进行内测。合规并通过审核后&#xff0c;Flutter应用可以顺利上架。但上架过程可能存在一些挑战&#xff0c;因此可能…

VMware虚拟机三种网络模式配置

vmware有三种网络工作模式&#xff1a;Bridged&#xff08;桥接模式&#xff09;、NAT&#xff08;网络地址转换模式&#xff09;、Host-Only&#xff08;仅主机模式&#xff09;。 1. 打开网络编辑器&#xff08;编辑 --> 虚拟网络编辑器&#xff09; 在主机上有VMware Ne…

提效5倍,传统零售企业实时数据分析策略与应用实践

国家统计局数据显示&#xff0c;在过去的 2023 年里&#xff0c;中国社会消费品零售额整体增长稳定&#xff0c;零售业态消费开始恢复性增长。 同时&#xff0c;随着移动互联网的持续爆发增长&#xff0c;零售市场的竞争仍日益激烈。传统零售企业不仅要面对来自其他传统零售商的…

C++ 中的 vector 的模拟实现【代码纯享】

文章目录 C 中的 vector 模拟实现1. vector 的基本概念2. vector 的基本操作3. vector 的模拟实现4.代码纯享5. 总结 C 中的 vector 模拟实现 在 C 中&#xff0c;vector 是一个非常重要的容器&#xff0c;它提供了动态数组的功能。在本篇博客中&#xff0c;我们将尝试模拟实现…

结构体,联合体,枚举( 2 )

目录 2.联合体 2.1联合体类型的声明 2.2联合体的特点 2.3联合体的内存大小 3.枚举 3.1枚举类型的声明 3.2枚举类型的优点 3.3枚举类型的使用 2.联合体 联合体&#xff08;Union&#xff09;是另一种复合数据类型&#xff0c;它允许我们在同一内存位置存储不同的数据类型…

携程获取景点详情 API 返回值说明,item_get_scenic-获取景点详情

xiecheng.item_get_scenic 请求示例&#xff0c;API接口接入Anzexi58 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_sea…

AI预测福彩3D第25弹【2024年4月2日预测--第6套算法开始计算第2次测试】

今天&#xff0c;咱们进行第6套算法测试&#xff0c;本套算法将结合012路直选共27种组合&#xff0c;同时考虑了对012路的和值进行统计分析。今天为第2次测试&#xff0c;好了&#xff0c;废话不多说了。直接上结果~ 仍旧是分为两个方案&#xff0c;1大1小。 经过人工神经网络计…

使用minikube安装使用单机版K8S(docker)

前置&#xff1a;作为一个开发&#xff0c;工作之余想玩一下k8s&#xff0c;但是搭建成本太高&#xff0c;所以就找到了minikube这个工具&#xff0c;快速搭建单机版k8s&#xff0c;下面是个人搭建流程&#xff0c;基于centos7&#xff0c;仅供参考。 1.下载kubectl&#xff0…

[强推] 免费AI学习资料丨学习完还能获得微软证书,太香了!

五分钟白嫖一张微软学习证书 &#x1f4c5; 重要日期&#xff1a; &#x1f680; 开始&#xff1a;2024年4月1日 &#x1f51a; 结束&#xff1a;2024年5月1日 如何参与&#xff1a; &#x1f517; 挑战链接&#xff1a;立即参与 &#x1f4c3; 提交表格&#xff1a;提交…

智慧公厕:提升城市公卫管理效率与环境舒适度的利器

公厕作为城市基础设施的重要组成部分&#xff0c;一直以来备受市民们的关注与诟病。然而&#xff0c;随着科技的发展和城市智慧化进程的推进&#xff0c;智慧公厕作为一种集成了物联网等技术的创新型公厕逐渐走入人们的视野。智慧公厕不仅实现了信息化、数字化和智慧化&#xf…

ATFX汇市:小非农ADP数据来袭,将为周五大非农提供前瞻指引

ATFX汇市&#xff1a;今日20:15&#xff0c;美国自动数据处理公司将公布美国3月ADP就业人数&#xff0c;前值为增加14万人&#xff0c;预期值增加14.8万人。上图为美国ADP数据的历史表现&#xff0c;最近七个月&#xff0c;新增就业人口的柱线呈现出显著震荡特征&#xff0c;最…

VPN——GRE

1、VPN概念 Virtual Private Network ①虚拟专用网络 ②在公有的网络上架设私有的通道&#xff0c;构建一个专用的、安全性、服务质量得到保障的网络 ③实质&#xff1a;数据包的再封装与解封装的过程 2、分类 按照业务用途&#xff1a;【1】access&#xff1a;外出员工…

【Go】十七、进程、线程、协程

文章目录 1、进程、线程2、协程3、主死从随4、启动多个协程5、使用WaitGroup控制协程退出6、多协程操作同一个数据7、互斥锁8、读写锁9、deferrecover优化多协程 1、进程、线程 进程作为资源分配的单位&#xff0c;在内存中会为每个进程分配不同的内存区域 一个进程下面有多个…

Emacs之解除comment-region绑定C-c C-c快捷键(一百三十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

​做一个个人博客第一步该怎么做?零基础就找一个现成的模板学一学呗

做一个个人博客第一步该怎么做&#xff1f; 好多零基础的同学们不知道怎么迈出第一步。 那么&#xff0c;就找一个现成的模板学一学呗&#xff0c;毕竟我们是高贵的Ctrl c v 工程师。 但是这样也有个问题&#xff0c;那就是&#xff0c;那些模板都&#xff0c;太&#xff01;…

pygame--坦克大战(一)

项目搭建 本游戏主要分为两个对象,分别是我方坦克和敌方坦克。用户可以通过控制我方的坦克来摧毁敌方的坦克保护自己的“家”,把所有的敌方坦克消灭完达到胜利。敌方的坦克在初始的时候是默认5个的(这可以自己设置),当然,如果我方坦克被敌方坦克的子弹打中,游戏结束。从…

C++的字节对齐

什么是字节对齐 参考什么是字节对齐&#xff0c;为什么要对齐? 现代计算机中&#xff0c;内存空间按照字节划分&#xff0c;理论上可以从任何起始地址访问任意类型的变量。但实际中在访问特定类型变量时经常在特定的内存地址访问&#xff0c;这就需要各种类型数据按照一定的规…