函数省公开课一等奖全国示范课微课金奖_第1页
函数省公开课一等奖全国示范课微课金奖_第2页
函数省公开课一等奖全国示范课微课金奖_第3页
函数省公开课一等奖全国示范课微课金奖_第4页
函数省公开课一等奖全国示范课微课金奖_第5页
已阅读5页,还剩82页未读 继续免费阅读

下载本文档

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

文档简介

第5章函数

C语言程序设计主编:冉崇善第1页人们在求解一个复杂问题时,通常采取是化整为零、逐一处理方法,也就是把一个大问题分解成若干个比较轻易求解小问题,然后分别求解。程序员在设计一个复杂应用程序时,往往也是把整个程序划分为若干功效较为单一程序模块,然后分别给予实现,最终再把全部程序模块像搭积木一样装配起来,这种在程序设计中逐一处理分而治之策略,被称为模块化程序设计方法。在C语言中,函数是程序基本组成单位(相当于其它高级语言子程序),所以能够很方便地用函数作为程序模块来实现C语言程序。C语言不但提供了极为丰富库函数(如Turbo

C、MS

C都提供了三百多个库函数),还允许用户建立自己定义函数。用户可把自己算法编成一个个相对独立函数模块,然后用调用函数换算方法来使用函数。这不但能够实现程序模块化,而且还能够使程序层次结构清楚,便于程序编写、阅读、调试。第2页5.1函数分类

5.1.1库函数和用户自定义函数从函数定义角度看,函数可分为库函数和用户自定义函数两种。1.库函数由C系统提供,用户无须定义,也无须在程序中作类型说明,只需在程序前包含有该函数原型头文件(即可在程序)中直接调用。在前面各章例题中重复用到printf、scanf、getchar、putchar、gets、puts、strcat等函数均属这类。2.用户自定义函数由用户按需要自己编写函数,称为用户自定义函数。对于用户自定义函数,不但要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。

第3页

5.1.2有返回值函数和无返回值函数C语言函数兼有其它语言中函数和过程两种功效,从这个角度看,可把函数分为有返回值和无返回值函数两种。1.有返回值函数这类函数被调用执行完后将向调用者返回一个执行结果,称为函数返回值。如数学函数即属于这类函数。由用户定义要返回函数值函数,必须在函数定义和函数说明中明确返回值类型。2.无返回值函数

这类函数用于完成某项特定处理任务,执行完成后不向调用者返回函数值。这类函数类似于其它语言过程。因为函数无须返回值,用户在定义这类函数时可指定它返回为“空类型”,空类型说明符为“void”。第4页

5.1.3无参函数和有参函数从主调函数和被调函数之间数据传送角度看函数可分为无参函数和有参函数两种。1.无参函数函数定义、函数说明及函数调用中均不带参数。主调函数和被调函数之间不进行参数传递。这类函数通惯用来完成一组指定功效,能够返回或不返回函数值。2.有参函数有参函数,也称为带参函数。在函数定义及函数说明时都有参数,此时参数称为形式参数(简称为形参)。在函数调用时也必须给出参数,此时参数称为实际参数(简称为实参)。进行函数调用时,主调函数将把实参值传递给形参,供被调函数使用。第5页5.2函数定义函数(含主函数)都是由函数说明和函数体两部分组成。5.2.1无参函数定义普通形式函数类型函数名(void){

说明语句部分;可执行语句部分;

}注意:在旧标准中,函数能够缺省参数表。但在新标准中,除主函数main()外,函数不可缺省参数表,假如不需要参数,则用“void”表示。

函数体函数说明第6页5.2.2有参函数普通形式函数类型函数名(

数据类型参数1[,数据类型

参数2…]

)

{说明语句部分;可执行语句部分;}形式参数:将函数定义中参数表称为形式参数表,简称形参表。与调用函数提供实际参数区分。

实际参数:调用有参函数时,调用函数必须赋予这些参数实际值,调用函数中参数称为实际参数。第7页空函数其形式为:

[函数类型]函数名(void){}“空函数”既无参数、函数体为空函数,什么操作也不做。其作用是在此处留一函数位置,方便未来扩充功效之用。函数名也在未来换取实际函数名。第8页函数定义说明:⑴在C语言中,全部函数(包含主函数main())都是平行、独立。

⑵在一个包含多个函数程序中,一个函数定义,能够放在程序中任意位置,即能够放在主函数main()之前或放在主函数main()之后。

⑶在一个函数函数体内,不能再定义另一个函数,即函数不能嵌套定义。

⑷函数之间允许相互调用,以能够嵌套调用。第9页

例5.1在程序主函数中,执行了3次调用无参函数s()语句。

#include<stdio.h>voidmain(){voids();/*函数调用说明*/inti=0,a=10;while(i<3){++a;printf(“%d,”,a++);s();i++;}}voids(void){intz=300;z++;printf(“%d\n”,z++);}函数调用运行结果:11,30113,30115,301定义函数无参数无返回值第10页

例5.2定义一个函数,用于求两个数中大数。

main(){intmax(intx,inty);/*函数调用说明*/inta1,a2,a3;printf("inputtwonumbers:\n");scanf("%d%d",&a1,&a2);a3=max(a1,a2);printf("max=%d\n",a3);}intmax(intx,inty)

{intz;

z=x>y?x:y;return(z);

}带参数用户自定义函数第11页

intmax(intx,inty)

{intz;

z=x>y?x:y;return(z);

}

在老版本C语言中,参数类型说明允许放在函数说明部分第2行单独指定。

例: intmax(x,y) intx,y;

{intz;

z=x>y?x:y; return(z); }在新版本中写成一行:

intmax(intx,inty)在老版本中写成二行:

intmax(x,y)intx,y;第12页5.2.3函数说明与返回值

当一个函数没有明确说明类型时,C语言编译程序自动将整型(int)作为该函数默认类型,默认类型适合用于很大一部分函数。当有必要返回其它类型数据时,需要分两步处理。(1)必须给函数以明确类型说明符;(2)函数类型说明必须处于对它首次调用之前。只有这么,C编译程序才能为返回非整型值函数生成正确代码。

1.函数类型说明可将函数说明为返回任何一个正当C语言数据类型。类型说明符告诉编译程序它返回什么类型数据。这个信息对于程序能否正确运行关系极大,因为不一样数据有不一样长度和内部表示。第13页

返回非整型数据函数被使用之前,必须把它类型向程序其余部分说明。若不这么做,C语言编译程序就认为函数是返回整型数据函数,假如调用点又在函数类型说明之前,编译程序就会对调用生成错误代码。为了预防上述问题出现,必须使用一个尤其说明语句。对被调函数说明有两种格式,一个为传统格式,一个为当代格式。(1)传统格式普通格式为:

类型说明符被调函数名();

这种格式只给出函数返回值类型,被调函数名及一个空括号。因为在括号中没有任何参数信息,所以不便于编译系统进行错误检验,易犯错。(2)当代格式普通形式为:

类型说明符

被调函数名(类型

形参,类型形参…);或类型说明符

被调函数名(类型,类型…);

当代格式括号内给出了形参类型和形参名,或只给出形参类型。这便于编译系统进行检错,以预防可能出现错误。第14页

例5.3main函数中对max函数说明若用传统格式可写为:

int

max();

用当代格式可写为:

int

max(int

a,int

b);

或写为:int

max(int,int);

C语言中又要求在以下几个情况时能够省去主调函数中对被调函数函数说明。

(1)假如被调函数返回值是整型或字符型时,能够不对被调函数作说明,而直接调用。这时系统将自动对被调函数返回值按整型处理。(2)当被调函数函数定义出现在主调函数之前时,

在主调函数中也能够不对被调函数再作说明而直接调用。比如,例5-1中,

函数max定义放在main

函数之前,所以可在main函数中省去对max函数函数说明int

max(int

a,int

b)。

(3)如在全部函数定义之前,在函数外预先说明了各个函数类型,则在以后各主调函数中,可不再对被调函数作说明。比如:

第15页char

str(int

a);

float

f(float

b);

main()

{

…}

char

str(int

a)

{…}

float

f(float

b)

{…}

其中第1、2行对str函数和f函数预先作了说明。

所以在以后各函数中无须对str和f函数再作说明就可直接调用。

第16页(4)对库函数调用不需要再作说明,

但必须把该函数头文件用include命令包含在源文件前部。数组作为函数参数数组能够作为函数参数使用,进行数据传送。

数组用做函数参数有两种形式,一个是把数组元素(下标变量)作为实参使用;

另一个是把数组名作为函数形参和实参使用。数组元素作函数实参时数组元素就是下标变量,它与普通变量并无区分。所以它作为函数实参使用与普通变量是完全相同,在发生函数调用时,把作为实参数组元素值传送给形参,实现单向值传送。第17页

2.函数返回值C语言函数从有没有返回值这个角度考虑,可把函数分为有返回值函数和无返回值函数两种。有参函数返回值,是经过函数中return语句来取得。(1)返回语句格式:

return(表示式);(2)return语句功效:返回调用函数,并将“表示式”值带给调用函数。

(3)

return语句返回值类型应与该函数类型一致。不然以函数类型为准。

第18页

(4)

return语句后面能够是变量,也能够是表示式。

如:

return(x>y?x:y);

(5)

return语句后面能够有括号,也能够没有。如:

returnz

;

return(z);

(6)

函数返回值是经过return语句取得。当不需返回函数值时,可省去return语句。

调用函数中无return语句,并不是不返回一个值,而是一个不确定值。为了明确表示不返回值,能够用“void”定义成“无(空)类型”。第19页

若函数中没有return语句,则该函数被调用后也会带回不确定值。如在执行以下函数体语句:

{inta,b,c;a=printstar();b=print_message();c=printstar();printf(“%d,%d,%d\n”,a,b,c);

}

输出a,b,c值将是各个被处理字符串长度。

为了明确表示不需要函数返回值,能够用“void”定义函数为“无类型”。此时,不得使用a=printstar()之类语句。3个被调函数中均没有return语句

第20页例5.4主函数分别调用空类型函数fu1和返回整型值fu2函数。程序以下:#include<stdio.h>main(){intfu1(inta,intb),fu2(inta,intb);inta,b;printf("inputtwonumbers:\n");scanf(“%d%d”,&a,&b);

fu1(a,b);

printf(“(a+b)*(a+b)=%d\n”,

fu2(a,b));}

第21页

voidfu1(intx,inty){intz1;z1=x*x+y*y;printf("x*x+y*y=%d\n",z1);}intfu2(intx,inty){intz2;z2=(x+y)*(x+y);

return(z2);}

其中fu1(),fu2()两个函数为用户自己定义函数,需要用户指定函数名及函数体内语句。无返回值函数返回值函数第22页5.3函数作用域规则“语言作用域规则”是一组确定一部分代码是否“可见”或可访问另一部分代码和数据规则。C语言中每一个函数都是一个独立代码块。一个函数代码块是隐藏于函数内部,不能被任何其它函数中任何语句(除调用它语句之外)所访问(比如,用goto语句跳转到另一个函数内部是不可能)。组成一个函数体代码对程序其它部分来说是隐蔽,它既不能影响程序其它部分,也不受其它部分影响。换言之,因为两个函数有不一样作用域,定义在一个函数内部代码和数据无法与定义在另一个函数内部代码和数据相互作用。C语言中全部函数都处于同一作用域级别上。这就是说,把一个函数定义于另一个函数内部是不可能。第23页

5.3.1局部变量在函数内部定义变量称为局部变量。在一些C语言教材中,局部变量又称为自动变量,这就与使用可选关键字auto定义局部变量这一作法保持一致。局部变量仅由其被定义模块内部语句所访问。换言之,局部变量在自己代码模块之外是不可知。注意:模块以左大括号开始,以右大括号结束。对于局部变量,最主要是它们仅存在于被定义当前执行代码块中,即局部变量在进入模块时生成,在退出模块时消亡。定义局部变量最常见代码块是函数。比如,考虑例5-7中两个函数。例5.5局部变量示例。第24页func1(){

intx; /*可定义为autointx;*/

x=10;}func2(){

intx; /*可定义为autointx;*/

x=-1999;}说明整数变量x被定义了两次,一次是在func1()中,一次是在func2()中。func1()和func2()中x互不相关。其原因是每个x作为局部变量仅在被定义函数块内可知。C语言中包含了关键字auto,它可用于定义局部变量。但自从全部非全局变量默认值假定为auto以来,auto就几乎极少使用了,所以在本教材全部示例中,均见不到这一关键字。第25页在每一函数模块内开始处定义全部需要变量,是最常见作法。这么做使得任何人读此函数时都很轻易,首先了解用到变量。但并非得这么做不可,因为局部变量能够在任何模块中定义。为了解其工作原理,请看例5-8中函数。例5.6局部变量示例。f(){

intt;

scanf("%d",&t);

if(t==1){

chars[80];/*此变量仅在这个块(复合语句)中起作用*/

printf("entername:");

gets(s); /*输入字符串*/

process(s); /*函数调用*/

}}第26页这里局部数组变量s就是在if块(复合语句)入口处建立,并在其出口处消亡。所以数组s仅在if块中可知,而在其它地方均不可访问,甚至在包含它f函数内部其它部分也不行。在一个条件块内定义局部变量主要优点是仅在需要时才为之分配内存。这是因为局部变量仅在控制转到它们被定义块内时才进入生存期。即使大多数情况下这并不十分主要,但当代码用于专用控制器(如识别数字安全码车库门控制器)时,这就变得十分主要了,因为这时随机存放器(RAM)极其短缺。因为局部变量伴随它们被定义模块进出口而建立或释放,它们存放信息在块工作结束后也就丢失了。切记,这点对相关函数访问尤其主要。当访问一函数时,它局部变量被建立,当函数返回时,局部变量被销毁。这就是说,局部变量值不能在两次调用之间保持。第27页

5.3.2全局变量

与局部变量不一样,全局变量贯通整个程序,而且可被任何一个模块使用。它们在整个程序执行期间保持有效。全局变量定义在全部函数之外,可由函数内任何表示式访问。在例5-9程序中能够看到,变量count定义在全部函数之外,函数main()之前。但其实它能够放置在任何第一次被使用之前地方,只要不在函数内就能够。实践表明,定义全局变量最正确位置是在程序顶部,也就是main()函数和其它子函数定义之前。例5.7全局变量示例。#include<stdio.h>intcount; /*定义count是全局变量*/main(){count=100;

func1(); /*调用无参函数func1*/}第28页func1() /*定义函数*/{

inttemp;

temp=count;

func2(); /*调用无参函数func2*/

printf("countis%d",count); /*打印100*/}func2() /*定义函数*/{

intcount;

for(count=1;count<10;count++)

putchar('.'); /*打印出"."*/}第29页全局变量由C编译程序在动态区之外固定存放区域中存放。当程序中多个函数都使用同一数据时,全局变量将是很有效。然而,因为三种原因,应防止使用无须要全局变量:(1)不论是否需要,它们在整个程序执行期间均占用存放空间。(2)因为全局变量必须依靠外部定义,所以在使用局部变量就能够到达其功效时使用了全局变量,将降低函数通用性,这是因为它要依赖其本身之外东西。(3)大量使用全局变量时,不可知和不需要副作用将可能造成程序错误。如在编写大型程序时有一个主要问题是:变量值都有可能在程序其它地点偶然改变。第30页

5.3.3动态存放变量从变量作用域标准出发,能够将变量分为全局变量和局部变量;从变量生存期来分,可将变量分为动态存放变量及静态存放变量。动态存放变量能够是函数形式参数、局部变量、函数调用时现场保护和返回地址。这些动态存放变量在函数调用时分配存放空间,函数结束时释放存放空间。动态存放变量定义形式为在变量定义前面加上关键字“auto”,比如:autointa,b,c;“auto”也能够省略不写。实际上,我们已经使用变量均为省略了关键字“auto”动态存放变量。有时我们甚至为了提升速度,将局部动态存放变量定义为存放器型变量,定义形式为在变量前面加关键字“register”,比如:registerintx,y,z;这么一来好处是:将变量值无需存入内存,而只需保留在CPU内存放器中,以使速度大大提升。

第31页5.3.4静态存放变量在编译时分配存放空间变量称为静态存放变量,其定义形式为在变量定义前面加上关键字“static”,比如:staticinta=8;定义静态存放变量不论是做全局变量或是局部变量,其定义和初始化都在程序编译时进行。作为局部变量,调用函数结束时,静态存放变量不消失而且保留原值。

例5.8静态变量示例。#include<stdio.h>main(){

intf();

intj; /*函数申明*/

for(j=0;j<3;j++)

printf("%d\n",f());}第32页intf() /*无参函数*/{

staticintx=1;

x++;

returnx;}运行程序:234说明:从上述程序看,函数f()被三次调用,因为局部变量x是静态存放变量,它是在编译时分配存放空间,故每次调用函数f()时,变量x不再重新初始化,保留加1后值,得到以上输出。第33页5.4函数参数与调用5.4.1形式参数与实际参数函数参数分为形参和实参两种。形参出现在函数定义中,在整个函数体内都能够使用,离开该函数则不能使用。形参定义是在函数名之后括号中或函数头之后大括号之前。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。

形参和实参功效是作数据传送。在函数调用时,主调函数把实参值传送给被调函数形参从而实现主调函数向被调函数数据传送。

函数形参和实参含有以下特点:

(1)形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配内存单元。所以,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。

第34页

(2)实参能够是常量、变量、表示式、函数等,

不论实参是何种类型量,在进行函数调用时,它们都必须含有确定值,

方便把这些值传送给形参。所以应预先用赋值、输入等方法使实参取得确定值。

(3)实参和形参在数量上、类型上、次序上应严格一致,不然会发生“类型不匹配”错误。(4)函数调用中发生数据传送是单向。即只能把实参值传送给形参,而不能把形参值反向地传送给实参。所以在函数调用过程中,形参值发生改变,而实参中值不会改变。例5-11能够说明这个问题。例5.9函数调用示例。#include<stdio.h>main()

{int

n;

printf("input

number:\n");

scanf("%d",&n);

s(n);

printf("n=%d\n",n);

}

第35页

int

s(int

n)

{

int

i;

for(i=n-1;i>=1;i--)

n=n+i;

printf("n=%d\n",n);

}

输入:input

number:100输出:n=5050 /*形参值*/n=100 /*实参值*/

说明:本程序中定义了一个函数s,该函数功效是求1+2+3+…+100值。在主函数中输入n值,并作为实参,在调用时传送给函数s形参变量n(注意,本例形参变量和实参变量标识符都为n,但这是两个不一样量,各自作用域不一样)。在主函数中用printf

语句输出一次n值,这个n值是实参n值。在函数s中也用printf

语句输出了一次n值,这个n值是形参最终取得n值。从运行情况看,输入n值为100。即实参n值为100。把此值传给函数s时,形参n初值也为100,在执行函数过程中,形参n值变为5050。返回主函数之后,输出实参n值仍为100。可见实参值不随形参改变而改变。第36页5.4.2赋值调用与引用调用1.赋值调用普通说来,有两种方法能够把参数传递给函数。第一个叫做“赋值调用”(callbyvalue),这种方法是把参数值复制到函数形式参数中。这么,函数中形式参数任何改变都不会影响到调用时所使用变量。传值流程如图5-1所表示。

图5-1赋值调用流程第37页2.引用调用把参数传递给函数第二种方法是“引用调用”(callbyreference)。这种方法是把参数地址复制给形式参数,在函数中,这个地址用来访问调用中所使用实际参数。这意味着,形式参数改变会影响调用时所使用那个变量。传址流程如图5-2所表示。图5-2引用调用流程除少数情况外,C语言使用赋值调用来传递参数。这意味着,普通不能改变调用时所用变量值。请看例5-12。第38页例5.10引用调用示例。#include<stdio.h>main(){

intt=10;

printf("%d%d",sqr(t),t); /*调用函数sqr(t),t是实参*/}intsqr(x) /*定义函数,x是形式参数*/intx; /*定义形式参数x是整形*/{x=x*x;

return(x); /*将x值作为sqr函数返回值传给主调函数*/}说明:在这个例子里,传递给函数sqr()参数值是复制给形式参数x,当赋值语句x=x*x执行时,仅修改局部变量x值。用于调用sqr()变量t,依然保持着值10。执行程序:10010注意:传给函数只是参数值复制品。全部发生在函数内部改变均无法影响调用时使用变量。第39页5.4.3函数调用形式

C程序经过对函数调用来执行函数体。

1、函数调用方式(1)表示式语句调用函数。被调函数作为表示式一项,出现在表示式中,以函数返回值参加表示式运算。这种方式要求函数是有返回值。比如:

a=b+fun(x,y);第40页(2)函数语句被调函数能够只进行一些操作而不返回函数值,这时函数调用可作为一条独立语句。比如:

funa();(3)函数实参被调函数作为另一个函数调用实际参数出现。即把该函数返回值作为实参进行传送,所以要求该函数必须是有返回值。比如:

printf(“%d\n”,max(a,b));第41页2.调用函数说明(1)调用函数时,函数名称必须与含有该功效自定义函数名称完全一致。(2)实参在类型上按次序与形参,必须一一对应和匹配。假如类型不匹配,C编译程序将按赋值兼容规则进行转换。假如实参和形参类型不赋值兼容,通常并不给出犯错信息,且程序依然继续执行,只是得不到正确结果。(3)假如实参表中包含多个参数,对实参求值次序随系统而异。有系统按自左向右次序求实参值,有系统则相反。TurboC和MSC是按自右向左次序进行。第42页

例5.11函数调用方式示例程序。#include<stdio.h>#include<math.h>main(){intfu1(inta,intb),fu2(inta,intb);inta,b,x1;printf("inputtwonumbers:\n");

scanf(“%d%d”,&a,&b);fu1(a,b);

/*调用求两数平方和函数*/printf(“(a+b)*(a+b)=%d\n”,

fu2(a,b));

x1=

sqrt(a+b);

printf(“x1=%d\n”,x1);

}

函数调用说明表示式语句函数调用作为函数实参被调用函数语句调用函数语句调用函数语句调用第43页3.函数类型在定义函数时,对函数类型说明,应与return语句中返回值表示式类型一致。假如不一致,则以函数类型为准。假如缺省函数类型,则系统一律按整型处理。良好程序设计习惯:为了使程序含有良好可读性并降低犯错,凡不要求返回值函数都应定义为空类型;即使函数类型为整型,也不使用系统缺省处理。第44页例5.12intf

(inta,intb);{intc;if(a>b)c=1;elseif(a=

=b)c=0;elsec=–1;return(c);}main(){inti=2,p;p=

f

(i,++i);

printf

(“%d”,p);}输出结果:0

注意:这里是按自右至左求值,相当于fun(3,3)。

3b3a函数类型为int第45页

4.对被调用函数说明ANSIC对被调用函数进行说明,其普通格式以下:函数类型函数名(形参表);

以下2种情况下,能够省去对被调用函数说明:(1)当被调用函数函数定义出现在调用函数之前时。因为在调用之前,编译系统已经知道了被调用函数函数类型、参数个数、类型和次序。(2)假如在全部函数定义之前,在函数外部(比如文件开始处)预先对各个函数进行了说明。第46页例5.13main(){intf

(inta,intb);inti=2,p;p=f

(i,++i);printf

(“%d”,p);}intf

(inta,intb){intc;if(a>b)c=1;elseif(a=

=b)c=0;elsec=–1;return(c);}输出结果:0被调用函数函数定义出现在调用函数之后,对被调用函数进行说明第47页5.4.4函数值

函数值是指函数被调用之后,执行函数体中程序段所取得并返回给主调函数值。如调用正弦函数取得正弦值,调用例5-1max函数取得最大数等。对函数值(或称函数返回值)有以下一些说明:

(1)函数值只能经过return语句返回主调函数。return

语句普通形式为:

return

表示式;

或者为:

return

(表示式);

该语句功效是计算表示式值,并返回给主调函数。在函数中允许有多个return语句,但每次调用只能有一个return

语句被执行,

所以只能返回一个函数值。

(2)函数值类型和函数定义中函数类型应保持一致。

假如二者不一致,则以函数类型为准,自动进行类型转换。第48页(3)如函数值为整型,在函数定义时能够省去类型说明。(4)不返回函数值函数,能够明确定义为“空类型”,

类型说明符为“void”。如函数s并不向主函数返回函数值时,可定义为:void

s(int

n)

{……}

一旦函数被定义为空类型后,就不能在主调函数中使用被调函数函数值了。比如,在定义s为空类型后,在主函数中写下述语句sum=s(n);

就是错误。为了使程序有良好可读性并降低犯错,凡不要求返回值函数都应定义为空类型。函数说明在主调函数中调用某函数之前应对该被调函数进行说明,这与使用变量之前要先进行变量说明是一样。在主调函数中对被调函数作说明目标是使编译系统知道被调函数返回值类型,方便在主调函数中按此种类型对返回值作对应处理。

第49页5.4.5数组作为函数参数使用时与普通变量一样值传递方式在函数调用时将值传递给实参形参

1.数组元素作函数参数第50页例5.14一个数组中有10个整型元素,求数组中全部素数之和。#include"stdio.h"#include"math.h"main(){inta[10],i,sum=0;intprime(intx);

printf("enter10numbers:\n");for(i=0;i<10;i++){scanf("%d",&a[i]);if(prime(a[i]))sum+=a[i];}printf("sum=%d\n",sum);}intprime(intx){intf=1,k;if(x==1)f=0;for(k=2;k<=sqrt(x);k++)if(x%k==0){f=0;break;}return(f);}第51页地址传递方式,实参加形参共地址,相当于对同一对象两个不一样名字。

2.数组名作为函数参数在函数调用时将其地址值传递给实参形参第七章7.4数组与函数第52页#include"stdio.h"intf(intb[],intm,intn){inti,s=0;for(i=m;i<n;i=i+2)s=s+b[i];returns;}main(){intx,a[]={1,2,3,4,5,6,7,8,9};x=f(a,3,7);printf("%d\n",x);}

例5.14分析下面程序运行结果。

第53页5.4.7函数嵌套调用函数嵌套调用:在一个函数函数体内调用另一个函数。scanf()x()printf()y()getchar()max()第54页main()

a()

b()调用a()调用b()

END调用一个函数过程中又调用了另一个函数在函数嵌套调用中,函数执行采取后调用先返回标准。即最内层函数调用最先返回函数值,由内到外依次返回。第55页例5.15输入年月日,计算出该日为该年第几天。

主控模块判断闰年求某月天数输出输入求总天数

第56页程序实现:(1)判断闰年。intleap(intyear){intlp;lp=(year%4==0&&year%100!=0||year%400==0)?1:0;returnlp;}第57页(2)求某月天数。intmonth_days(intyear,intmonth){intds,d;switch(month){case1:case3:case5:case7:case8:case10:case12:d=31;break;case2:d=leap(year)?29:28;break;default:d=30;}returnd;}第58页(3)求天数和。intdays(intyear,intmonth,intday){inti,ds=0;for(i=1;i<month;i++)ds=ds+month_days(year,i);ds=ds+day;returnds;}第59页4)在主函数中分别调用三个函数。voidmain(){intyear,month,day,t_day;printf("Inputyear-month-day:\n");scanf("%d-%d-%d",&year,&month,&day);t_day=days(year,month,day);printf("%d-%d-%dis%dthdayoftheyear!\n",year,month,day,t_day);}

注意: 在完整程序中,前三个函数应放在main()函数之前。第60页例5.16求解x3-5x2+16x-80=0根算法。解题步骤:1.取两个不一样点x1,x2

假如f(x1)和f(x2)符号相反,则[x1,x2]区间必有一个根,假如同号,则应改变x1和x2,直至异号为止2.连接f(x1)和f(x2),交x轴于x点,则x=[x1*f(x2)-x2*f(x1)]/[f(x2)-f(x1)]3.求f(x)4.若f(x)与f(x1)同号,则(x,x2)有根,此时将x作为x1若f(x)与f(x2)同号,则(x,x1)有根,此时将x作为x25.重复2~4步骤,直至f(x)<某个值,视精度要求而定x1f(x1)xx2f(x2)f(x)第61页例5.17函数调用#include<math.h>floatroot(x1,x2)floatx1,x2;{inti;floatx,y,y1;y1=f(x1);do{x=xpoint(x1,x2);y=f(x);if(y*y1>0){y1=y;x1=x;}elsex2=x;}while(fabs(y)>=0.0001);return(x);}floatxpoint(x1,x2)floatx1,x2;{floaty;y=(x1*f(x2)-x2*f(x1))/(f(x2)-f(x1));return(y);}第62页/*1.从以上函数可知,全部函数均是独立*//*2.全部函数均设为"float"*//*3.全部函数均在main()之前定义*/main(){floatx1,x2,f1,f2,x;do{printf("inputx1,x2:\n");scanf("%f,%f",&x1,&x2);

f1=f(x1);f2=f(x2);}while(f1*f2>=0);x=root(x1,x2);printf("Arootofequationis%8.4f",x);}floatf(floatx){floaty;y=((x-5.0)*x+16)*x-80;return(y);}第63页5.5函数递归调用

5.5.1递归调用递归调用:一个函数函数体直接或间接调用该函数本身.

intf(intx){inty,z;┆z=f(z);/*直接调用该函数本身*/┆return(2*z);}第64页例5.18用递归法计算n!。分析:计算n!可用下述公式表示:1!=1递归条件所以,要想计算出n!,必须计算出(n-1)!;计算出(n-1)!,必须计算出(n-2)!;…;依这类推,直到推到1!=1,返回后即可依次计算出2!,3!,…,(n-1)!,n!。比如,计算4!,其递归过程是:

第65页程序以下:longff(intn) /*求阶乘函数*/{longf;if(n<0)printf("n<0,inputerror");elseif(n==0||n==1)f=1;elsef=ff(n-1)*n;return(f);}第66页main(){intn;longy;printf("\ninputainteagernumber:n");

scanf("%d",&n);y=ff(n);printf("%d!=%ld",n,y);}说明:程序中给出函数ff是一个递归函数。主函数调用ff后即可进入函数ff执行,假如形参n<0,n==0或n==1时都将结束函数执行,不然就递归调用ff函数本身。因为每次递归调用实参为n-1,即把n-1值赋予形参n,最终当n-1值为1时再作递归调用,形参n值也为1,将使递归终止。然后可逐层退回。第67页下面再举例说明该过程。设执行本程序时输入为4,即求4!。在主函数中调用语句即为y=ff(4),进入ff函数后,因为n=4,不等于0或1,故应执行f=ff(n-1)*n,(即f=ff(4-1)*4)。该语句对ff作递归调用(即ff(4))。进行三次递归调用后,ff函数形参取得值变为1,故不再继续递归调用而开始逐层返回主调函数。ff(1)函数返回值为1,ff(2)返回值为1*2=2,ff(3)返回值为2*3=6,最终返回值为6*4=24。第68页5.5.2递归说明(1)当函数调用自己时,在栈中为新局部变量和参数分配内存,函数代码用这些变量和参数重新运行。递归调用并不是把函数代码重新复制一遍,仅仅参数是新。当每次递归调用返回时,老局部变量和参数就从栈中消除,从函数内此次函数调用点重新开启运行。可递归函数被说成是对本身“推入和拉出”。(2)递归调用层次越多,同名变量占用存放单元也就越多。一定要记住,每次函数调用,系统都会为该函数变量开辟新内存空间。(3)当此次调用函数运行结束时,系统将释放此次调用时所占用内存空间。程序流程返回到上一层调用点,同时取得当初进入该层时,函数中变量和形参所占用内存空间数据。第69页(4)全部递归问题都能够用非递归方法来处理,但对于一些比较复杂递归问题用非递归方法往往使程序变得十分复杂难以读懂,而函数递归调用在处理这类问题时能使程序简练明了有很好可读性;但因为递归调用过程中,系统要为每一层调用中变量开辟内存空间、要记住每一层调用后返回点、要增加许多额外开销,所以函数递归调用通常会降低程序运行效率。(5)大部分递归例程没有显著地降低代码规模和节约内存空间。另外,大部分例程递归形式比非递归形式运行速度要慢一些。这是因为附加函数调用增加了时间开销(在许多情况下,速度差异不太显著)。对函数屡次递归调用可能造成堆栈溢出。不过溢出可能性不大,因为函数参数和局部变量是存放在堆栈中。每次新调用就会产生一些变量复制品。这个堆栈冲掉其它数据和程序存放区域可能性是存在。不过除非递归程序运行失控,不然无须为上述情况担心。第70页(6)递归函数主要优点是能够把算法写比使用非递归函数时更清楚更简练,而且一些问题,尤其是与人工智能相关问题,更适宜用递归方法。递归另一个优点是,递归函数不会受到怀疑,较非递归函数而言,一些人更相信递归函数。编写递归函数时,必须在函数一些地方使用if语句,强迫函数在未执行递归调用前返回。假如不这么做,在调用函数后,它永远不会返回。在递归函数中不使用if语句,是一个很常见错误。在开发过程中广泛使用printf()和getchar()能够看到执行过程,而且能够在发觉错误后停顿运行。第71页有五个人坐在一起,求第5个人多少岁。第5个人多少岁?比第4个人大2岁。第4个人多少岁?比第3个人大2岁。第3个人多少岁?比第2个人大2岁。第2个人多少岁?比第1个人大2岁。第1个人多少岁?我10岁。表示为:age(5)=age(4)+2age(4)=age(3)+2age(3)=age(2)+2age(2)=age(1)+2age(1)=10;递归表示式:例5.19第72页求解过程:第73页用一个函数age()来求第n个人多少岁。main(){intage();/*申明函数原型*/printf("%d\n",age(5));}intage(intn)/*求第n个人多少岁*/

{intc;if(n==1)c=10;elsec=age(n-1)+2;returnc;}

第74页利用递归方法处理以下问题1、求斐波那契亚数列第n项。2、求n阶乘。从例题中能够看到,有些问题,采取递归方法处理,会变得非常简单,且源程序也很简练。一个问题要采取递归方法处理时,要符合以下条件:1、能够把一个问题转化为一个新问题,而新问题处理方案仍与原问题相同,只是问题规模不一样。它们只是有规律递增或递减。2、能够经过转化过程使问题得到处理。3、必须有一个明确结束递归条件。第75页5.6函数库和文件

一个函数设计完后,我们能够用三种方法处理它:(1)把它放在main()函数同一个文件中;(2)把它和写好其它函数一起放在另一个文件中;(3)把它放在函数库中。

5.6.1程序文件大小因为C语言允许分别编译,所以一个文件最适宜规模是多大?这规模很主要,因为编译时间与被编译文件大小直接相关。普通来说,链接处理时间比编译处理时间短得多,且不需要经常去重新编译已经运行过代码;另首先,不得不一样时处理多个文件也确实是件厌烦事。对于上述问题用户必须明确了解,因为每个用户、每个编译程序、每个操作系统环境都是不一样,所以文件规模第76页各不相同,但对大部分微型机和普通C编译程序来说,源程序文件不应长于10000个字节。建立短于5000个字节文件,能够防止不少麻烦。5.6.2分类组织文件在开发一个大型程序时,最令人烦恼而又是最常碰到工作之一就是需要检验每个文件,以确定某个函数存放。(1)把概念上相关函数组织到一个文件中。假如在编写正文编辑程序时,把删除正文所用全部函数放进另一个文件,等等。(2)把全部通用函数放在一起。比如,在数据库程序中,输入/输出格式编排函数是被其它函数调用通用函数,应把它们放进一个单独文件里。(3)把最高层函数放进一个单独文件中,假如空间允许,就和main()放在一起。最高层函数被用来开启程序总体活动。这些例程从本质上定义了程序操作。第77页

5.7.1C语言预处理程序ANSI标准定义C语言预处理程序包含命令有:#define、#error、#include、#if、#else、#elif、#endif、#ifdef、#ifndef、#undef、#line、#pragma。非常显著,全部预处理命令均以符号#开头,下面介绍惯用预处理命令。

5.7.2#define命令#define定义了一个标识符及一个串。在源程序中每次碰到该标识符时,均以定义串代换它。ANSI标准将标识符定义为宏名,将替换过程称为宏替换。命令普通形式为:#define标识符串注意:该语句没有分号。在标识符和串之间能够有任意个空格,串一旦开始,仅由一新行结束。5.7C语言预处理程序与注释第78页例5.20宏代换用于参数。#include<stdio.h>#defineMIN(a,b)(a<b)?a:bmain(){

intx,y;

x=10;

y=20;

printf("theminimumis:%d",MIN(x,y));}当编译该程序时,由MIN(a,b)定义表示式被替换,x和y用做操作数,即printf()语句被代换后取以下形式:printf("theminimumis:%d",(x<y)?x:y);用宏代换代替实在函数一大好处是宏替换提升了代码速度,因为不存在函数调用开销。但提升速度也有代价,因为重复编码而增加了程序长度。第79页

5.7.3#include命令#include使编译程序将另一源文件嵌入带有#include源文件,被读入源文件必须用双引号或尖括号括起来。比如:#include"stdio.h"#include<stdio.h>这两行代码均使用C编译程序读入并编译用于处理磁盘文件库子程序。假如文件路径名为文件标识符一部分,则仅在那些子目录中搜索被嵌入文件。不然,假如文件名用双引号括起来,则首先检索当前工作目录。假如未发觉文件,则在命令行中说明全部目录中搜索。假如仍未发觉文件,则搜索实现时定义标准目录。假如没有文件路径名且文件名被尖括号括起来,则首先在编译命令行中目录内检索。假如文件没找到,则检索标准目录,不检索当前工作目录。第80页

5.7.4注释注释,即用于解释标注,是一些文字信息,用以向看源代码读者解释这段代码是什么意思,因为读者认知空间和电脑完全不一样,这在以后说明怎样编程时会详细讨论。在C语言中,全部注释由/*开始,以*/结束。在星号及斜杠之间不允许有空格。编译程序忽略注释开始符到注释结束符间任何文本,也就是说注释只是一个对一行或一段程序说明,起到备忘录作用,而在程序编译和执行时忽略。注释不但仅是对程序解释,有时它对于程序调试也非常有用,譬如说能够利用注释屏蔽一条语句以观察改变,发觉问题和错误。以后注释语句将是我们在编程里最经惯用到语句之一。比如,下面程序在屏幕上只打印“hello”而屏蔽掉了

温馨提示

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

评论

0/150

提交评论