从0到1保姆级教程,14天搞定Go语言—Go语言的函数与指针
已经分享了Go语言的流程控制(从0到1保姆级教程,14天搞定Go语言视频教程—Go语言的流程控制)的相关内容。随着编程经验的提高,很多同学常常会发现自己写的代码有很多重复,一旦决定要更新代码,就必须将每个重复的部分修改,费时费力。因此,大家希望可以避免代码的重复,第四天内容可以帮助读者消除重复,使程序更短、更易读、更容易更新。 很久以前的人们想要制作果汁,每次都用手捏,非常麻烦,后来有人发明了榨汁机,人们只要把水果放进去,榨汁机就会把果汁榨出来。函数的功能就像榨汁机一样,帮助人们重复的做任务。函数是组织好的、可重复使用的执行特定任务的代码块。它可以提高应用程序的模块性和代码的重复利用率。Go语言从设计上对函数进行了优化和改进,让函数使用起来更加方便。因为Go语言的函数本身可以作为值进行传递,既支持匿名函数和闭包又能满足接口,所以Go语言的函数属于一等公民。 函数 Ø 函数声明 普通函数需要先声明才能调用,一个函数的声明包括参数和函数名等。编译器通过声明才能了解函数应该怎样在调用代码和函数体之间传入参数和返回参数。语法格式如下所示。
1. 函数定义解析 func:函数关键字。函数由 func 开始声明。 funcName:函数名。函数名和参数列表一起构成了函数签名。函数名由字母、数字和下画线组成。函数名的第一个字母不能为数字。在同一个包内,函数不能重名。 parametername type:参数列表。定义函数时的参数叫作形式参数,形参变量是函数的局部变量;函数被调用时,可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序及参数个数。参数是可选的,也就是说函数可以不包含参数。 output1 type1, output2 type2:返回值列表。返回值返回函数的结果,结束函数的执行。Go语言的函数可以返回多个值。返回值可以是返回数据的数据类型,也可以是变量名+变量类型的组合。函数声明时有返回值,必须在函数体中使用return语句提供返回值列表。如果只有一个返回值并且没有声明返回值变量,那么可以省略包括返回值的括号。return后的数据,要保持和声明的返回值类型、数量、顺序一致。如果函数没有声明返回值,函数中也可以使用return关键字,用于强制结束函数。 函数体:函数定义的代码集合,是能够被重复调用的代码片段。 2. 参数类型简写 在参数列表中,如果有多个参数变量,则以逗号分隔;如果相邻变量是同类型,则可以将类型省略。语法格式如下所示。
Go语言的函数支持可变参数。接受变参的函数是有着不定数量的参数的。语法格式如下所示。
arg …int告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是int。在函数体中,变量arg是一个int的slice(切片)。 Ø 变量作用域 作用域是变量、常量、类型、函数的作用范围。 在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,生命周期同所在的函数。参数和返回值变量也是局部变量。 在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用,全局变量的生命周期同 main()。 全局变量可以在任何函数中使用。Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。 函数中定义的参数称为形式参数,形式参数会作为函数的局部变量来使用。 为了更直观的理解作用域,下面通过一个案例分析,如例所示。
Ø 函数变量(函数作为值) 在Go语言中,函数也是一种类型,可以和其他类型(如int32、float等等)一样被保存在变量中。 在Go语言中可以通过type来定义一个自定义类型。函数的参数完全相同(包括参数类型、个数、顺序),函数返回值相同。 函数变量的使用步骤及意义如下。 l 定义一个函数类型 l 实现定义的函数类型 l 作为参数调用 函数变量的用法类似接口的用法。 Ø 匿名函数 Go语言支持匿名函数,即在需要使用函数时再定义函数。匿名函数没有函数名,只有函数体,函数可以被作为一种类型被赋值给变量,匿名函数也往往以变量方式被传递。 匿名函数经常被用于实现回调函数、闭包等。语法格式如下所示。
1. 在定义时调用匿名函数 使用方式如例所示。
2. 将匿名函数赋值给变量 使用方式如例所示。
3. 匿名函数的用法——作回调函数 使用方式如例所示。
Ø 闭包 1. 闭包的概念 闭包并不是什么新奇的概念,它早在高级语言开始发展的年代就产生了。闭包(Closure)是词法闭包(Lexical Closure)的简称。 闭包是由函数和与其相关的引用环境组合而成的实体。在实现深约束时,需要创建一个能显式表示引用环境的东西,并将它与相关的子程序捆绑在一起,这样捆绑起来的整体被称为闭包。函数+引用环境=闭包。 闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,这些代码在函数被定义后就确定了,不会在执行时发生变化,所以一个函数只有一个实例。 闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。闭包在某些编程语言中被称为Lambda表达式。 函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有“记忆性”。函数是编译器静态的概念,而闭包是运行期动态的概念。 对象是附有行为的数据,而闭包是附有数据的行为。 2. 闭包的优点 (1)加强模块化。闭包有益于模块化编程,它能以简单的方式开发较小的模块,从而提高开发速度和程序的可复用性。和没有使用闭包的程序相比,使用闭包可将模块划分得更小。 比如要计算一个数组中所有数字的和,这只需要循环遍历数组,把遍历到的数字加起来就行了。如果现在要计算所有素的积,又或者要打印所有的素呢?解决这些问题都要对数组进行遍历,如果是在不支持闭包的语言中,程序员不得不一次又一次重复地写循环语句。而这在支持闭包的语言中是不必要的。这种处理方法多少有点像回调函数,不过要比回调函数写法更简单,功能更强大。 (2)抽象。闭包是数据和行为的组合,这使得闭包具有较好抽象能力。 (3)简化代码。一个编程语言需要以下特性来支持闭包。 l 函数是一阶值(First-class value,一等公民),即函数可以作为另一个函数的返回值或参数,还可以作为一个变量的值。 l 函数可以嵌套定义,即在一个函数内部可以定义另一个函数。 l 允许定义匿名函数。 l 可以捕获引用环境,并把引用环境和函数代码组成一个可调用的实体; 没有使用闭包进行计数的代码。
使用闭包函数实现计数器。
闭包案例。
由于闭包函数“捕获”了和它在同一作用域的其他常量和变量。所以当闭包在任何地方被调用,闭包都可以使用这些常量或者变量。它不关心这些变量是否已经超出作用域,只要闭包还在使用它,这些变量就依然存在。 Ø 可变参数 如果一个函数的参数,类型一致,但个数不定,可以使用函数的可变参数。语法格式如下所示。
该语法格式定义了一个接受任何数目、任何类型参数的函数。这里特殊的语法是三个点“…”,在一个变量后面加上三个点后,表示从该处开始接受不定参数。 当要传递若干个值到不定参数函数中得时候,可以手动书写每个参数,也可以将一个slice传递给该函数,通过”…”可以将slice中的参数对应的传递给函数。 计算学员考试总成绩及平均成绩。
使用可变参数注意如下细节。 l 一个函数最多只能有一个可变参数 l 参数列表中还有其他类型参数,则可变参数写在所有参数的最后 Ø 递归函数 在函数内部,可以调用其他函数。如果一个函数在内部调用自身,那么这个函数就是递归函数。递归函数必须满足以下两个条件。 (1)在每一次调用自己时,必须是(在某种意义上)更接近于解; (2)必须有一个终止处理或计算的准则。 下面通过案例来理解递归函数的作用。 计算阶乘n! = 1 x 2 x 3 x … x n,用函数fact(n)表示,可以看出:fact(n) = n! = 1 x 2 x 3 x … x (n-1) x n = (n-1)! x n = fact(n-1) x n。所以,fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。 阶乘。
递归的计算过程如下所示。
使用递归需要注意如下事项。 l 递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以用循环的方式实现,但循环的逻辑不如递归清晰。 l 使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈,每当函数返回,栈就会减一层。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。 l 使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。 指针 指针的概念 指针是存储另一个变量的内存地址的变量。 变量是一种使用方便的占位符,变量都指向计算机的内存地址。 一个指针变量可以指向任何一个值的内存地址。 例如:变量b的值为156,存储在内存地址0x1040a124。变量a持有b的地址,则a被认为指向b。如图所示。
在Go 语言中使用取地址符(&)来变量的地址,一个变量前使用&,会返回该变量的内存地址。如例所示。
注意:在不同的环境下输出结果可能不同。 Go语言指针的特点如下。 Go语言指针的最大特点是:指针不能运算(不同于C语言)。 在Go语言中如果对指针进行运算会报错:nvalid operation: p++ (non-numeric type *int)。 声明指针 声明指针,*T是指针变量的类型,它指向T类型的值。
* 号用于指定变量是一个指针。
指针使用流程如下。 定义指针变量。 为指针变量赋值。 访问指针变量中指向地址的值。 指针的值:在指针类型的变量前加上 * 号(前缀)来指针所指向的内容。 一个指针意味着访问指针指向的变量的值。语法是:*a 指针示例一。
指针示例二。
空指针 在Go语言中,当一个指针被定义后没有分配到任何变量时,它的值为 nil。 nil 指针也称为空指针。nil在概念上和其它语言的null、None、NULL一样,都指代零值或空值。 假设指针变量命名为 ptr。空指针判断:
使用指针 1. 通过指针修改变量的数值。 如例所示。
2.使用指针作为函数的参数 基本数据类型指针的使用方式如例所示。
虽然将指针传递给一个切片作为函数的参数,可以实现对该切片中素的修改,但这并不是实现这一目标的惯用方法。惯用做法是使用切片。 指针数组 指针数组:就是素为指针类型的数组。 定义一个指针数组,语法格式如下所示。
有一个素个数与之相同的数组,将该数组中每个素的地址赋值给该指针数组。也就是说该指针数组与某一个数组完全对应。可以通过*指针变量到该地址所对应的数值。 指针数组 指针数组的使用,如例所示。
指针的指针 如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址,如图所示
指向指针的指针变量声明格式如下:
以上指向指针的指针变量为整型。 访问指向指针的指针变量值需要使用两个 * 号。 指针的指针使用的方式,如例所示。
函数的参数传递 函数如果使用参数,该参数变量称为函数的形参。形参就像定义在函数体内的局部变量。调用函数,可以通过两种方式来传递参数。即值传递和引用传递,或者叫做传值和传引用。 值传递(传值) 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到原内容数据。 默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到原内容数据。 每次调用函数,都将实参复制一份再传递到函数中。每次都复制一份,性能会下降,但是 Go语言中使用指针和值传递配合就避免了性能降低问题,也就是通过传指针参数来解决实参拷贝的问题。 引用传递(传引用) 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到原内容数据。 严格来说Go语言只有值传递这一种传参方式,Go语言是没有引用传递的。 Go语言中可以借助传指针来实现引用传递的效果。函数参数使用指针参数,传参的时候其实复制一份指针参数,也就是复制了一份变量地址。 函数的参数如果是指针,当函数调用时,虽然参数仍然是按复制传递的,但是此时仅仅只是复制一个指针,也就是一个内存地址,这样就不用担心实参复制造成的内存浪费、时间开销、性能降低的情况。 引用传递的作用如下。 传指针使得多个函数能操作同一个对象。 传指针更轻量级 (8bytes),只需要传内存地址。如果参数是非指针参数,那么值传递的过程中,每次在复制上面就会花费相对较多的系统开销(内存和时间)。所以当要传递大的结构体的时候,用指针是一个明智的选择。 Go语言中slice、map、chan类型的实现机制都是类似指针,所以可以直接传递,而不必取地址后传递指针。 函数传int类型的值与引用的对比,如例所示。
函数传slice类型的值与引用的对比,如例所示。
函数传数组,其类型的值与引用的对比,如例所示。
函数传结构体,其类型的值与引用的对比,如例所示。
值传递和引用传递细节问题 Go语言中所有的传参都是值传递(传值),都是一个副本、一个复制。 复制的内容有时候是值类型(int、string、bool、数组、struct属于值类型),这样就在函数中就无法修改原内容数据; 有的是引用类型(指针、slice、map、chan属于引用类型),这样就可以修改原内容数据。 是否可以修改原内容数据,和传值、传引用没有必然的关系。在C++中,传引用肯定是可以修改原内容数据的,在Go语言里,虽然只有传值,但是也可以修改原内容数据,因为参数可以是引用类型。 传引用和引用类型是两个概念。虽然Go语言只有传值一种方式,但是可以通过传引用类型变量达到跟传引用一样的效果。 小结:Go语言的函数与指针 介绍了Go语言的函数和指针,函数部分包括函数的定义、函数的参数、函数的返回值、匿名函数、闭包、可变参数和递归调用。指针部分包括指针的定义、指针的使用方式、指针数组和指针的指针。最后通过案例对函数的传参方式进行对比。在实际编程时,应尽量使用函数来提高代码的复用性,对于占用内存较大的变量应尽量使用指针来减少资源的消耗。 Go语言视频教程,持续更新中…… 14天搞定Go语言-变量 14天搞定Go语言-数据类型 14天搞定Go语言-打印格式化 14天搞定Go语言-数据类型转换 14天搞定Go语言-常量 14天搞定Go语言-类型别名 14天搞定Go语言-GO语言运算符 14天搞定Go语言-运算符优先级 14天搞定Go语言(自学第二天)-知识点总结 14天搞定Go语言-流程控制概述 14天搞定Go语言-if条件判断语句 14天搞定Go语言-if嵌套语句 14天搞定Go语言-switch分支语句 14天搞定Go语言-for循环语句 14天搞定Go语言-for嵌套循环结构 14天搞定Go语言-循环控制语句 14天搞定Go语言-第三天,知识点总结 14天搞定Go语言-函数视频教程 14天搞定Go语言-函数的参数传递教程 14天搞定Go语言-指针视频教程 14天搞定Go语言-第四天,知识点总结
2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/70571.html