1、2019/12/2,1,5 函数,5.1 函数概述 5.2 函数基本用法 5.3 函数应用举例 5.4 数组名作函数参数 5.5 递归函数 5.6 变量作用域与编译预处理,2019/12/2,2,思考:输入n个整数到一维数组中,然后完成以下任务: (1)输出数组元素; (2)把数组就地逆置后后输出; (3)把数组升序排列并输出。 提示:输出数组元素在(1)(3)中都有要求,每次输出都写一遍输出语句不是不可以,但代码冗余大,有没有更好的方法?思考并讨论:输入2个整数,求它们各自逆序之后的和的逆序数。如输入123,456;求321+654=975;975的逆序数579为所求结果。,2019/12/
2、2,3,5.1 函数概述,把相关语句组织在一起,并给它们注明相应的名称,利用这种方法把程序分块,这种形式的组合就称为函数。 从用户的角度分函数分为库函数和用户自定义函数。库函数有abs、fabs、ceil、floor、rand等,使用时须包含相应头文件。我们主要介绍用户自定义函数。 一个大的程序一般分为若干个程序模块,每个模块用来实现一个特定的功能,每个模块一般由一个函数定义来实现。,2019/12/2,4,一个C/C+程序由一个main函数及若干个其它函数构成。程序从main 函数中开始执行,在main函数中结束。 函数的作用是通过函数调用实现的。由主函数调用其它函数,其它函数可以相互调用。
3、同一函数可以被一个或几个函数调用任意次。主函数由操作系统调用。 例如,main函数调用f1、f2函数,f1函数调用f3函数的函数的调用示意图如下:,2019/12/2,5,5.2 函数基本用法,5.2.1 函数的定义 任何函数定义都是由函数头和函数体两部分组成。一般形式如下:函数类型 函数名(形参列表) 函数体 函数类型可以是各种基本数据类型、指针类型、结构体类型、void(指定函数不返回值)等。 函数名必须是合法的标识符。,2019/12/2,6,函数定义中的参数为形式参数,简称形参。形参列表的每个参数包括参数类型和参数名,可以带默认值;形参列表可以没有,此时为无参函数;形参列表若有多个参数
4、,则以逗号分开。 函数可分为有返回值函数和无返回值函数两种。函数的返回值是通过函数中的return语句来获得的。return语句的一般格式: return 返回值表达式 ; / 表示可选 返回值表达式的类型一般应与返回类型一致,否则以返回类型为准。 return语句后带返回值表达式时控制程序流程返回调用点的同时带回一个值,语句“return;”控制程序流程返回到调用点。,2019/12/2,7,下面是若干函数定义的例子: void print()/无参函数,也没有返回值 printf(“hellon“); int max(int a, int b)/求两个整数的最大值 if (a=b) ret
5、urn a;else return b; ,2019/12/2,8,5.2.2 函数的声明 函数调用之前必须先进行函数声明,形式为:“函数类型 函数名(形参列表);”, 函数声明中形参名可以省略; 若函数定义在函数调用之前,则定义时的函数头可以充当函数声明,此时,可以不必进行函数声明。 例如,下面是函数声明的例子: int max(int a, int b); int max(int, int);/省略形参名,2019/12/2,9,5.2.3 函数的调用 函数的调用的形式一般如下:变量=函数名(实际参数表); void返回类型的函数只能以语句形式调用,其它返回类型的函数一般以表达式形式调用。
6、 调用时的参数称为实际参数,简称实参,一般不需要指定数据类型,除非是进行强制类型转换。 参数的类型、顺序、个数一般须与函数定义中的一致。 函数调用时,把实参依序传递给形参,然后执行函数定义体中的语句,执行到函数结束或return语句时,程序流程返回到调用点。,2019/12/2,10,例如调用上面定义的函数的方法如下: print();/void返回类型的函数以语句形式调用 有返回值的函数一般以表达式形式调用: int t=max(123,99); printf(“%dn“,max(1, 2);,2019/12/2,11,5.3 函数应用举例,例1:逆序数的加法:输入两个正整数,先将它们分别倒
7、过来,然后再相加,最后再将结果倒过来输出。注意:前置的零将被忽略。例如,输入305和794。倒过来相加得到1000,输出时只要输出1就可以了。因为得到逆序数的方法是一样的,故编写一个求逆序数的函数,调用3次即可完成2个输入的整数和1个结果整数的逆序。 思考:n=1234,如何逆置? 提示:考虑(4*10+3)*10+2)*10+1=4321。,2019/12/2,12,#include /构成逆序数的函数 int reverseNum(int x) / x 是正整数 int r=0;while(x0)r = r*10 + x%10; x = x/10;return r; ,2019/12/2,
8、13,int main() int a,b,c;scanf(“%d%d“, ,2019/12/2,14,例2:素数判定素数是除了1与本身之外没有其它因子的自然数(1不是素数),可以考虑从2n-1判断是否有n的因子,若有,则n不是素数。可以仅在2 sqrt(n)的范围内判断是否有因子。因为若n不是素数,则n=i*j(i=sqrt(n)),故只需判断2sqrt(n)范围内是否有因子。代码如下:#include#include /使用系统函数sqrt求开方须包含此头文件,2019/12/2,15,int isPrime(int n)/n为素数返回1,否则返回0 int i, flag=1;/一开始假
9、设n为素数double limit=sqrt(1.0*n);/参数须为double类型for(i=2; i=limit; i+)if(n%i=0) /有因子,则可以判断为不是素数flag=0;break;if (n=1) flag=0;return flag; ,2019/12/2,16,int main() int i, n, T;scanf(“%d“, ,2019/12/2,17,例3:最小回文数输入整数n,输出比该数大的最小回文数。其中回文数指的是正读、反读一样的数,如131,1221等。故可以调用例1中的求逆序数的函数reverseNum再判断该数与逆序数是否相等来判断是否回文数。,i
10、nt isSymmetric(int n) /n为回文数返回1,否返回0 if(n=reverseNum(n)/回文数的判断,调用reverseNumreturn 1;elsereturn 0; ,2019/12/2,18,#include int main() int i, n, T;scanf(“%d“, ,2019/12/2,19,5.4 数组名作函数参数,数组元素作函数实际参数时与普通的简单变量相同。以数组元素调用例1中定义的函数reverseNum的代码如下: #includeint main() int i,a5=1234,56789,34567,43663464,46563432
11、; for(i=0; i5; i+) ai=reverseNum(ai);/数组元素作为函数实参for(i=0; i10; i+) printf(“%d“, ai);/数组元素作为函数实参return 0; ,2019/12/2,20,例5:有一个一维数组a,内放10个整数,分别输出这十个数的平方。 #include int square(int n) return n*n; int main() int a10, i, j;for(i=0;i10;i+) scanf(“%d“, ,2019/12/2,21,数组名作函数的参数时,传递的是数组的首地址(数组名代表数组的首地址),参数传递之后,形
12、参数组与实参数组同占一段内存单元,对形参数组的改变就是对实参数组的改变。 例6:有一个一维数组score,内放10个学生成绩,对成绩作如下处理。若成绩s=36, s=sqrt(q)*10; 否则, s=sqrt(q)*11。程序如下: #include #includevoid process(double array , int n)/数组名作函数形参 int i;for(i=0; i=36) arrayi=sqrt( arrayi )*10;else arrayi=sqrt( arrayi )*11; ,2019/12/2,22,int main() double score10;for(
13、int i=0; i0) printf(“ “);printf(“%7.2lf“,scorej);return 0; ,2019/12/2,23,说明: (1)用数组名作函数参数,应该在主调函数和被调用函数分别定义数组,例6中array是形参数组名,score是实参数组名,在其所在函数中分别定义。 (2)实参数组与形参数组类型应一致(本例都为double型),如不一致,将出错。 (3)由于将实参数组的首地址传给形参数组,因此,scorei和arrayi代表的是同一单元。 (4)string类形的形参的改变不会影响实参,除非使用引用参数。例7:输入n个数据,利用选择排序进行编程,输出第k轮排序后
14、的数列状况。选择排序的思想、方法在前面的章节中已经讨论过,这里我们把选择排序写成函数的形式。,2019/12/2,24,#include #define N 100 /n个数,进行m趟排序 void selectSort(int a, int n, int m) int i, j, k, t;for(i=0;iaj) k=j;if (k!=i) t=ak; ak=ai; ai=t; ,2019/12/2,25,/输出n个数据,每个数据之间一个空格 void prt(int a, int n) for(int i=0;i0) printf(“ “);printf(“%d“, ai);printf
15、(“n“); ,2019/12/2,26,int main() int aN, n, k, i; scanf(“%d%d“, ,2019/12/2,27,5.5 递归函数,递归函数:函数调用了自身(直接递归),或者是一个函数调用了另一个函数,而另一个函数却调用了该函数(间接递归)。这里只讨论直接递归。 递归的本质:将大问题的求解转化为较小问题的求解。持续不断的分解过程最终将问题化简成一个最基本的形式(该形式是一个已知解)。 例8:用递归法求N! int f(int n) /N!增长速度很快,注意越界问题。 if (n=1 | n=0) return 1;else return f(n-1) *
16、 n; ,N!=1;N=0,N=1 N!=N * (N-1)!;N1,2019/12/2,28,调用f(5)的过程如上所示。 递归分为扩展阶段和回代阶段,先扩展到递归出口(n=1 | n=0)求出结果为1,然后逐步返回结果到各调用点,最终f(5)的调用结果为120。,2019/12/2,29,例9:求两个整数的最大公约数两个整数的最大公约数是能够同时整除它们的最大的正整数。求最大公约数可以采用辗转相除法。辗转相除法,又名欧几里德算法(Euclidean algorithm),是求两个正整数之最大公因子的算法。利用辗转相除法确定两个正整数a和b的最大公因子的算法思想是:令r是ab的余数,则gcd
17、(a,b) = gcd(b,r),至r为0为止。例如:a=63,b=36,则:a b r63 36 2736 27 927 9 0,2019/12/2,30,int gcd(int a, int b) /求最大公约数的递归版函数 if(a%b=0) return b;return gcd(b, a%b); int gcd1(int a, int b) /求最大公约数的迭代版函数 while(b!=0)int t = a % b; a = b; b = t;return a; ,2019/12/2,31,思考1:求两个整数的最小公倍数?提示:其中一个先除以最大公约数,再乘以另一个数。 思考2:如
18、何求多个整数的最大公约数/最小公倍数?提示:使用循环。例9:斐波那契 (Fibonacci) 数列斐波那契(Leonardo Fibonacci,约1170约1250),意大利数学家,是12、13世纪欧洲数学界的代表人物。他的一个“兔子问题”引起了后人的极大兴趣。题目假定一对大兔子每一个月可以生一对小兔子,而小兔子出生后两个月就有生殖能力,问从一对小兔子开始,n个月后能繁殖成多少对兔子?写出数列:1,1,2,3,5,8,13,21,发现规律:从第3项起,每一项都是前两项的和。,2019/12/2,32,时间(月) 初生兔子(对) 成熟兔子(对) 兔子总数(对) 1 1 0 1 2 0 1 1
19、3 1 1 2 4 1 2 3 5 2 3 5 6 3 5 8 7 5 8 13 8 8 13 21 9 13 21 34 10 21 34 55,2019/12/2,33,递归式: f1=1 (n=1) f2=1 (n=2) fn=fn-1+fn-2 (n3)/求解斐波那契数列(递归法) int fib(int n) / n=0 if(n=2) return 1;else return fib(n-1)+fib(n-2); ,2019/12/2,34,/求解斐波那契数列(迭代法,非递归) int fib1(int n)/ n=0 int i, f1=1, f2=1, fib;if(n=2)
20、return 1;for(i=3; i=n; i+)fib=f1+f2;f1=f2;f2=fib;return fib; ,2019/12/2,35,int main() int n;scanf(“%d“, 思考:有一头母牛,它每年年初生一头小母牛。每头小母牛从第四个年头开始,每年年初也生一头小母牛。请编程实现在第n年的时候,共有多少头母?提示:数列为1,2,3,4,6,9,13,19,28,2019/12/2,36,例10:Hanoi塔问题,设A、B、C是三个塔座。开始时,在塔座A上有一叠共n个圆盘,这些圆盘自下而上,由大到小地叠在一起,如上图所示。现在要求将塔座A上的这一叠圆盘借助塔座B移
21、到塔座C上,并仍按同样顺序叠置。在移圆盘时应遵守以下移动规则: 规则1:每次只能移动一个圆盘; 规则2:任何时刻都不许将较大的圆盘压在较小的圆盘之上; 规则3:在满足移动规则1和2的前提下,可将圆盘移至A,B,C中任何一塔座上。,2019/12/2,37,n=3,汉诺塔的移动过程(XZ)?,X,Y,Z,X,Y,Z,移动分析: 当n2时,要将塔座X上的n个圆盘全部转移到塔座Z上,可以这样设想: (1)先把塔座X上的 n-1个圆盘想法转移到塔座Y上; (2)然后把塔座X上的最后一个大圆盘转移到塔座Z上; (3)最后再把塔座Y上的 n-1个圆盘转移到塔座Z上。,2019/12/2,38,下面是显示H
22、anoi塔问题中圆盘移动过程的程序。 void move(char get, char put) printf(“%c%cn“,get,put); void hanoi(int n, char from, char to, char by) if(n=1) move(from,to);else hanoi(n-1,from,by,to);move(from,to);hanoi(n-1,by,to,from); ,2019/12/2,39,int main() int m;printf(“input total diskes“);scanf(“%d“, ,2019/12/2,40,变量的作用域变
23、量都有自己的作用域,变量说明的位置不同,其作用域也不同,常见的有:块作用域、文件作用域。 块作用域:变量在花括号内声明时,该变量的作用域从声明位置开始,到块结束处为止。通常称之为局部变量。 文件作用域:变量在函数外部声明时,该变量的作用域从声明位置开始,到文件结束处为止。通常称之为全局变量。,5.6 变量作用域与编译预处理,2019/12/2,41,块作用域示例,int main(int y) int sum,n; scanf(“%d“, ,sum,n作用域,a,b,i作用域,2019/12/2,42,文件作用域示例,int n; void fun1(void)scanf(“%d“, ,n作用
24、域,sum作用域,2019/12/2,43,变量的可见性说明:有些情况下,变量在作用域内是不可见的(最近优先),例如:float x=1.0; void fun( ) printf(“%dn“,x); / 全局变量float x可见 int main() int x=100;printf(“%dn“,x); / 全局变量float x不可见fun( );return 0; 运行结果: 200 1.0 为避免这种易引起误解的情况,建议不要取相同的名字。,2019/12/2,44,对于全局变量还有以下几点说明:,全局变量可加强函数模块之间的数据联系,但又使这些函数依赖这些外部变量,因而使得这些函数
25、的独立性降低。从模块化程序设计的观点来看这是不利的,因此不是非用不可时,一般不使用外部变量。在同一源文件中,允许外部变量和内部变量同名。在内部变量的作用域内,外部变量将被屏蔽而不起作用。外部变量的作用域是从定义点到本文件结束。如果定义点之前的函数需要引用这些外部变量时,需要在函数内对被引用的外部变量进行说明。 为了便于管理,有时会使用多个文件。此时,全局变量可以起到数据共享的功能。,2019/12/2,45,编译预处理,文件包含宏定义条件编译,2019/12/2,46,文件包含,所谓“文件包含”处理是指一个源文件可以将另外一个源文件的全部内容包含进来,即将另外的文件包含到本文件之中。C语言提供
26、了#include命令用来实现“文件包含”的操作。其一般形式为#include “文件名” 或 #include ,2019/12/2,47,在#include命令中,文件名可以用双引号或尖括号括起来。二者的区别是: 用尖括弧时,系统到存放C库函数头文件所在的目录中寻找要包含的文件,这称为标准方式。 用双引号时,系统先在用户当前目录中寻找要包含的文件,若找不到,再按标准方式查找(即再按尖括号的方式查找)。一般来说,如果为调用库函数而用#include命令来包含相关的头文件,则用尖括号,以节省查找时间。如果要包含的是用户自己编写的文件(这种文件一般都在当前目录中),一般用双引号。在VC等特定的开
27、发工具中使用多个文件,还可以使用该开发工具所提供的其他方式。,2019/12/2,48,宏定义,语法形式: 定义宏: #define 宏名 宏定义指令 取消宏: #define 宏名 在C语言中使用较为常见,C+中相对较少 在编译过程中,凡程序中出现宏名的地方都被换成宏体,且只是简单替换。 例如:#define PI 3.1415926#define ADD(a,b,c) a+b+c C+的替代方法:const double PI=3.1415926;,2019/12/2,49,条件编译,主要指令: #if #else #endif #ifdef #else #endif #ifndef #else #endif,