版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第六章函数与编译预处理6.1模块化程序设计与函数6.2函数的定义与调用6.4变量作用域与存储方式返回6.3函数的递归调用6.5编译预处理C语言程序设计教程6.1模块化程序设计与函数
在设计较复杂的程序时,我们一般采用的方法是:把问题分成几个部分,每部分又可分成更细的若干小部分,逐步细化,直至分解成很容易求解的小问题。这样的话,原来问题的解就可以用这些小问题来表示。下一页第6章函数与编译预处理基本模块模块模块模块模块模块模块模块模块模块模块结构图6.1.1模块与函数
C语言程序由基本语句和函数组成,每个函数可完成相对独立的任务,依一定的规则调用这些函数,就组成了解决某个特定问题的程序。下一页第6章函数与编译预处理把大任务分解成若干功能模块,用多个函数来实现这些功能模块。通过函数的调用来实现完成大任务的全部功能。任务、模块与函数的关系:一个大任务分成多个功能模块,功能模块则由一个或多个函数实现。模块化的程序设计是靠设计函数和调用函数实现的。任务:输入三个数,从大到小的顺序的输出。如果大于等于85,在该数后面输出‘A’,小于85且大于等于70,则输出‘B’,小于70且大于等于60,输出‘C’,如果小于60,则输出‘D’。思路:scanf()输入分数
另建一个排序函数
判断并输出等级函数
打印分数及等级的函数虽然也可以由一个主函数来完成,但这样做可读性及操作性会更好。下一页第6章函数与编译预处理例如分数排序点击查看程序请6.1.2模块设计的原则模块独立规模适当层次分明功能专一下一页第6章函数与编译预处理
独立性原则表现在模块完成独立的功能,和其它模块间的关系简单,各模块可以单独调试。修改某一模块,不会造成整个程序的混乱。要做到模块的独立性要注意以下几点每个模块有特定功能每个模块力求简单每个模块应用独立变量点击模块不能太大,但也不能太小。模块的功能复杂,可读性就不好,而且也违背独立性原则。但如果做得太小,实际上也会复杂各个模块间反复调用,可读性也会降低。这点需要慢慢积累经验,好好把握。下一页第6章函数与编译预处理分解模块要注意层次,更多层次的分解任务,要注意对问题进行抽象化。开始不要过于注意细节,要做到逐步细化求精。6.1.3算法描述简介什么是算法?
通俗地说,算法是解决一类特定问题的方法和步骤。
算法是一个有限操作的序列。算法的每一步都是确定的。算法的每一步计算机都能操作。有一个或多个的输入或输出。下一页第6章函数与编译预处理算法描述的任务是将解题步骤和方法用一定的形式表示出来,要清楚、准确、严谨,还要可读性好,方便实现。
算法两大要素:
一是操作,用类计算机语句或自然语言描述。
二是控制结构,描述算法一般可以用流程图描述。设计算法:找出a,b两数中的较大者,并输出分析:这个问题分三个步骤:输入两个数;找出其中的大数;输出大数。第6章函数与编译预处理例6.2开始输入a,ba<b交换a,b输出a结束非00算法流程图见右返回6.2函数的定义与调用在C语言中,函数(Function)是一个处理过程,可以进行数值运算、信息处理、控制决策,即一段程序的工作放在函数中进行,函数结束时可以携带或不带处理结果。
库函数(标准函数):系统提供
自定义函数:用户自己写下一页第6章函数与编译预处理
C语言程序处理过程全部都是以函数形式出现,最简单的程序至少也有一个main函数。函数必须先定义和声明后才能调用。“函数”的主要知识点函数的定义函数的参数和返回值函数的调用嵌套和递归变量的作用域下一页第6章函数与编译预处理6.2.1标准库函数C语言有丰富的库函数,这些函数的说明在不同的头文件(*.h)中。想要调用标准的库函数,就必须include。#include<stdio.h>
main()
{printf(“%d”,1024*768);
}调用printf函数时,
必须include<stdio.h>下一页第6章函数与编译预处理6.2.2函数的定义可以把完成一个任务的过程写成函数。intA_to_a(intcapital)
{intsmall;
if(capital>=‘A’&&capital<=‘Z’)
small=capital–(‘A’-’a’);
returnsmall;
}返回值类型名函数名注意不要与已有库函数重名参数说明和参数列表调用函数时输入参数的格式要与之相同定义局部变量最好只使用局部变量,这样将方便调试。另外请注意这样的判断,如写成‘A’<capital<‘Z’是不行的下一页第6章函数与编译预处理如果不需返回则可return0;返回值#include<conio.h>
main()
{inta,b,m;
/*说明变量*/
intmax(inta,intb);
/*函数声明*/
scanf("%d,%d",&a,&b);
/*调用库函数scanf*/
m=max(a,b);
/*调用字定义函数max*/
printf("max=%d\n",m));
/*调用库函数printf*/
getch();
/*调用库函数getch*/
}下一页第6章函数与编译预处理举例intmax(inta,intb)
/*定义函数max*/
{
inty;
y=(a>b)?a:b;/*条件表达式*/
returny;
}if(a>b)y=a;
elsey=b;自定义函数的声明自定义函数在调用前应先声明。使系统知道将要用到某个函数及它的类型,以便处理。函数声明应与该函数定义时给出的函数类型与名字、形参的个数、类型、次序相一致。#include“stdio.h”
voidmain()
{floatx,y;
intn;
floatpower(floatx,intn);
scanf("%f,%d",&x,&n);
y=power(x,n);printf(“%8.2f”,y);
}floatpower(floatx,intn)
{inti;
floatt=1;
for(i=1;i<=n;i++)
t=t*x;
returnt;
}下一页第6章函数与编译预处理求1!+2!+3!+…+10!算法:i=1;s=0;当i<=10s=s+i!定义求i!的函数下一页第6章函数与编译预处理voidmain(){longmm(int);/*自定义求阶乘函数应先声明*/inti;longs=0;for(i=1;i<=10;i++)s+=mm(i);/*调用求阶乘函数,求I的阶乘*/printf(“\n%ld”,s);}举例longmm(intn){longt=1;inti;for(i=1;i<=n;i++)t*=i;returnt;}定义n!的函数同样的,在调用
m=max(a,b)时,其形参
的值是a和b
而m将会得到y的值函数的参数intmax(inta,intb)
{
inty;
y=(a>b)?a:b;
returny;
}调用时:m=max(3,6);
m=max(a,b);
返回值括号里是实参在这一句调用时,
形参的值是3和6其返回值y将被赋给
调用语句中的m下一页第6章函数与编译预处理括号里是形式参数形式参数与实际参数的关系形式参数在函数中是变量名,在函数调用时,形参被分配相应的内存。实际参数是表达式负责向对应的形参标识的内存单元传递数据。实参与形参必须个数相同。对应的形参和实参的类型必须一致。
下一页第6章函数与编译预处理主调函数中有如下语句:
scanf("%d,%d",&a,&b);
m=max(a,b+3);如果输入6,2函数intmax(inta,intb)形参a得到第一个实际参数a的值6形参b得到第二个实际参数b+3的值5例如函数返回值函数返回值通过return语句获得函数返回值的类型就是函数的类型
returny;
将变量y的值返回给调用者
returny+3;
将表达式的值返回给调用者
return的数据类型与函数的类型矛盾时,自动将数据转换成函数的类型下一页第6章函数与编译预处理intfunct1(){charch;
while
((ch=getch())<'a'||(ch>'z')
;
returnch;}调用:i=funct1();返回的是int类型函数没有返回值,函数定义成空类型voidputline()
{inti;
for(i=0;i<35;i++)
printf("-");
printf("\n");
}函数的功能就是输出35个‘-’调用:putline();
应该的语句形式
i=putline();是错的下一页第6章函数与编译预处理调用函数a=function(x,y);
或者
function(x,y);取返回值
只是操作解决更复杂问题时可以嵌套调用longfac(intk)
{longf=1;
inti;
for(i=1;i<=n;i++)
f=f*i;
retrunf;
}
longcombination(intn,intm)
{longc;
inti;
c=fac(m)/(fac(n)*fac(m-n));
retrunc;
}
主函数:
main()
{intn,m;
longc;
scanf(“%d,%d”,&n,&m);
c=combination(n,m);
prnit(“%ld”,c);
}理论上可以a(b(d(e(x))),c(f))
般嵌套无数层第6章函数与编译预处理返回6.3函数的递归调用函数调用它本身,称为递归。直接在函数内调用自己为直接递归,通过别的函数调用自己为间接递归。voida()
{......
a();
......
}voida()
{......
b();......
}
voidb()
{......
a();
......
}递归在解决某些问题中,是一个十分有用的方法。因为其一,有的问题它本身就是递归定义的;其二,它可以使某些看起来不易解决的问题变得容易解决,写出的程序较简短。下一页第6章函数与编译预处理例:递归方法求n!
由于n!=n*(n-1)!是递归定义所以求n!(n-1)!
(n-1)!(n-2)!
(n–2)!(n-3)!
……
0!的问题,
根据公式有0!=1。
再反过来依次求出1!,2!……直到最后求出n!。下一页第6章函数与编译预处理longfac(intn)
{longf;
if(n==0)
f=1;
else
f=n*fac(n-1);
returnf;
}
main()
{longy;
intn;
scanf(“%d”,&n);
y=fac(n);
printf(“%d!=%ld”,n,y);
}刚开始的时候,这个n是前面
输入的需要阶乘的n所以在这里带入的值是n而这个函数里又调用
了本身,不过参数已经
变成了n-1所以这里再次调用时
参数已经变成了n-1注意:上次调用fac(n)
还没有完,只是由于遇到
了fac(n-1)而执行fac(n-1)
去了.而在调用fac(n-1)时同样
遇到了要调用fac(n-2)的问题,于是一层一层的
包裹下去,每次调用的
时候都会在内部调用一
个结构相同但变量不同
的函数,直到。。。直到调用到fac(0)时,由于内部if判断,已经
不需要再继续调用另一
个fac(n-1),而直接有了f=1fac(0)已经执行完毕,它的
返回值被fac(1)中的f=n*fac(n-1)
语句赋给了f值,同时返回了f。而这个返回的f又被fac(2)乘上
当前的n值以后继续返回f直到最后的fac(n)都做完了,
f的值被返回到了它的调用点:
主函数中,这样就是一个递归
运算。下一页第6章函数与编译预处理程序
第1个月有1对兔子过2个月,兔子就可每个月生1对兔子问第n个月有多少对兔子?分析:设第n个月有f(n)对兔子根据题意有f(0)=0,f(1)=1f(n)=f(n-1)+f(n-2)f(n-1):前一个月的兔子数f(n-2):本月生的兔子数
下一页第6章函数与编译预处理递归举例例1定义函数f(n)longf(intn)
{
switch(n){case0:return0;break;case1:return1;break;
default:returnf(n-1)+f(n-2);/*调用函数f(n)*/}}下一页第6章函数与编译预处理下一页第6章函数与编译预处理兔子问题主函数voidmain(){longf(intn);/*自定义函数声明*/intn;
printf(“\ninputn:”);
scanf(“%d”,&n);printf(“\nf(%d)=%ld”,n,f(n));
/*调用函数f(n)*/}辗转相除法求最大公约数
求m和n的公约数算法if(m%n)==0
n是公约数;else
求n和m%n的公约数;下一页第6章函数与编译预处理例2求最大公约数的递归算法
intgcd(intm,intn)
{if(m%n)==0
returnn;else
returngcd(n,m%n);}求最大公约数的主函数voidmain(){intm,n,t;
intgcd(intm,intn);
scanf(“%d%d”,&m,&n);
if(m<n)
{t=a;a=b;b=t;}
t=gcd(m,n);/*调用函数gcd(m,n);
*/print(“\ngcd=%d”,t);}下一页第6章函数与编译预处理汉诺塔说到递归,就不能不提汉诺塔,汉诺塔是一个很繁杂的游戏,但可以用递归的方法异常简单的完成。规则:(1)一次只能移动一个
(2)大的不能放在小的上面
(3)只能在三个位置中移动下一页第6章函数与编译预处理例3问题可分为三个步骤对于把n个金片从第一根针a上移到第三根针c的问题可以分解成如下步骤:(1)将n-1个金片从a经过c移动到b。(2)将第n个金片移动到c。(3)再将n-1个盘子从b经过a移动到c。这样我们就将移动n个金片的问题变成了移动n-1个金片的问题。这样做下去的话最后就会变成移动一个金片的问题。下一页第6章函数与编译预处理递归方法解汉诺塔voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}n=1时,直接将金片
从a移动到cn-1个金片从a经过
c移动到b将第n个金片
从a移动到c再将n-1个盘子从
b经过a移动到c主函数:main()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}下一页第6章函数与编译预处理递归汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}main()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}输入3则n=3下一页第6章函数与编译预处理n=3
主函数调用hanoi(n,1,2,3);第一次调用。第一次调用hanoi(n,a,b,c)(第一层)即要把三个金片移到c由于n>1则执行hanoi(n-1,a,c,b)
(第二次调用)n=3由于仍然n>1则执行
hanoi(n-1,a,c,b)
(第三次调用)n=2下一页第6章函数与编译预处理递归汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}main()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}由于n=1则printf(“%d->%d”,a,c);
即把金片从a移动到cn=1下一页第6章函数与编译预处理第三层执行完毕,返回到第二层,即下去执行printf(“%d->%d”,a,c);把第二个金片摆到第二根针上n=2递归汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}main()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}执行下一条语句,又调用第三层hanoi(1,3,1,2)n=2n=1,执行结果为32n=1下一页第6章函数与编译预处理
递归汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}main()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}第二层也执行完了,返回第一层,执行接下来的语句,结果为13。n=3下一页第6章函数与编译预处理递归汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}main()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}
执行接下来的语句,再次调用
第二层hanoi(2,2,1,3)n=3第6章函数与编译预处理递归汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}main()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}
返回6.4变量的作用域与存储方式先看一个例子,错在那里?:voidf1(){intt=2;a*=t;b/=t;}main(){inta,b;printf(“Entera,b:”);scanf(“%d,%d”,&a,&b);f1();/*调用函数f1()*/printf(“a=%d,b=%d”,a,b);}编译程序会提示出错:Undefinedsymbol‘a’和Undefinedsymbol‘b’
。为什么?下一页第6章函数与编译预处理变量的作用域即变量的有效范围1.变量按作用域分为全局变量和局部变量2.比较:全局变量(外部变量)局部变量(内部变量)
定义位置:函数体外函数体内作用域:从定义处到本源从定义处到本函数结束文件结束举例:所有函数体外定义(1)所有在函数体内定义的变量(2)形式参数
与局部变量同名的处理局部变量屏蔽全局变量不同函数中同名局部变量互不干扰下一页第6章函数与编译预处理6.4.1变量的作用域注意#include<stdio.h>inta,b;/*a,b为全局变量*/voidf1(intx){intt1,t2,a;a=t1=x*4;t2=b*3;b=10;printf(“f1:t1=%d,t2=%d,a=%d,b=%d\n”,t1,t2,a,b);}main(){a=2;b=4;/*此a,b是全局变量,赋值*/f1(a);/*调用函数f1()*/printf(“main:a=%d,b=%d”,a,b);}f1:t1=8,t2=12,a=8,b=10main:a=2,b=10下一页第6章函数与编译预处理举例结果若将程序改为:#include<stdio.h>inta=2,b=4;/*a,b为全局变量*/voidf1(){intt1,t2;t1=a*2;t2=b*3;b=100;printf(“t1=%d,t2=%d,b=%d\n”,t1,t2,b);}main(){intb=4;/*此b是局部变量,赋值*/f1();/*调用函数f1()*/printf(“a=%d,b=%d”,a,b);}全局变量与局部变量同名时,局部变量起作用,全局变量被屏蔽(不影响),应小心使用下一页第6章函数与编译预处理结果t1=4,t2=12,b=100a=2,b=4结论变量按存在时间分静态存储类型的变量的生存期为程序执行的整个过程,在该过程中占有固定的存储空间,通常称它们为永久存储。动态存储类型变量只生存在某一段时间内。例如,函数的形参和函数体或分程序中定义的变量,只是在程序进入该函数或分程序时才分配存储空间,当该函数或分程序执行完后,变量对应的存储空间又被撤销了。下一页第6章函数与编译预处理6.4.2变量的存储方式静态变量动态变量
c语言中每一个变量有两个属性:数据类型,存储特性完整的变量定义:[存储特性][数据类型]变量名;
自动型auto静态型static寄存器型register外部型extern(1)auto型:每次进入程序是自动分配内存,不长期占用内存例如:形式参数,自动型局部变量(2)static型:①局部静态变量②全局静态变量长期占用内存下一页第6章函数与编译预处理变量的存储特性分析执行结果f(inta){intb=0;staticintc=3;b++;c++;printf(“%5d%5d%5d”,a,b,c);return(a+b+c);}main(){inta=2,k;for(k=0;k<3;k++)printf(“%5d\n”,f(a));}静态变量只初始化一次结果:214(a,b,c)7(f(a))21582169下一页第6章函数与编译预处理例如
数据内存运算器运算器结果控制器数据寄存器寄存器变量只限于整型、字符型、指针型的局部变量。寄存器变量是动态变量,而且数目有限,一般仅允许说明两个寄存器变量。例如:registerintd;registercharc;下一页第6章函数与编译预处理(3)register型将使用频率高的变量定义为register型,可以提高运行速度。(4)extern型引用:
extern类型变量名;如果某个模块文件中要用到另一个模块文件中的全局变量,就要用extern说明第6章函数与编译预处理例如:程序模块file1.c中定义了全局变量
ints;而在另一个程序模块file2.c中的函数fun1()中需要使用这个变量s。为此,可以在file2.c的函数fun1()中加上外部变量说明语句:fun1(){externints;/*表明变量s是在其他文件定义的*/.......}定义时分配内存,其他文件引用时不再分配内存.返回6.5编译预处理“编译预处理”是C语言编译系统的一个组成部分。是在编译前由编译系统中的预处理程序对源程序的预处理命令进行加工。源程序中的预处理命令均以“#”开头,结束不加分号,以区别源程序中的语句,它们可以写在程序中的任何位置,作用域是自出现点到源程序的末尾。预处理命令包括执行宏定义(宏替换)、包含文件和条件编译。下一页第6章函数与编译预处理1.一般形式为:#define宏名串(宏体)如:#definePI3.14159/*定义后,可以用PI来代替串3.14159*/2.宏定义的作用:在宏定义之后,该程序中宏名就代表了该字符串。3.说明①可以用#undef命令终止宏定义的作用域。例如:#undefPI②宏定义的嵌套使用#defineR3.0#definePI3.1415926#defineL2*PI*R/*宏体是表达式*/#defineSPI*R*R下一页第6章函数与编译预处理6.5.1宏定义无参宏定义例如:main(){printf("L=%f\nS=%f\n",L,S);/*2*PI*R替换L,PI*R*R替换S*/}程序运行结果:L=18.849556③双引号内与宏同名的字母不作宏展开.(见上例)带参数的宏定义1.带参数的宏定义的一般形式为#define宏名(参数表)字符串如:#defineS(a,b)a*b#definePR(x)printf("s=%f\n”,x)下一页第6章函数与编译预处理2.带实参的宏名被展开下一页第6章函数与编译预处理宏定义与函数的区别
(1)引用宏只占编译时间,不占运行时间。(2)引用宏没有返回值
宏名被所定义的宏体替换,宏体中的形参按从左到右的顺序被实参替换。例如:area=S(3,2);PR(area);展开为:area=3*2;PR(area)展开的结果是:printf("s=%f\n”,area);#definesqu(n)n*nvoidmain(void){printf("%f\n",27.0/squ(3.0));}注意,展开为27.0/3.0*3.0不是27.0/(3.0*3.0)程序输出结果为:27.000000例如下一页第6章函数与编译预处理(3)宏替换的形参无类型(4)实参为表达式的情况函数调用是先计算出实参的值,再将值传递给形参;宏的引用是用表达式替换形参.
例如:
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026年自动驾驶保险合同协议
- 结构加固修复用纤维复合材料片材
- 山东大学哲学试题及答案
- 四川省泸州市多校联考2025-2026学年高二下学期期末联合考试生物学试题(文字版含答案)
- 消防安全四大能力解析
- 施工企业管理人员及特种作业人员持证上岗制度
- 市政道路建设瓦斯爆炸专项应急预案
- 肉牛养殖项目可行性研究报告
- 2026年直播话术综合培训测试卷含答案解析
- 公司招商会活动策划方案
- 屋顶光伏发电项目EPC总承包工程招标文件
- 渤海大学《大学物理》2018-2019期末试卷(C卷)
- 舞台用升降机械系统
- 房产测量作业指导书
- 学前儿童发展心理学PPT中职完整全套教学课件
- 金融专业英语PPT完整全套教学课件
- 新汉语水平考试HSK5级写作解题攻略
- RS1200软件操作手册(C-MARK)
- 绝缘子的污闪与防治
- 泌尿外科-泌尿系梗阻的诊疗
- JJG 703-2003光电测距仪
评论
0/150
提交评论