《第06章 函数与模块化程序设计基础.ppt》由会员分享,可在线阅读,更多相关《第06章 函数与模块化程序设计基础.ppt(61页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第第第第6 6 6 6章章章章 函数与模块化程函数与模块化程函数与模块化程函数与模块化程序设计基础序设计基础序设计基础序设计基础6.1 6.1 概述概述6.2 6.2 函数的定义函数的定义6.3 6.3 函数的调用函数的调用6.4 6.4 变量的作用域与存储特性变量的作用域与存储特性 6.1 6.1 概述概述6.1 6.1 概述概述一一.模块与函数模块与函数1.结构化程序设计 C语言是结构化的程序设计语言。一种设计程序的技术。通常采用自顶向下逐步求精的设计方法和单入口单出口控制结构。2.“自顶向下”的模块化程序设计方法将一个大问题按层次分解成多个方便解决小问题的模块的设计思想.3.功能模块 求
2、解较小问题的算法和程序称作“功能模块”,各功能模块可以先单独设计,然后将求解所有子问题的模块组合成求解原问题的程序。主控模块主控模块模块模块1_1模块模块1_n模块模块2_1模块模块2_n模块模块n_1模块模块n_n模块模块1模块模块2模块模块n由功能模块组成程序的结构图 6.1 6.1 概述概述4.函数:完成相对独立功能的程序例6-1,输入年月日,计算出该日为该年的第几天。分析:(1)判断年份是否为闰年。年份有闰年与平年之分,两者的区别在于闰年的二月为29天,平年的二月为28天。因此,给定一个年份,首先应确定其是否是闰年。(2)求月份对应的天数。月份不同,其对应的天数不同,1、3、5、7、8
3、、10、12月每月为31天,4、6、9、11月每月为30天,2月根据所在年份是否为闰年来确定。(3)求总天数。分为经历完整的月份天数与经历不完整的月份天数。(4)输出数据。年月日及相应的天数。6.1 6.1 概述概述程序实现:(1)判断闰年。int leap(int year)int lp;lp=(year%4=0&year%100!=0|year%400=0)?1:0;return lp;主控模块主控模块判断闰年判断闰年求某月的天数求某月的天数输输 出出输输 入入求总天数求总天数程序模块结构图程序模块结构图 6.1 6.1 概述概述(2)求某月的天数。/*函数month_days调用函数le
4、ap判断year是否为闰年*/int month_days(int year,int month)int ds,d;switch(month)case 1:case 3:case 5:case 7:case 8:case 10:case 12:d=31;break;case 2:d=leap(year)?29:28;break;/*若为闰年,d赋值29,否则赋值28*/default:d=30;return d;6.1 6.1 概述概述(3)求天数和。/*函数days调用函数month_days,求各月份对应的天数*/int days(int year,int month,int day)in
5、t i,ds=0;for(i=1;i0?1:(x=0?0:-1);return y;/*返回函数值返回函数值*/6.2 6.2 函数的定义函数的定义(3)return语句的表示形式如下:语句的表示形式如下:return 表达式;表达式;或或 return(表达式);表达式);符号函数的另一种定义形式:符号函数的另一种定义形式:int sign(int x)/*形式参数类型说明与参数列表与函数名一起说明形式参数类型说明与参数列表与函数名一起说明*/return y=x0?1:(x=0?0:-1);/*返回一个复杂的表达式的值返回一个复杂的表达式的值*/当函数类型为当函数类型为float型并且型并
6、且return语句缺省时,调用出错。语句缺省时,调用出错。当函数类型为当函数类型为int型并且型并且return语句的表达式缺省或语句的表达式缺省或return语句缺省时,返回不确定的值(在语句缺省时,返回不确定的值(在TC2中返回值为中返回值为0)6.2 6.2 函数的定义函数的定义例例6-3:写出以下程序的运行结果。:写出以下程序的运行结果。max()/*函数类型缺省,为函数类型缺省,为int型,型,return语句缺省语句缺省*/min()/*函数类型缺省,函数类型缺省,return语句缺省表达式语句缺省表达式*/return;main()printf(%d,%dn,max(),min(
7、)););程序运行结果为:程序运行结果为:-256,-256 /*返回值不确定,返回值不确定,TC2开发环境中返回值为开发环境中返回值为0*/若函数若函数max与函数与函数min的类型为的类型为float,则返回值为:则返回值为:Floating point error:Domain.Abnormal program termination 6.2 6.2 函数的定义函数的定义在具有多个函数的在具有多个函数的C程序中,主函数出现的位置并不重程序中,主函数出现的位置并不重要。为阅读方便,可将主函数要。为阅读方便,可将主函数main放在最前面。为了避免放在最前面。为了避免过多的函数声明语句,习惯上
8、将主函数放在所有函数之后。过多的函数声明语句,习惯上将主函数放在所有函数之后。不论主函数放在什么位置,一旦启动该程序,总是从主函不论主函数放在什么位置,一旦启动该程序,总是从主函数开始执行,并且最终在主函数结束整个程序的执行。数开始执行,并且最终在主函数结束整个程序的执行。3.函数函数main一个一个C语言程序至少包含一个函数,并且必须有且只能有一语言程序至少包含一个函数,并且必须有且只能有一个名为个名为main的函数,称之为主函数。的函数,称之为主函数。在包含多个函数的程序中,不仅可以由主函数调用其它函在包含多个函数的程序中,不仅可以由主函数调用其它函数,还可以由被调函数调用其它函数,但任何
9、函数都不能调数,还可以由被调函数调用其它函数,但任何函数都不能调用主函数。用主函数。通常主函数的类型定义为通常主函数的类型定义为void(在在TC2中可省略函数中可省略函数main的的类型说明)。类型说明)。6.2 6.2 函数的定义函数的定义6.3 函数的调用函数的调用在在C语言的一般函数体中,可以包含对其它函数的调用,语言的一般函数体中,可以包含对其它函数的调用,称为函数的嵌套调用,甚至包含对自身的调用,称为函数的称为函数的嵌套调用,甚至包含对自身的调用,称为函数的递归调用。递归调用。一个函数一旦被定义,就可在程序的其它函数中使用它,这一个函数一旦被定义,就可在程序的其它函数中使用它,这个
10、过程称为函数调用。个过程称为函数调用。一一.函数的一般调用与声明函数的一般调用与声明函数名函数名(实际参数列表)实际参数列表)1.函数调用形式函数调用形式函数调用的一般形式:函数调用的一般形式:6.3 6.3 函数的调用函数的调用实际参数也称为实在参数,简称为实参,实际参数之间以实际参数也称为实在参数,简称为实参,实际参数之间以“,”分隔。分隔。函数调用时,实参与形参应保持个数、次序及类型的一致性,函数调用时,实参与形参应保持个数、次序及类型的一致性,以确保实参与形参之间数据的正确传递。以确保实参与形参之间数据的正确传递。实际参数一般为表达式,可以是常量、变量(调用时必须有实际参数一般为表达式
11、,可以是常量、变量(调用时必须有确定的值或确定的地址)。确定的值或确定的地址)。形式参数必须为变量。形式参数必须为变量。当实际参数的个数、次序、类型与对应形式参数的个数、次当实际参数的个数、次序、类型与对应形式参数的个数、次序、类型不一致时,系统并不提示错误,后果却难以预测。序、类型不一致时,系统并不提示错误,后果却难以预测。注意注意 6.3 6.3 函数的调用函数的调用C语言中函数调用的三种形式语言中函数调用的三种形式:函数语句调用函数语句调用函数表达式调用函数表达式调用函数参数调用函数参数调用在函数调用后加在函数调用后加“;”,构成一个语句。,构成一个语句。调用函数的目的可能是执行一个动作
12、或调用函数的目的可能是执行一个动作或完成特定的功能。完成特定的功能。大多数函数的调用形式。大多数函数的调用形式。被调用函数执行的结果为调用函数提供被调用函数执行的结果为调用函数提供一个值,除非一个函数的类型说明为一个值,除非一个函数的类型说明为void型。型。调用函数通过表达式接收值。调用函数通过表达式接收值。被调函数作为某个函数的一个参数。被调函数作为某个函数的一个参数。6.3 6.3 函数的调用函数的调用例例6-4,函数语句调用与函数表达式调用。函数语句调用与函数表达式调用。#include void main()()printf(nk1=%d,printf(n%k2););程序运行结果为
13、:程序运行结果为:k2k1=3 6.3 6.3 函数的调用函数的调用void main()/*主函数中采用函数参数调用形式调用函数主函数中采用函数参数调用形式调用函数max2*/int x,y,z,m;scanf(%d,%d,%d,&x,&y,&z);printf(max=%dn,max2(max2(x,y),z);/*内层函数内层函数max2的值作为外层函数的值作为外层函数max2的实参,整个函数的实参,整个函数max2的值的值又作函数又作函数printf的的实参的的实参*/例例6-5:函数参数调用形式调用函数:函数参数调用形式调用函数max2:int max2(int a,int b)/*
14、求两个数中较大者求两个数中较大者*/int y;y=(ab)?a:b;return y;6.3 6.3 函数的调用函数的调用2.函数声明函数声明调用用户自定义函数时,一般调用函数和被调用函数应在同调用用户自定义函数时,一般调用函数和被调用函数应在同一个文件中,在调用函数中对被调用函数返回值的类型、函数一个文件中,在调用函数中对被调用函数返回值的类型、函数名称、函数形式参数的类型进行说明,这种说明称为函数声明。名称、函数形式参数的类型进行说明,这种说明称为函数声明。函数声明的一般形式如下:函数声明的一般形式如下:类型名类型名 函数名(类型函数名(类型1 形参形参1,类型,类型2 形参形参2,类型
15、,类型n 形参形参n););或或类型名类型名 函数名(类型函数名(类型1,类型,类型2,类型,类型n););或或类型名类型名 函数名();函数名();函数声明是以语句形式出现的,因此其后有语句结束标记函数声明是以语句形式出现的,因此其后有语句结束标记“;”。若函数定义放在主调函数之前,遵循先定义后调用原则若函数定义放在主调函数之前,遵循先定义后调用原则,函数函数声明可以省略。声明可以省略。6.3 6.3 函数的调用函数的调用例例6-6:编程求两个整数的阶乘之和。编程求两个整数的阶乘之和。long fac(n)/*定义函数定义函数fac,其功能是求其功能是求n!*/int n;int i;lon
16、g r=1;/*注意注意r的初始化值为的初始化值为1*/for(i=1;i=n;i+)r=r*i;return r;#include void main()int m1,m2;long result;long fac(int);/*在主函数中声明后面定义的函数在主函数中声明后面定义的函数fac*/scanf(%d,%d,&m1,&m2);result=fac(m1)+fac(m2);printf(%d!+%d!=%ldn,m1,m2,result);6.3 6.3 函数的调用函数的调用二二.形参与实参形参与实参形式参数形式参数(简称形参简称形参)定义函数使用的参数定义函数使用的参数实际参数实际
17、参数(简称实参简称实参)调用函数使用的参数调用函数使用的参数C语言中,采用函数之间的参数传递方式或用全局变量共享数语言中,采用函数之间的参数传递方式或用全局变量共享数据方式,使一个函数能对不同的数据进行相同功能的处理。据方式,使一个函数能对不同的数据进行相同功能的处理。C语言函数的参数传递均采用单向值传递方式(或称复制方式)。语言函数的参数传递均采用单向值传递方式(或称复制方式)。单向值传递方式是指在函数调用时,将实参之值传递给对应单向值传递方式是指在函数调用时,将实参之值传递给对应的形式参数,使形参具有与实参相同的值。的形式参数,使形参具有与实参相同的值。当实际参数是变量的地址值、指针常量或
18、指针变量时,实际参当实际参数是变量的地址值、指针常量或指针变量时,实际参数传递给形式参数的是地址值,也同样是单向值传递方式。数传递给形式参数的是地址值,也同样是单向值传递方式。6.3 6.3 函数的调用函数的调用例例6-76-7,求方程,求方程ax2+bx+c=0(a 0)的实数根。的实数根。分析:分析:(1)一元二次方程的实根的条件是:)一元二次方程的实根的条件是:。(2)定义函数)定义函数dict来判断数方程是否有实根,有实根则返回函数来判断数方程是否有实根,有实根则返回函数值值1,否则返回函数值,否则返回函数值0;然后在主函数中求方程的实根。;然后在主函数中求方程的实根。#include
19、#include void main()float a,b,c,x1,x2,d,dt;int dict(float,float,float);/*声明函数声明函数dict及形式参数类型及形式参数类型*/printf(Input a,b,c:);scanf(%f,%f,%f,&a,&b,&c);/*输入方程输入方程*/d=dict(a,b,c);/*调用函数调用函数dict,传递实参传递实参a,b,c*/dt=b*b-4*a*c;/*,可以用一个函数实现可以用一个函数实现*/6.3 6.3 函数的调用函数的调用if(d)x1=(-b+sqrt(dt)/(2*a););x2=(-b-sqrt(dt
20、)/(2*a););printf(实实根根x1=%f,x2=%fn,x1,x2);else printf(无无实实数根数根!n););int dict(a,b,c)/*定定义义函数函数dict及形参及形参说说明明*/float a,b,c;float d;d=b*b-4*a*c;/*可以用一个函数实现可以用一个函数实现*/if(d=0)return(1);else return(0);6.3 6.3 函数的调用函数的调用函数main函数dictabcabc实参与形参数据传递通过函数调用,将实参的值传递给形参通过函数调用,将实参的值传递给形参.函数调用时,系统对参数的处理步骤为:函数调用时,系统
21、对参数的处理步骤为:(1)计算各实参的值,将实参值压入形参栈中,然后执行函数体。)计算各实参的值,将实参值压入形参栈中,然后执行函数体。(2)当函数执行完返回时,形参从栈顶弹出(取走)。)当函数执行完返回时,形参从栈顶弹出(取走)。6.3 6.3 函数的调用函数的调用有关实参与形参的几点说明:有关实参与形参的几点说明:(1)在函数定义中指定的形参,未调用时,它们不占用存储单元。)在函数定义中指定的形参,未调用时,它们不占用存储单元。只有调用该函数时,形参才被分配空间,函数调用结束后,形参只有调用该函数时,形参才被分配空间,函数调用结束后,形参所占的存储单元被释放。所占的存储单元被释放。(2)实
22、参为表达式。函数调用时,先计算表达式的值,然后将值)实参为表达式。函数调用时,先计算表达式的值,然后将值传递给形参。常量、变量、函数值都可看成是表达式的特殊形式。传递给形参。常量、变量、函数值都可看成是表达式的特殊形式。(3)定义函数时,形参的排列没有次序要求,但对形参列表中每)定义函数时,形参的排列没有次序要求,但对形参列表中每个参数要进行说明。调用函数时,实参类型、个数及排列次序应个参数要进行说明。调用函数时,实参类型、个数及排列次序应与形参一一对应。若类型不一致,必须在参数前加上强制转换符,与形参一一对应。若类型不一致,必须在参数前加上强制转换符,否则会发生否则会发生“类型不匹配类型不匹
23、配”的错误。的错误。(4)实参与形参的数据传递为单向传递,只可由实参向形参传)实参与形参的数据传递为单向传递,只可由实参向形参传递,不能由形参传回实参。实参与形参处在不同的函数中,作用递,不能由形参传回实参。实参与形参处在不同的函数中,作用的区域不同,即使实参与形参同名,也是不同的变量。的区域不同,即使实参与形参同名,也是不同的变量。6.3 6.3 函数的调用函数的调用三三.函数的嵌套调用函数的嵌套调用 所谓函数的嵌套调用是指一个函数调用另一函数的过程中所谓函数的嵌套调用是指一个函数调用另一函数的过程中又出现对其它的函数调用。又出现对其它的函数调用。这种嵌套调用的层次原则上不限制。这种嵌套调用
24、的层次原则上不限制。函数1 函数2 函数3 .调用函数2 调用函数3 .6.3 6.3 函数的调用函数的调用例例6-8,采用函数多重嵌套调用求方程,采用函数多重嵌套调用求方程ax2+bx+c=0(a 0)的实数根。的实数根。#include#include void main()float a,b,c,x1,x2;int dict(float,float,float););float dt(float,float,float););float root(float,float,float,int););printf(Input a,b,c:););scanf(%f,%f,%f,&a,&b,&c
25、););函数声明函数声明if(dict(a,b,c)x1=root(a,b,c,1);/*调用函数调用函数root*/x2=root(a,b,c,0);printf(实根实根x1=%f,x2=%fn,x1,x2);else printf(无实根无实根!n);6.3 6.3 函数的调用函数的调用int dict(float a,float b,float c)int f;if(dt(a,b,c)=0)f=1;else f=0;return f;float root(float a,float b,float c,int flag)float d,x;d=dict(a,b,c);/*调用函数调用函
26、数 dict*/if(d)x=flag?(-b+sqrt(dt(a,b,c)/(2*a):(-b-sqrt(dt(a,b,c)/(2*a);return x;float dt(float a,float b,float c)return b*b-4*a*c;6.3 6.3 函数的调用函数的调用四四.函数的递归调用函数的递归调用 在在C语言的函数调用过程中,若函数直接或间接调用函数自语言的函数调用过程中,若函数直接或间接调用函数自身,则这种调用称为函数的身,则这种调用称为函数的“递归调用递归调用”。1.直接递归与间接递归直接递归与间接递归直接递归直接递归 函数体内直接调用函数自身函数体内直接调用
27、函数自身间接递归间接递归 函数调用其它函数,而其它函数又调用该函数自身函数调用其它函数,而其它函数又调用该函数自身(1)直接递归)直接递归void fun().fun();.(2)间接递归)间接递归void fun1().fun2();.void fun2().fun2();.6.3 6.3 函数的调用函数的调用 无论是直接还是间接递归,两者都是无终止的调用自身。无论是直接还是间接递归,两者都是无终止的调用自身。要避免这种情况的发生,使用递归解决的问题应满足两个要避免这种情况的发生,使用递归解决的问题应满足两个基本条件:基本条件:(1)问题的转化。有些问题不能直接求解或难以求解,但它)问题的转
28、化。有些问题不能直接求解或难以求解,但它可以转化为一个新问题,这个新问题相对较原问题简单或更接近可以转化为一个新问题,这个新问题相对较原问题简单或更接近解决方法。这个新问题的解决与原问题一样,可以转化为下一个解决方法。这个新问题的解决与原问题一样,可以转化为下一个新问题,新问题,。(2)转化的终止条件。原问题到新问题的转化是有条件的、)转化的终止条件。原问题到新问题的转化是有条件的、次数是有限的,不能无限次数地转化下去。次数是有限的,不能无限次数地转化下去。这个终止条件也称为边界条件,相当于递推关系中的初始条这个终止条件也称为边界条件,相当于递推关系中的初始条件。件。6.3 6.3 函数的调用
29、函数的调用例例6-9,编程求,编程求mn(m,n都为整数且都为整数且n0)。)。分析:分析:(1)mn可以通过累乘方法实现。可以通过累乘方法实现。(2)mn又可写成递归公式:又可写成递归公式:即即mn问题可以理解为:直接计算问题可以理解为:直接计算mn没有相应的运算没有相应的运算符,但可以将符,但可以将mn转化为转化为mmn-1的形式,即将的形式,即将mn转化为一转化为一个乘法问题,但乘法中的个乘法问题,但乘法中的mn-1与与mn是同一类问题,需要继是同一类问题,需要继续进行转化,直到问题趋于边界条件续进行转化,直到问题趋于边界条件m0=1。6.3 6.3 函数的调用函数的调用#include
30、 void main()int m,n;long power(int,int);/*函数声明函数声明*/scanf(%d,%d,&m,&n);printf(power(%d,%d)=%ldn,m,n,power(m,n);long power(int m,int n)/*求求mn的递归函数的定义的递归函数的定义*/long p;if(n=0)p=1;else p=m*power(m,n-1);/*注意:不可写成注意:不可写成power(m,n)=m*power(m,n-1)*/return p;6.3 6.3 函数的调用函数的调用23 2*22 2*21 2*20power(2,3)返回3 2
31、*power(2,2)返回2 2*power(2,1)返回1 2*power(2,0)23的递归执行过程 6.3 6.3 函数的调用函数的调用例例6-10,编程求裴波那契(,编程求裴波那契(Fibonacci)数列(数列(1,1,2,3,5,8,)第,)第10项的值。项的值。分析:分析:(1)对于裴波那契数列而言,有两个初始条件,分别为:)对于裴波那契数列而言,有两个初始条件,分别为:fib(1)=1,fib(2)=1;(2)其递推关系为从第三项开始,每项是前两项之和。因其递推关系为从第三项开始,每项是前两项之和。因此,可以写出它的递归公式:此,可以写出它的递归公式:6.3 6.3 函数的调用
32、函数的调用#include void main()int n;long m;long fib(int);/*函数声明函数声明*/scanf(%d,&n);/*求第求第n项项*/m=fib(n);printf(fib(%d)=%ldn,n,m);long fib(int n)/*定义函数定义函数fib及形参说明及形参说明*/if(n=1)return(1););else if(n=2)return(1);/*递归终止条件递归终止条件*/else return fib(n-1)+fib(n-2);6.3 6.3 函数的调用函数的调用例例6-11,汉诺(,汉诺(Hanoi)塔游戏。塔游戏。汉诺塔(汉
33、诺塔(Tower of Hanoi)游戏据说来源于布拉玛神庙。游戏的游戏据说来源于布拉玛神庙。游戏的装置如图装置如图6-6所示(图上以所示(图上以3个金片例),底座上有三根金的针,个金片例),底座上有三根金的针,第一根针上放着从大到小第一根针上放着从大到小64个金片。游戏的目标是把所有金片个金片。游戏的目标是把所有金片从第一根针移到第三根针上,第二根针作为中间过渡。每次只从第一根针移到第三根针上,第二根针作为中间过渡。每次只能移动一个金片,并且大的金片不能压在小的金片上面。该游能移动一个金片,并且大的金片不能压在小的金片上面。该游戏的结束就标志着戏的结束就标志着“世界末日世界末日”的到来。的到
34、来。ABC三个金片的Hanoi游戏的装置 6.3 6.3 函数的调用函数的调用ABC三个金片的Hanoi游戏的装置分析:分析:游戏中金片移动是一个很繁琐的过程。通过计算,对于游戏中金片移动是一个很繁琐的过程。通过计算,对于64个金个金片至少需要移动片至少需要移动 264 1=1.81019 次次。不妨用不妨用A表示被移动金片所在的针(源),表示被移动金片所在的针(源),C表示目的针,表示目的针,B表表示过渡针。对于把示过渡针。对于把n(n1)个金片从第一根针个金片从第一根针A上移到第三根针上移到第三根针C的问题可以分解成如下步骤:的问题可以分解成如下步骤:(1)将)将n-1个金片从个金片从A经
35、过经过C移动到移动到B。(2)将第将第n个金片移动到个金片移动到C。(3)再将再将n-1个金片从个金片从B经过经过A移动到移动到C。这样就把移动这样就把移动n个金片的问题转化为移动个金片的问题转化为移动n-1个金片的问题,个金片的问题,即移动即移动n个金片的问题可用移动个金片的问题可用移动n-1个金片的问题递归描述,以此个金片的问题递归描述,以此类推,可转化为移动一个金片的问题。显然,一个金片就可以直类推,可转化为移动一个金片的问题。显然,一个金片就可以直接移动。接移动。6.3 6.3 函数的调用函数的调用i=0;/*i为移动的次数,说明为全局变量为移动的次数,说明为全局变量*/void ha
36、noi(int n,int A,int B,int C)if(n=0)return;/*0个金片不处理个金片不处理*/if(n=1)step(A,C);/*n=1时,时,直接将金片从直接将金片从A移动到移动到C*/else hanoi(n-1,A,C,B);/*先将先将n-1个金片从个金片从A经过经过C 移动到移动到B*/step(A,C);/*将第将第n个金片从个金片从A移动到移动到C*/hanoi(n-1,B,A,C);/*再将再将n-1个金片从个金片从B经过经过A移动到移动到C*/void step(int take,int put)i+;/*第第i次移动次移动*/printf(%d%d
37、-%dn,i,take,put);#include void main()int n;printf(Input n:);scanf(%d,&n);hanoi(n,1,2,3);/*n个金片从第一根针经过第二根针移动到第三根针个金片从第一根针经过第二根针移动到第三根针*/6.3 6.3 函数的调用函数的调用程序运行结果:程序运行结果:Input n:31 1-3 32 1-2 23 3-2 34 1-3 15 2-1 36 2-3 27 1-3 3说明:说明:结果中第结果中第1列表示第列表示第i次移动,第次移动,第2列表示从第列表示从第m根针移动到第根针移动到第n根针,第根针,第3列表示第列表示
38、第i个金片(个金片(i=1,2,3,最小的金片编号为最小的金片编号为3)。)。递归在算法上简单而自然,递归过程结构清晰,源程序代码递归在算法上简单而自然,递归过程结构清晰,源程序代码紧凑,因而递归调用在完成诸如阶乘运算、级数运算以及对递紧凑,因而递归调用在完成诸如阶乘运算、级数运算以及对递归的数据结构进行处理等方面特别有效。归的数据结构进行处理等方面特别有效。6.3 6.3 函数的调用函数的调用2.递归与递推递归与递推递推方法递推方法程序描述繁杂,可读性差;程序描述繁杂,可读性差;主要采用循环技术;主要采用循环技术;逐步执行;逐步执行;当前值的求得总建立在前面求解的基础上;当前值的求得总建立在
39、前面求解的基础上;递归方法递归方法描述与原始问题(递归公式)比较接近;描述与原始问题(递归公式)比较接近;书写简洁、易读易写;书写简洁、易读易写;易于分析算法的复杂性和证明算法的正确性;易于分析算法的复杂性和证明算法的正确性;在问题转化时,需要花时间和存储空间将有关在问题转化时,需要花时间和存储空间将有关的的“现场信息现场信息”保存起来;当达到中止条件时,保存起来;当达到中止条件时,系统又需要花时间将有关的系统又需要花时间将有关的“现场信息现场信息”恢复恢复以便处理未曾处理完的函数调用以便处理未曾处理完的函数调用。占用存储空间少,执行速度快。占用存储空间少,执行速度快。6.3 6.3 函数的调
40、用函数的调用6.4 变量的作用域及存储特性变量的作用域及存储特性变量的数据类型变量的数据类型变量应占用的内存空间大小变量应占用的内存空间大小变量在存储空间分配时所限定的边界条件变量在存储空间分配时所限定的边界条件变量的性质变量的性质变量的数据类型变量的数据类型变量存储类型变量存储类型变量的作用域变量的作用域变量的生存期变量的生存期共同决定共同决定 6.4 6.4 作用域及存储特性作用域及存储特性一一.变量的作用域变量的作用域 例例6-12:void f1()int t=2;a*=t;b/=t;void main()int a,b;printf(”Enter a,b:”);scanf(”%d,%
41、d”,&a,&b);f1();/*调用函数调用函数f1()*/printf(”a=%d,b=%d”,a,b);编译程序会提示出错编译程序会提示出错:Undefined symbol a 和和 Undefined symbol b。为什么为什么?6.4 6.4 作用域及存储特性作用域及存储特性1.变量按作用域:分为全局变量和局部变量变量按作用域:分为全局变量和局部变量2.区别:区别:全局变量全局变量(外部变量外部变量)局部变量局部变量(内部变量内部变量)定义位置定义位置 函数体外函数体外函数体内函数体内作用域作用域从定义处到文件结束从定义处到文件结束从定义处到本函数结束从定义处到本函数结束举例举
42、例所有在函数体外定义的所有在函数体外定义的变量变量(1)所有在函数体内定所有在函数体内定义的变量义的变量(2)形式参数形式参数注意注意与局部变量同名的处理与局部变量同名的处理不同函数中同名局部变不同函数中同名局部变量互不干扰量互不干扰 6.4 6.4 作用域及存储特性作用域及存储特性例例6-13:#include int a,b;/*a,b为全局变量为全局变量*/void f1()int t1,t2;t1=a*2;t2=b*3;b=100;printf(”t1=%d,t2=%dn”,t1,t2);void main()a=2;b=4;f1();printf(”a=%d,b=%d”,a,b);程
43、序输出结果为程序输出结果为:t1=4,t2=12 a=2,b=100 6.4 6.4 作用域及存储特性作用域及存储特性例例6-14:#include int a=2,b=4;/*a,b为全局变量为全局变量*/void f1()int t1,t2;t1=a*2;t2=b*3;b=100;printf(”t1=%d,t2=%dn”,t1,t2);void main()int b=4;f1();printf(”a=%d,b=%d”,a,b);程序输出结果为程序输出结果为:t1=4,t2=12 a=2,b=4 结论:结论:全局变量与局部变量全局变量与局部变量同名时,局部变量的同名时,局部变量的作用域屏
44、蔽全局变量作用域屏蔽全局变量 6.4 6.4 作用域及存储特性作用域及存储特性二二.变量的存储特性变量的存储特性 变量的存储特性变量的存储特性变量的生存期变量的生存期变量的存储类型变量的存储类型变量的存在时间,即变变量的存在时间,即变量的存在性。量的存在性。变量存放的存储媒介变量存放的存储媒介,如存储器、外存储器如存储器、外存储器和和CPU的通用寄存器的通用寄存器.6.4 6.4 作用域及存储特性作用域及存储特性变量变量(按存在时间按存在时间)静态存储变量静态存储变量动态存储变量动态存储变量生存期为程序执行的整个生存期为程序执行的整个过程,在该过程中占有固过程,在该过程中占有固定的存储空间,也
45、称永久定的存储空间,也称永久存储。存储。只生存在某一段时间内只生存在某一段时间内函数的形参、函数体或分函数的形参、函数体或分程序程序中定义的变量,只有中定义的变量,只有当程序进入该函数或分程当程序进入该函数或分程序时才分配存储空间,函序时才分配存储空间,函数数/分程序执行完后,变量分程序执行完后,变量的存储空间又被释放。的存储空间又被释放。变量属性变量属性数据类型数据类型存储特性存储特性 6.4 6.4 作用域及存储特性作用域及存储特性C语言变量的存储特性可分四类:语言变量的存储特性可分四类:自动型(自动型(auto)、)、静态型(静态型(static)、)、外部型(外部型(extern)和寄
46、存和寄存器型(器型(register)。)。完整的变量定义完整的变量定义:存储特性存储特性 数据类型数据类型 变量名变量名;1.自动型变量自动型变量auto 类型标识符类型标识符 变量列表;常常可以缺省变量列表;常常可以缺省auto。函数内定义的局部变量、函数的形式参数和函数体内分程序函数内定义的局部变量、函数的形式参数和函数体内分程序的局部变量。的局部变量。随函数的调用而存在,随函数的返回而消失,它们在一次随函数的调用而存在,随函数的返回而消失,它们在一次调用结束到下一次调用开始之间不再占有存储空间。调用结束到下一次调用开始之间不再占有存储空间。作用域局限于所定义的函数,其生存期就是函数的生
47、存期,作用域局限于所定义的函数,其生存期就是函数的生存期,在一个函数中引用另一个函数的自动型变量的值是错误的。在一个函数中引用另一个函数的自动型变量的值是错误的。6.4 6.4 作用域及存储特性作用域及存储特性2.静态型变量静态型变量静态型变量是指在编译时分配存储空间的变量。静态型变量是指在编译时分配存储空间的变量。static 类型标识符类型标识符 变量列表;变量列表;局部静态变量局部静态变量全局静态变量全局静态变量 函数内部定义的静态变量函数内部定义的静态变量 作用域与自动型变量相同作用域与自动型变量相同 当所在的函数执行结束后,静态变量所占内当所在的函数执行结束后,静态变量所占内存单元并
48、不释放,其值仍然保留存单元并不释放,其值仍然保留 函数外部定义的静态变量函数外部定义的静态变量 在程序中凡未指明存储类型的全局变量均为在程序中凡未指明存储类型的全局变量均为全局静态型变量全局静态型变量 作用域仅限所在的源程序文件作用域仅限所在的源程序文件 6.4 6.4 作用域及存储特性作用域及存储特性例例6-15:int f(int a)int b=0;static int c=3;b+;c+;printf(”%5d%5d%5d”,a,b,c);return(a+b+c);void main()int a=2,k;for(k=0;k3;k+)printf(”%5dn”,f(a);6.4 6.
49、4 作用域及存储特性作用域及存储特性3.外部型变量外部型变量“外部型变量外部型变量”,是相对于在函数,是相对于在函数“内部内部”说明的变量而言的,说明的变量而言的,它们在函数外部定义。它们在函数外部定义。外部型变量又称为全局变量,在编译时分配存储单元。外部型变量又称为全局变量,在编译时分配存储单元。若变量的定义与引用分别处于不同的源文件中,即将变量的作若变量的定义与引用分别处于不同的源文件中,即将变量的作用域延伸到其它的源文件,则需要在引用该变量的源文件中对用域延伸到其它的源文件,则需要在引用该变量的源文件中对它进行声明,源文件或源文件中的函数才能引用它。它进行声明,源文件或源文件中的函数才能
50、引用它。若变量的定义与引用处于同一源文件(即全局变量),在该若变量的定义与引用处于同一源文件(即全局变量),在该源文件的不同函数中可以通过变量名对这种变量进行直接引用,源文件的不同函数中可以通过变量名对这种变量进行直接引用,作用域在整个源文件。作用域在整个源文件。外部型变量的声明形式:外部型变量的声明形式:extern 类型标识符类型标识符 变量名;变量名;在引用外部型变量的文件中不会为其分配内存空在引用外部型变量的文件中不会为其分配内存空间。间。外部型变量生存期为整个程序的运行期。外部型变量生存期为整个程序的运行期。6.4 6.4 作用域及存储特性作用域及存储特性void main()/*在