函数的递归
#include <stdio.h>
void diguifunc()
{
printf("diguifunc()函数执行\n");
diguifunc();//自己调用自己
}
void main(){
diguifunc();
}
把程序执行起来,等几秒钟,可以看到,屏幕不断滚动并输出如下内容:
diguifunc()函数执行
diguifunc()函数执行
继续等待一会儿(几秒)后,有的Visual Studio编译环境版本直接出现程序报错,弹出异常对话框,有的Visual Studio编译环境版本直接退出了整个程序的执行,等等,各种不正常的现象都会发生,但总归就是一句话,程序执行不正常,出现了各种问题。报错也好,执行崩溃或者程序退出也罢,根本原因是系统的资源(内存)耗尽了,这是因为不断无限次地调用函数自身所导致。调用函数是要占内存的,每多调用一次函数,系统的内存就要多占用一些,当函数调用完成,从函数中返回时,调用该函数时所占用的内存才能被系统释放掉。
以图7.2为例,如果是在第3步定义一个变量,那么这个变量所占用的内存需要到第11步才能被释放,这也说明了,函数嵌套调用的层次越深,所需要占用的系统内存就越大。
对于函数嵌套调用来讲,系统会给函数调用分配一些内存来保存这些信息(局部变量、函数参数、函数调用关系等),但分配的内存大小是固定和有限的,一旦超过这个内存大小,程序执行就会出现上述崩溃或者异常退出的情况。
本节开头做了一个函数递归调用(自己调用自己)的代码演示,针对这个调用,可以绘制一个比较形象的图看看函数调用关系,如图7.3所示。
这个例子导致函数自己不断地调用自己(递归调用),造成了调用的死循环。所以递归调用这种自己调用自己的方式必须要有一个出口,这个出口也叫作递归结束条件,有了这个递归结束条件,就能够让这种函数调用结束,可以用图7.4做一个形象一点的说明。
总结一下图7.4:递归调用就是一个函数在它的函数体内部调用它自身。执行递归函数将反复调用其自身,每调用一次就进入新的一层,递归函数必须有结束条件(递归调用的出口),从而引出下一个话题:递归调用的出口。
用递归实现阶乘:
#include <stdio.h>
int dg_jiecheng(int n)
{
int result; //保存阶乘结果
if(n==1)
{
result=1; //这里就是该递归调用函数的出口
}
else
{
result=dg_jiecheng(n-1)*n;//递推关系,这个数与上一个数之间的关系
return result;
}
}
void main(){
printf("5的阶乘结果是%d",dg_jiecheng(5)); //5的阶乘结果是120
}
递归的缺点,可以用三条来总结。
(1)虽然代码简洁,代码也精妙,但代码理解起来比较有难度。
(2)如果调用层次太深,调用栈(保存函数调用关系等需要用到的内存)可能会溢出,如果真出现这种情况,那么说明不能用递归调用解决该问题。例如,可以自己演示一下计算50000的阶乘,堆栈会溢出,结果也会溢出,但结果溢出与否不重要,关注的重点是堆栈的溢出,也就是内存装不下这么多层调用了,此时,程序执行并等待几秒钟后,程序要么报告异常,要么无征兆地退出。总之一句话:程序运行不正常。
(3)效率和性能都不算高。这么深层次的函数调用,调用中间要保存的内容也很多,所以效率和性能肯定高不起来。
局部变量和全局变量
在函数内定义的变量叫局部变量。那么在函数外定义的变量就称为全局变量(也叫外部变量)。全局变量可以为本源程序文件中其他函数所共用(全局变量还可以被其他源程序文件所用,后续会讲解),它的有效范围从定义该变量的位置开始到本源程序文件结束为止,如果在整个源程序文件开头定义该变量,则整个文件范围内都可以使用该全局变量。
全局变量说明
(1)如果某个函数想引用在它后面定义的全局变量,可以使用关键字extern做一个“外部变量说明”,表示该变量在函数的外部定义,这样在函数内就能使用,否则编译就会出错,但有一点要注意,全局变量在定义的时候是可以给初值的,但是在做外部变量说明时,是不可以给变量初值的。看看如下范例:
#include <stdio.h>
extern int c1,c2; //外部变量说明,不可以写成extern int c1=0,c2=1这样
void lookvalue() //一个自定义函数
{
c1=5; //因为前面用了extern作外部变量说明,所以这里可以用c1和c2
c2=8;
return;
}
int c1,c2; //这里才是全局变量定义的地方
int main() //主函数
{
lookvalue();
printf("c1 = %d\n",c1); //c1=5
printf("c2 = %d\n",c2); //c2=8
printf("断点停在这"); //可以在这里设置断点跟踪调试查看全局变量的值
return 0;
}
所以,容易想到,如果全局变量的定义放在引用它的所有函数之前,就可以避免使用关键字extern做外部变量说明了。
(2)严格区分全局变量(外部变量)定义和外部变量说明。全局变量定义只能有一次,位置是在所有函数之外,定义时会分配内存,定义时可以初始化该全局变量的值。
而在同一个文件中,外部变量说明是可以有很多次的(不分配内存)。在上面的范例中,是把外部变量说明放在文件最上面,所有函数之外。其实在每个函数内部做外部变量说明也是可以的(不过一般极少看到有人这样做),所以这更进一步加深了对外部变量说明的理解:所声明的变量是已在外部定义过的变量,仅仅是引用该变量而做的“声明”。看看如下范例:
#include <stdio.h>
void lookvalue(int a)
{
extern int c1,c2; //外部变量说明
c1=5;
c2=8;
return;
}
void lookvalue2(){
extern int c1,c2; //外部变量说明
c1=51;
c2=81;
return;
}
int c1,c2; //全局变量(外部变量)定义
int main() //主函数
{
int q = 1;
lookvalue(q);
printf("c1 = %d\n",c1); //c1=5
printf("c2 = %d\n",c2); //c2=8
lookvalue2();
printf("c1 = %d\n",c1); //c1=51
printf("c2 = %d\n",c2); //c2=81
printf("断点停在这");
return 0;
}
(3)在同一个源文件中,如果全局变量和局部变量同名,则在局部变量作用范围(作用域)内,全局变量不起作用,如果给局部变量赋值,当然也不会影响全局变量的值。看看如下范例:
#include <stdio.h>
int a=4,b=5; //全局变量定义
void lookvalue(int a,int b)
{
a=123;
b=456; //局部变量b作用范围内,全局变量b不起作用
}
int main() //主函数
{
int i=2, j=5;
lookvalue(i,j);
printf("a =%d\n",a); //a=4
printf("b= %d\n",b); //b=5
return 0;
}
(4)针对一个项目中包含多个源程序文件的情形(后面会详细讲解如何在一个项目中包含多个源程序文件),如果在一个源程序文件中定义的全局变量想在该项目的其他源程序文件中使用,则只需要在其他的源程序文件中使用上面介绍的extern关键字做外部变量说明,就可以在其他源程序文件中使用该全局变量了。
例如,在MyProject.cpp中定义了一个全局变量:
假设要在MyProject2.cpp(后面会讲一个新的.cpp源程序文件如何加入到当前项目中来)中的func1函数内使用MyProject.cpp中定义的全局变量a,则首先在MyProject2.cpp的开头使用关键字extern对全局变量a做外部变量说明(表示这个变量在其他文件中已经定义了),然后就可以开始使用了。MyProject2.cpp的代码如下:
局部变量的存储方式
1.传统情形函数中的局部变量,一般来说,都是动态分配存储空间,也就是说,存储在动态存储区中。对这些变量分配和释放存储空间由系统自动处理:函数被调用时分配存储空间,函数执行完成后自动释放其所占用的存储空间。
2.特殊情形有时希望函数中局部变量的值在函数调用结束后不消失(不被系统自动释放)而保留原值,也就是说,它占用的存储单元不释放,在下一次调用该函数时,该变量中保存的值就是上一次该函数调用结束时的值,这是可以做到的,只需指定该局部变量为“局部静态变量”,用static关键字加以说明即可。看看如下范例,先看看传统的函数内的局部变量输出求值结果:
#include <stdio.h>
void funcTest() //自定义函数
{
int c = 4; //如果把断点设置到这行,调试执行会注意到断点确实能够停留在该行
printf("c=%d\n",c);
c++;
return;
}
int main() //主函数main中调用三次上述的自定义函数
{
funcTest() ;
funcTest() ;
funcTest() ;
return 0;
}
执行上面的代码可以看到,程序连续输出三次相同的结果如下:
c=4
c=4
c=4
现在,修改上述的自定义函数funcTest(),修改“intc=4;”这行的内容,其他内容不变。修改后的内容如下:
static int c = 4;
再次执行上面的代码可以看到,程序三次输出的结果发生了改变,如下:
c=4
c=5
c=6
通过分析执行的结果,不难发现,定义一个变量时,在前面加上static(局部静态变量说明),变量的表现就不同了。具体有如下几点不同:
· 在静态存储区(见图7.12)中分配存储单元,程序整个运行期间都不释放。· 局部静态变量是在编译时赋初值的,只赋初值一次,在程序运行的时候它已经有了初值,以后每次调用函数时不再重新赋初值,只是保留上次函数调用结束时的值,而普通变量的定义和赋值是在函数调用时才进行的。
· 定义局部静态变量时如果不赋初值,则编译时自动给其赋初值0,而常规变量,如果不赋初值,则它是一个不确定的值。
· 虽然局部静态变量在函数调用结束后仍然存在,但在其他函数中是不能引用的。
· 局部静态变量长期占用内存,降低了程序可读性(当多次调用该函数时往往弄不清当前该静态变量的值是多少)。
内部函数和外部函数
1.内部函数
只能被本文件中其他函数调用。定义内部函数时,在最前面加static关键字即可,内部函数又称为静态函数,使用内部函数,可以使函数只局限于其定义所在的源程序文件中,所以不同源程序文件中的同名函数彼此不受干扰,试想,如果分工不同的两个人编写两个不同的.cpp源程序文件,如果他们所写的函数同名,则在编译链接时会报错,如果用了static修饰这些函数,那么即使所起的函数名相同,也互相不受影响(因为这些函数被限制在当前定义所在的源文件中)。
2.外部函数
定义一个函数时,如果在其前面不使用static关键字修饰,它就是外部函数。
在需要调用此函数的其他源程序文件中,只需要增加该函数的函数声明即可。