ANSI C函数原型
作者:互联网
一、函数原型
1.在ANSIC标准之前,声明函数的方案有缺陷,因为只需要声明函数的类型,不用声明任何参数。下面我们看一下使用旧式的函数声明会导致什么问题。
下面是ANSI之前的函数声明,告知编译器imin()返回int类型的值:
intimin();
然而,以上函数声明并未给出imin()函数的参数个数和类型。因此,如果调用imin()时使用的参数个数不对或类型不匹配,编译器根本不会察觉出来。
2.解决办法
C标准要求在函数声明时还要声明变量
的类型,即使用函数原型(functionprototype)来声明函数的返回类型、参数的数量和每个参数的类型。未标明imax()函数有两个int类型的参数,可以使用下面两种函数原型来声明:
intimax(int,int);
intimax(inta,intb);
第1种形式使用以逗号分隔的类型列表,第2种形式在类型后面添加了变量名。注意,这里的变量名是假名,不必与函数定义的形式参数名一致。有了这些信息,编译器可以检查函数调用是否与函数原型匹配。参数的数量是否正确?参数的类型是否匹配?以imax()为例,如果两个参数都是数字,但是类型不匹配,编译器会把实际参数的类型转换成形式参数的类型。
例如,imax(3.0,5.0)会被转换成imax(3,5)
3.无参数和未指定参数
假设有下面的函数原型:
void print_name();
一个支持ANSIC的编译器会假定用户没有用函数原型来声明函数,它将不会检查参数。为了表明函数确实没有参数,应该在圆括号中使用void关键字:
void print_name(void);
支持ANSIC的编译器解释为print_name()不接受任何参数。然后在调用该函数时,编译器会检查以确保没有使用参数。一些函数接受(如,printf()和scanf())许多参数。例如对于printf(),第1个参数是字符串,但是其余参数的类型和数量都不固定。对于这种情况,ANSIC允许使用部分原型。例如,对于printf()可以使用下面的原型:
intprintf(constchar,...);
这种原型表明,第1个参数是一个字符串,可能还有其他未指定的参数。C库通过stdarg.h头文件提供了一个定义这类(形参数量不固定的)函数的标准方法。
4.函数原型的优缺点
(1)函数原型是C语言的一个强有力的工具,它让编译器捕获在使用函数时可能出现的许多错误或疏漏。如果编译器没有发现这些问题,就很难觉察出来。是否必须使用函数原型?不一定。你也可以使用旧式的函数声明(即不用声明任何形参),但是这样做的弊大于利。
(2)有一种方法可以省略函数原型却保留函数原型的优点。首先要明白,之所以使用函数原型,是为了让编译器在第1次执行到该函数之前就知道如何使用它。因此,把整个函数定义放在第1次调用该函数之前,也有相同的效果。此时,函数定义也相当于函数原型。对于较小的函数,这种用法很普
遍:
//下面这行代码既是函数定义,也是函数原型
intimax(inta,intb){returna>b?a:b;}
intmain()
{
intx,z;
...
z=imax(x,50);
...
}
二、递归函数
1.定义
(1)C允许函数调用它自己,这种调用过程称为递归(recursion)。
(2)递归由两部分构成:函数关系和出口递归有难以捉摸,有时却很方便实用。结束递归是使用递归的难点,因为如果递归代码中没有终止递归的条件测试部分,一个调用自己的函数会无限递归。可以使用循环的地方通常都可以使用递归。有时用循环解决问题比较好,但有时用递归更好。递归方案更简洁,但效率却没有循环高。
2.递归的两个必要条件
(1)存在限制条件,当满足这个条件时,递归便不再继续。
(2)每次递归调用之后越来越接近这个限制条件。
3.演示递归
/recur.c--递归演示/
include<stdio.h>
voidup_and_down(int);
intmain(void)
{
up_and_down(1);
return0;
}
voidup_and_down(intn)
{
printf("Level%d:nlocation%p\n",n,&n);//#1
if(n<4)
up_and_down(n+1);
printf("LEVEL%d:nlocation%p\n",n,&n);//#2
}
下面是在我们系统中的输出:
Level1:nlocation0x0012ff48
Level2:nlocation0x0012ff3c
Level3:nlocation0x0012ff30
Level4:nlocation0x0012ff24
LEVEL4:nlocation0x0012ff24
LEVEL3:nlocation0x0012ff30
LEVEL2:nlocation0x0012ff3c
LEVEL1:nlocation0x0012ff48
我们来仔细分析程序中的递归是如何工作的。首先,main()调用了带参数1的up_and_down()函数,执行结果是up_and_down()中的形式参数n的值是1,所以打印语句#1打印Level1。然后,由于n小于4 up_and_down()(第1级)调用实际参数为n+1(或2)的up_and_down()(第2级)。于是第2级调
用中的n的值是2,打印语句#1打印Level2。与此类似,下面两次调用打印的分别是Level3和Level4。当执行到第4级时,n的值是4,所以if测试条件为假。up_and_down()函数不再调用自己。第4级调用接着执行打印语句#2,即打印LEVEL4,因为n的值是4。此时,第4级调用结束,控制被传回它的主调函数(即第3级调用)。在第3级调用中,执行的最后一条语句是调用if语句中的第4级调用。被调函数(第4级调用)把控制返回在这个位置,因此,第3级调用继续执行后面的代码,打印语句#2打印LEVEL3。然后第3级调用结束,控制被传回
第2级调用,接着打印LEVEL2,以此类推。
注意,每级递归的变量n都属于本级递归私有。这从程序输出的地址值
可以看出(当然,不同的系统表示的地址格式不同,这里关键要注意,Level1和LEVEL1的地址相同,Level2和LEVEL2的地址相同,等等)。
如果觉得不好理解,可以假设有一条函数调用链——fun1()调用
fun2()、fun2()调用
fun3()、fun3()调用fun4()。当
fun4()结束时,控制传回
fun3();当fun3()结束时,控制传回
fun2();当fun2()结束时,控制传回
fun1()。递归的情况与此类似,只不过fun1()、fun2()、fun3()和fun4()都是相同的函数。
4.递归原理
第1,每级函数调用都有自己的变量。也就是说,第1级的n和第2级的n不同,所以程序创建了4个单独的变量,每个变量名都是n,但是它们的值各不相同。当程序最终返回up_and_down()的第1级调用时,最初的n仍然是它的初值1
第2,每次函数调用都会返回一次。当函数执行完毕后,控制权将被传回上一级递归。程序必须按顺序逐级返回递归,从某级up_and_down()返回上一级的up_and_down(),不能跳级回到main()中的第1级调用。
第3,递归函数中位于递归调用之前的语句,均按被调函数的顺序执
行。例如,程序清单9.6中的打印语句#1位于递归调用之前,它按照递归的顺序:第1级、第2级、第3级和第4级,被执行了4次。
第4,递归函数中位于递归调用之后的语句,均按被调函数相反的顺序执行。例如,打印语句#2位于递归调用之后,其执行的顺序是第4级、第3级、第2级、第1级。递归调用的这种特性在解决涉及相反顺序的编程问题时很有用。稍后将介绍一个这样的例子。
第5,虽然每级递归都有自己的变量,但是并没有拷贝函数的代码。程序按顺序执行函数中的代码,而递归调用就相当于又从头开始执行函数的代码。除了为每次递归调用创建变量外,递归调用非常类似于一个循环语句。实际上,递归有时可用循环来代替,循环有时也能用递归来代替。最后,递归函数必须包含能让递归调用停止的语句。通常,递归函数都使用if或其他等价的测试条件在函数形参等于某特定值时终止递归。为此,
每次递归调用的形参都要使用不同的值。例如,程序up_and_down(n)调用up_and_down(n+1)。最终,实际参数等于4时,if的测试条件(n<4)为假。
5.递归的本质
在传递的过程将问题化简 归一的过程将化简的问题解决
6.递归的应用
(1).问题的定义是按递归定义的(Fibonacci函数,阶乘,…);
(2)问题的解法是递归的(有些问题只能使用递归方法来解决,例如,汉诺塔问题,…);
(3).数据结构是递归的(链表、树等的操作,包括树的遍历,树的深度,…)
最简单的递归形式是把递归调用置于函数的末尾,即正好在return语句
之前。这种形式的递归被称为尾递归(tailrecursion),因为递归调用在函
数的末尾。尾递归是最简单的递归形式,因为它相当于循环。
下面要介绍的程序示例中,分别用循环和尾递归计算阶乘。一个正整数
的阶乘(factorial)是从1到该整数的所有整数的乘积。例如,3的阶乘(写
作3!)是1×2×3。另外,0!等于1,负数没有阶乘。程序清单9.7中,第1个
函数使用for循环计算阶乘,第2个函数使用递归计算阶乘。
递归实战
(1)阶乘
阶乘递归解法
define _CRT_SECURE_NO_WARNINGS
include<stdio.h>
int factorial(int x) // 递归体
{
if (x == 1)// 函数出口
{
return 1;
}
return x * (factorial(x - 1));//就x!转换成X*((x-1)!) 达到传递化简的目的
}
int main()
{
int i = 0;
scanf("%d", & i);
int k = factorial(i);
printf("%d", k);
return 0;
}
(2)斐波拉契数列
斐波拉契数列 即0、1、1、2、3、5、8、13、21、34、………这样一串数字
斐波拉契数列递归解法
递归解法通过数学函数定义可轻松得到 他的递归体是当n>1时 fib(n-2)+fib(n-1)
递归出口就是n=0 0,n=1,返回1
define _CRT_SECURE_NO_WARNINGS
include<stdio.h>
int fib(int x) //第0个元素为0 第一个元素为1
{
if (x == 0)
{
return 0;
}
else if (x == 1) //出口
{
return 1;
}
else
return fib(x - 2) + fib(x - 1); //循环体
}
int main()
{
int k = 0;
scanf("%d", &k);
int sum = fib(k);
printf("%d", sum);
return 0;
}
标签:调用,函数,递归,int,down,ANSI,原型,参数 来源: https://www.cnblogs.com/mz77/p/15857460.html