函数的嵌套调用,链式访问。
函数可以嵌套调用,但是不能嵌套定义,就是在一个函数内部不能再次定义函数。但是在一个函数内部可以调用别的函数。
嵌套调用:就是在一个函数内部调用别的函数。
链式访问:把一个函数的返回值作为另一个函数的参数。
下图就是链式访问,把strlen的返回值作为printf的参数。
//printf的返回值是打印的字符数,(注意在%d后面加上\n,空格等都属于字符),如果出现错误则返回负数
函数的声明和定义
函数的声明:
1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
3. 函数的声明一般要放在头文件中的。
例如
如果函数的定义在调用的后面,这里会爆出一个警告说函数Add未定义,例如图二,这个时候可以在函数调用之前声明一下例如图二,形参的名字可以省略,也可以不省,就是图二的a,b可以省略,写成int Add(int,int);
函数的定义:函数的定义是指函数的具体实现,交待函数的功能实现。
注意:函数的的声明一般用不到,在实际中写代码的时候,很少这样使用。如果只有一个函数的话直接定义在前面不就可以了吗?不用声明。在企业中一般会创建加法模块---创建一个Add.c和Add.h把这两个合起来叫加法模块。
在Add.c(源文件2)里面放入函数的定义,在Add.h(头文件)里面放入函数的声明。在使用的时候直接引用头文件 #include"Add.h" 就可以在本来的源文件里面使用。
如果不愿意暴露函数定义代码就可以将Add.c编译成一个静态库。就是再开一个项目创建Add.h和Add.c的两个文件然后,
点击项目名称右击属性,常规-配置类型-静态库.lib-生成解决方案。然后在那个项目文件的Debug下面就会生成一个Add.lib的文件。打开Add.lib文件是二进制的信息看不懂的。这个时候就可以将Add.h和Add.lib这俩文件给别人用,别人将这俩个文件加入项目文件就可以使用这个函数。这个时候就可以实现,声明暴露给你但是函数的实现不给你看的情况。就和库函数十分相似了。
函数递归(递推和回归)
程序调用自身的编程技巧称为递归。
递归作为一种算法在程序设计语言中广泛应用。一个过程或者函数在其定义或者说明中有直接或间接的调用自身的一种方法,他通常把一个大型的复杂问题层层转化为一个与原函数相似的规模较小的问题来求解,递归策略,只需要少量的程序就可以描述出解题过程所需的多次重复计算,大大地减少了程序的代码量。
主要的思考方式在于:把大事化小。
每一次递归都是完整的独立的函数调用。
举一个最简单的递归:
虽然这里是个错误(栈溢出)但是这个确实是一个函数的递归。
举例:接受一个整型值按照顺序打印它的每一位。输入1234,输出 1 2 3 4
这个就是print()函数的递归。n>9就是递归跳出的条件,不然会出现无线递推就无法回归了
递归的两个必要条件(如果没有递归代码肯定是错的)
存在限制条件,当满足这个限制条件的时候,递归便不再继续了。
每次递归调用后越来越接近这个限制条件。
举例:
1.编写函数允许创建临时变量,求字符串长度。
2.编写函数不允许创建临时变量,求字符串长度。
递归与迭代
递归有时候可以写成迭代,迭代有时候可以写成递归。
1.求n的阶乘。
2..求第n个斐波那契数。(不考虑溢出)
斐波那契数列:前两个数相加等于第三个数。fib(n) = fib(n-1) + fib(n-2)
这个很明显第二种非递归的效率更高一些。运算较少运算很快。
在写完递归的时候,没有明显的缺陷我们这个时候用递归来完成,否则一定要有非递归来完成。
提示:
许多问题是以递归的形式进行解释的,这只是因为他比非递归的形式更为清晰
但是这些问题的迭代实现往往比递归实现的效率更高,虽然代码可读性变差了
一个复杂问题不能用迭代来实现的时候,此时递归实现的简洁性便可以补偿它所带来的运行开销。
函数递归的经典例题:
1.汉诺塔问题。
2.青蛙跳台阶。假设有n个台阶一个青蛙一次跳一个或者两个台阶,一共有多少可能。