计算机二级C语言 第七章函数.docx
《计算机二级C语言 第七章函数.docx》由会员分享,可在线阅读,更多相关《计算机二级C语言 第七章函数.docx(40页珍藏版)》请在冰点文库上搜索。
计算机二级C语言第七章函数
第七章函数
【本章要求】
1、C语言程序中函数的基本概念
2、函数定义和声明的方法
3、函数调用方法,尤其是类型匹配问题
4、值传递方式下,函数形式参数和实际参数的变化
5、函数返回值类型的一致性问题
6、函数嵌套调用和递归调用的过程
7、局部变量、全局变量、静态变量的作用域和生存期
7.1模块化程序设计和C语言程序组成
7.1.1模块化程序设计
将重复使用的程序,设计成能够完成一定功能的可供其他程序使用(调用)的相对独立的功能模块。
它独立存在,但可以被多次调用,调用的程序称为主程序。
高级语言程序设计中的基本方法,即自顶向下、逐步细化和模块化。
使用独立模块化的优点有:
1、消除重复的程序行;
2、使程序容易阅读;
3、使程序开发过程简化;
4、可以在其他程序中重用;
5、使C语言得到扩充。
独立模块由顺序、选择、循环这3种基本结构所组成。
由于模块是通过执行一组语句来完成一个特定的操作过程,因此模块又称为“过程”,执行一个过程就是调用一个子程序或函数模块。
结构化程序设计的基本思想是“自顶向下、逐步求精”,即将一个教大的程序按其功能分成若干个模块,每个模块具有单一的功能。
7.1.2C语言程序的组成
一个较大的程序一般应分为若干个程序模块,每一个模块用来实现一个特定的功能。
所有的高级语言中都有子程序这个概念,用子程序实现模块的功能。
在C语言中,子程序的作用是由函数完成的。
一个C程序可由一个主函数和若干个函数构成。
由主函数调用其他函数,其他函数也可以互相调用。
同一个函数可以被一个或多个函数调用任意多次。
【说明】
1、一个C程序有且只有一个主函数main。
2、C程序的执行从main函数开始,调用其他函数后流程回到main函数,在main函数中结束整个程序的运行。
3、一个C程序由一个或多个源程序文件组成。
这样可以分别编写、分别编译,提高调度效率。
一个源文件可以为多个C程序公用。
4、一个源程序文件由—个或多个函数组成。
一个源程序文件是一个编译单位,而不是以函数为单位进行编译。
5、所有函数都是平行的,即在定义函数时是互相独立的,一个函数并不从属于另一函数,即函数不能嵌套定义。
6、一个函数可以调用其他函数或者本身,即函数间可以嵌套调用,但任何函数均不能调用main函数。
7.1.3函数分类
(一)从用户使用的角度看,函数有两种:
1、标准函数,即库函数。
这是由系统提供的,用户不必自己定义这些函数,可以直接使用它们。
2、用户自己定义的函数。
用以解决用户的专门需要。
(二)从C语言函数兼有其他语言中的函数和过程两种功能,函数有两类:
1、有返回值函数。
有返回值函数被调用执行完成后将向调用者返回一个执行结果,这一结果称为函数返回值。
2、无返回值函数。
无返回值函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值。
由于函数无须返回值,用户在定义此类函数时可指定它的返回为“空类型”,说明符为“void”。
(三)从函数的形式看,函数分为两类:
1、无参函数。
在调用无参函数时,主调函数并不将数据传送给被调用函数,—般用来执行指定的一组操作。
无参函数可以带回或不带回函数值,但一般以不带回函数值的居多。
2、有参函数。
在调用函数时,主调函数和被调用函数之间有数据传递。
也就是说,主调函数可将数据传给被调用函数使用,被调用函数中的数据也可以带回来供主调函数使用。
7.2库函数及预处理命令
7.2.1C语言常用库函数
一、数学函数
数学函数用于数学计算:
#include“math.h”
二、字符函数和字符串函数
字符函数和字符串函数用于将字符按其ASCII码进行分类,
调用字符函数:
#include“ctype.h”
调用字符串函数时,要求在源文件中包含:
#include“string.h”
三、输入输出函数
输入输出函数用于完成输入输出功能:
#include“stdio.h”
7.2.2预处理命令
C提供预处理功能主要有以下3种:
1、宏定义
2、文件包含
3、条件编译
一、宏定义
(一)不带参数的宏定义
用一个指定的标识符(即名字)来代表一个字符串,它的一般形式为:
#define标识符字符串
例如:
#definePI3.1415926
用一个简单的标识符代替一个长的字符串,这个标识符称为“宏名”,在预编译时将宏名替换成字符串的过程称为“宏展开”。
#define是宏定义命令。
【例7.1】使用不带参数的宏定义
#include
#definePI3.1415926
voidmain()
{floatl,s,r,v;
printf("inputradius:
");
scanf("%f",&r);
l=2.0*PI*r;
s=PI*r*r;
v=4.0/3*PI*r*r*r;
printf("l=%10.4f\ns=%10.4f\nv=%10.4f\n",l,s,v);
}
【说明】
1、宏名一般习惯用大写字母表示,以便与变量名相区别,但并非规定,也可用小写字母。
2、使用宏名代替一个字符串,可以减少程序中重复书写某些字符串的工作量。
3、宏定义是用宏名代替一个字符串,也就是作简单的置换,不作正确性检查。
如果把#definePI3.1415926中的数字1写成小写字母l,预处理时照样代入,不管是否符合用户原意,也不管含义是否有意义。
4、宏定义不是C语句,不必在行末加分号。
5、#define命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束。
6、可以用#undef命令终止宏定义的作用域。
7、在进行宏定义时,可以引用已定义的宏名,可以层层置换。
【例7.2】在宏定义中引用已定义的宏名
#include
#defineR3.0
#definePI3.1415926
#defineL2*PI*R
#defineSPI*R*R
voidmain()
{
printf("L=%f\nS=%f\n",L,S);
}
8、对程序中用双撇号括起来的字符串内的字符,即使与宏名相同,也不进行置换。
9、宏定义是专门用于预处理命令的一个专用名词,它与定义变量的含义不同,只作字符替换,不分配内存空间。
(二)带参数的宏定义
带参数的宏定义不是进行简单的字符串替换,还要进行参数替换。
其定义的形式为:
#define宏名(参数表)字符串
【例7.3】使用带参数的宏
#include
#definePI3.1415926
#defineS(r)PI*r*r
viodmain()
{
floata,area;
a=3.6;
area=S(a);
printf("r=%f\narea=%f\n",a,area);
}
【说明】
1、对带参数的宏的展开只是将语句中的宏名后面括号内的实参字符串代替#define命令行中的形参。
如果有#defineS(r))PI*r*r这样的宏定义,则以下语句:
area=S(a+b);
它等价于:
area=PI*a+b*a+b;
请注意在a+b外面没有括号,而原意是希望得到
area=PI*(a+b)*(a+b);
为了得到这个结果,应当在定义时,在字符串中的形式参数外面加一个括号。
即
#defineS(r)PI*(r)*(r)
2、在宏定义时,在宏名与带参数的括号之间不应加空格;否则将空格以后的字符都作为替代字符串的一部分。
例如,如果有
#defineS(r)PI*r*r
被认为S是符号常量(不带参数的宏名),它代表字符串“(r)PI*r*r”。
如果在程序中有语句
area=S(a);
则被展开为
area=(r)PI*r*(a);
3、带参数的宏定义与函数是不同的,主要有:
(1)函数使用时,先求出实参表达式的值,然后代入形参。
而使用带参数的宏知识进行简单的字符替换。
(2)函数调用是在程序运行时处理的,为形参分配临时的内存单元。
而宏展开则是在编译前进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有“返回值”的概念。
(3)对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换。
而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时代入指定字符串即可。
宏定义时,字符串可以是任何类型的数据。
(4)调用函数只可得到一个返回值,而用宏可以设法得到几个结果。
(5)使用宏次数多时,宏展开后源程序变长,因为每展开一次都使程序增长,而函数调用不会使源程序变长。
(6)宏替换不占运行时间不占运行时间,只占编译时间。
而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。
二、“文件包含”命令include的使用
文件包含的一般形式如下:
#include"文件名"
或:
#include<文件名>
该控制行一般放在源文件的起始部分,它告诉编译程序把由<文件名>标识的文件的整个内容来替换该控制行。
该文件提供了C语言标准输入输出中的有关常量、变量和函数原型的信息,它们一般被称为头文件(headfile),用扩展名.h表示。
【说明】
1、一个include命令只能指定一个被包含文件,如果要包含n个文件,要用n个文件,要用n个include命令。
2、如果文件1包含文件2,而文件2中要用到文件3的内容,则可在文件1中用两个include命令分别包含文件2和文件3,而且文件3应出现在文件2之前,即在file1.c中定义.
3、在一个被包含文件中又可以包含另一个被包含文件,即文件包含是可以嵌套的。
4、在#include命令中,文件名可以用双引号或尖括号括起来。
用双引号或尖括号宋括起文件是有区别的:
若用双引号,则系统先在引用被包含文件的C源程序所在的文件目录中寻找,若找不到,再按系统指定的标准方式寻找其他目录;而用尖括号则仅查找按系统标准方式指定的目录。
5、被包含文件与其所在的文件,在预编译后已成为同一个文件。
7.3函数的定义和调用
7.3.1函数的定义
一、无参函数的定义形式
类型标识符函数名()
{声明部分
语句}
二、有参函数定义的一般形式
类型标识符函数名(形式参数表列)
{声明部分
语句}
例如:
intmax(inta,intb)
{intc;
z=a>b?
a:
b;
return(c);}
这是一个求x和y二者中较大者的函数,笫1行第一个关键字int表示函数值是整型的。
max为函数名。
括号中有两个形式参数x和y,它们都是整型的。
在调用此函数时,主调函数把实际叁数的值传递给被调且函数中的形式参数x和y。
如果在定义函数时不指定函数类型,系统会隐含指定函数类型为int型。
三、可以有“空函数”。
类型说明符函数名()
{}
例如:
dummy(){}
调用此函数时,什么工作也不做,没有任何实际作用。
在编写程序的开始阶段,可以在将来准备扩充功能的地方写上一个空函数,这些函数只是未编好,先占—个位置,以后用一个编好的函数代替它。
四、对形参的声明的传统方式
在原来的C语言中,对形参类型的声明是放在函数定义的笫2行,也就是不在笫1行的括号内指定形参的类型,而在括号外单独指定。
传统的声明:
现代的声明:
intmax(a,b)intmax(inta,intb)
inta,b
{intc;{intc;
z=a>b?
a:
b;z=a>b?
a:
b;
return(c);}return(c);}
7.3.2函数的调用
一、函数调用的一般形式
函数调用的一般形式为-
函数名(实参表列);
如果是调用无参函数,则“实参表列”可以没有,但括弧不能省略。
如果实参表列包含多个实参,则各参数间用逗号隔开。
实参与形参的个数应相等,类型应一致。
实参与形参按顺序对应,一一传递数据。
如果实参表列包括多个实参,对实参求值的顺序并不是确定的,有的系统按自左至右顺序求实参的值,有的系统则按自右至左顺序。
TurboC是按自右而左的顺序求值。
【例7.4】函数调用的程序
main()
{inti=2,p;
p=f(i,++i);
printf("%d",p);
}
intf(intx,inty)
{intz;
if(x>y)z=1;
elseif(x==y)z=0;
elsez=-1;
return(z);
}
在TurboC系统上运行的结果如下:
0
如果按自左至右顺序求实参的值,则函数调用相当于f(2,3),程序运行应得结果为“-1”。
若按自右至左顺序求实参的值,则它相当于f(3,3),程序运行结果为“0”。
二、函数调用的方式
按函数在程序中出现的位置来分,可以有以下三种函数调用方式:
1、函数语句
把函数调用作为一个语句。
printstar();
这时不要求函数带回值,只要求函数完成一定的操作。
2、函数表达式
函数出现在一个表达式中,这种表达式称为函数表达式。
这时要求函数带回一个确定的值以参加表达式的运算。
例如:
z=2*max(x,y);
函数max是表达式的一部分,它的值乘2再赋给z。
3、函数参数
函数调用作为一个函数的实参。
例如:
m=max(x,max(y,z));
其中max(b,c)是一次函数调用,它的值作为max另一次调用的实参。
m的值是x、y、z三者最大的。
三、对被调用函数的声明和函数原型
1、首先被调用的函数必须是已经存在的函数。
2、如果使用库函数,一般还应该在本文件开头用#include命令将调用有关库函数时所需用到的信息“包含”到本文件中来。
#include
使用数学库中的函数,应该用
#include
.h是头文件所用的后缀,标志头文件(headerfile)。
3、如果使用用户自己定义的函数,面且该函数与调用它的函数(即主调函数)在同一个文件中,一般还应该在主调函数中对被调用的函数作声明,即向编译系统声明将要调用此函数,并将有关信息通知编译系统。
声明的格式:
类型标识符被调用函数的函数名();
【例7.5】对被调用的函数作声明。
main()
{floatadd(floata,floatb);
floatx,y,z;
scanf("%f,%f",&x,&y);
z=add(x,y);
printf("sumis%f",z);
}
floatadd(floata,floatb);
{floatc;
c=a十b;
return(c);
}
运行结果:
3.6,6.5
sumis10.000000
【注意】
1、对函数的“定义”和“声明”不是一回事。
2、“定义”是指对函数功能的确立,包括指定函数名,函数值类型、形参及其类型、函数体等,它是一个完整的、独立的函数单位。
3、“声明”是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查。
函数原型的一般形式为
1、函数类型函数名(参数类型1,参数类型2……)
2、函数类型函数名(参数类型1,参数名l,参数类型2,参数名2……)
【说明】
1、以前的C语言的函数声明方式不是采用函数原型,而只声明函数名和函数类型。
2、实际上,如果在函数调用之前.没有对函数作声明,则编译系统会把第—次遇到的该函数形式(函数定义或函数调用)作为函数的声明,并将函数类型默认为int型。
3、如果被调用函数的定义出现在主调函数之前,可以不必加以声明。
floatadd(floata,floatb);
{floatc;
c=a十b;
return(c);
}
main()
{
floatx,y,z;
scanf(“%f,%f”,&x,&y);
z=add(x,y);
printf(“sumis%f”,z);
}
4、如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必对所调用的函数再作声明。
例如:
charletter(char,char);
floatf(float,float);
inti(float,float);
main()
{…}
charletter(charc1,charc2)
{…}
floatf(floatx,floaty)
{…}
inti(floata,floatb)
7.4函数参数和函数的值
7.4.1形式参数和实际参数
在调用函数时,大多数情况下,主调函数和被调用函数之间有数据传递关系。
在定义函数时函数名后面括弧中的变量名称为“形式参数”(简称“形参”),在主调函数中调用一个函数时,函数名后面括弧中的参数(可以是一个表达式)称为“实际参数”(简称“实参”)。
【例7.6】调用函数时的数据传递。
main()
{intx,y,z;
scanf(“%d,%d”,&x,&y);
z=max(x,y);
printf(“Maxis%d”,z);
}
max(inta,intb)
{intc;
c=a>b?
a:
b;
return(c);
}
【说明】
1、在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。
只有在发生函数调用肘,函数max中的形参才被分配内存单元。
在调用结束后,形参所占的内存单元也被释放。
2、实参可以是常量、变量或表达式,如:
max(3,x+y);
但要求它们有确定的值。
3、在被定义的函数中,必须指定形参的类型。
4、实参与形参舶类型应相周或赋值兼容。
字符型与整型可以相互通用。
5、C语言规定,实参变量对形参变量的数据传递是“值传递”,即单向传递,只由实参传给形参,而不能由形参传回来给实参。
在内存中,实参单元与形叁单元是不同的单元。
在调用函数时.给形参分配存储单元,并将实叁对应的值传递给形参,调用结束后,形参单元被释放,实参单元仍保留并维持原值。
7.4.2函数的返回值
1、函数的返回值是通过函数中的return语句获得的。
return语句将被调用函数中的一个确定值带回主调函数中去。
(1)如果需要从被调用函数带回一个函数值(供主调函数使用),被调用函数中必须包含return语句。
如果不需要从被调用函数带回函数值可以不要return语句。
(2)一个函数中可以有一个以上的return语句,执行到哪一个return语句,哪一个语句起作用。
(3)return语句的格式为:
return;
return变量名;
return(变量名);
return(表达式);
2、函数值的类型。
应当在定义函数时指定函数值的类型。
例如:
intmax(floata,floatb)
charletter(chara1,chara2)
doublemin(intx,inty)
C语言规定,凡不加类型说明的函数,一律自动按整型处理。
在定义函数时对函数值说明的类型一般应该和return语句中的表达式类型一致。
3、如果函数值的类型和return语句中表达式的值不一致,则以函数类型为准。
对数值型数据,可以自动进行类型转换。
即函数类型决定返回值的类型。
【例7.7】返回值类型与函数类型不同。
main()
{intz;
floatx,y;
scanf(“%d,%d”,&x,&y);
z=max(x,y);
printf(“Maxis%d”,z);
}
max(floata,floatb)
{
floatc;
c=a>b?
a:
b;
return(c);
}
运行结果:
1.5,2.5
Maxis2
4、如果被调用函数中没有return语句,并不带回一个确定的、用户所希望得到的函数值,但实际上,函数并不是不带回值,而只是不带回有用的值,带回的是—个不确定的值。
main()
{intx,y,z;
a=printstar();
b=print_message();
c=printstar();
printf(“%d,%d,%d\n”,x,y,z);
}
printstar()
{
printf(“***********************\n”);
}
print_message()
{
printf(“Howdoyoudo!
\n”);
}
运行时除了得到函数所对应的字符串外,还可以输出x、y、c的任意的值。
5、为了明确表示“不带回值”,可以用"void"定义“无类型”(或称“空类型”)。
voidprintstar()
{…}
voidprint_message()
{…}
这样,系统就保证不使函数带回任何值,即禁止在调用函数中使用被调用函数的返回值。
如果已将printstar和print_message函数定义为void类型,则下面的用法就是错误的:
a=prmtstar();
b=print_message();
7.5函数调用时参数间的传递
C语言中函数之间的联系,是通过调用函数时参数的传递及函数值的返回来完成的。
在定义函数时,函数名后面圆括号内的参数称为形式参数。
调用函数时,函数名后面圆括号内的参数称为实参。
由调用函数的实参向被调函数的形参进行参数传递。
7.5.1将变量、常量、数组元素作为参数时的传递
在函数调用时,使用变量、常量或数组元素作为函数参数时,将实参的值复制到形参相应的存储单元中,即形参和实参分别占用不同存储单元,这种传递方式称为“值传递”。
值传递的特点是单向传递,即只能把实参的值传递给形参,而形参值的任何变化都不会影响实参。
【例7.8】有两个数组a、b,各自10个元素,将它们对应地逐个相比。
如果a数组中的元素大于b数组中的相应元素的数目多于b数组中元素大于a数组中相应元素的数目,则认为a数组大于b数组,并分别统计出两个数组相应元素大于、等于、小于的次数。
main()
{intlarge(ihtx,inty);
inta[10],b[10],i,n=0,m=0,k=0;
printf("enterarraya:
\n”);
for(i=0;i<10;i++)
scanf(“%d”,&a[i]);
printf(“\n”):
printf("enterarray