1、第5章 函数和预处理,河南机电高等专科学校 计算机科学与技术系,主要内容,5.1 函数的定义和调用 5.2 函数的参数传递、返回值及函数声明 5.3 全局变量和局部变量 5.4 函数的调用机制 5.5 作用域与标识符的可见性 5.6 函数的递归调用 5.7 函数的重载、内联及默认参数 5.8 头文件与多文件结构 5.9 编译预处理,5.1 函数的定义和调用,将相对独立、经常使用的功能抽象为函数。函数作为程序的模块,可以单独编写、调试、编译。一个C+程序由一个主函数main()和若干子函数组成,子函数还可调用其他子函数。main()永远处于顶层。C+规定函数必须定义在其他函数的外部。main()
2、 f1() f11() 1 2 f1(); f11(); ; 4 3 ,函数的定义函数定义的语法形式如下:类型标识符 函数名(形式参数表)语句序列函数不允许嵌套定义。 函数首部函数体 由一组语句序列,实现一个功能,由return返回。,int fun(char c,float f),返回一个int类型的值,void fun(char c,float f),void类型的函数无返回值,函数首部,函数体,主函数main()返回值是void型(不向调用函数返回值)或int型(默认)#includemain() 或 int main() cout“ok”;return 0; 当返回值是int时,用0或
3、非0表示 程序是否正常结束(0正常),函数的调用,调用前先说明函数原型: 在调用函数的说明部分,或程序文件开头所有函数之前,按如下形式说明:类型标识符 被调用函数名 (含类型说明的形参表); 调用形式函数名(实参列表) 实参列表与形参列表应符合以下两条原则:1)实参与形参个数相同;2)实参与形参类型相同。函数必须先定义,再使用(调用)。不被调用时,形参不占用内存空间。,嵌套调用函数不允许嵌套声明,但可以嵌套调用。递归调用函数直接或间接调用自身。,函数的调用,调用前先说明函数原型 函数原型(函数模型)函数原型:由函数定义中抽取出来的能代表函数应用特征的部分:函数类型、函数名、参数个数及其类型。函
4、数原型形式:类型 被调函数名(参数类型1,) 例: 有函数 : 函数原型:float Area(float tr) float Area(float)return(pi*tr*tr),函数调用,函数调用:调用某函数之前,要在调用函数中声明函数原型。用实参向形参传递参数;中断现行函数,执行被调函数。 函数调用形式:函数名(实参表),函数调用的执行过程,嵌套调用,函数的参数传递机制 传递参数值,在函数被调用时才分配形参的存储单元。 实参可以是常量、变量或表达式。 实参类型必须与形参相符。 传递时是传递参数值,即单向传递。,函数的参数传递机制 参数值传递举例,编写函数计算123100的结果,并输出。
5、 #include “iostream.h“ void Addup() int sum=0;for(int i=1;i=100;i+)sum+=i;cout“1加到100的和是:“sumendl; void main() Addup(); ,编写函数计算123n的结果(n1),并输出。 #include “iostream.h“ void Addup(int n) /增加一个整型参数n,传值调用 int sum=0;for(int i=1;in; Addup(n); ,编写函数计算m(m+1)+(m+2)n的结果(nm),并输出。 #include “iostream.h“ void Addu
6、p(int m,int n) /再增加一个整型参数m int sum=0;for(int i=m;im;coutn;Addup(m,n); ,函数在未被调用时,形参不占实际存储空间,没有实际值, 调用时为形参分配存储单元,并将实参与形参结合,要求二者类型必须相符。参数传递(形参与实参结合)的方式: 值调用与引用调用 值调用指在参数传递时,直接将实参的值传递给形参。这一过程是单向的,一旦形参获得了值便与实参脱离关系, 其值的改变也不影响实参。例:从键盘输入两个整数,交换并输出。,函数的参数传递 传值调用与引用调用,#include void swap(int a ,int b);int main
7、()int x(5),y(10);cout“x=“x“ Y=“yendl;swap(x,y);cout“x=“x“ Y=“yendl;return 0;void swap(int a, int b)int t;t=a; a=b; b=t;,引用调用引用是一种特殊类型的变量,可被认为是另一个变量的别名。引用声明格式 类型 将in声明为int类型变量I的引用(别名)注: 一旦将in作为I的别名,就不能再另做他用。用引用作为形参的调用,称为引用调用。引用作为形参,通过形实结合,成为实参的一个别名,那么, 对形参的任何操作都会影响实参。,函数的参数传递 传值调用与引用调用,有语句如下:int i =
8、5; 考int ,函数的参数传递 传值调用与引用调用,例 输入两个整数交换后输出,#include void Swap(int ,void Swap(int 运行结果:x=5 y=10x=10 y=5,Swap(x,y);,函数返回值return语句来返回 【例5.8】编写函数计算m(m+1)+(m+2)(m+3)n的结果(nm),将结果返回主函数输出#include “iostream.h“ int Addup(int m,int n) int sum=0;for(int i=m;i=n;i+)sum+=i;return sum; ,void main() int m,n,sum;coutm
9、;coutn;sum=Addup(m,n); coutsumendl; ,使用引用返回处理结果 【例5.9】编写函数将一个float类型的数据分开成整数部分和小数部分,将结果返回主函数输出。 #include “iostream.h“ void DF (float origin, int ,void main() float origin;int intpart;float decimalpart;coutorigin; DF (origin,intpart,decimalpart); cout“浮点数“origin“分开后得到整数部分是: “intpart“,小数部分是:“decimalpa
10、rtendl; ,使用指针返回处理结果 【例5.12】编写函数将两个变量交换,并将结果返回给主函数(使用指针作为函数参数)。 #include “iostream.h“ void Exchange(int * m,int * n) int t = *m; *m = *n; *n = t;cout“在子函数中输出结果:“;cout“*m=“*m“ “*n=“*nendl; ,void main() int a=35,b=25;cout“在主函数中输出结果:“;cout“a=“a“ “b=“bendl;Exchange( ,【例】假设有一个浮点类型的一维数组,编写函数将数组所有元素的值加在一起,结
11、果放在数组的第一个元素中。 #include “iostream.h“ void AddArray(float * p,int n) float sum=0.0;for(int i=0; i=n-1; i+) sum=sum+pi;p0=sum; ,void main() float array5=1.1,2.2,3.3,4.4,5.5;for(int i=0; i5; i+)coutarrayi“ “;coutendl;AddArray(array,5); for(i=0;i5;i+)coutarrayi“ “;coutendl; ,函 数 声 明 功能 通知编译器,某一个标识符是一个函数,
12、其返回值类型和形式参数分别是什么,函数的具体实现在程序的其它地方。 格式类型说明符 被调用函数名(含类型说明的形参表);例如有下面的函数声明语句:void fun(int a);注意两个问题,第一,函数声明语句最后面的分号不可省略;第二,形式参数表中形式参数可以只写出数据类型,形式参数的名字可写可不写。,【例】函数声明语句的使用。 #include “iostream.h“ void fun(int); /函数声明语句 void main() fun(5); void fun(int m) cout“函数定义体放在了调用位置的后面!“endl;cout“函数声明语句中的形参只有类型没有名字!“
13、endl;cout“m=“mendl; ,5.3 全局变量和局部变量,变量的存储机制与C+的内存布局操作系统管理内存,把分配给某一个应用程序的内存空间依次划分成代码段、数据段、堆和栈。 代码段中存放程序代码,即C+的函数,C+程序中使用到的常数、符号常量、常量字符串也都放在代码段;全局变量和静态变量存放在数据段;堆中存放的是用户动态申请的内存空间,程序员可以通过new操作符向操作系统申请,使用完成后用delete操作符删除;程序员在语句块中声明的局部自动变量存放在栈中。,全局变量 一个变量在所有函数的外部定义,则称该变量为全局变量。 全局变量在多个函数中都可以直接访问,因而可以实现数据的共享。
14、#include “iostream.h“int globalint; /全局变量void Function(void); /函数声明语句void main() cout“主函数中globalint=“globalintendl; globalint+; Function();void Function() cout“子函数中globalint=“globalintendl;,局部变量 定义在函数体内或语句块内的变量。 #include “iostream.h“void Function(void);void main() int a=16; cout“a=“aendl; int b=25;
15、cout“a=“a“ b=“bendl; Function(); cout“a=“aendl; void Function() int c=13; c+;cout“c=“cendl; ,#include “iostream.h“void Function(void);void main() int a=16; cout“a=“aendl; int a=25; cout“a=“aendl;Function(); cout“a=“aendl; void Function() int c=13; c+;cout“c=“cendl; ,5.5 作用域与标识符的可见性 作用域描述的是标识符能够起作用的程
16、序范围问题。 四种,分别是函数原型作用域、块作用域、类作用域和文件作用域。 函数原型作用域,是指在函数声明语句的形式参数表中定义的变量,其作用在于向编译器说明所定义的函数具有什么类型的参数,系统并不为这种变量分配内存空间,因而变量名称可以省略,其作用域范围仅限于函数原型的形式参数表。 在块中定义的标识符具有块作用域,其作用域范围从定义的地方开始,到块的结束(与之匹配的右大括号)为止。,在类中定义标识符具有类作用域,类作用域中定义的标识符还有多种访问属性,其作用域范围较为复杂,具体内容将在下一章讨论。 如果一个标识符不是函数原型作用域,也不是块作用域和类作用域,则该标识符就只能在所有函数和类的外
17、部声明,称该标识符具有文件作用域,对变量来讲,就是全局变量。 两个作用域可能会有包含关系,但不可能会有交叉关系。,注意: 标识符必须先定义,才能使用; 在同一作用域范围内,不能够定义相同名称的标识符; 如果两个作用域没有相互包含关系,则可以在两个块中分别声明同名的标识符,这两者之间互不影响; 如果外层的作用域包含了内层的作用域,在内层作用域中定义了与外层作用域中标识符同名的标识符,则内层的标识符会屏蔽外层的标识符,在内层标识符定义后,使用的是内层的标识符。,可见性,#include “iostream.h“ int a=12; void fun1(int var) ; void fun2(vo
18、id); void main( ) int b=3;coutabendl;int a=8; coutabendl;fun1(b); void fun1(int var) coutavarendl; ,a全局变量,文件作用域,var函数原型作用域,b块作用域,a 块作用域,屏蔽全局变量a,var函数原型作用域,存储类型与标识符的生命期,变量的存储类型 auto register extern static 标识符的生命期 标识符从诞生到到消亡的时间段称为其生命期。 对于变量来讲,程序运行到什么时候,系统为变量分配了内存空间,此时变量诞生;到什么时候系统收回变量所占的内存空间,此时变量消亡,这个时
19、间段被称为变量的生命期。,递归调用,函数直接或间接地调用自身,称为递归调用。 递归过程的两个阶段: 递推:4!=43! 3!=32! 2!=21! 1!=10! 0!=1 未知 已知 回归: 4!=43!=243!=32!=62!=21!=21!=10!=10!=1 未知 已知,#include void main( ) int n;cinn; coutn“!= ”fac(n)endl;,Int fac(int n) int result=1;if(n1) result*=n;n-;reutrn result; ,【例】用递归算法求123n的和。 #include “iostream.h“ i
20、nt AddSum(int n); void main() int n,sum;coutn;sum=AddSum(n);cout“1+2+.+n=“sumendl; int AddSum(int n) if(n=1) return 1;elsereturn n+AddSum(n-1); ,#include “iostream.h“ int f(int a ,int n) if(n=1) return a0*a0;return an-1*an-1+f(a,n-1); void main() int a3=1,2,3;int n=3;coutf(a,3); ,【例5.24】汉诺塔问题。 古代有一个
21、梵塔,塔内有三个座A、B、C,A座上有64个盘子,盘子大小不等,大的在下,小的在上(如图5-3所示)。有一个和尚想把这64个盘子从A座移到C座,但每次只能允许移动一个盘子,并且在移动过程中,3个座上的盘子始终保持大盘在下,小盘在上。在移动过程中可以利用B座,要求打印移动的步骤。 #include “iostream.h“ void MoveOne(char source,char destination) /直接移动一个盘子的函数,打印该步做法 coutn; cout“移动过程如下:“endl;HanoiTower(n,A,B,C); ,【例5.25】汉诺塔的步骤数问题。 分析:移动n个盘子的
22、过程可以分为三个步骤,第一步移动n-1个盘子,第二次移动一个盘子,第三次再移动n-1个盘子,这是其递推过程;移动一个盘子的步骤数为1,这是其结束条件。可编程如下: /例5.25源代码 #include “iostream.h“ int StepNum(int n) if(n=1)return 1; /结束条件elsereturn StepNum(n-1)*2+1; /递推过程 void main() int n,int num; coutn; num=StepNum(n);cout“需要的步骤数为:“numendl; ,5.7 函数的重载、内联及默认参数,函数重载 指多个函数的函数名称相同,但
23、各个函数形式参数表中的参数个数或参数类型有区别,在进行函数调用时,编译器会根据所给出的实在参数的个数和类型,找到一个最佳匹配的函数进行调用。 采用函数重载的机制,多个功能相同或相近的函数可以使用同一个函数名,从而减少整个程序中需要的标识符的数目,减轻程序员编程时的记忆负担。但要注意,重载的函数仍然是多个独立的函数,并不能减少程序代码的编制量。,【例】编写三个重载的函数,分别实现两个整数的加法,浮点数的加法和双精度数的加法。 #include “iostream.h“ int add(int a,int b); float add(float a,float b); double add(dou
24、ble a,double b); void main( ) int ia=3,ib=6;float fa=3.5,fb=4.8;double da=6.7,db=9.8;coutadd(ia,ib)endl;coutadd(fa,fb)endl;coutadd(da,db)endl; ,int add(int a,int b) cout“本次调用的是整型的加法!“endl;return a+b; float add(float a,float b) cout“本次调用的是浮点型的加法!“endl;return a+b; double add(double a,double b) cout“本次
25、调用的是双精度型的加法!“endl;return a+b; ,【例5.27】编写三个重载的函数,分别输出一个、两个或三个整型数。#include “iostream.h“ void display(int a); void display(int a,int b); void display(int a,int b,int c); void main( ) int a=3,b=4,c=5;display(a);display(a,b);display(a,b,c); ,void display(int a) cout“本次调用输出一个整数:“aendl; void display(int a,
26、int b) cout“本次调用输出两个整数:“a“,“bendl; void display(int a,int b,int c) cout本次调用输出三个整数:“ a “,“b“,“cendl; ,函数重载要求多个函数的形式参数的个数或类型不一致,与函数的返回值类型无关,与形式参数的名称无关。 请看下面的两组函数原型: void fun(int a); int fun(int a); 这两个函数不能构成重载,因为这两个函数的形式参数表完全一致,是否构成重载与函数的返回值类型无关。 void fun(int a, int b); void fun(int x, int y); 这两个函数同样
27、不能构成重载,因为这两个函数的形式参数个数和类型完全一致,是否构成重载与函数的形式参数名称无关。,默认参数 -缺省的形参值,函数在声明时可以预先给出默认的形参值,调用时如给出实参,则采用实参值,否则采用预先给出的默认形参值。,int add(int x=5,int y=6) return x+y; ,void main(void) add(10,20); /10+20add(10); /10+6add( ); /5+6 ,缺省形参值必须从右向左顺序声明,并且在缺省形参值的右面不能有非缺省形参值的参数。因为调用时实参取代形参是从左向右的顺序。 例: int add(int x,int y=5,i
28、nt z=6); /正确 int add(int x=1,int y=5,int z); /错误 int add(int x=1,int y,int z=6); /错误,缺省形参值与函数的调用位置,调用出现在函数体实现之前时,缺省形参值必须在函数原形中给出;而当调用出现在函数体实现之后时,缺省形参值需在函数实现时给出。 例:,int add(int x=5,int y=6); void main(void) add( ); /调用在实现前 int add(int x,int y) return x+y; ,int add(int x=5,int y=6) return x+y; void ma
29、in(void) add( ); /调用在实现后 ,缺省形参值的作用域,在相同的作用域内,缺省形参值的说明应保持唯一,但如果在不同的作用域内,允许说明不同的缺省形参。 例: int add(int x=1,int y=2); void main(void) int add(int x=3,int y=4);add( ); /使用局部缺省形参值(实现3+4) void fun(void) .add( ); /使用全局缺省形参值(实现1+2) ,内联函数声明与使用,声明时使用关键字 inline。 编译时在调用处用函数体进行替换,节省了参数传递、控制转移等开销。 注意: 内联函数体内不能有循环语句
30、和switch语句。 内联函数的声明必须出现在内联函数第一次被调用之前。 对内联函数不能进行异常接口声明。,【例】内联函数使用举例。 #include “iostream.h“ inline float RectangleArea(float length,float width) return length*width; void main() float a=3.5,b=4.0; float area; area=RectangleArea(a,b);coutareaendl; ,C+中的头文件是后缀名为“.h”的文件(也有一些系统头文件没有后缀名) 头文件中通常包含用户自定义的数据类型(
31、如枚举、结构体或类定义)、外部变量、外部函数、常量等内容。如果程序中的某一个CPP文件需要用到这些内容,可以使用#include命令包含该头文件。一个C+程序中可以有多个不同的头文件。,5.8 头文件与多文件结构,/例5.32源代码 /文件1,头文件,文件名:header.h struct STR /结构体 int partone;int parttwo; ; int globalint=365; /全局变量 void printglobalint(); /全局函数声明 /文件2,CPP文件,文件名:main.cpp #include “iostream.h“ #include “header
32、.h“ /包含用户自定义头文件 void main() STR s1; /使用头文件中定义的结构体s1.partone=3;s1.parttwo=4;couts1.partone“,“s1.parttwoendl;printglobalint();/使用头文件中定义的函数声明语句 void printglobalint() coutglobalintendl;/使用头文件中定义的全局变量 ,多文件结构,编译器在编译C+程序时,首先将程序员编写的源程序文件(后缀名为CPP的文件)编译成目标文件(后缀名为OBJ的文件),一个或多个OBJ文件经过链接可生成可执行文件(后缀名为EXE的文件)。一个源程
33、序文件又可以包含一个或多个不同的头文件。 1.使用外部声明实现数据和代码的共享 2.使用头文件实现代码的共享,5.9 编译预处理,编译器在对源程序进行编译生成目标文件之前,一般要先用编译预处理程序对源程序文件进行预处理,如删掉程序中的空白(包括多余的空格、换行符、制表符、注释等)、进行宏替换等等。编译预处理主要用于扩展C+程序设计的环境,实际上并不是C+语言的组成部分。编译预处理指令主要包括宏定义命令、文件包含命令以及条件编译指令。宏定义命令用于定义符号常量或定义带参数的宏,以实现简单的函数计算功能;文件包含命令用于将其它的源程序文件或头文件插入到本源程序文件中;条件编译指令可以根据条件判断来
34、决定是否编译某一程序段。,宏定义命令,#define命令用于定义一个宏,#undef用于取消用#define命令定义的宏使之不再起作用。定义一个宏之后,它将一直起作用,直到文件结束或使用#undef命令取消该宏定义为止。 1.定义符号常量 使用#define命令定义符号常量 2.定义带参数宏 使用#define命令定义带参数宏 3.定义一个用于条件编译的宏 #define 宏名,文件包含命令,文件包含命令指的是#include命令,该指令在前面的程序中已经多次使用,其作用是将另外一个源文件或头文件(可以是系统头文件或用户自定义头文件)的内容插入到当前源文件的本#include指令处。文件包含命
35、令在使用时,需要用双引号或者尖括号把将要包含进来的源程序文件或头文件括起来。 #include “文件名“ #include ,条件编译命令,条件编译指令用于对某些程序段进行有条件的编译,如果条件满足,则编译该程序段;如果条件不满足,则不予编译。常见的条件编译指令有以下几个: #if 常量表达式 #else #elif 常量表达式 #ifndef 宏名 #ifdef 宏名 #endif,1.格式1 #if 常量表达式程序段 #endif 2.格式2 #if 常量表达式程序段1 #else程序段2 #endif 3.格式3 #if 常量表达式1程序段1 #elif 常量表达式2程序段2 #eli
36、f 常量表达式n程序段n #else程序段n+1 #endif,4.格式4 #ifdef 标识符程序段 #endif 5.格式5 #ifdef 标识符程序段1 #else程序段2 #endif 6.格式6 #ifndef 标识符程序段 #endif 7.格式7 #ifndef 标识符程序段1 #else程序段2 #endif,在头文件中可以定义一个唯一的标识符,用此标识符来判断程序段是否已经参加过编译,如果已经参加过,就不再编译;如果没有参加过,则编译程序。例如下面的头文件: /系统主要头文件,文件名:main.h #ifndef MAIN_H /注意与#endif配对 #define MAIN_H/变量定义/类型定义/. #endif /MAIN_H,