本文章是c语言中介绍函数的第二篇,第二篇的主要内容大概由:
5.函数的嵌套调用和链式访问
6.函数的声明与定义
7.函数递归
5.函数的嵌套调用和链式访问
5.1函数的嵌套调用
在学习函数的嵌套调用之前,我们首先明确一个定义:函数可以嵌套调用,但是不可以嵌套定义!
下面,我们对这两种情况分别进行解释
5.1.1函数的嵌套定义(再次强调函数不可以嵌套定义!函数不可以嵌套定义!函数不可以嵌套定义!)例如:
可以看到,这就是一个函数嵌套定义的例子,当我们在定义一个函数时,在这个函数内部再定义一个函数,则显示错误 。
5.1.2函数的嵌套调用:
同样,我们先来举个例子:
可以看到,如果我们在名为dayin的函数中去调用上面定义的add函数,则显示正常,这里,为了更加清楚的说明函数在嵌套调用的情况下是可以正常运行的,我们写一个代码:先写一个加法函数,如果这个加法函数的结果是5,则在另一个函数中打印正确 ,否则显示错误
如图所示,我们在主函数中调用了dayin函数,在dayin函数中,我们又调用了add函数,同时,为了方便说明,我们直接人工将打印函数的结果设置为5,结果如下:
可以看到,结果显示是正确。
如果我们更改加法函数的值,使他的结果不等于5
结果如下,显示为错误:
这个例子,再次验证了我们开头提到的那句话,即函数可以嵌套调用,但是不可以嵌套定义
5.2函数的链式访问
首先我们简要说一下链式访问的概念,即:把一个函数的返回值作为另一个函数的参数
下面,我们再举个例子:
如图,我们写这样一个函数,用于打印字符串的长度,可以在下图看到,结果为5
如果,我们将上述代码简化,把两行代码合并成一行代码,则:
打印结果依旧是5,这就是把strlen函数的返回值作为printf函数的参数,也就是我们上面所提出的,链式访问概念:把一个函数的返回值作为另一个函数的参数。
在了解了链式访问的的概念之后,我们来看一个有趣的代码
首先,我们需要明确一点,就是Printf函数的返回值,是他打印的字符的数目,所以,这里的链式访问,可以理解为三个步骤:最里面的printf函数的打印了字符串“43”,因为一共打印了两个字符,所以他的返回值是2。
中间的printf把最里面的printf的返回值,也就是“2”,当做参数,进行打印。因为只打印了2这一个字符,所以他的返回值就是1
最外层的printf函数接收到的参数是中间printf函数的返回值,也就是1,所以最外层的函数打印“1”
最后的结果就是“4321”。结果如下:
如果我们对上述代码再进行更改,把printf函数的打印格式从“%d" 改成”%d ",又会变成什么样子呢?
首先我们先放上更改后的代码:
首先,我们直接放上结果:
此时可以看到
结果从“4321”变成了“43 3 2”,这是因为,我们更改了printf函数打印的格式,在后面多加了一个空格,所以,最内层的printf函数的打印内容便是“43 ”,此时最内层函数的返回值便是“3 ”,中间的printf函数接收到的参数就是“3 ”,此时进行打印,且中间的printf函数的返回值是2,因此,最外层printf函数的打印结果就是“2”,因此,最终结果便是:“43 3 2”
6.函数的声明与定义
同样,我们通过一个例子来解释函数的声明与定义
如图所示,这是一个简单的加法函数,但是如果我们把函数定义的部分,放到主函数下面,会发生甚, ?
可以看到,此时程序运行发生了错误
这是因为,编译器在编译代码的时候,是从上往下一依次编译的。编译器在编译上面的代码时,因为只看到了我们调用了一个函数,但是并没有找到我们对于该函数的定义,所以会发生错误,此时,如果我们遇到了函数定义必须在主函数之后的情况,就可以使用函数声明 ,如下图
当我们在主函数前面加上函数声明的时候,虽然我们对于函数的定义在主函数后面,但是代码依然可以运行。所以,在使用一个函数之前,必须对这个函数提前声明。
通过上面的例子,接下来,我们直接引出函数声明的定义:
1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数
声明决定不了。
2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
3. 函数的声明一般要放在头文件中的。
7.函数递归
首先,我们先说明函数递归的定义:
程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接
调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相
递归策略
规模较小的问题来求解,只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
可以看到,函数递归的定义是相对复杂的,如果我们用一句话来概括函数递归,就是:函数自己调用自己就称为函数递归
为了更形象的解释递归,下面我们给出一个最简单的关于递归的例子:
可以看到,main函数在打印函数后面,自己调用了自己,这就是一个简单的递归的例子。
(注:上述关于递归的代码只是用于举例,这并不是一个好的代码,无限次的打印hehe会造成栈溢出导致程序卡死)
下面,我们通过一个具体的问题,来详细说明函数递归的特点,例如:
接受一个整型值(无符号),按照顺序打印它的每一位。 例如: 输入:1234,输出 1 2 3 4. |
我们先对这个题目进行简单的解析,输入1234,如果我们想要得到4,则用1234%10就可以得到,如果我们想要得到3,则我们先1234/10得到123,再用123%10就可以得到,同理,如果想得到2,则先123/10得到12,再12%10,最后再单独打印1即可。不过这样用常规方法输入会过于啰嗦,在函数递归的定义中我们得知,函数递归的主题思想就是把问题由大化小,我们可以通过这个思想,把这个问题拆分,通过上面对于题目进行的简单的解析,我们发现,相对于打印1 2 3 ,打印4会简单很多,只需要让12345\%10即可,所以,将题目拆分为,先打印(123) 4,再把(123)拆开,先打印(12) 3,再把(12)拆开,变成打印 1 2,同时可以看到,需要进行拆分时,数据都是2位数,即都是>9的,由此,我们可以通过下面这个函数递归来解决这个问题。
很难想象,通过函数的递归,原本复杂的问题所需的代码只是短短的这十几行,接下来,我们将对于这串代码如何实现目的进行详细的解读:
首先,我们通过主函数,输入1234,此时1234被传入函数中
,因为1234>9,所以进入(print/10),此时,代码不会执行printf函数。
此时print的值为123,下面经过判定,123>9,所以,再次进入print(n/10)
这是,得出来的结果等于12,再次进入print函数,
经过判定,因为12>9,所以在进入print(n/10)。这时,print(n/10) = 1
经过判定,1<9,不符合if语句的条件,于是进入下方的printf函数,打印n%10,因为此时的n = 1,所以最终打印结果为1,这时,函数在返回去向上执行每个步骤没有执行的printf函数,
这时,不再执行if语句中的内容,而是单纯的执行上面没有执行的printf函数,此时的n =12,n%10 = 2,最终打印结果为2,然后再向上返回去执行没有执行的printf
此时的n = 123,n%10 = 3,最终打印结果为3,打印结束后,再次向上返回执行没有执行的printf函数
此时的n = 1234,n%10 = 4,打印结果最终为4,由此可见,经过printf函数的四次打印,我们通过函数递归,成功的将“1234”分成:1 2 3 4,四步进行打印。
(注:关于内存如何保存1234 123 12 1,并在printf函数返回时进行四次打印时进行使用,将在后面单独写一篇文章进行介绍,这里不过解释)
通过上面一个例子,相信我们对递归的理解又加深了一点,这里我们直接总结出函数递归的两个必要条件:
存在限制条件,当满足这个限制条件的时候 递归便不再继续。 每次递归调用之后越来越接近这个限制条件。 |