1第4章模块化程序设计北京交通大学计算机学院赵宏2教学目标理解如何用函数模块构造程序熟悉标准库中常见的函数会定义和调用函数并理解函数调用的机制掌握变量的作用域和存储类别理解并运用递归函数编写程序。34.1模块化程序设计概述4.2函数的声明、定义和调用4.3函数的多级调用4.4变量的作用域和存储类别4.5计算机随机模拟方法4.6编译预处理4复杂任务可以分解为若干子任务。重复使用的程序段,将其进行独立设计,使计算机可以重复执行。4.1模块化程序设计概述main()func1()func2()func3()func4()func5()func6()图4-1程序模块结构图51.引例4.2函数的声明、定义和调用/*程序名:4_1.cpp*//*功能:计算两个实数中大的值*/#includestdio.hfloatfmax(floatx,floaty);/*函数说明*/voidmain(){floata,b,c;scanf(“%f,%f”,&a,&b);c=fmax(a,b);/*函数调用*/printf(“max=%f\n”,c);}floatfmax(floatx,floaty)/*函数定义*/{floatz;z=xy?x:y;returnz;}62.函数说明说明格式为:函数返回值类型函数名(参数表);#includestdio.hfloatfmax(floatx,floaty);/*函数说明*/voidmain(){floata,b,c;scanf(“%f,%f”,&a,&b);c=fmax(a,b);printf(“max=%f\n”,c);}•函数说明是一条语句,它指出函数返回值的类型、函数的名称、函数要接收的参数的个数、顺序和类型。•如果在一个函数中要调用另外一个函数,则在调用之前要对该函数进行说明。4.2函数的声明、定义和调用73.函数定义函数定义的一般形式:函数值类型函数名(形参表)/*函数头*/{/*函数体*/说明部分执行部分}4.2函数的声明、定义和调用floatfmax(floatx,floaty){floatz;z=xy?x:y;returnz;}函数头函数体84.函数调用函数调用的一般形式为:函数名(实参表);4.2函数的声明、定义和调用voidmain(){floata,b,c;scanf(“%f,%f”,&a,&b);c=fmax(a,b);printf(“max=%f\n”,c);}可用两种方式调用函数:(1)函数的调用可以在允许表达式出现的任何地方。如:c=fmax(a,b);(2)函数调用可以作为一条独立的语句。比如,有函数定义:voidprintstar(){printf(“***************”);}则可以把该函数调用作为一个独立语句,printstar();94.2函数的声明、定义和调用函数返回值函数返回值的类型是由函数定义或说明中的函数返回类型决定的。如果返回的类型与函数说明的不同,则在返回值时,先作隐含的类型转换,然后再返回。#includestdio.hintfmax(floatx,floaty){returnxy?x:y;}voidmain(){floatmax;max=fmax(3.5,2.6);printf(“max=%f\n”,max);}结果?104.2函数的声明、定义和调用形参和实参形式参数:定义函数时放在函数名称之后括号中的参数,简称形参。实际参数:调用函数时括号中的参数,简称实参。形参与实参的结合:函数在调用时,将生成实参值的一个副本传递给对应的形参,这个过程称为形参与实参的结合。如果只允许实参向形参传递数据,则被称为“单向传递”。114.2函数的声明、定义和调用参数传递实例voidswap(floatx,floaty){floatt;t=x;x=y;y=t;}voidmain(){floata,b;scanf(“%f,%f”,&a,&b);swap(a,b);printf(“%f,%f\n”,a,b);}3.53.52.62.6实参a实参b形参x形参y图4-2实参和形参数据的传递124.2函数的声明、定义和调用函数的调用过程voidmain(){floata,b,c;scanf(“%f,%f”,&a,&b);c=fmax(a,b);printf(“max=%f\n”,c);}floatfmax(floatx,floaty){floatz;z=xy?x:y;returnz;}保存返回地址及当前现场,为形参分配内存并将实参的值传给形参变量恢复main函数的现场,取得返回地址和返回值134.带自定义函数的程序设计4.2函数的声明、定义和调用程序设计思路:(1)定义一个函数isprime(intm)判断m是否为素数,若是素数,函数返回1,否则返回0。intisprime(intm){inti;for(i=2;i=m-1;i++)if(m%i==0)return0;return1;}(2)在主函数中输入一个整数,调用isprime函数,如果函数值为1,则打印是素数,否则打印不是素数。voidmain(){intiNumber;printf(请输入一个整数:);scanf(%d,&iNumber);if(isprime(iNumber))printf(%d是素数,iNumber);elseprintf(%d不是素数,iNumber);}【例4-2】从键盘输入一个整数,判断该整数是否为素数。141.嵌套调用intmin2(inta,intb){returnab?a:b;}intmin3(inta,intb,intc){intx,y;x=min2(a,b);y=min2(t,c);returny;}#includestdio.hvoidmain(){intt1,t2;t1=min2(-2,8);printf(“min=%d\n”t1,);t2=min3(-2,8,-6);printf(“min=%d\n”,t2);}4.3函数的多级调用154.3函数的多级调用main函数{……t1=min2(-2,8);t2=min3(-2,8,-6);……}min3函数{……x=min2(a,b);……}min2函数{……returnab?a:b;……}图4-5函数调用关系192.递归调用递归调用指的是一个函数执行过程中出现了直接或间接调用函数本身的调用方式。如果直接调用函数本身称为直接递归;如果调用了另外一个函数,那个函数又调用该函数,则称为间接递归。递归方法的基本思想是将一个问题向下分解具有同样解决方法但规模不断缩小的子问题,不断进行这样的分解,直到分解的子问题有一个已知解。某数列为k(n)的定义为:1n=1k(n)=2×k(n-1)n为偶数3×k(n-1)n为奇数4.3函数的多级调用204.3函数的多级调用k(4)=k(3)×2k(3)=k(2)×3k(2)=k(1)×2k(1)=1k(2)=1×2=2k(3)=2×3=6k(4)=6×2=12图4-6回推和递推过程21intk(intn)/*递归计算函数*/{intm;/*m存放函数的返回值*/if(n==1)m=1;elseif(n%2==0)m=k(n-1)*2;/*调用自身,n为偶数*/elsem=k(n-1)*3;/*调用自身,n为奇数*/return(m);}voidmain(){printf(\nk(%d)=%f,k(4));}4.3函数的多级调用22【例4-6】求Fibonacci数列第n项的值。Fibonacci数列以1,1开头,以后每一项都是前两项之和。1,1,2,3,5,8,13,21……程序设计思路:(1)求Fibonacci数列第n项的值可用递归形式定义为:fibonacci(0)=1fibonacci(1)=1fibonacci(n)=fibonacci(n-1)+fibonacci(n-2)(2)定义fibonacci函数计算第n项的值intfibonacci(intn){if(n==0||n==1)return1;elsereturnfibonacci(n–1)+fibonacci(n–2);}4.3函数的多级调用3.递归调用举例23【例4-8】反向输出一个长整数程序设计思路:(1)如果要输出的数据只有一位,则“反向输出”问题可简化为输出一位整数。(2)如果要输出的数据超过一位,则可将该整数分为两部分:个位上的数字和个位以前的数字。个位上的数字可以直接输出,而个位以前的数字又可以看成一个新的整数,重复执行“反向输出”的操作。这时,反向输出在规模上缩小了一位,但求解的方法还是一致的。(3)用表达式x%10可以分离出一个整数个位上的数字,用表达式x/10可以表示出个位以前的数。定义一个反向输出函数invertLongInt,每次先用x%10计算并输出个位上的数,然后用x/10(即个位以前的数)做参数调用自己,不断进行下去,直到只剩一位数字。4.3函数的多级调用24递归函数可以写为:voidinvertLongInt(longx){if(x=0&&x=9)printf(%d\n,x);else{printf(%d,x%10);invertLongInt(x/10);}}4.3函数的多级调用25小结:函数分为系统函数和自定义函数。每个函数的都是独立定义的,如果函数定义在后、调用在前,要对函数原型进行说明。除了主函数外,其他函数可以相互调用,如果A调用B,B又调用C,称为嵌套调用,如果直接或间接调用自己,称为递归。26练习:1.函数fun实现计算两个数之差的绝对值,并将差值返回调用函数,请编写fun函数fun(intx,inty).27第7周作业:1.在主函数中输入三角形的的三条边,调用子函数,判断是否能组成三角形,若可以则返回1否则返回0。在主函数中输出判断结果。2.编写函数,求两个正整数m和n的最大公约数。m和n作为函数的参数。函数返回运算结果,由主函数输出。4.习题4.2要求:第8周上课前提交28(1)程序区:存放用户程序代码,即程序中各个函数的代码。(2)静态存储区:存放程序的全局数据和静态数据。分配在静态存储区中的变量的生命期最长,它们在main函数运行之前就存在了,在程序的整个活动期(从程序开始执行到执行结束)中,这些变量始终占用静态存储区中对应的存储空间,即程序开始执行时分配存储单元,程序执行完毕后释放。(3)动态存储区:存放局部变量。分配在动态存储区中的变量只有在所定义的函数被调用时才分配存储单元,函数结束时就释放。系统对函数调用时的现场保护、返回地址等也占用动态保护区。(4)堆:自由存储区,用于运行程序时动态申请内存。4.4变量的作用域和存储类别1.程序在内存中的分布区域29局部变量:在块内定义的变量。局部变量作用域:块内定义、块内使用。所谓块内是指一对以{}为界限的若干个语句,例如函数体、复合语句。而块内使用,是指变量的作用范围仅仅局限在从变量定义处开始、到变量定义所在的那个块结束。如:4.4变量的作用域和存储类别2.局部变量及存储类别局部于main的局部变量局部于func的局部变量voidmain(){intn=5;printf(“%d”,n);func();}voidfunc(){intn=8;printf(“%d”,n);}30形式参数也为局部变量,其作用范围是形式参数所在的整个函数。例如:voidmain(){printf(“%d,%d”,x,y);/*error*/}voidfunc(intx,inty){/*……*/}4.4变量的作用域和存储类别31局部变量的存储类别:自动变量:用关键字auto(可缺省)加以说明的局部变量。如:autofloatb;或floatb;特点:是短生命期的局部变量,安排在动态存储区,由系统自动分配和释放,用到时分配内存,不用时释放内存,以节省程序执行时的内存资源。局部静态变量:用关键字static加以说明的局部变量。局部静态变量在