Go语言之函数,返回值,作用域,传参,匿名函数,高阶函数,闭包函数

函数声明和调用

go语言是通过func关键字声明一个函数的,声明语法格式如下

func 函数名(形式参数) (返回值) {函数体return 返回值   // 函数终止语句
}

函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
形式参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
函数体:实现指定功能的代码块。

func cal_sum100()  {// 计算1-100的和var s = 0for i := 1; i <= 100; i++ {s += i}fmt.Println(s)
}

声明一个函数并不会执行函数内代码,只是完成一个一个包裹的作用。真正运行函数内的代码还需要对声明的函数进行调用,一个函数可以在任意位置多次调用。调用一次,即执行一次该函数内的代码。

func cal_sum100()  {// 计算1-100的和var s = 0for i := 1; i <= 100; i++ {s += i}fmt.Println(s)
}
cal_sum100()  

函数参数

什么是参数,函数为什么需要参数呢?将上面的打印的两个菱形换乘打印一个6行的和一个8行的,如何实现呢?这就涉及到了函数参数。
再比如上面我们将计算1-100的和通过函数实现了,但是完成新的需求:
分别计算并在终端打印1-100的和,1-150的和以及1-200的和

package mainimport "fmt"func cal_sum100()  {// 计算1-100的和var s = 0for i := 1; i <= 100; i++ {s += i}fmt.Println(s)
}func cal_sum150()  {// 计算1-100的和var s = 0for i := 1; i <= 150; i++ {s += i}fmt.Println(s)
}func cal_sum200()  {// 计算1-100的和var s = 0for i := 1; i <= 200; i++ {s += i}fmt.Println(s)
}func main() {cal_sum100()cal_sum150()cal_sum200()
}

这样当然可以实现,但是是不是依然有大量重复代码,一会发现三个函数出了一个变量值不同以外其他都是相同的,所以为了能够在函数调用的时候动态传入一些值给函数,就有了参数的概念。
参数从位置上区分分为形式参数和实际参数。

// 函数声明
func 函数名(形式参数1 参数1类型,形式参数2 参数2类型,...){函数体
}
// 调用函数
函数名(实际参数1,实际参数2,...)  

函数每次调用可以传入不同的实际参数,传参的过程其实就是变量赋值的过程,将实际参数按位置分别赋值给形参。
还是刚才的案例,用参数实现:

package mainimport "fmt"func cal_sum(n int)  {// 计算1-100的和var s = 0for i := 1; i <= n; i++ {s += i}fmt.Println(s)
}func main() {cal_sum(100)cal_sum(101)cal_sum(200)
}

这样是不是就灵活很多了呢,所以基本上一个功能强大的函数都会有自己需要的参数,让整个业务实现更加灵活。

位置参数

位置参数,有时也称必备参数,指的是必须按照正确的顺序将实际参数传到函数中,换句话说,调用函数时传入实际参数的数量和位置都必须和定义函数时保持一致。

// 函数声明 两个形参:x,y
func add_cal(x int,y int){fmt.Println(x+y)
}func main() {// 函数调用,按顺序传参// add_cal(2) // 报错// add_cal(232,123,12) // 报错add_cal(100,12)
}

不定长参数

如果想要一个函数能接收任意多个参数,或者这个函数的参数个数你无法确认,就可以使用不定长参数,也叫可变长参数。Go语言中的可变参数通过在参数名后加…来标识。

package mainimport "fmt"func sum(nums ...int) { //变参函数fmt.Println("nums",nums)total := 0for _, num := range nums {total += num}fmt.Println(total)
}func main() {sum(12,23)sum(1,2,3,4)}

注意:可变参数通常要作为函数的最后一个参数。

package mainimport "fmt"func sum(base int, nums ...int) int {fmt.Println(base, nums)sum := basefor _, v := range nums {sum = sum + v}return sum
}func main() {ret := sum(10,2,3,4)fmt.Println(ret)}

go的函数强调显示表达的设计哲学,没有默认参数

函数返回值

函数的返回值是指函数被调用之后,执行函数体中的代码所得到的结果,这个结果通过 return 语句返回。return 语句将被调函数中的一个确定的值带回到主调函数中,供主调函数使用。函数的返回值类型是在定义函数时指定的。return 语句中表达式的类型应与定义函数时指定的返回值类型必须一致。

func 函数名(形参 形参类型)(返回值类型){//  函数体return 返回值
}变量 = 函数(实参)    // return 返回的值赋值给某个变量,程序就可以使用这个返回值了。

同样是设计一个加法计算函数:

func add_cal(x,y int) int{return x+y
}func main() {ret := add_cal(1,2)fmt.Println(ret)
}

无返回值,声明函数时没有定义返回值,函数调用的结果不能作为值使用

func foo(){fmt.Printf("hi,yuan!")return  // 不写return默认return空
}func main() {// ret := foo() // 报错:无返回值不能将调用的结果作为值使用foo()}

返回多个值,函数可以返回多个值

func get_name_age() (string, int) {return "yuan",18
}func main() {a, b := get_name_age()fmt.Println(a, b)
}

返回值命名

函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。

func calc(x, y int) (sum, sub int) {sum = x + ysub = x - yreturn     //  return sum sub
}

作用域

所谓变量作用域,即变量可以作用的范围。
作用域(scope)通常来说,程序中的标识符并不是在任何位置都是有效可用的,而限定这个标识符的可用性的范围就是这个名字的作用域。
变量根据所在位置的不同可以划分为全局变量和局部变量
局部变量 :写在{}中或者函数中或者函数的形参, 都是局部变量

1、局部变量的作用域是从定义的那一行开始, 直到遇到 } 结束或者遇到return为止
2、局部变量, 只有执行了才会分配存储空间, 只要离开作用域就会自动释放
3、局部变量存储在栈区
4、局部变量如果没有使用, 编译会报错。全局变量如果没有使用, 编译不会报错
5、:=只能用于局部变量, 不能用于全局变量

全局变量 :函数外面的就是全局变量

1、全局变量的作用域是从定义的那一行开始, 直到文件末尾为止
2、全局变量, 只要程序一启动就会分配存储空间, 只有程序关闭才会释放存储空间,
3、全局变量存储在静态区(数据区)

func foo()  {// var x =10x = 10fmt.Println(x)
}var x = 30   // 全局变量func main() {// var x = 20foo()fmt.Println(x)
}

注意,if,for语句具备独立开辟作用域的能力:

// if的局部空间
if true{x:=10fmt.Println(x)
}fmt.Println(x)// for的局部空间
for i:=0;i<10 ;i++  {}
fmt.Println(i)

值传递

// 案例1
var x = 10
fmt.Printf("x的地址%p\n", &x)
y := x
fmt.Printf("y的地址%p\n", &y)
x = 100
fmt.Println(y)// 案例2
var a = []int{1, 2, 3}
b := a
a[0] = 100
fmt.Println(b)// 案例3
var m1 = map[string]string{"name":"yuan"}
var m2 = m1
m2["age"] = "22"
fmt.Println(m1)

函数传参

package mainimport "fmt"func swap(a int, b int) {c := aa = bb = cfmt.Println("a", a)fmt.Println("b", b)
}func main() {var x = 10var y = 20swap(x, y)fmt.Println("x", x)fmt.Println("y", y)
}
package mainimport "fmt"func func01(x int) {x = 100
}func func02(s []int) {fmt.Printf("func02的s的地址:%p\n",&s)s[0] = 100// s = append(s, 1000)
}func func03(p *int)  {*p = 100
}
func main() {// 案例1var x = 10func01(x)fmt.Println(x)// 案例2var s = []int{1, 2, 3}fmt.Printf("main的s的地址:%p\n",&s)func02(s)fmt.Println(s)//案例3var a = 10var p *int = &afunc03(p)fmt.Println(a)}

思考之前的scan函数为什么一定传参&
Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据。

func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
}

make函数创建的map就是一个指针类型,工作原理类似于案例3,所以map数据和切片数据一样虽然值拷贝但依然可以修改原值。

匿名函数

匿名函数,顾名思义,没有函数名的函数。
匿名函数的定义格式如下:

func(参数列表)(返回参数列表){函数体
}

匿名函数可以在使用函数的时候再声明调用

//(1)
(func() {fmt.Println("yuan")
})()
//(2)
var z =(func(x,y int) int {return x + y})(1,2)fmt.Println(z)

也可以将匿名函数作为一个func类型数据赋值给变量

var f  = func() {fmt.Println("yuan")
}fmt.Println(reflect.TypeOf(f))  // funcf() // 赋值调用调用

Go语言不支持在函数内部声明普通函数,只能声明匿名函数。

func foo()  {fmt.Println("foo功能")f := func(){fmt.Println("bar功能")}fmt.Println(f)
}

高阶函数

一个高阶函数应该具备下面至少一个特点:
将一个或者多个函数作为形参
返回一个函数作为其结果
首先明确一件事情:函数名亦是一个变量

package mainimport ("fmt""reflect"
)func addCal(x int, y int)int{return x + y
}func main() {var a = addCala(2, 3)fmt.Println(a)fmt.Println(addCal)fmt.Println(reflect.TypeOf(addCal))  // func(int, int) int}

结论:函数参数是一个变量,所以,函数名当然可以作为一个参数传入函数体,也可以作为一个返回值。

函数参数

package mainimport ("fmt""time"
)func timer(f func()){timeBefore := time.Now().Unix()f()timeAfter := time.Now().Unix()fmt.Println("运行时间:", timeAfter - timeBefore)}func foo() {fmt.Println("foo function... start")time.Sleep(time.Second * 2)fmt.Println("foo function... end")
}func bar() {fmt.Println("bar function... start")time.Sleep(time.Second * 3)fmt.Println("bar function... end")
}func main() {timer(foo)timer(bar)
}

注意如果函数参数也有参数该怎么写呢?

package mainimport "fmt"func add(x,y int ) int {return x+y
}func mul(x,y int ) int {return x*y
}// 双值计算器
func cal(f func(x,y int) int,x,y int,) int{return f(x,y)}func main() {ret1 := cal(add,12,3,)fmt.Println(ret1)ret2 := cal(mul,12,3,)fmt.Println(ret2)}

函数返回值

package mainimport ("fmt"
)func foo() func(){inner := func() {fmt.Printf("函数inner执行")}return inner
}func main() {foo()()}

闭包函数

闭包并不只是一个go中的概念,在函数式编程语言中应用较为广泛。

首先看一下维基上对闭包的解释:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量(外部非全局)的函数。

简单来说就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。这样的一个函数我们称之为闭包函数。

闭包就是当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之 外执行。
需要注意的是,自由变量不一定是在局部环境中定义的,也有可能是以参数的形式传进局部环境;另外在 Go 中,函数也可以作为参数传递,因此函数也可能是自由变量。
闭包中,自由变量的生命周期等同于闭包函数的生命周期,和局部环境的周期无关。
闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
实现一个计数器函数,不考虑闭包的情况下,最简单的方式就是声明全局变量:

package mainimport "fmt"var i = 0func counter() {i++fmt.Println(i)
}func main() {counter()counter()counter()
}

这种方法的一个缺点是全局变量容易被修改,安全性较差。闭包可以解决这个问题,从而实现数据隔离。

package mainimport "fmt"func getCounter() func() {   var i = 0return func() {i++fmt.Println(i)}}func main() {counter := getCounter()counter()counter()counter()counter2 := getCounter()counter2()counter2()counter2()
}

getCounter完成了对变量i以及counter函数的封装,然后重新赋值给counter变量,counter函数和上面案例的counter函数的区别就是将需要操作的自由变量转化为闭包环境。

闭包函数应用案例

在go语言中可以使用闭包函数来实现装饰器

案例1:计算函数调用次数


package mainimport ("fmt""reflect""runtime"
)// 函数计数器
func getCounter(f func()) func() {calledNum := 0 // 数据隔离return func() {f()calledNum += 1// 获取函数名fn := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()fmt.Printf("函数%s第%d次被调用\n", fn, calledNum)}
}// 测试的调用函数
func foo() {fmt.Println("foo function执行")
}func bar() {fmt.Println("bar function执行")
}func main() {/*fooAndCounter := getCounter(foo)   // 针对foo的计数器fooAndCounter()fooAndCounter()fooAndCounter()barAndCounter := getCounter(bar)barAndCounter()barAndCounter()barAndCounter()*/foo := getCounter(foo) // 开放原则foo()foo()foo()bar := getCounter(bar)bar()bar()
}

案例2:计算函数运行时间

package mainimport ("fmt""time"
)func GetTimer(f func(t time.Duration)) func(duration time.Duration) {return func(t time.Duration) {t1 := time.Now().Unix()f(t)t2 := time.Now().Unix()fmt.Println("运行时间:", t2-t1)}}func foo(t time.Duration) {fmt.Println("foo功能开始")time.Sleep(time.Second * t)fmt.Println("foo功能结束")
}func bar(t time.Duration) {fmt.Println("bar功能开始")time.Sleep(time.Second * t)fmt.Println("bar功能结束")
}func main() {var foo = GetTimer(foo)foo(3)var bar = GetTimer(bar)bar(2)}

关键点:将一个功能函数作为自由变量与一个装饰函数封装成一个整体作为返回值,赋值给一个新的函数变量,这个新的函数变量在调用的时候,既可以完成原本的功能函数又可以实现装饰功能。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot使用JWT进行身份验证

JWT身份验证的流程 用户登录&#xff1a; 用户向服务器提供他们的用户名和密码。 服务器验证&#xff1a;服务器接收到请求&#xff0c;验证用户名和密码。 生成JWT&#xff1a;如果用户名和密码验证通过&#xff0c;服务器将创建一个 JWT。 JWT 包含了一些数据&#xff08;称…

【activiti】工作流入门基础概念

Activiti 为什么使用activitiactiviti流程步骤表结构分四种&#xff1a;四个重要的服务类BusinessKey挂起流程变量&#xff1a;注意&#xff1a;网关&#xff1a;精细控制流程走向组任务分配&#xff1a;抄送&#xff1a;activiti工作流获取流程定义中所有的节点: 为什么使用ac…

[JVM] 5. 运行时数据区(2)-- 程序计数器(Program Counter Register)

一、概述 JVM中的程序计数器&#xff08;Program Counter Register&#xff09;是对物理PC寄存器的一种抽象模拟。它是一块很小的内存空间&#xff0c;几乎可以忽略不记。也是运行速度最快的存储区域。在 JVM 规范中&#xff0c;每个线程都有它自己的程序计数器&#xff0c;是…

redis之主从复制、哨兵、集群

文章目录 一、redis的高可用1.1 redis高可用的概念1.2 Redis的高可用技术 二、redis 主从复制2.1主从复制的原理2.2搭建Redis 主从复制 三、Redis 哨兵模式3.1搭建Redis 哨兵模式3.2启动哨兵模式3.3查看哨兵信息3.4故障模拟 四、Redis 群集模式4.1搭建Redis 群集模式 一、redis…

【Excel】excel多个单元格的内容合并到一个单元格,并使用分隔符

方法一&#xff1a;使用连接符 & 左键单击选中“D2”单元格&#xff0c;在D2单元格中输入公式“A2&B2&C2”&#xff0c;按“Enter”即可实现数据合并。 ------如果想连接的时候&#xff0c;中间加分隔符&#xff0c;可以使用&#xff1a;公式A2&"&#xf…

常用的rsync使用方式

发送文件夹并保持文件属性不变 rsync -avr path xxx192.168.x.x:~/path 关于rsync的说明&#xff1a; rsync is a file transfer program capable of efficient remote update via a fast differencing algorithm. Usage: rsync [OPTION]... SRC [SRC]... DEST or rsyn…

STL unordered_set的eraseyong fa

如下unordered_set的erase操作导致程序崩溃&#xff0c;crash。 #include <iostream> #include <string> #include <unordered_set> int main () { std::unordered_set<std::string> myset {"USA","Canada","France&qu…

Azure Kinect 之 Note(一)

Azure Kinect Azure Kinect DK 是一款开发人员工具包&#xff0c;配有先进的AI 传感器&#xff0c;提供复杂的计算机视觉和语音模型。 Kinect 将深度传感器、空间麦克风阵列与视频摄像头和方向传感器整合成一体式的小型设备&#xff0c;提供多种模式、选项和软件开发工具包(S…

面试题更新之-HTML5的新特性

文章目录 导文新特性有哪些&#xff1f;HTML5的新特性带来了许多好处 导文 面试题更新之-HTML5的新特性 新特性有哪些&#xff1f; HTML5引入了许多新特性和改进&#xff0c;以下是一些HTML5的新特性&#xff1a; 语义化标签&#xff1a;HTML5引入了一系列的语义化标签&#…

远程在Ubuntu20.04安装nvidia显卡驱动

第零步&#xff0c;找人装一个todesk。 在终端运行&#xff1a; ifconfig 记住ip地址&#xff0c;后面要用。 第一步&#xff0c;安装软件&#xff1a; sudo apt-get update sudo apt-get install g gcc make 第二步&#xff0c;下载显卡驱动&#xff1a; 官方驱动 | NVI…

【ThinkPHP】实现一个逆向工程生成model

ThinkPHP为了节省一些重复的步骤&#xff0c;写了个简单版的生成model的工具&#xff0c;逆向生成model代码&#xff0c;节省时间&#xff0c;专注写业务代码。 ThinkPHP中的命令行也提供了一些生成代码的命令&#xff1a; make:controller 创建控制器 make:model 创建模型 m…

医院制剂研发与真实世界评价论坛圆满闭幕

医院制剂是新药的摇篮和宝库&#xff0c;现代科技为医院制剂的研发和转化赋能。在新时代新政策下&#xff0c;2023年07月16日&#xff0c;由湖南省药学会医院制剂研发与真实世界评价专业委员会&#xff08;下称“专委会”&#xff09;主委单位湖南易能生物医药有限公司&#xf…

划片机的技术分解

划片机是一种切割设备&#xff0c;主要用于将硬脆材料&#xff08;如硅晶圆、蓝宝石基片、LED基片等&#xff09;分割成较小的单元。其工作原理是以强力磨削为划切机理&#xff0c;通过空气静压电主轴带动刀片与工件接触点的划切线方向呈直线运动&#xff0c;将每一个具有独立电…

MVVM模式的具体实现

MVVM即Model-View-ViewModel的简写。即模型-视图-视图模型。 模型&#xff08;Model&#xff09;指的是后端传递的数据。 视图(View)指的是所看到的页面。 视图模型(ViewModel)是mvvm模式的核心&#xff0c;它是连接view和model的桥梁。 它有两个方向&#xff1a; 一是将视图(V…

概率论的学习和整理18:为什么 P(至少成功1次) = Σ P(几何分布) ,总结几何分布和连续失败概率的关系,二项分布和累计成功k次的关系

目录 1 先说结论&#xff1a; 2 Σ几何分布的P(xn) P(n次试验至少成功1次) 2.1 几何分布的概率 2.2 这个是可以证明的&#xff0c;下面是推导过程 2.3 怎么理解呢&#xff1f; 3 另外&#xff0c;P(累计成功k次) ΣP(成功k次的二项分布) 3.1 成功k次的概率 和 累计成…

回收站怎么看当天删除的文件?在回收站中找不到被删除文件怎么回事

在日常使用电脑的过程中&#xff0c;我们常常会遭遇删除文件的错误&#xff0c;这时回收站就像是一剂“后悔药”。然而&#xff0c;当回收站中堆积了许多已删除的文件时&#xff0c;我们如何才能找到当天删除的文件呢&#xff1f;如果回收站在这时无法提供文件&#xff0c;我们…

本地Linux 部署 Dashy 并远程访问

文章目录 简介1. 安装Dashy2. 安装cpolar3.配置公网访问地址4. 固定域名访问 转载自cpolar极点云文章&#xff1a;本地Linux 部署 Dashy 并远程访问 简介 Dashy 是一个开源的自托管的导航页配置服务&#xff0c;具有易于使用的可视化编辑器、状态检查、小工具和主题等功能。你…

Python应用实例(二)数据可视化(一)

数据可视化&#xff08;一&#xff09; 1.安装Matplotlib2.绘制简单的折线图2.1 修改标签文字和线条粗细2.2 矫正图形2.3 使用内置样式2.4 使用scatter()绘制散点图并设置样式2.5 使用scatter()绘制一系列点2.6 自动计算数据2.7 自定义颜色2.8 使用颜色映射2.9 自动保存图表 数…

Visual Studio 自定义的颜色字体不生效

问题描述&#xff1a; 1、dll1中引用第三方库的类不识别&#xff0c;颜色黑白&#xff0c;自定义颜色不生效&#xff1b;定义的是结构体 2、在dll2引用另一个dll1中的结构体。结构体不识别&#xff0c;今天成员函数cpp中自定义颜色不生效。 问题解决方式&#xff1a; 全部清…

【MySQL备份与还原、索引、视图】练习

一、备份与还原 /***************************样例表***************************/CREATE DATABASE booksDB;use booksDB;CREATE TABLE books(bk_id INT NOT NULL PRIMARY KEY,bk_title VARCHAR(50) NOT NULL,copyright YEAR NOT NULL);INSERT INTO booksVALUES (11078, Lear…