第 5章函数和编译预处理
2009-7-28 2
函数的定义和调用函数?完成某一特殊任务的程序块,分 库函数 和 用户函数 。
C++函数作用,1、大任务化小时,表示小任务;
2、定义方法。
文件? 编译的独立单位。
n
2
1
m
2
1
件 文
...
件 文件 文序 程数 函
...
数 函数 函件 文
2009-7-28 3
库函数? 由编译系统自定义,可直接调用,
它们的声明和定义包含在相应的头文件中,
应用程序只要包含相应的头文件就可以调用函数。
库函数
2009-7-28 4
常用的函数库有:
math.h?数学运算,如 sqrt(x),fabs(x).
stdlib.h?类型转换、存储分配等
string.h?字符串处理
iostream.h?键盘和文件输入 /输出的成员函数
iomanip.h?输出格式等
fstream.h?定义了 C++的文件流体系例 1,char *pChar;
pChar=new char; //调用库函数
char *string=new char[25]; //调用库函数
//…
delete pChar; //调用库函数
delete [ ] string; //调用库函数
2009-7-28 5
例 2,库函数的调用。
#include <iostream.h>
#include <math.h>
void main( )
{
float a=900.0;
float root = sqrt(a); //调用库函数
cout << " The square root of " <<a
<< " is," << root << endl;
}
执行结果,The square root of 900 is,30
2009-7-28 6
自定义函数函数的声明,类型名 函数名 (参数表 );
其中,类型名,函数返回的类型。
参数表,函数的形式参数表,
具体为,type1 p1,…,typen p2,且 p1、
p2,…,pn可省略。
函数作用过程,1.函数声明(函数原型说明)
2.函数定义
3.函数调用
2009-7-28 7
注意,1.函数的声明必须在函数的调用之前。其目的为告诉编译程序,该函数的返回值类型、
参数个数及各参数的类型,以便调用时作有效性检查。
2.当函数的定义在前,函数的调用在后时不需要声明。
函数声明如,int max(int a,int b);

int max(int,int);
2009-7-28 8
求两个整数中的大数,若如 531,则出现错误。 531
应改为,533
2009-7-28 9
函数的定义:
类型名 函数名 (参数表 ) {
函数体
}
1.类型名给出函数返回值的类型;
2.参数表又称形式参数表,含有每个形参定义,
每个形参定义包含类型和参数名;
3.函数体是要完成的具体操作。
2009-7-28 10
类型名可以是空( void),且是整型时可省略。
参数表也可为空( void)。
如,int prime(int x) //定义函数,int可省略
{ for(int i=2; i<=x/2; i++)//从 1到 x/2的循环体
if(x%i= =0)return 0; // x被 i整除
return 1;
}
2009-7-28 11
函数的调用,函数在调用时才被执行,其调用参数称为 实参数,在前例中已调用了函数。
注意,函数的形参的次序和类型要与实参一样如在求素数的例题中:
函数声明为:
int prime(int x); //x为形参函数调用为:
for(int n=3; n<=20; n++) //从 3到 20的循环体
if(prime(n)) //调用函数,n为实参
cout << n << "," ; //1=素数; 0=非素数
2009-7-28 12
形参与实参的结合方式:
传值调用(即值调用)
传地址调用
引用调用传值调用要点:
1.每个实参均可为表达式。
2.调用时先求出实参表达式的值,并将值传给形参。
3.函数处理的结果不能带给调用者。
4.函数完全独立,通过 return语句返回值或不返回值。
2009-7-28 13
函数调用示例,532
执行后输出:
a=40 b=70
a=40 b=70
例 2,说明实参与形参对应关系示例。
52
执行后输出,5 5.6 98 98 50 50.6
2009-7-28 14
注,若可将实参的值转换成对应形参,称为 兼容的 ;
否则称为 不兼容 的。
当函数的类型名不空时,要求返回一个值给调用者。
返回值语句格式为:
return <表达式 >;
例 3,编写一函数,设输入参数为 n,当 n<0时,则输出“负数不能开平方!”;否则输出 0~n之间所有整数的平方根。
53
在函数体中要结束函数的执行并返回调用者时,也要使用 return语句。此时的格式为:
return;
2009-7-28 15
例 4,打印 3到 20之间的全部素数。
#include <iostream.h>
int prime( int n); //函数声明
void main( void )
{ cout << "The primes in [3,20] are:"<<endl;
for(int n=3; n<=20; n++) //从 3到 20的循环体
if( prime( n)) ) //调用函数
cout << n << "," ; //1=素数; 0=非素数
}
int prime( int x) //定义函数
{ for( int i=2; i<=x/2; i++)) //从 1到 x/2的循环体
if( x%i= =0) return 0; // x被 i整除
return 1; }
执行结果,3,5,7,11,13,17,19
2009-7-28 16
例 5,输入两个实数,并求出其中的大数。设计一个函数 max( )来实现这一功能。
51若 输入,243.2 321.9
则 输出,
两个数中的大数为,321.9
函数的调用过程如下:
main( )函数调用函数 max( ) 函数 max( )
结束 执行函数 max( )的函数体
2009-7-28 17
参数缺省函数在函数声明时,其形式参数可以赋值作为省略值,在调用函数时,若实参数目少于形参数目时,参数缺省值从右到左调用。
注意:声明和调用时都从右到左。
为什么要用参数缺省函数?
1,对函数的实参实行初始化。
2,使函数的定义和调用更具有一般性。
3,降低编程的复杂性,降低程序出错的可能性。
2009-7-28 18
例 6,函数可声明为( 从右到左缺省 )
void f0(float x,int y,char z);
void f1(float x,int y,char z=`B`);
void f2(float x,int y=4,char z=`B`);
void f3(float x=1,int y=4,char z=`B`);
调用时( 从右到左补充 ),
float a=2.1; int b= 5; char c=`C`;
f3(a,b,c); //三参数值为,a,b,c
f3(a,b); //三 参数值为,a,b,`B`
f3( ); //三 参数值为,1,4,`B`
f1(a,b); //三 参数值为,a,b,`B`
f0(a,b,c); //三 参数值为,a,b,c
f0(a,b); f1(a); f2( ); //错误问题:是否只要定义一个函数 f3?
2009-7-28 19
函数的嵌套和递归调用在调用一个函数时,该函数体内又调用另一个函数,称为 函数的嵌套 。
2009-7-28 20
例 7,求 5!和 10!。 54
函数 A中出现调用函数 A,或 A中调用函数 B,B中又调用函数 A,则称为 递归调用 。前者称 直接递归,
后者称 间接递归 。
递归调用和回推过程如下:
5*f(4)
4*f(3)
3*f(2)
2*f(1)
1
5*24=120
4*6=24
3*2=6
2*1=2
2009-7-28 21
例 8,用递归函数求 Fibonacci数列 。
1 n=1
f(n)= 1 n=2
f(n-1)+f(n-2) n>2
55
结果,Enter an integer,4
Fibonacci(3) = 3
2009-7-28 22
递归执行流程
fibo(4)
return fibo(3)+fibo(2)
return fibo(2)+fibo(1) return 1
return 1 return 1
2009-7-28 23
例 9,汉诺(河内)( Hanoi)塔问题。
柱上有 n个盘子,盘子的大小不等,大的盘子在下,小的盘子在上。要求将 A柱上的 n个盘子移到 C柱上,每次只能移一个盘子。在移动的过程中,可以借助于任一根柱子,但必须保证三根柱子上的盘子都是大盘子在下,小盘子在上。要求编一个程序打印出移动盘子的步骤。 A B C
n=3时的演示
2009-7-28 24
本题算法分析如下,设 A上有 n个盘子。
如果 n=1,则将圆盘从 A直接移动到 C。
如果 n=2,则:
1.将 A上的 n-1(等于 1)个圆盘移到 B上;
2.再将 A上的一个圆盘移到 C上;
3.最后将 B上的 n-1(等于 1)个圆盘移到 C上。
如果 n=3,则:
1.将 A上的 n-1(等于 2)个圆盘移到 B(借助于 C);
2.再将 A上的一个圆盘移到 C上;
3.最后将 B上的 n-1(等于 2)个圆盘移到 C上。
2009-7-28 25
由此可以看出,移动的过程可分解为三个步骤:
第一步 把 A上的 n-1个圆盘移到 B上;
第二步 把 A上的一个圆盘移到 C上;
第三步 把 B上的 n-1个圆盘移到 C上;其中第一步和第三步是类同的。
显然这是一个递归过程,可分为两类操作:第一类将 m( m>1)个盘子从一根柱移到另一根柱,第二类将一个盘子从一根柱移到另一根柱。据此算法可编程如下:
56
2009-7-28 26
作用域和存储类作用域 即程序中所说明的标识符在哪个区间内有效。
分五类:
块作用域
文件作用域
函数原型作用域
函数作用域
类的作用域存储类 表示何时为变量分配存储空间及该存储空间所具有的特征。在变量说明时指定。
2009-7-28 27
块作用域块 是用花括号括起来的程序的一部分。块内说明的标识符的作用域从标识符的说明开始,到块结束处结束。块作用域的变量均为 局部变量 。如:
void ex(float x,float y){
cout<<“Input i,j:” ;
int i,j; //定义局部变量 i,j,到 }止
cin>>i>>j;
{int a,b; //定义局部变量 a,b,到 }止
a=6;
j=a;
}

}
2009-7-28 28
块作用域的作用,解决标识符同名问题。
当作用域相同时,不允许用同名标识符;当作用域不同时可以用同名标识符。下例是允许的:
int ab(void){ //块 A
int i,j;

{int i,j; //块 B

}
}
这里对块 B内的变量作了如下规定:
2009-7-28 29
1,局部优先。即屏蔽块 A内的变量,但退出后 B内变量不存在了。如:
#include <iostream.h>
void main(void){
int i=100,j=200,k=300;
cout<<i<<?\t?<<j<<?\t?<<k<<?\n?;
{int i=500,j=600;
k=i+j;
cout<<i<<?\t?<<j<<?\t?<<?\n?;
}
cout<<i<<?\t?<<j<<?\t?<<k<<?\n?;
}
执行后输出,100 200 300
500 600
100 200 1100
2009-7-28 30
2.for语句中说明的变量的作用域为含 for语句的内层块。如:
{…
for(int i=0; i<10; i++){
cout<<i*i<<?\t?;
}
cout<<“i=”<<i; //输出 i的值为 10
}
它等同于:
{…
int i;
for( i=0; i<10; i++){
cout<<i*i<<?\t?;
}
cout<<“i=”<<i;
}
2009-7-28 31
但下列循环体内的变量说明不同。如:
#include <iostream.h>
void main(void){
for(int i=0; i<5; i++){ //j的作用域开始
int j=0;
cout<<i+j<<?\t?;
} //j的作用域结束
}
输出结果为:
0 1 2 3 4
2009-7-28 32
文件作用域全局变量的作用域称为 文件作用域 。 全局变量 是在函数外部定义的或用 extern说明的变量(或标识符)。
注,块作用域内的变量与全局变量同名时,局部优先,
但可通过运算符,::”来引用全局变量。
:,称 作用域分辨符,用它可以访问隐藏的全局变量。
例 10,在块作用域内引用文件作用域的同名变量。
57其输出结果为:,:i=104
i=18
j=122
2009-7-28 33
外层作用域不能访问内层作用域;利用,:运算符可访问当前作用域所隐藏的全局名,但不能访问隐藏的局部名。作用域总是始于说明点。
如,int x=11;
void main(void){
int z;
int y=x; //此时 y为 11
int x=1;
::x=2;
y=x;
cout<<"x="<<x<<","<<"y="<<y<<",“
<<"::x="<<::x<<'\n';
}
输出结果,x=1,y=1,::x=2
2009-7-28 34
函数原型作用域函数原型作用域 是指在函数原型的参数表中说明的标识符所具有的作用域。其范围从说明起,至说明结束止。如:
float tt(int x,float y); //函数 tt的原型说明

float tt(int a,float b) //函数 tt的定义
{

}
说明的标识符与函数的定义及调用无关,故在原型说明中可省略参数名。上面的原型说明可写成:
float tt(int,float);
2009-7-28 35
函数作用域 函数作用域 指在函数内定义的标识符在函数内均有效。
注,1.C++中,只有标号具有函数作用域。
2.同一函数内不允许标号相同。
3.不允许在一个函数内用 goto语句转到另一个函数内执行。
下列函数定义错误:
void f(float x){
float y;
label,cout<<“输入 y的值:” ;
cin>>y;
{ label,y+=256; //同名标号
if(y<1000)goto label;
if(x>2000)goto label3;//标号没有定义
} }
void f2(void)
{label3,…}
void main(void)
{…}
2009-7-28 36
存储类存储类对变量而言,反映了变量占用内存空间的期限。
程序在内存占用的空间分三部分:
程序区(存可执行程序的代码)
静态存储区
动态存储区由变量的存储类型确定。
变量的存储类型分 静态存储变量:程序开始执行时分配动态存储变量:程序执行过程中分配
2009-7-28 37
C++中变量的存储类型分为四种:
▲ 自动类型( auto)
▲ 静态类型( static)
▲ 寄存器类型( register)
▲ 外部类型( extern)
自动类型变量 是用关键字 auto修饰的变量。是局部变量。属动态存储变量。
注,C++默认局部变量为自动类型变量,故说明时可省略 auto。如,void f(void){
int x;
auto int y; //x,y均为自动类型变量,
//且初值不定

}
2009-7-28 38
静态类型变量 是用关键字 static修饰的变量。属静态存储变量。如:
static int y=5;
static char s; //全局变量,初值为 0,即为空
void f(void){
static float x; //局部变量,初值为 0

}
注,1.静态类型的局部变量当再次执行函数时,变量仍使用相同的空间,故变量保留原有值。
例 10,使用静态类型的局部变量。 58
输入结果,105 110
2009-7-28 39
2.对静态变量在作用域外不可通过变量名来使用。
如上例在 main中不可使用变量 i。
3.程序中说明的全局变量必为静态存储类型,缺省的初值为 0。
函数也可以声明为静态的,局部于它所在的文件。
如,//file1.cc
static void f ( char ) { }
2009-7-28 40
寄存器类型变量 是用关键字 register定义的局部变量。
表示不为变量分配内存空间,而尽可能分配使用 CPU
的寄存器。因而速度较快。
主要用于控制循环次数的临时变量。
格式,register 数据类型 变量表;
如,register int i,j;
2009-7-28 41
注,1.只能用于存放临时值,不能长期存放变量的值;
2.静态变量和全局变量不能定义为寄存器变量。
3.对寄存器变量的实际处理,随系统而异。
4.允许使用的寄存器数目是有限的,不能定义任意多个寄存器变量 。
例 12,利用寄存器类型变量求 1~15的阶乘。
510
2009-7-28 42
外部类型变量 是指用关键字 extern修饰的变量。必为全局变量。
同一个源程序文件中定义的全局变量,定义性说明在后,使用在前时,使用前须说明外部类型变量。如:
//file.cpp
void f(int i){
extern int x,y; //x,y为外部类型变量,指明在后面说明
x+=i; y+=x; //使用全局变量
}
int x=100,y; //x,y为全局变量
void main(void)
{f(10);
cout<<“x=”<<x<<?\t?<<“y=”<<y<<?\n?;
f(20);
cout<<“x=”<<x<<?\t?<<“y=”<<y<<?\n?;
}
2009-7-28 43
内联函数为什么要内联函数? 程序中对函数的调用过于频繁,导致程序执行时间加长。内联函数的使用可提高程序执行效率。
内联函数的定义?在函数声明时加 inline
例如,inline void swap(int,int );
2009-7-28 44
内联函数作用机理? 把内联函数的代码直接嵌入函数调用处,而不是转到调用函数的代码处。
这样减少了调用转换的开销。
内联函数的缺点? 程序执行效率提高了,但可执行程序规模变大了。实际应用中要权衡效率和规模之间的关系。
2009-7-28 45
例 13,用内联函数实现求两个实数的大数。
511
注,1.除在函数体内含有循环,switch分支、复杂嵌套的 if语句外,均可定义为内联函数。
2.内联函数要定义在前,调用在后。
3.指定的内联函数,编译器决定是否作为内联函数处理。
4.内联函数的本质是用空间换速度,故当函数调用多次时,不宜采用。
结果,Enter two integers,10 28
max= 28
2009-7-28 46
具有缺省参数值的函数具有缺省参数值的函数,定义函数时给参数指定一个缺省的值,调用时若明确给出了实参的值,则使用实参值;若没有给出实参,使用缺省的值。
例 13,具有缺省参数值的延时函数。
512
2009-7-28 47
注,1.缺省参数的说明必须在函数调用前。两种方法:
( 1)函数定义在前。如上例 13。
( 2)先给出原型说明,在原型说明中列出参数的缺省值,定义函数时,不重复指定缺省参数的值。
513
例 14,设计一程序,输入长方体的长度、宽度和高度,求出长方体的体积。
2009-7-28 48
2.参数的缺省值可以是表达式,但表达式中所用的量必须有确定的值。
3.定义函数时,具有缺省值的参数可有多个,但必须位于参数表的最右边。如例 14中函数的说明不能写成:
float v(float a,float a=10,float b);
或 float v(float a=20,float b,float=20);
4.同一函数在不同的作用域内,可提供不同的缺省参数值。
如:
2009-7-28 49
void delay(int n=100);

void b( ){
void delay(int=200);

delay( ); //缺省值为 200

}
float cc( ){
void delay(int=300);

delay( ); //缺省值为 300

}
float dd( ){

delay( ); //缺省值为 100

}
void delay(int n){
for(; n>0; n- -);
}
2009-7-28 50
函数的重载
C++的两种重载,函数的重载 和 运算符的重载 。
函数的重载 指完成不同功能的函数可以具有相同的函数名。
例 15,重载求绝对值的函数,实现求整数、单精度数和双精度数的绝对值。
515
2009-7-28 51
注,1.定义的重载函数必须具有不同的参数个数或不同的参数类型。否则无法调用。
2.仅是返回值不同,不能定义重载函数。如:
float fun(float x) 和 void fun(float x)
{…} {…}
3.两同名函数,仅用 const或引用使参数类型有所不同,不能定义重载函数。 如:
int print (const int & );
和 int print (int );
2009-7-28 52
4.加修饰符使参数有所不同,取决于实参的具体类型。如
void print (unsigned int );
void print (int );
//…
print (1l); //出错
print (1u); //正确
2009-7-28 53
5,缺省参数有时能导致二义性。如:
void print ( int a,int b=1 );
void print (int a);
//…
print(1); //二义性
参数类型的次序参数个数参数类型重载函数的参数差别
2009-7-28 54
编译预处理编译预处理 是指在对源程序进行编译之前,先对源程序中的编译预处理命令进行处理;然后再将处理的结果,和源程序一起进行编译,以得到目标代码。
编译预处理不属 C++的语法范畴,故编译预处理指令用符号 #开头,以回车符结束。按功能编译预处理分为三种:
宏定义
文件包含(嵌入指令)
条件编译
2009-7-28 55
“包含文件”处理
“包含文件” 处理 是指一个源文件可以将另一个源文件的全部内容包含进来。
格式:
# include,包含文件名”
或 # include <包含文件名 >
编译预处理时,预处理程序将查找指定的被包含文件,并将其复制到 #include命令出现的位置上。
2009-7-28 56
如:设文件 file1.h的内容为:
int x=200,y=100;
float x1=25.6,x2=28.9;
设文件 flie2.cpp的内容为:
#include,file1.h”
void main(void){
cout<<x<<?\t?<<y<<?\n?;
cout<<x1<<?\t?<<x2<<?\n?;
}
2009-7-28 57
用文件 file1.h的内容替换编译预处理指令行后,
产生一个临时文件,其内容为:
int x=200,y=100;
float x1=25.6,x2=28.9;
{ cout<<x<<?\t?<<y<<?\n?;
cout<<x1<<?\t?<<x2<<?\n?;
}
2009-7-28 58
注,1.两种格式的区别仅在于:
( 1)使用双引号:系统首先到当前目录下查找被包含文件,如果没找到,再到系统指定的“包含文件目录”(由用户在配置环境时设置)去查找。
( 2)使用尖括号:直接到系统指定的“包含文件目录”去查找。一般地说,使用双引号比较保险。
2009-7-28 59
2.常用在文件头部的被包含文件,称为,标题文件,
或,头部文件,,常以,h”( head)作为后缀,简称 头文件 。在头文件中,除可包含宏定义外,还可包含外部变量定义、结构类型定义等。
3.一条包含命令,只能指定一个被包含文件。如果要包含 n个文件,则要用 n条包含命令。
4.文件包含可以嵌套,即被包含文件中又包含另一个文件。
2009-7-28 60
文件包含的优点:
一个大程序,通常分为多个模块,并由多个程序员分别编程。有了文件包含处理功能,就可以将多个模块共用的数据(如符号常量和数据结构)或函数,集中到一个单独的文件中。这样,凡是要使用其中数据或调用其中函数的程序员,只要使用文件包含处理功能,将所需文件包含进来即可,不必再重复定义它们,从而减少重复劳动。
2009-7-28 61
宏定义宏定义分:
不带参数的宏定义
带参数的宏定义不带参数的宏定义格式:
#define 标识符 字符 或 字符串或 #define 标识符,字符” 或,字符串”
其中标识符称为 宏名。
2009-7-28 62
如,#define PI 3.1415926
表示将宏名 PI定义为实数 3.1415926。编译预处理时将该指令后所有出现 PI的地方用 3.1415926代替。该过程称为,宏扩展,或,宏展开,。
又如,#define PROMPT,面积为:”
表示将宏名 PROMPT定义为字符串“面积为:”。编译预处理时将所有出现 PROMPT的地方用“面积为:”代替。
例 16,宏定义的使用。 516
执行结果:
面积为,24.6301?!?
2009-7-28 63
注,1.任一标识符均可作宏名,但通常用大写字母以示区别。
2.宏定义可位于程序的任一位置,其作用域从宏定义起,至源程序结束止。通常放在源程序的开始。
3.宏定义中可用已定义的宏名。预编译时先作替换。
4.宏扩展时,只对宏名作代换,不作计算,也不作检查,若有错则得到不正确的结果或编译时出现语法错误。
2009-7-28 64
如,#include <iostream.h>
#define A 3+5
#define B A*A
void main(void)
{cout<<B<<?\n?; }
经宏扩展后为
void main(void)
{
cout<<3+5*3+5<<?\n?;
}
故输出结果为,23,而不是 64。
2009-7-28 65
又如,#include <iostream.h>
#define PI 3.1415;
void main(void){
float r,area;
cout<<“输入半径,”;
cin>>r;
area=PI*r*r; //编译到该行发生错误
cout<<“面积为:” <<area<<?\n?;
}
因为该行经宏扩展后为:
area=3.1415; *r*r;
2009-7-28 66
5.要终止宏的作用域,可用预处理命令:
#undef 宏名如,#define PI 3.1415926

#undef PI

2009-7-28 67
6.当宏名出现在字符串中时,编译预处理不进行宏扩展。
如,#include <iostream.h>
#define A,中国”
#define B,A人民共和国”
void main(void){
cout<<“A南京” <<?\t?;
cout<<B<<?\n?;
}
执行后输出:
A南京 A人民共和国
7.在同一个作用域内,同一个宏名不允许定义两次或两次以上。
2009-7-28 68
带参数的宏定义的一般格式:
#define 宏名 (形参表 ) 语言符号字符串调用格式:
宏名 (实参表 ) 称为 宏调用如,#define VOLUMN(a,b,c) a*b*c //定义宏

d=VOLUMN(2.0,7.8,1.215); //调用宏经宏扩展后为:
d=2.0*7.8*1.215;
2009-7-28 69
注:
1.定义有参宏时,宏名与左圆括号之间不能留有空格。
否则,编译系统将空格以后的所有字符均作为替代字符串,而将该宏视为无参宏。
如,#define v1 ( a,b,c) ( a) *( b) *( c)
预处理程序认为是将无参宏 V1定义为
“( a,b,c)( a) *( b) *( c)”
2009-7-28 70
2.有参宏的展开,只是将实参作为字符串,简单地置换形参字符串,而不做任何语法检查。在定义有参宏时,在所有形参外和整个字符串外,均应加一对圆括号。
如,#define V(a,b) a*b //宏定义

c=V(e+f,d+c); //宏调用经宏扩展后成为,c=e+f*d+c;
应将宏定义改为:
#define V(a,b) ( a) *( b)
则扩展后为,c=( e+f) *( d+c);
2009-7-28 71
3.宏定义一般以换行符结束。当宏定义较长,多于一行时,需在换行符(回车键盘)前用转义符‘ \?。
如,#define swap(a,b,c,t) t=a; a=b; b=c; \
c=a
有参宏 与 有参函数 的不同之处 主要有以下几个方面:
1)调用有参函数时,是先求出实参的值,然后再复制一份给形参。而扩展有参宏时,只是将实参简单地置换形参。
2)在有参函数中,形参是有类型的,所以要求实参的类型与其一致;而在有参宏中,形参是没有类型信息的,因此用于置换的实参,什么类型都可以。有时,可利用有参宏的这一特性,实现通用函数功能。
2009-7-28 72
3)使用有参函数,无论调用多少次,都不会使目标程序变长,但每次调用都要占用系统时间进行调用现场保护和现场恢复;而使用有参宏,
由于宏扩展是在编译时进行的,所以不占运行时间,但是每引用 1次,都会使目标程序增大 1
次。
4)函数可以用 return语句返回一个值,而宏不返回值。
2009-7-28 73
条件编译条件编译 是设置条件,使对源程序中满足条件的行编译,而对某些不满足条件的程序行不进行编译。
条件编译可有效地提高程序的可移植性,并广泛地应用在商业软件中,为一个程序提供各种不同的版本。
两类条件编译指令:
根据宏名是否已定义确定是否要编译某些程序行;
根据表达式的值来确定。
2009-7-28 74
六种形式:
1.宏名作为编译指令的条件( 4种格式)。
格式 1:
#ifdef 宏名程序段
#endif
2009-7-28 75
注,( 1)当程序调试时,有时需要输出一些信息,
而调试结束,则不需要输出,此时可将输出语句用条件编译括起来。
如,#ifdef DEBUG
cout<<“x=”<<x<<?\n?;
……
#endif
在源程序开头增加宏定义:
#define DEBUG
一旦调试好,只需删除宏定义,此时输出调用信息部分不编译,故不需要在程序中删除。
2009-7-28 76
( 2) 宏名可用无参宏格式。
( 3)若宏名已定义,则编译程序段,否则不编译程序段。
( 4)该格式可简化为:
#define 宏名格式 2:
#ifdef 宏名程序段 1
#else
程序段 2
#endif
该格式编译时,根据条件决定取哪部分编译。
2009-7-28 77
格式 3:
#ifndef 宏名程序段
#endif
此格式表示若没有定义宏,则编译程序段,否则不编译程序段。
格式 4:
#ifndef 宏名程序段 1
#else
程序段 2
#endif
此格式表示若没有定义宏,则编译程序段 1,不编译程序段 2;否则不编译程序段 1,编译程序段 2。
2009-7-28 78
2,表达式的值作为条件编译的条件( 2种格式)。
格式 5:
#if 表达式程序段
#endif
该格式表示若表达式的值不等于 0,则编译程序段;否则,不编译程序段。
格式 6:
#if 表达式程序段 1
#else
程序段 2
#endif
该格式表示若表达式的值不等于 0,则编译程序段 1,不编译程序段 2;否则,不编译程序段 1,编译程序段 2。
2009-7-28 79
注,1,条件编译指令可出现在程序的任何位置。处理时将要编译的程序段写入一个临时文件,该临时文件作为编译程序的输入文件。
2,若条件编译的条件是表达式,编译预处理时先求出表达式的值,故表达式只能是含常量的运算。
3,条件编译还可用于包含文件中。如:
//a2.h //a1.h
#define AA1 6 #include,a2.h”
float area; #define A1 AA1*16
2009-7-28 80
//a.cpp
#include <iostream.h>
#include,a1.h”
#include,a2.h” //出错
#define PI 3.1415926
#define R 2.8
void main(void){
area=PI*R*R;
cout<<“圆面积 =”<<area<<?\n?;
cout<<长方形面积 =”<<A1<<?\n?;
}
同一变量重复定义
2009-7-28 81
两种解决方法:
1,在程序中保证同一个头文件在源程序文件中只包含一次。
2,使用条件编译,不论头文件被包含多少次,只编译第一次的包含指令。
2009-7-28 82
程序的多文件组织程序的多文件组织,指将一个完整的程序存放在两个及两个以上的文件中。通常是在设计功能复杂的大程序时用,可便于设计和调试。
内部函数内部函数 是指在一个源程序文件中定义的函数,
限定它只能在本源程序文件内使用。
2009-7-28 83
定义方法:
static 函数类型标识符 函数名()
{…}
如,static float fun()
{…}
2009-7-28 84
外部函数外部函数 是指在多文件组织的程序中,一个源程序文件中定义的函数,不仅能在本源程序文件内使用,也可在其他源程序文件中使用。
定义方法,…
extern 函数类型标识符 函数名(){…}

如,…extern int f1()
{…}…
float f2() //也为外部函数,extern可省。
{…}
2009-7-28 85
注,1,一个文件中要调用在另一个程序文件中定义的外部函数前,要对被调用的函数作原型说明,在说明前加 extern。
如设在程序文件 c1.cpp中定义了函数 f1():
int f1( float x,float y) //函数定义
{…} // 函数体的定义在程序文件 c2.cpp中要调用 f1(),则在调用前应有如下函数原型说明:
extern int f1( float,float); //函数原型说明

i=f1( x,y); //调用函数 f1()
2009-7-28 86
2,在程序的多文件组织中,定义全局变量时,若加上 static,则表示该全局变量只能在所定义的文件中使用,不加则表示允许在其他程序文件中使用。
3,一个文件中要用到另一个文件中定义的全局变量时,应对其作外部变量说明。如:
//t.cpp
extern float x=2.71828; //可省略 extern
//tt.cpp
#include <iostream.h>
void main(void){
extern float x; //说明 x为外部变量
cout<<“x=”<<x<<?\n?;
}
执行结果:
x=2.71828
2009-7-28 87
课堂总结
函数的三要素,函数的声明、定义和调用
函数包括:库函数和自定义函数
作用域:标识符的作用域有,块、函数、
文件、全局 4种。
在块和函数内声明的变量 —— 作用域是块和函数;
在函数外声明的静态全局变量 —— 作用域是定义它的文件;
全局变量的作用域 —— 整个程序。