七 Go语言之函数

通常,项目里有很多的代码是重复的,我们在编程中将这些重复的代码称为冗余代码,这种冗余代码带来的问题是,当我们的需求发生了变化后,需要进行多次的修改,这是一件非常痛苦的事情。那么我们应该怎样解决这个问题呢?就是用我们今天讲的函数。

一 函数定义

1.1 什么是函数

函数就是将一堆代码进行重用的一种机制。函数就是一段代码,一个函数就像一个专门做这件事的人,我们调用它来做一些事情,它可能需要我们提供一些数据给它,它执行完成后可能会有一些执行结果给我们。要求的数据就叫参数,返回的执行结果就是返回值。

1.2 函数基本语法

func 函数名(){
    
    
	函数体
}

通过func关键字来定义函数,函数名后面必须加括号。

案例
在这里插入图片描述

注意
函数要执行必须调用,调用的方式通过函数名进行调用,但是千万不能忘记括号。以上代码的执行流程是:先执行main()函数,前面我们讲过,main函数是整个程序的入口,所以我们一般将需要调用的函数名称写在main函数中。当执行到PlayGame()时,就会去执行PlayGame()函数体中的代码,该函数体代码执行完毕后,又回到main()函数,继续往下执行,这时执行到WuDi(),就去执行WuDi()函数体中的代码,执行完后又回到main()函数,以此类推,指导将main()函数中的所有代码执行完毕为止。

上面的函数是我们自己定义的(一般我们称为自定义函数),但是我们也已经学过不少GO语言自己定义的函数。例如:fmt.Println(“麦芽糖”),fmt.Scanf(“%d”,&d),main()等。我们发现有些函数在使用的时候,必须给它提供一些数据,例如Println()函数和Scanf()函数。我们将给函数提供的这些数据称为参数,那么我们自己定义的函数是否可以有参数呢?完全可以,下面我们就看一下怎样给自己定义的函数提供参数。

给函数传递参数分为两种情况:第一种情况为普通参数列表,第二种情况为不定参数列表。

我们先讲解普通参数列表

二 普通参数列表

所谓的普通参数列表指的是,我们给函数传递的参数的个数都是确定好。基本语法如下:
在这里插入图片描述
首先我们定义了一个Test()函数,该函数有两个参数,a,和b .并且这两个参数的类型都是整型的(这两个参数我们称之为形参),在调用Test()函数时,我们将3传递给参数a,将5传递给参数b(在调用时输入的3和5这个参数我们称之为实参)。我们把这个过程称为参数的传递,并且在Test()函数中输出两个变量的值。
什么时候传递参数呢?其实就是根据我们的需求,例如:定义一个函数,专门实现两个数的和。
在这里插入图片描述
根据上面的案例我们总结出,参数的个数和类型可以根据需要去确定。
但是一定要注意:在定义函数时,形参与实参的个数与类型都要保持一致。

三 不定参数列表

根据前面的讲解,我们都知道了,在定义函数的时候根据需求指定参数的个数和类型,但是有时候如果无法确定参数的个数呢?

举例说明:上一小节我们写过一个求两个整数之和的函数,但是在实际的开发中,也会经常遇到这样的情况,就是项目经理要求你写一个函数,实现整数的和。在这个要求中,项目经理并没有说清楚到底是有几个整数,那么我们应该怎样确定该函数的参数呢?就用接下来给大家讲解的“不定参数列表”来解决这个问题

那么我们可以通过如下的方式来定义函数:
在这里插入图片描述
Test()函数的参数名字叫args(参数的名字可以随便起),类型是整型的。但是,大家一定要注意,在args后面跟了三个点,就是表示该参数可以接收0或多个整数值。所以,args这个参数我们可以想象成是一个集合(类似数学中集合),可以存放多个值。
所以,在Test()函数内,我们通过以前学习的一个函数叫len(),来计算出args这个集合中存储了多少个数(如果args这个集合中存储了5个数,那么len()函数的值就是5),通过for循环将该集合中的数全部输出,在输出时我们通过下标的方式将args集合中的值输出的。所谓的下标,我们可以理解成就是一个编号,对存储在args这个集合中每个数字都加上了编号。在这里要注意的是:下标是从0开始计算的。如下图所示:
在这里插入图片描述
args集合中存储了5,6,7三个数,对应的下标(编号),分别是0,1,2. 如果该集合中存储了4个数,那么第4个数的编号就是3.现在取出第一个数就是args[0]值为5,第二个数args[1]值为6,以此类推。

在main()函数中,我们分别调用了三次Test()函数,在第一次调用时,我们只传递了一个参数1,那么形参args中也就只有1,所以只循环了一次就将该值输出。第二次调用时,传递了两个参数,循环两次输出,第三次调用,传递了三个参数,循环了三次输出。

在Test()函数中,我们除了使用len()函数,计算出集合中存储的数据的个数,然后再输出以外,还有另外一种输出的方式就是使用range关键字。如下所示:
在这里插入图片描述
range会从集合中返回两个数,第一个是对应的坐标,赋值给了变量i,第二个就是对应的值,赋值了变量data

以上两种输出集合的方式,大家在以后的开发过程中都可以使用。

当然在使用不定参数时,要注意几个问题:
第一:一定(只能)放在形参中的最后一个参数。例如:
在这里插入图片描述
上面我们定义了一个Test()函数,该函数第一个参数是一个普通的整型类型,第二个参数是不定参数。那么不定参数args,必须放在后面,整型类型的参数a必须放在前面.如果两者的位置进行互换,在编译的时候就会出错;

在对函数进行调用时,固定参数必须传值,不定参数可以根据需要来决定是否要传值。
示例如下:
在这里插入图片描述
我们定义了一个Test()函数,该函数需要两个固定参数,和不定参数,在main()函数中,我们对Test函数进行了调用,实参1,2这两个值分别传递给形参中的a和b,实参中的3,5,6传递给了不定参数。
但是如果我们在调用Test()函数时,一个参数都不传递,那么会出错。

如果Test()函数只有不定参数,没有固定参数,那么在调用时,可以根据需要来决定是否进行传值。

案例:实现所传参数的和
在这里插入图片描述
在以上的案例中,我们用到了一个下划线( _ ),该下划线表示匿名变量,丢弃数据不进行处理,也就是任何赋予它的值都会被丢弃。

四 函数嵌套调用

4.1 基本函数嵌套调用

函数也可以像我们在前面学习if选择结构,for循环结构一样进行嵌套使用。所谓函数的嵌套使用,其实就是在一个函数中调用另外的函数。
在这里插入图片描述
函数的嵌套调用在以后的开发中应用场景有很多,例如:大家都有在网站注册信息的经历,下面我们模拟一下这个注册过程,让大家体会一下函数嵌套调用在实际开发中的应用场景。

一般的注册过程如下:在网页上填写信息,信息填写完成后,点击注册按钮,如果信息填写全部正确,会提示注册成功,并给您的邮箱发送信息,如果您填写的信息有错误(例如:邮件格式不正确),会提示相应的错误信息,不会注册成功,也不会发邮件。

根据以上信息,大家思考一下,我们可以定义几个函数?
前面我们在讲解函数定义的时候说过,定义函数的基本原则是:基本上每一个函数都是实现单独的功能。所以我们可以定义如下几个函数:Register( )注册函数,作用是接收用户在网页上填写的信息,完成用户信息的保存(只有将信息存储起来,等你下次登录时,将你填写的用户名和密码与注册时保存的用户名和密码进行比较)。CheckInfo( )函数,该函数的作用是对用户填写的信息进行校验,SendMsg( )函数完成邮件的发送。

那么这三个函数之间的调用关系是怎样的?
示例如下所示:
在这里插入图片描述
通过以上案例,希望大家仔细体会嵌套函数的应用场景。而且,我也相信大家对函数的好处有了很深入的理解(例如:用户登录成功,如果也要发送邮件,可以直接调用SendMsg()函数)。

4.2 不定参数函数调用

不定参数的函数在调用的时候,要注意一些细节问题。
通过案例进行演示一下:
在这里插入图片描述
Test1(args…)表示将参数全部传递,所以Test1( )函数最终的输出结果是:3,5,9

如果我们只想传递一部分数据,而不是传递所有的数据,应该怎样进行传递呢?
在这里插入图片描述
以上案例的输出结果 :9, 13

另外一种写法:
在这里插入图片描述
以上程序的输出结果是3, 5

五 函数返回值

5.1 返回值函数基本定义

前面我们学习过一个GO自带的函数,len( )函数。该函数的作用是获取集合中数据的个数,也就是说该函数有返回值。
我们拿到该返回值后,就可以做进一步的处理,例如:可以用来作为循环条件。
我们自己定义的函数怎样返回值呢?
基本语法如下:
在这里插入图片描述

  1. 在定义函数Test( )时,后面加了int,表示该函数最终返回的是一个整型的数据
  2. 在Test( )函数中要返回数据,必须要返回的数据放在return关键字之后(通过return关键字返回数据)。
  3. 在main( )中调用Test( )函数,这时会执行Test( )函数中的代码,当执行完 return sum时,会将sum变量中保存的值返回。
  4. Test( )函数返回的值会赋值给main( )函数中的result变量。

以上是定义一个具有返回值函数的基本语法,当然,GO语言也提供了另外一种语法定义具有返回值的函数,如下所示:
在这里插入图片描述
第三种写法:
在这里插入图片描述
以上几种写法,都可以大家可以根据自己的习惯进行选择,由于本人是java出身,所以习惯第一种写法。

以上案例中,没有给Test( )函数传递参数,如果需要对Test( )函数进行参数传递,可以按照前面讲解的参数传递的内容,对函数进行参数进行传递。
案例演示如下:
在这里插入图片描述

5.2 返回多个值

上面案例中,我们定义的函数都是返回一个指,那么一个函数是否可以返回多个值呢?可以,具体语法如下:
在这里插入图片描述
第二种写法:
在这里插入图片描述

六 函数类型

在讲解函数类型之前,我们先简单的回顾一下,前面我们是怎样定义一个函数,以及怎样调用一个函数的。
我们通过如下的案例简单复习一下:
在这里插入图片描述
在GO语言中还有另外一种定义使用函数的方式,就是函数类型,所谓的函数类型就是将函数作为一种类型可以用来定义变量,这种用法类似于前面我们讲过的int ,float64,string等类型,这些类型都是可以用来定义变量。基本语法如下:
在这里插入图片描述
说明如下:type关键字后面跟着类型的名字(FunType),FunType就是一个类型.那么FunType是一个什么类型呢?
是一个函数类型,因为FunType后面跟着func(用来定义函数的),但是这里注意的是没有函数名字。那么FunType是怎样的一个函数类型呢?是一个需要传递两个整型参数,有一个整型返回值的函数类型。

既然函数类型类似于我们前面学习过的 int ,string 等类型,那么函数类型可以用来定义变量。

  1. var result FuncType //表示定义了一个变量叫result,该变量的 类型是FuncType类型,而该类型是一个函数类型。
    下面我们可以使用result这个函数类型的变量来调用函数了。
  2. result=Test //将要调用的函数的名字赋值给result变量(也可以理解成将result变量指向了要调用的函数),这里要注意的是:第一:Test后面不能加括号。第二:函数类型变量result要和将要调用的函数Test保持一致,所谓一致就是我们定义的函数类型FuncType的变量result,只能调用参数是两个整型的,并且有一个返回值,而且也是整型的函数。那么Test函数完全满足要求。
  3. 现在已经完成了函数类型变量result指向了函数Test,那么我们可以使用函数类型的变量result调用函数:
    result(3,6)//完成函数的调用。

这是我们在GO语言中使用函数类型的方式完成函数的调用,可能有同学感觉这种方式比较麻烦,不如我们前面讲解的方式使用起来简单。那么在这里,要求大家先理解这种方式,能记住语法就可以了。关于该方式的优势,等我们讲解完面向对象编程这个知识点后,大家就有深刻的理解了。

七 函数作用域

7.1 局部变量

前面我们定义的函数中,都经常使用变量。那么我们看一下如下程序的输出结果:
在这里插入图片描述
最终的输出结果是9,为什么呢?在执行fmt.Println(a)语句之前,我们已经调用了函数Test(),并在该函数中我们已经重新给变量a赋值了。但是为什么结果没有发生变化呢?这就是变量的作用范围(作用域)的问题。在Test( )函数中定义的变量a,它的作用范围只在改函数中有效,当Test( )函数执行完成后,在该函数中定义的变量也就无效了。也就是说,当Test( )函数执行完以后,定义在改函数中所有的变量,所占有的内存空间都会被回收。

所以,我们把定义在函数内部的变量称为局部变量。
局部变量的作用,为了临时保存数据需要在函数中定义变量来进行存储,这就是它的作用。

并且,通过上面的案例我们发现:不同的函数,可以定义相同的名字的局部变量,但是各用个的不会产生影响。例如:我们在main( )函数中定义变量a,在Test( )函数中也定义了变量a,但是两者之间互不影响,就是因为它们属于不同的函数,作用范围不一样,在内存中是两个存储区域。

7.2 全局变量

有局部变量,那么就有全局变量。所谓的全局变量:既能在一个函数中使用,也能在其他的函数中使用,这样的变量就是全局变量。也就是定义在函数外部的变量就是全局变量。全局变量在任何的地方都可以使用。案例如下:
在这里插入图片描述
注意:在上面的案例中,我们在函数外面定义了变量a,那么该变量就是全局变量,并且Test( )函数和main( )函数都可以使用该变量。该程序的执行流程是:先执行main( )函数,给变量a赋值为9,紧接着调用Test( )函数,在改函数中完成对变量a的修改。
由于main( )函数与Test( )函数所使用的变量a是同一个,所以当Test( )函数执行完成后,变量的a已经变成了6. 回到main( )函数执行后面的代码,也就是 fmt.Println(a),输出的值就是6

可能大家已经发现该程序和我们前面写的程序还有一点不同的地方是:第一个程序我们是a:=9,但是第二个程序执行修改成了 a=9, 现在修改一下第二个程序如下:

在这里插入图片描述
该程序与上面的程序不同之处在于,该程序是a:=9,上面的程序是a=9.
现在大家思考一下该程序的结果是多少?
最终结果是9.
原因是:a:=9等价于
var a int
a=9
也就是定义一个整型变量a,并且赋值为9.

那么现在的问题是,我们定义了一个全局变量a,同时在main( )中又定义了一个变量也叫a,但是该变量是一个局部变量。

当全局变量与局部变量名称一致时,局部变量的优先级要高于全局变量。所以在main( )函数中执行fmt.Println(a)时输出的是局部变量a的值。但是Test( )函数中的变量a还是全局变量。

注意:大家以后在开发中,尽量不要让全局变量的名字与局部变量的名字一样。

八 匿名函数

前面我们定义函数的时候,发现是不能在一个函数中,再次定义一个函数。如果我们想在一个函数中再定义一个函数,那么可以使用匿名函数,所谓匿名函数就是没有名字的函数。
如下所示:
在这里插入图片描述
在main( )函数中定义了一个匿名函数,定义的方式非常简单func(){ 函数体 }, 一定要注意的是在func的后面没有函数的名字,同时在这里定义的该匿名函数也没有参数。我们将定义好的匿名函数赋值给了变量f,那么变量f就是一个函数类型。要想执行该匿名函数,就可以通过f( )的方式去调用执行。

在这里,有一件非常有意思的事情,就是在匿名函数中可以直接访问main( )函数中定义的局部变量,并且在匿名函数中对变量的值进行了修改,最终会影响到整个main( )函数中定义的变量的值。所以上面两行输出结果都是10.
关于这一点,一定与上一节讲解的函数作用域进行区别。

匿名函数还有其它调用方式如下:
在这里插入图片描述
上面案例中,定义的匿名函数赋值给了变量f,那么f的类型就是函数类型,所以我们自己也可以定义一个函数类型的变量来调用匿名函数。但是上面的应用比较繁琐,实际用的比较少。

定义匿名函数时,直接调用
在这里插入图片描述
该方式,需要在匿名函数的末尾加上小括号,表示调用。同时也不需要将定义好的匿名函数赋值给某个变量。

下面看一下怎样给匿名函数传递参数:
在这里插入图片描述
或者如下方式:
在这里插入图片描述
匿名函数如果有返回值,怎样进行处理呢?
在这里插入图片描述
以上案例中定义了一个匿名函数,该匿名函数需要两个整型参数,同时指定了该函数返回值的名字是变量max与min
当执行到return时,让变量manx与min返回,赋值了变量x,y。x中存储的是max变量的值,y中存储的是min变量的值。

以上就是关于什么是匿名函数,以及匿名函数的使用。匿名函数最主要的功能就是实现了闭包。

九 递归函数

如果一个函数在内部不调用其它的函数,而是调用自己本身的话,这个函数就是递归函数。
例如:
在这里插入图片描述
递归函数的作用:
举个例子,我们来计算阶乘 n! = 1 * 2 * 3 * … * n

其它应用场景:
电商网站中的商品类别菜单的应用。
查找某个磁盘下的文件。

Guess you like

Origin blog.csdn.net/qq_33417321/article/details/115024854