C语言 模块化程序设计_第1页
C语言 模块化程序设计_第2页
C语言 模块化程序设计_第3页
C语言 模块化程序设计_第4页
C语言 模块化程序设计_第5页
已阅读5页,还剩43页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第4章模块化程序设计

北京交通大学计算机学院赵宏1教学目标理解如何用函数模块构造程序熟悉标准库中常见的函数会定义和调用函数并理解函数调用的机制掌握变量的作用域和存储类别理解并运用递归函数编写程序。

24.1模块化程序设计概述

4.2函数的声明、定义和调用

4.3函数的多级调用

4.4变量的作用域和存储类别

4.5计算机随机模拟方法

4.6编译预处理

3复杂任务可以分解为若干子任务。重复使用的程序段,将其进行独立设计,使计算机可以重复执行。

4.1模块化程序设计概述main()func1()func2()func3()func4()func5()func6()图4-1程序模块结构图41.引例

4.2函数的声明、定义和调用

/*程序名:4_1.cpp*//*功能:计算两个实数中大的值*/#include<stdio.h>floatfmax(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=x>y?x:y;returnz;}52.函数说明

说明格式为:

函数返回值类型函数名(参数表);#include<stdio.h>floatfmax(floatx,floaty);/*函数说明*/voidmain(){floata,b,c;

scanf(“%f,%f”,&a,&b);c=fmax(a,b);

printf(“max=%f\n”,c);}

函数说明是一条语句,它指出函数返回值的类型、函数的名称、函数要接收的参数的个数、顺序和类型。如果在一个函数中要调用另外一个函数,则在调用之前要对该函数进行说明。4.2函数的声明、定义和调用

63.函数定义

函数定义的一般形式:

函数值类型函数名(形参表)/*函数头*/{/*函数体*/

说明部分执行部分

}4.2函数的声明、定义和调用

floatfmax(floatx,floaty){floatz;z=x>y?x:y;returnz;}函数头函数体74.函数调用

函数调用的一般形式为:

函数名(实参表);

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();84.2函数的声明、定义和调用

函数返回值

函数返回值的类型是由函数定义或说明中的函数返回类型决定的。如果返回的类型与函数说明的不同,则在返回值时,先作隐含的类型转换,然后再返回。#include<stdio.h>int

fmax(floatx,floaty){returnx>y?x:y;}voidmain(){floatmax;max=fmax(3.5,2.6);

printf(“max=%f\n”,max);}结果?94.2函数的声明、定义和调用

形参和实参

形式参数:定义函数时放在函数名称之后括号中的参数,简称形参。实际参数:调用函数时括号中的参数,简称实参。形参与实参的结合:函数在调用时,将生成实参值的一个副本传递给对应的形参,这个过程称为形参与实参的结合。如果只允许实参向形参传递数据,则被称为“单向传递”。

104.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实参和形参数据的传递114.2函数的声明、定义和调用

函数的调用过程

voidmain(){floata,b,c;

scanf(“%f,%f”,&a,&b);c=fmax(a,b);

printf(“max=%f\n”,c);}floatfmax(floatx,floaty){floatz;z=x>y?x:y;returnz;}保存返回地址及当前现场,为形参分配内存并将实参的值传给形参变量恢复main函数的现场,取得返回地址和返回值124.带自定义函数的程序设计

4.2函数的声明、定义和调用

程序设计思路:(1)定义一个函数isprime(intm)判断m是否为素数,若是素数,函数返回1,否则返回0。int

isprime(intm){

inti;

for(i=2;i<=m-1;i++)if(m%i==0)return0;return1;}(2)在主函数中输入一个整数,调用isprime函数,如果函数值为1,则打印是素数,否则打印不是素数。voidmain(){

int

iNumber;

printf("请输入一个整数:");

scanf("%d",&iNumber);if(isprime(iNumber))

printf("%d是素数",iNumber);else

printf("%d不是素数",iNumber);}【例4-2】从键盘输入一个整数,判断该整数是否为素数。131.嵌套调用intmin2(inta,intb){returna<b?a:b;}intmin3(inta,int

b,intc){

int

x,y;x=min2(a,b);y=min2(t,c);returny;}#include<stdio.h>voidmain(){

intt1,t2;t1=min2(-2,8);

printf(“min=%d\n”t1,);t2=min3(-2,8,-6);

printf(“min=%d\n”,t2);}4.3函数的多级调用144.3函数的多级调用main函数{……t1=min2(-2,8);t2=min3(-2,8,-6);……}min3函数{……x=min2(a,b);……}min2函数{……returna<b?a:b;

……}图4-5函数调用关系

151.嵌套调用(续)

【例4-5】用弦截法求方程x3-5x2+16x-80=0

的根。4.3函数的多级调用求解的方法是:yxx1x2xf(x1,f(x1))f(x,f(x))f(x2,f(x2))图4-4弦截法求方程的根16程序设计思路:(1)输入x1,x2代表根的初始区间,f1,f2代表函数f在x1,x2两点的函数值。要保证初始的f1,f2异号,如果不是异号,要重新输入x1,x2。(2)定义函数f(x)计算多项式f(x)=x3-5x2+16x-80的值。(3)用函数xpoint(x1,x2)计算f(x1,f(x1))和f(x2,f(x2))的连线与x轴交点x的值,公式为:(4)定义函数root(x1,x2)来求解实根。首先调用用函数xpoint(x1,x2)计算f(x1,f(x1))和f(x2,f(x2))的连线与x轴交点x的值,再判断f(x)与f(x1)和f(x2)哪一个同号,如果f(x)与f(x1)同号,就用x作为新的x1,否则就用x作为新的x2。再在新的区间内进行计算。4.3函数的多级调用174.3函数的多级调用main函数{……x=root(x1,x2);……}root函数{……x=xpoint(x1,x2);……}xpoint函数{……return(x1*f(x2)-x2*f(x1))/(f(x2)–f(x1));……}f函数{……}图4-5函数调用关系

182.递归调用

递归调用指的是一个函数执行过程中出现了直接或间接调用函数本身的调用方式。如果直接调用函数本身称为直接递归;如果调用了另外一个函数,那个函数又调用该函数,则称为间接递归。递归方法的基本思想是将一个问题向下分解具有同样解决方法但规模不断缩小的子问题,不断进行这样的分解,直到分解的子问题有一个已知解。某数列为k(n)的定义为:

1n=1

k(n)=2×k(n-1)n为偶数

3×k(n-1)n为奇数4.3函数的多级调用194.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回推和递推过程20int

k(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函数的多级调用21【例4-6】求Fibonacci数列第n项的值。Fibonacci数列以1,1开头,以后每一项都是前两项之和。

1,1,2,3,5,8,13,21……程序设计思路:(1)求Fibonacci数列第n项的值可用递归形式定义为:

fibonacci(0)=1

fibonacci(1)=1

fibonacci(n)=fibonacci(n-1)+fibonacci(n-2)(2)定义fibonacci函数计算第n项的值

int

fibonacci(intn){

if(n==0||n==1)return1;elsereturnfibonacci(n–1)+fibonacci(n–2);}4.3函数的多级调用3.递归调用举例22【例4-8】反向输出一个长整数程序设计思路:(1)如果要输出的数据只有一位,则“反向输出”问题可简化为输出一位整数。(2)如果要输出的数据超过一位,则可将该整数分为两部分:个位上的数字和个位以前的数字。个位上的数字可以直接输出,而个位以前的数字又可以看成一个新的整数,重复执行“反向输出”的操作。这时,反向输出在规模上缩小了一位,但求解的方法还是一致的。(3)用表达式x%10可以分离出一个整数个位上的数字,用表达式x/10可以表示出个位以前的数。定义一个反向输出函数invertLongInt,每次先用x%10计算并输出个位上的数,然后用x/10(即个位以前的数)做参数调用自己,不断进行下去,直到只剩一位数字。4.3函数的多级调用23递归函数可以写为:voidinvertLongInt(longx){

if(x>=0&&x<=9)

printf("%d\n",x);else {

printf("%d",x%10);

invertLongInt(x/10); }}4.3函数的多级调用24小结:函数分为系统函数和自定义函数。每个函数的都是独立定义的,如果函数定义在后、调用在前,要对函数原型进行说明。除了主函数外,其他函数可以相互调用,如果A调用B,B又调用C,称为嵌套调用,如果直接或间接调用自己,称为递归。25练习:1.函数fun实现计算两个数之差的绝对值,并将差值返回调用函数,请编写fun函数fun(intx,inty).26第7周作业:1.在主函数中输入三角形的的三条边,调用子函数,判断是否能组成三角形,若可以则返回1否则返回0。在主函数中输出判断结果。2.编写函数,求两个正整数m和n的最大公约数。m和n作为函数的参数。函数返回运算结果,由主函数输出。4.习题4.2要求:第8周上课前提交27(1)程序区:存放用户程序代码,即程序中各个函数的代码。(2)静态存储区:存放程序的全局数据和静态数据。分配在静态存储区中的变量的生命期最长,它们在main函数运行之前就存在了,在程序的整个活动期(从程序开始执行到执行结束)中,这些变量始终占用静态存储区中对应的存储空间,即程序开始执行时分配存储单元,程序执行完毕后释放。(3)动态存储区:存放局部变量。分配在动态存储区中的变量只有在所定义的函数被调用时才分配存储单元,函数结束时就释放。系统对函数调用时的现场保护、返回地址等也占用动态保护区。(4)堆:自由存储区,用于运行程序时动态申请内存。4.4变量的作用域和存储类别1.程序在内存中的分布区域

28局部变量:在块内定义的变量。局部变量作用域:块内定义、块内使用。所谓块内是指一对以{}为界限的若干个语句,例如函数体、复合语句。而块内使用,是指变量的作用范围仅仅局限在从变量定义处开始、到变量定义所在的那个块结束。如:4.4变量的作用域和存储类别2.局部变量及存储类别

局部于main的局部变量局部于func的局部变量voidmain(){

intn=5;

printf(“%d”,n);

func();}voidfunc(){

intn=8;

printf(“%d”,n);}29形式参数也为局部变量,其作用范围是形式参数所在的整个函数。例如:voidmain(){

printf(“%d,%d”,x,y);/*error*/}voidfunc(intx,inty){/*……*/}4.4变量的作用域和存储类别30局部变量的存储类别:自动变量:用关键字auto(可缺省)加以说明的局部变量。如:autofloatb;或floatb;

特点:是短生命期的局部变量,安排在动态存储区,由系统自动分配和释放,用到时分配内存,不用时释放内存,以节省程序执行时的内存资源。局部静态变量:用关键字static加以说明的局部变量。局部静态变量在静态存储区分配空间。如:

staticintcount;

特点:是长生命期的局部变量。函数执行结束后,分配给该变量的存储区不释放。局部静态变量安排在静态存储区。寄存器变量:用关键字register说明的局部变量为寄存器变量。特点:寄存器变量的值存放在CPU的寄存器中。可以提高程序的执行效率。4.4变量的作用域和存储类别31【例4-10】局部变量存储方式举例,分析下面程序运行结果:/*程序名:4_10.cpp*//*功能:局部变量存储方式示例*/#include<stdio.h>intfun1(int);intfun2(int);voidmain(){

inti;

for(i=2;i<5;i++)

printf("fun1(%d)=%d\t",i,fun1(i));

printf("\n");

for(i=2;i<5;i++)

printf("fun2(%d)=%d\t",i,fun2(i));

printf("\n");}4.4变量的作用域和存储类别32续上页:intfun1(intx){

intf=1;return(f*=x);}intfun2(intx){

staticintf=1;return(f*=x);}

4.4变量的作用域和存储类别程序执行结果为:fun1(2)=2fun1(3)=3fun1(4)=4fun2(2)=2fun2(3)=6fun2(4)=2433#include<stdio.h>int

func(int,int);voidmain(){

intk=4,m=1,p; p=func(k,m);

printf("%d,",p); p=func(k,m);

printf("%d\n",p);}int

func(int

a,intb){ staticintm,i=2; i+=m+1; m=i+a+b;

return(m);}结果:8,17主函数:第一次fun函数:km41mi02mi83第二次fun函数:mi83mi171234全局部变量:函数外定义的变量。全局变量作用域:全局变量的作用范围是从变量定义处开始到所定义的源文件结束处,即从全局变量定义所在处开始到源文件结束处之间的所有函数都可以访问该变量。如:intn=1;voidmain(){

printf(“%d”,n);

func();

printf(“%d”,n);}floatm;voidfunc(){n=5;m=3;}4.4变量的作用域和存储类别2.全局部变量及存储类别

35【例4-11】全局变量的作用域举例,分析下面程序运行结果。#include<stdio.h>voidswap(void);inta,b;

/*是两个函数公用的变量*/voidmain(){scanf("%d%d",&a,&b);

printf("交换前的a和b是%d,%d\n",a,b);swap();

printf("交换后的a和b是%d,%d\n",a,b);}voidswap(){intt;t=a;a=b;b=t;/*使用的变量名a和b全局变量a和b*/}4.4变量的作用域和存储类别36局部变量与全局变量同名时的处理:小范围优先【例4-12】分析下面程序运行结果:

inti=1;/*变量i定义在所有函数之外,属于全局变量*/voidmain(){

printf(“主函数中访问的变量i:%d\n",i);i=test()+1;

printf(“主函数中访问的变量i:%d\n",i);}inttest(){

inti;

printf(“test中访问的变量i:%d\n",i);i=2;

printf(“test中访问的变量i:%d\n",i);returni;}4.4变量的作用域和存储类别程序运行结果:主函数中访问的变量i:1test中访问的变量i:28345test中访问的变量i:2主函数中访问的变量i:337全局部变量的存储类别

:静态全局变量:使用关键字static定义的全局变量是文件内部的全局变量。特点:只能被定义所在的源文件中的所有函数访问,同一程序的其它源文件中的函数都不能访问该变量。非静态全局变量:使用缺省关键字的全局变量。特点:则该变量不仅能被定义所在的源文件中的所有函数访问,而且组成程序的其它源文件中的所有函数也都能访问该变量。因此,从作用范围看,缺省关键字的全局变量要比使用关键字static的静态全局变量大。关键字extern的作用关键字extern的作用是对要使用的某个尚未定义的全局变量在使用前作变量说明,该全局变量或者是以后会在该源文件中定义的全局变量,或者是在另一个源文件中使用缺省关键字定义的全局变量。4.4变量的作用域和存储类别38【例4-13】全局变量存储方式举例,分析下面程序运行结果:/*程序名:4_13.cpp*//*功能:全局变量存储方式示例*/#include<stdio.h>externinta;/*对4_13_2.cpp中定义的变量进行说明*/voidfun1();voidfun2();voidmain(){fun1();fun2();

printf("函数main中的a是%d\n",a);}4.4变量的作用域和存储类别39/*程序名:4_13_1.cpp*/#include<stdio.h>staticinta;/*只允许文件4_13_1.cpp中函数访问的全局变量*/voidfun1(){a=2;

printf("函数fun1中的a是%d\n",a);}/*程序名:4_13_2.cpp*/#include<stdio.h>inta;/*允许其它文件中函数访问的全局变量*/voidfun2(){a=4;

printf("函数fun2中的a是%d\n",a);}4.4变量的作用域和存储类别40rand()函数可随机生成0~RAND_MAX之间的一个整数。RAND_MAX是头文件<stdlib.h>中定义的一个符号常量。ANSI规定RAND_MAX的值不小于32767。根据下面公式可以得到所需范围内的随机数:

n=a+rand()%b

其中a为位移,是所需连续整数范围的第一个数,b是比例因子,是所需连续整数范围的宽度,则希望产生1~6之间随机数的公式为:

face=1+rand()%64.5计算机随机模拟方法1.伪随机数的产生

41【例4-14】编写一个模拟投掷硬币的程序,模拟20次,统计出正面出现的次数。#include<stdio.h>#include<stdlib.h>voidmain(){

inti,face,iCount=0;

for(i=1;i<=20;i++) { face=rand()%2; printf("%5d",face); if(i%10==0)printf("\n"); if(face)iCount++; }

printf("正面出现次数:%d次\n",iCount);}4.5计算机随机模拟方法42运行程序,结果为:11001000001111111010正面出现次数:11次再次运行该程序结果为:110010000011111110104.5计算机随机模拟方法43用srand()函数进行随机化voidmain(){unsignedseed;printf(“输入一个非负整数做种子:”);scanf(“%d”,&seed);srand(seed);for(inti=1;i<=10;i++)printf("%3d",1+rand()%6);}运行3次程序:输入一个非负整数做种子:161343565262输入一个非负整数做种子:333153565415输入一个非负整数做种子:1613435652624.5计算机随机模拟方法44使用系统定时/计数器的值做为随机种子:

srand(time(NULL));time()函数返回以秒为单位的当前时间值,因为有时钟参数,而时间始终在变,随机数序列就不会固定不变了。【例4-16】编写程序,用来生成一个随机小写字符串。

#include<stdio.h>#include<time.h>#include<stdlib.h>voidmain(){

srand(time(NULL));

for(inti=1;i<=20;i++)

printf("%c",97+rand()%26);}4.5计算机随机模拟方法45用事件发生的“频率”来决定事件的“概率”考虑平面上的一个边长为1的正方形及其内部的一个形状不规则的“图形”,如何求出这个“图形”的面积呢?MonteCarlo方法是这样一种“随机化”的方法:向该正方形“随机地”投掷N个点落于“图形”内,则该“图形”的面积近似为M/N。【例4-17】计算图形面积。图中有两条半径为1的圆弧,求s的面积。4.5计算机随机模拟方法2.蒙特卡罗(MonteCarlo)方法

xyo11s图4-10求面积46程序设计思路:模拟雨点落在阴影区域的情况。对于以x=0,y=0位圆心的圆,s中每个点应该满足:(1)对于以x=1,y=0位圆心的圆,s中每个点应该满足:(2)同时满足(1)和(2)就可以保证雨点落在s区域。可以使用如下语句:if(sqrt(x*x+y*y)>1&&sqrt((x–1)*(x-1)+y*y))g=g+1;g为落入s区雨点的数目。程序参见教材4_17.cpp。4.5计算机随机模拟方法47“文件包含”是指一个源文件可以将另一个源文件的全部内容包含进来。在编译预处理时#include指令让预处理器在程序该点处加入指定文件内容,然后作为一个源文件编译形成新文件。文件包含的一般形式为:(1)#include<文件名>(2)#include"文件名"例如:#include<stdio.h>#include"stdio.h"4.6编译预处理1.文件包含#include48(1)定义符号常量

#define标识符字符串如:#definePI3.14预处理器将在源文件中搜索PI,并把每个PI替换成3.14。完成搜索和操作替换后,预处理器删除#define行。(2)定义宏

#define宏名(参数表)字符串如:#defineS(a,b)a*b对程序中带有实参的宏(如S(3,4)),按#define命令中指定的字符串从左到右进行替换。如果字符串中包含宏的形参(如a,b),则将程序语句中相应的实参代替形参,如果宏定义中字符串中的字符不是参数字符(如a*b中的*)则原样保留。4.6编译预处理2.宏定义#define

49(3)宏与函数宏替换只占编译时间,不占运行时间。但函数调用时分配单元、传值、返回值时都占时间,特别是多次调用时尤为严重。但使用宏的程序代码较长,因为宏在程序中存在了多次,而使用函数时,虽然多次调用,但函数代码只存在一次。函数调用时先求实参表达式的值,再传给形参。而带参数的宏只是简单的字符替换,宏展开时并不对实参表达式求值。函数调用时临时分配存储单元,宏替换并不分配存储单元,也没有返回值。宏是无类型的。宏既可以用于浮点型,也可以用于整型,因为预处理器只是完成字符替换,而不考虑它的参数类型。如果使用函数,则对于每种求平方的数据类型都需要一个函数。4.6编译预处理50条件编译就是按条件对C/C++程序的一部分进行编译,其它部分不编译。(1)使源代码能更迅速、更容易地进行修改,并使目标代码缩短。如:#if(MAX>99)

printf("Compiledforarraygreaterthan99\n");#else

printf("Compiledforsmallarray\n");#endif(2)协调多个头文件,避免一个符号或一个头文件被多次包含。例如,符号NULL在几个不同的头文件中都进行了定义,如果一个源文件包含其中几个头文件,则会出现符号NULL被多次定义的错误,这时需要使用条件编译命令:#ifdefNULL#defineNULL((void)0)#endif4.6编译预处理3.条件编译514.7本章小结函数分为系统函数和自定义函数。每个函数的都是独立定义的,如果函数定义在后、调用在前,要对函数原型进行说明。除了主函数外,其他函数可以相互调用,如果A调用B,B又调用C,称为嵌套调

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论