请问:函数可以被其他函数直接或间接的调用,那它可不可以被自己调用?
答:可以!这样的函数叫做“递归函数”。
递归函数
递归函数(Recursive Function):即自调用函数,即在函数体内有直接或间接的自己调用自己的语句。
递归函数很特殊,它可能因为自己直接或间接的调用自己,很容易造成死循环。因此我们使用递归函数前,要先写出递归调用的结束条件!递归函数会一直递归调用到结束条件,才开始进行真正的计算,返回回去才得到最终结果。
例1:求n的阶乘。
long fact(int n) { if(n==0) return 1; // 递归结束条件 else return(n*fact(n-1)); }
有递归性质的问题,我们可以用递归函数进行描述,函数简洁易读性强。程序调用时是通过参数和返回值,进行函数递归调用的数据传递的,就涉及到栈的使用、内存的分配和释放、数据的拷贝等,因此效率很低,时间空间消耗较大,因此我们常用非递归函数代替。大多数递归函数都能用非递归函数代替。
例2:求两个整数a, b的最大公约数。
算法:
(a, b)的最大公约数也是(b, a%b)的最大公约数。
证明:
我们将a表示为a=kb+r,那么r=a%b,即r是a除以b的余数。
(1)若D是a和b的公约数,那么D可以整除a和b,而r=a-kb,因此D也可以整除r,所以D也是r的公约数,所以D也是(b, r)的公约数,即D是(b, a%b)的公约数。
(2)若D是(b, a%b)的公约数,则D%b=D%r=0,因为a=kb+r,所以D%a=0,所以D也是(a, b)的公约数。
综上,(a, b)的公约数和(b, a%b)的公约数一样,最大公约数也一样。
递推关系的结束条件:a%b=0。
/* 求a和b的最大公约数 */ long gcd1(int a, int b) // 递归程序 { if(a%b==0) return b; else return gcd1(b,a%b); // 尾递归 } long gcd2(int a,int b) // 非递归程序 { int temp; while(b!=0) // 递归结束条件 { temp=a%b; // a存储原来的b的值,b存放a%b并且进行判断。 a=b; b=temp; } return a; }
递归在语句的最后,这种递归叫做“尾递归”,尾递归可以轻松的转换为非递归函数(通过循环语句实现)。只要没有达到结束条件,就会一直执行。
疑问:若a<b???
注意:
(1)使用递归函数的目的是简化程序设计,提高程序的可读性,但增加了系统的开销。
(2)自调用过程在函数内必须设置某些条件(结束条件),当条件成立时终止自调用过程,并使程序控制逐步从函数中返回。
(3)递归调用机制是通过栈数据结构实现的。
(4)函数值间由参数传递和返回值相联系。
例3:变量的作用域与存储类型
/* 变量的作用域 */ #include <stdio.h> #include<stdlib.h> void func() { int a=5; } void main() { int a=10; func(); printf("a=%d\n",a); system("pause"); //return 0; }
请问:为什么都是变量a,为什么func函数的变量不会对main函数中的a造成影响?
答:变量有生存期和作用域。
变量的作用域与存储类型
- 变量的生存期:是从变量分配到内存开始到从内存中删除变量空间结束这段时间,即变量的存在时间。
- 变量的作用域:也称为可见性,指变量能够被访问的范围。
- 各种变量的生存期和作用域:
(1)全局变量:生存期和程序的生存期一致。也就是程序运行就为全局变量分配空间,程序结束才会释放全局变量的空间。全局变量的作用域从定义的地方开始到所在文件的结束都可见。
(2)局部变量:通常指动态局部变量(如函数的形参或者函数内的内部变量),生存期和函数一致,函数调用就为变量分配空间,函数结束就释放变量内存空间。作用域是从定义的位置开始,到func的右大括号结束。
/* 变量的作用域 */ #include <stdio.h> #include<stdlib.h> int a=-1; // 全局变量 void func() { int a=5; // 局部变量 } void main() { int a=10; // 局部变量 func(); // 在main函数中调用func函数时,在这个func中是看不到main中的a的 // func调用结束之后,它的变量a的空间被释放,不可见,因此看到的是main中的a printf("a=%d\n",a); system("pause"); //return 0; }
心得:
如果在函数内部有同名变量,同名的全局变量就会被函数内部同名的局部变量覆盖,其作用域也被覆盖,出了被同名变量覆盖的地方这个全局变量才又可见。
复合语句中的动态局部变量,生存期与作用域都是在复合运行,变量定义开始,到复合语句右大括号结束。
- 变量的作用域:指在源程序中定义变量的位置及其能被读写访问的范围。分为:局部变量和全局变量。
1. 局部变量:在语句块内定义的变量(形参也是局部变量)
特点:
(1)生存期是该语句块,进入语句块时获得内存,仅能由语句块内语句访问,退出语句块时释放内存,不再有效。
(2)定义时不会自动初始化,除非程序员指定初值。
(3)并列语句块各自定义的同名变量互不干扰。(形参和实参可以同名)
疑问:为什么形参和实参同名会出错???
2. 全局变量:在所有函数之外定义的变量。生存期是整个程序,从程序运行起占据内存,程序运行过程中可随时访问,程序退出时释放内存。有效范围是从定义变量的位置开始到本程序结束。
- 变量的存储类型:指数据在内存中存储的方式。即编译器为变量分配内存的方式,它决定变量的生存期。
- 调用形式:存储类型 数据类型 变量名
- C程序的存储类别(Storage Class):
auto型(自动变量)
static型(静态变量)
extern型(外部变量)
register型(寄存器变量)
静态和动态数据都存储在RAM存储区,寄存器是在CPU中的。
静态存储区的变量:和程序共存亡。
动态存储区的变量:和所在的函数共存亡。
寄存器中的变量:同动态存储区,和所在函数共存亡。
1. 自动变量定义:auto 数据类型 变量名(就是在数据类型前面加上auto关键字)
(1) 进入语句块时自动申请内存,退出时自动释放内存。
(2)动态局部变量,缺省的存储类型。(如果没有指明变量类型,那么就是默认缺省的auto)
2. 静态变量定义:static 数据类型 变量名
(1)存储期同动态局部变量
(2)生存期为整个程序运行期间
(3)作用域同动态局部变量,是在函数内部可见。
例4:利用静态变量计算整数n的阶乘n!
/* 利用静态变量求n! */ #include <stdio.h> #include<stdlib.h> long func(int n); int main() { int i,n; printf("Please input n:\n"); scanf_s("%d",&n); for(i=1;i<=n;i++) { printf("%d!=%ld\n",i,func(i)); } system("pause"); return 0; } long func(int n) { static long p=1; // 定义静态变量 p=p*n; return p; }
/* 修改为动态变量 */ #include <stdio.h> #include<stdlib.h> long func(int n); int main() { int i,n; printf("Please input n:\n"); scanf_s("%d",&n); for(i=1;i<=n;i++) { printf("%d!=%ld\n",i,func(i)); } system("pause"); return 0; } long func(int n) { auto long p=1; // 定义静态变量 p=p*n; return p; }
结果错误!
心得:若静态局部变量和全局变量自动初始化为0,自动变量不初始化时,值是随机值,因此我们必须明确初始化。静态变量仅初始化一次,变量的值可保存到下次进入函数,使函数具有记忆功能。而自动变量是每次调用函数时都会初始化(每次进来都重新分配空间),没有记忆功能。虽然静态局部变量作用域是函数,即只在函数内部可见,但它的生存期是整个程序。
- 寄存器:是CPU内部容量有限,但速度极快的存储器。
3. 寄存器变量定义:register 类型名 变量名
注意:
(1)使用频率比较高的变量声明为register,可使程序更小,执行速度更快。
(2)现在编译器有能力自动把普通变量优化为寄存器变量,并且可以忽略用户的指定。
(3)所以一般无需特变声明变量为register。
小结:
(1)全局变量:生存期是从出生到结束,作用域是整个程序。
(2)局部变量:其中静态局部变量生存期是整个程序,作用域是函数内。其它的局部变量,如自动变量、寄存器变量的生存期和作用域都是函数内(即函数内可见)。
(3)递归函数要先写出结束条件。