函数是 C++程序的基本模块。可将一些功能相对独立的
或经常使用的操作或运算抽象出来,定义为函数。使用时只
要考虑其功能和使用接口即可。
在结构化程序设计中,函数是将任务进行模块划分的基
本单位。
在面向对象的程序设计中,类中所封装的操作是用函数
进行描述的,因此函数在 C++程序中具有非常重要的意义。
要掌握函数的使用,必须理解函数调用时的内部实现机
制,以及与此相关的内存分配机制、变量生命期和作用域。
本章还将介绍关于函数重载的概念,介绍递归算法、内
联函数、默认参数函数以及多文件组织、编译预处理、工程
文件的概念和运行库函数。
第四章 函数
第四章 函数
4,1 函数的定义与调用
4,5 作用域与存储类型
4,4 函数调用机制
4,3 全局变量和局部变量
4,2 函数的参数传递,
返回值及函数原型说明
4,9 编译预处理
4,8 头文件与多文件结构
4,7 C++的系统库函数
4,6 函数的一些高级议题
4.1 函数的定义与调用
4.1.1 函数概述
4.1.2 函数的定义
4.1.3 函数的调用
4.1.1 函数概述
函数是 C++程序的基本组成模块 。
通过函数, 可以把一个复杂任务分解成为若干
个易于解决的小任务 。 充分体现结构化程序设计由
粗到精, 逐步细化的设计思想 。
组成 C++程序的若干函数中, 有一个称为 main()
( Winmain()) 函数, 是程序执行的入口, 它可以调
用其他函数 。 而其他一般函数既可以调用也可以被调
用 。 函数之间的调用关系见下图:
4.1.1 函数概述
main ( )
fun2( )fun1( ) fun3( )
fun1_1( ) fun2_1( ) fun2_2( )
图 4.1 函数调用层次关系
4.1.1 函数概述
4.1.1
结束
函数按其是否系统预定义分为两类,一类是编译
系统预定义的,称为 库函数 或 标准函数,如一些常
用的数学计算函数、字符串处理函数、图形处理函
数、标准输入输出函数等。这些 库函数都按功能分
类,集中说明在不同的头文件中 。用户只需在自己
的程序中包含某个头文件,就可直接使用该文件中
定义的函数。另一类是用户 自定义函数,用户可以
根据需要将某个具有相对独立功能的程序定义为函
数。
4.1.2 函数的定义
函数定义格式如下:
数据类型 函数名(形式参数列表)
{
函数体;
}
说明:
1、函数的数据类型规定函数执行后所返回值的类型,如果没有值需要返回
,则定义为 void;
2、函数名是函数的标识,必须应该符合 C++标识符的定义,而且取名应做
到“见名知意”
3、形式参数列表中,如果有多个参数,用逗号分隔,并且每个参数要说明
数据类型,这些参数在调用之前没有实际的存储空间,也就没有具体的内
容,只有在在调用时候,才临时分配空间,并从调用函数中接收数据的实
际内容。一个函数也可以没有任何参数。
4、函数体的语句来描述了函数功能的实现的代码,函数执行完需要返回的
值是通过 return语句来实现。
如定义一个求最大值的函数 max
float max(float x,float y)
{ float z;
if(x > y) z = x;
else z = y;
return z;
}
定义函数时可能会涉及若干个变量,究竟哪些变量应当
作为函数的参数?哪些应当定义在函数体内?这有一个原则:
作为一个相对独立的模块,函数在使用时完全可以被看成
,黑匣子,,除了输入输出外,其他部分可不必关心 。从函
数的定义看出,函数头正是用来反映函数的功能和使用接口,
它所定义的是, 做什么,,在这部分必须明确, 黑匣子, 的
输入输出部分,输出就是函数的返回值,输入就是参数 。因
此,只有那些功能上起自变量作用的变量才必须作为参数定
义在参数表中;函数体中具体描述, 如何做,,因此除参数
之外的为实现算法所需用的变量应当定义在函数体内。
C++中不允许函数的嵌套定义,即在一个函数中定义另一
个函数。
提示
4.1.3 函数的调用
在 C++中, 除了主函数外, 其他任何函数都不能单独作为程
序运行 。 任何函数功能的实现都是通过被主函数直接或间接
调用进行的 。 所谓函数调用, 就是使程序转去执行函数体 。
无参函数的调用格式为:
函数名 ( );
有参函数的调用格式为:
函数名 (实际参数表 );
其中实际参数简称实参, 用来将实际参数的值传递给形参,
因此可以是常量, 具有值的变量或表达式 。
4.1.3 函数的调用
main( )
函数
调用
max(2.5,4.7 )
函数
max(2.5,4.7 )
return 4.7主程序后
续语句
【 例 4,1】 输入两个实数, 输出其中较大的数 。 其中求两个实
数中的较大数用函数完成 。
程序如下,
#include<iostream.h>
float max(float x,float y)
{ return(x>=y?x:y);
}
void main()
{ float x,y;
cout<<"输入两个实数,"<<endl;
cin>>x>>y;
cout<<x<<“和, <<y<<“中较大数为:,
<<max(x,y)<<endl;
}
4.2 函数的参数传递、返回值及
函数原型说明
4,2,1 参数传递及传值调用和引用概念
4,2,3 函数原型说明
4,2,2 函数返回值
函数调用首先要进行参数传递,参数传递的方向是由实参
传递给形参。传递过程是,先计算实参表达式的值,再将
该值传递给对应的形参变量 。一般情况下,实参和形参的
个数和排列顺序应一一对应,并且对应参数应类型匹配
(赋值兼容),即实参的类型可以转化为形参类型。而对
应参数的参数名则不要求相同。某些特殊情况下也允许参
数不对应,这将在函数高级议题中讨论。
按照参数形式的不同,C++有两种调用方式,传值调用 和
引用调用 。顾名思义,传值调用传递的是实参的值,本章
主要介绍传值调用。关于引用调用,将在第五章类与对象
中介绍。
4.2.1 函数的参数传递
传值调用和引用调用
【 例 4,2】 说明实参和形参对应关系的示例 。
#include <iostream.h>
#include <math.h>
float max(float x,float y)
{ int z;
z = x>y?x:y;
return z;
}
void main()
{ float a,b;
a = 10;
b = 5;
cout<<“最大值,"<<max(a,b)<<endl;
cout<<“最大值,"<<max(2.6,8.5)<<endl;
}
4.2.1.1 函数的传值调用
调用
max(10,5 )
函数
max(10,5 )
return
10
主程序后续语
句
a=10
b= 5
【 例 4,2】 说明实参和形参对应关系的示例 。
#include <iostream.h>
#include <math.h>
float max(float x,float y)
{ int z;
z = x>y?x:y;
return z;
}
void main()
{ float a,b;
a = 10;
b = 5;
cout<<“最大值,"<<max(a,b)<<endl;
cout<<“最大值,"<<max(2.6,8.5)<<endl;
}
4.2.1.1 函数的传值调用
调用
max(2.6,8.5 )
函数
max(2.6,8.5 )
return
8.5
主程序后续语
句
4.2.1.2 引用概念
引用类型,引用是 C++引进的,C没有这个概念。 C++引进引
用概念的主要用它做函数的参数和返回值。
什么是引用,引用其实是变量或对象的别名,这跟我们很多
作家有笔名是一样,如周树人,其笔名是鲁迅,其实是同
一个人,引用也是一样,它自身没有存储单位,引用的值
就是被它引用的变量或对象的值,引用的地址值也是被它
引用的变量或对象的地址值。
引用定义和声明,类型 &引用名 = 变量名
&是说明符,说明后面的标识符是个引用名
如,int I = 5;
int &ri = I;那么 ri就是被声明为变量 I的引用,
不能够是 int &ri
4.2.1.2 引用概念
引用变量的例子:
#include <iostream.h>
int main()
{ int i = 5,&ri = i;
cout<<“I=”<<i<<,RI=”<<ri<<endl;
i *= 3;
cout<<“I=”<<I<<,RI=”<<ri<<endl;
ri += 5;
cout<<“I=”<<I<<,RI=”<<ri<<endl;
cout<<“I Addr=”<<&I
<<“RI Addr=”<< &ri<<endl;
return 0;
} 结果是,I=5 RI=5
I=15 RI=15
I=20 RI=20
I Addr=0x8f8bfff4 RI Addr=0x8f8bfff4
4.2.1.2 引用概念
从程序中可以看出:
( 1)、对引用的操作就是对变量的操作,反之也一样
( 2)、引用和被引用的实体具有相同的地址
( 3)、程序中有一个求地址符号的 &和引用声明中的 &不一样
( 4)、指针和引用的区别:指针是变量,引用不是变量;指
针可以初始化,也可以不初始化,而引用必须初始化;指针
可以做数组元素,而引用不可以;指针也可以引用,而引用
不可以引用。 int &&ri=i; // 这样是不行的
int *p;
int *&rp = p;
int m = 5;
rp = &m;
( 5)、创建常量引用时候,系统会建立一个临时变量
const int i = 5;
const int &ri = i;
4.2.1.2 引用概念
( 6)、引用做参数,这是引用很重要的使用功能
#include <iostream.h>
void main()
{ int x = 3,y = 5;
void swap(int &,int &);
swap(x,y);
cout<<“x=“<<x<<“,y=“<<y<<endl;
}
void swap(int &n1,int &n2)
{ int temp = n1;
n1 = n2;
n2 = temp;
}
结果是 x=5,y=3
从运行的结果可以看出,函数之间的参数传递采用引用参数后,成功地 实现数据调用。
4.2.2 函数返回值
return语句的一般格式为:
return 表达式;
函数的计算结果通过该语句传递回主调函数 。
【 例 4,3】 设计函数,根据三角形的三边长求面积。如
果不能构成三角形,给出提示信息。
分析:函数为计算三角形面积,一般三角形返回面积值,
若不能构成三角形则返回 -1。设计一个主函数完成函数
测试。根据返回值情况输出相应结果。
程序见下页:
4.2.2 函数返回值
#include<iostream.h>
#include<math.h>
float TriangleArea(float a,float b,float c){
if ((a+b<=c)||(a+c<=b)||(b+c<=a)) return -1;
float s;
s=(a+b+c)/2;
return sqrt(s*(s-a)*(s-b)*(s-c));
}
void main(){
float a,b,c,area;
cout<<"输入三角形三边 a,b,c:"<<endl;
cin>>a>>b>>c;
area=TriangleArea(a,b,c);
if(area==-1) cout<<'('<<a<<','<<b<<',' <<c
<<')'<<"不能构成三角形! "<<endl;
else cout<<"三角形 ("<<a<<','<<b<<','<<c
<<")面积为,"<<area<<endl;
}
4.2.2 函数返回值
函数可以有返回值, 也可以没有返回值 。 对
于没有返回值的函数, 功能只是完成一定操作, 应
将返回值类型定义为 void, 函数体内可以没有
return语句, 当需要在程序指定位置退出时, 可
以在该处放置一个:
return ;
4.2.2
结束
4.2.3 函数原型说明
函数原型是一条以分号结束的语句, 实际上
就是所定义函数的函数头, 形如:
,函数返回值类型, 函数名 (,形参表, )
语法上对程序文件中函数的排列次序是没有固定要求的,
只要满足 先定义后使用 即可 。 但从结构化程序设计的角度,
通常是先调用后定义 。 使用函数原型, 则既符合由粗到精
的思维方式, 又满足了语法要求 。
其中形参表可以逐个列出每个参数的类型和参数名,
也可以列出每个形参的类型,参数名可省略,各形参之间
以逗号分隔。函数原型和所定义的函数必须在返回值类型、
函数名、形参个数和类型及 次序 等方面完全对应一致,否
则将导致编译错误。
下面是一个使用结构化程序设计思想开发的企业管理
报表程序的框架 。 它使用了函数原型说明 。
#include <iostream.h>
void menu_print();
void account_report();
void engineering_report();
float marketing_report(float x,int n);
void main()
{ int choice;
do{
menu_print();
cin>>choice;
} while(choice<=0||choice>=4);
switch(choice){
case 1,account_report(); break;
case 2,engineering_report(); break;
case 3,marketing_report(); break;
}
}
void menu_print() {
cout<<”系统功能:” <<endl;
cout<<”1 财务报表” <<endl;
cout<<”2 工程报表” <<endl;
cout<<”3 销售情况” <<endl;
cout<<”选择业务序号:” ;
}
void account_report(){
//生成财务报表
}
void engineering_report(){
//生成工程报表
}
float marketing_report(float x,int n) {
//生成市场报表;
}
4.3 变量的存储属性
4,3,1 变量的存储机制与 C++的内存布局
4,3,2 全局变量
4,3,3 局部变量
系统核心区( OS)
代码区(程序代码)
全局数据区 (全局、静态变量 )
栈区(函数局部数据)
堆区 (动态数据 )
4.3.1 C++的内存布局与变量的存储机制
操作系统为一个 C++程序的运行所分配的内
存分为四个区域, 如图 4.3 程序在内存中的
区域 所示:
动态存储区
静态存储区
( 1)代码区( Code area):存放程序代码,
即程序中各个函数的代码块;
( 2)全局数据区( Data area):存放全局数
据和静态数据;分配该区时内存全部清零。
( 3)栈区( Stack area):存放局部变量,如
函数中的变量等;分配栈区时内存不处理。
( 4)堆区( Heap area):存放与指针相关的
动态数据。分配堆区时内存不处理。参见第七
章。
4.3.1 C++的内存布局与变量的存储机制
4.3.2 全局变量
在所有函数之外定义的变量称为 全局变量 。
全局变量在编译时建立在全局数据区, 在未给
出初始化值时系统自动 初始化为全 0。
全局变量可定义在程序开头, 也可定义在中间
位置, 该全局变量 在定义处之后 的任何位置都是可
以访问的, 称为可见的 。
请看下例:
4.3.2 全局变量
打印
200
调用
func( )
函数
func( )
200*2
=400打印400
n=100
n=100*2
=200
【 例 4,5】 多个函数使用全局变量的例子 。
#include<iostream.h>
int n=100;
void func(){
n*=2;
}
void main(){
n*=2;
cout<<n<<endl;
func();
cout<<n<<endl;
}
4.3.3 局部变量
定义在函数内或块内的变量称为 局部变量 。
程序中使用的绝大多数变量都是局部变量 。
局部变量在程序运行到它所在的块时建立在栈中,
该块执行完毕局部变量占有的空间即被释放 。
局部变量在定义时可加修饰词 auto,但通常省略 。
局部变量在定义时若未初始化, 其值为随机数 。
4.3.3 局部变量
打印 main()
中的 t=3.5
调用
fun( )
函数
fun( )
打印 fun()
中的 t=5打印 main()中的 t=3.5
t= 3.5
t = 5
【 例 4,9】 使用局部变量的例子 。
#include<iostream.h>
void fun(){
auto int t=5;
// fun()中的局部变量, auto可省略
cout<<"fun()中的 t="<<t<<endl;
}
void main(){
float t=3.5;
//main()函数中的局部变量
cout<<"main()中的 t="<<t<<endl;
fun();
cout<<"main()中的 t="<<t<<endl;
}
4.4 函数调用机制
局部变量占用的内存是在程序执行过程中, 动态, 地建立
和释放的 。 这种, 动态, 是通过栈由系统自动管理进行的 。 当
任何一个函数调用发生时, 系统都要作以下工作:
( 1) 建立栈空间;
( 6) 恢复现场:取主调函数运行状态及返回地址, 释放栈空间;
( 7) 继续主调函数后续语句 。
( 5) 释放被调函数中局部变量占用的栈空间;
( 4) 执行被调函数函数体;
( 3) 为被调函数中的局部变量分配空间, 完成参数传递;
( 2) 保护现场:主调函数运行状态和返回地址入栈;
4.4 函数调用机制
void fun1(int,int);
void fun2(float);
void main(){
int x=1;y=2;
fun1(x,y);
}
void fun1(int a,int b){
float x=3;
fun2(x);
}
void fun2(float y) {
int x;
…
}
x 栈顶
栈底
y 3
fun2() fun1()运行状态及返回地址
x 3
b 2
a 1
fun1() main()运行状态及返回地址
y 2
x 1
main() 操作系统运行状态及返回地址
此图例说明 在程序执行过程中怎样通过栈,动态,地建立和
释放局部变量占用的内存的
4.5 作用域与存储类型
4.5.1 作 用 域
4.5.2 变量的存储类型
4.5.3外部存储类型与静态存储类型
4.5.4 生命期与可见性
4.5.1 作用域
1 块作用域 3 文件作用域2 函数原型作用域
作用域 指标识符能够被使用的范围 。 只有在作用
域内标识符才可以被访问 ( 称为可见 ) 。
本节只讨论 局部域 和文件域 ( 全局域 ), 其中局
部域包括 块域 和 函数原型域 。 任何 标识符 作用域的起
始点均为 标识符说明 处 。
下面分别介绍,
参和函数体中定义的局部变量, 作用域都在该函数内,
也称作 函数域 。
1,块域
块 指一对大括号括起来的程序段 。 块中定义的标
识符, 作用域在块内 。
复合语句是一个块 。
函数也是一个块 。
复合语句中定义的标识符,
作用域仅在该复合语句中 。
函数中定义的标识符, 包括形
1,块域
a= 3
b= 53
5
a=3 b=5
a=5 b=3
【 例 4,7】 输入两数, 将两数按从大到小的顺序保存, 并输出结果 。
结果
栈
t = 3
#include<iostream.h>
void main(){
int a,b; //具有函数域
cout<<"输入两整数,"<<endl;
cin>>a>>b;
cout<<“a="<<a<<'\t'<<"b="<<b<<endl;
if(b>=a){
int t; //具有块域
t=a; a=b; b=t; //交换 a,b的值
}
cout<<"a="<<a<<'\t'<<"b="<<b<<endl;
}
【 例 4,8】 设计函数完成两数交换, 用主函数进行测试, 看结果如何 。
#include<iostream.h>
void swap(int,int);
void main(){
int a,b; //a,b作用域为 main()
cout<<"输入两整数,"<<endl;
cin>>a>>b;
cout<<"调用前:实参 a="<<a<<',' <<"b="<<b<<endl;
swap(a,b); //传值
cout<<"调用后:实参 a="<<a<<','<<"b="<<b<<endl;
}
void swap(int a,int b){ //a,b作用域为 swap()
cout<<"调用中 …"<<endl;
cout<<"交换前:形参 a=,<<a<<','<<"b="<<b<<endl;
int t;
t=a; a=b; b=t; //交换 swap()中的 a,b的值
cout<<"交换后:形参 a="<<a<<','<<"b="<<b<<endl;
}
局部变量具有局部作用域使得程序在不同块中可以使用同名变量。这些
同名变量各自在自己的作用域中可见,在其它地方不可见 。
1,块作用域
由 VC++平台运行,结果如下:
输入两整数:
3 5
调用前:实参 a=3,b=5
调用中 …
交换前:形参 a=3,b=5
交换后:形参 a=5,b=3
调用后:实参 a=3,b=5
交换失败
对于块中 嵌套 其它块的情况,如果嵌套块中有同名局部变量,服从
局部优先原则,即在内层块中 屏蔽 外层块中的同名变量,换句话说,内
层块中局部变量的作用域为内层块;外层块中局部变量的作用域为外层
除去包含同名变量的内层块部分。
如果块内定义的局部变量与全局变量同名,块内仍然局部变量优先
,但与块作用域不同的是,在块内可以通过域运算符,::,访问同名
的全局变量。
全局 n= 100
100 200 300
内 i= 500
内 j= 600
内 n=500+600
=1100
1100 500 600
100
200+300=500 500
500 200 300
外部 i=200
外部 j=300
【 例 4,9】 显示同名变量可见性 。
int n=100;
#include<iostream.h>
void main(){
int i=200,j=300;
cout<< n<<'\t'<<i<<'\t'<<j<<endl;
{ //内部块
int i=500,j=600,n;
n=i+j;
cout<< n<<'\t'<<i<<'\t'<<j<< endl;
//输出局部变量 n
cout<<::n<<endl;//输出全局变量 n
}
n=i+j; //修改全局变量
cout<< n<<'\t'<<i<<'\t'<<j<< endl;
}
2 函数原型作用域
函数原型不是定义函数, 在作函数原型声
明时, 其中的形参作用域只在原型声明中,
即 作用域结束于右括号 。 正是由于形参不能
被程序的其他地方引用, 所以通常只要声明
形参个数和类型, 形参名可省略 。
3 文件作用域
文件作用域 也称全局作用域 。 定义在所有函
数之外的标识符, 具有文件作用域, 作用域
为从定义处到整个源文件结束 。 文件中定义
的全局变量和函数都具有文件作用域 。
如果某个文件中说明了具有文件作用域的标
识符, 该文件又被另一个文件包含, 则该标
识符的作用域延伸到新的文件中 。 如 cin和
cout是在头文件 iostream.h中说明的具有文
件作用域的标识符, 它们的作用域也延伸到
嵌入 iostream.h的文件中 。
存储类型决定了变量的 生命期, 变量生命期指从
获得空间到空间释放之间的时期 。
4.5.2 变量的存储类型
存储类型的说明符有四个,auto,register,static和
extern。 前两者称为自动类型, 后两者分别为静态和外
部类型 。
本节重点掌握 static和 extern这两种类型的使用和
区别 。 具体说, 区分 局部变量和静态局部变量, 全局
变量和静态全局变量 。
auto,前面提到的局部变量都是自动类型 。 其空间分配
于块始, 空间释放于块终, 且由系统自动进行 。 自
动变量保存在栈中, 且是在程序运行过程中获得和
释放空间, 未初始化时值为随机数 。
4.5.2 变量的存储类型
register:为提高程序运行效率, 可以将某些变量保存在
寄存器中, 即说明为寄存器变量, 但不提倡使用 。
static,静态变量 。 根据被修饰变量的位置不同, 分为
局部 ( 内部 ) 静态变量和全局 ( 外部 ) 静态变量 。
所有静态变量均存放在全局数据区, 编译时获得存
储空间, 未初始化时自动全 0,且只初始化一次 。
局部静态变量的 作用域 为 块域, 但 生命期 为 整个
文件 。 即当块结束时, 局部静态变量空间仍然保持,
直到整个程序文件结束时该局部静态变量空间才释放,
生命期结束 。
局部静态变量
【 例 4,10】 自动变量与局部静态变量的区别 。 ( 演
示 )
#include <iostream.h>
st(){
static int t=100; //局部静态变量
t++; return t;
}
at(){
int t=100; //自动变量
t++;return t;
}
void main(){
int i;
for(i=0;i<5;i++) cout<<at()<<'\t';
cout<<endl;
for(i=0;i<5;i++) cout<<st()<<'\t';
cout<<endl;
}
4.5.2 变量的存储类型
i= 0
t= 100
12345
101
4.5.2 变量的存储类型
i= 0
t=100
12
101
345
102103104105
#include <iostream.h>
st(){
static int t=100; //局部静态变量
t++; return t;
}
at(){
int t=100; //自动变量
t++;return t;
}
void main(){
int i;
for(i=0;i<5;i++) cout<<at()<<'\t';
cout<<endl;
for(i=0;i<5;i++) cout<<st()<<'\t';
cout<<endl;
}
全局静态变量
全局静态变量是指用 static修饰的全局
变量 。 有关内容在下节静态存储类型中介绍 。
4.5.3 外部存储类型与静态存储类型
1,外部存储类型
2,静态存储类型
一个 C++程序可以由多个源程序文件组成,编译系统
将这若干个文件连接在一起,产生可执行程序。外部
存储类型和静态存储类型确定了变量和函数在多文件
程序中的联络关系。
1 外部存储类型
外部存储类型包括外部变量和外部函数 。 在由多个源程序
文件组成的程序中, 如果一个文件要使用另一个文件中定
义的全局变量或函数, 这些源程序文件之间通过外部类型
的变量和函数进行沟通 。
在一个文件中定义的全局变量和函数都缺省为外部的,即
其作用域可以延伸到程序的其他文件中。但其他文件如果
要使用这个文件中定义的全局变量和函数,必须在使用前
用, extern”作外部声明,外部声明通常放在文件的开头。
变量定义时编译器为其分配存储空间,而变量声明指明该
全局变量已在其他地方说明过,编译系统不再分配存储空
间,直接使用变量定义时所分配的空间。
函数声明缺省为外部的,因此修饰词 extern通常省略。
1 外部存储类型
【 例 4.11】 外部存储类型的例子。假定程序包含两个源程序文件
Ex4_11_1.cpp和 Ex4_11_2.cpp,程序结构如下:
/* Ex4_11_1.cpp,由 main()组成 */
# include <iostream.h>
void fun2(); //外部函数声明,等价于 extern void fun2();
int n; //全局变量定义
void main(){
n=1;
fun2(); // fun2()定义在文件 Ex4_11_2.cpp中
cout<<″n=″<<n<<endl;
}
/* Ex4_11_2.cpp,由 fun2()组成 */
extern int n; //外部变量声明,n定义在文件 Ex4_11_1.cpp
中
void fun2() { //fun2()被文件 Ex4_11_1.cpp中的函数调用
n=3;
}
运行结果,n=3
2 静态存储类型
静态存储类型包括静态全局变量和静态函数。在定义全局
变量或函数时加说明符 static,就成为静态变量或静态函
数。静态存储类型的作用域与外部存储类型相反,一旦定
义为静态存储类型,就限制该变量或函数只能在定义它的
文件中使用。静态全局变量在编译时分配存储空间,如果
定义时不指定初值,则编译系统将其初始化为全 0。
一个全局变量和一个静态全局变量在使用上是不同的,其
他文件通过外部变量声明可以使用一个全局变量,但却无
法使用静态全局变量,静态全局变量只能被定义它的文件
所独享 。函数与静态函数之间的区别是相同的。
4.5.4 生命期与可见性
1,生命期
2,可见性
1 生命期
( 1)静态生命期
( 2)局部生命期
( 3)动态生命期
生命期( Life time)也叫生存期。生命期与存储区域相关,
存储区域分为代码区、静态数据区、栈区和堆区,相应地,
生命期分为 静态生命期、局部生命期和动态生命期 。
( 1) 静态生命期
静态生命期 指的是标识符从程序开始运行时
存在, 即具有存储空间, 到程序运行结束时消亡,
即释放存储空间 。 具有静态生命期的标识符存放
在静态数据区, 属于静态存储类型, 如全局变量,
静态全局变量, 静态局部变量 。 具有静态生命期
的标识符在未被用户初始化的情况下, 系统会自
动将其初始化为全 0。
函数驻留在代码区, 也具有静态生命期 。 所
有具有文件作用域的标识符都具有静态生命期 。
( 2) 局部生命期
在函数内部或块中定义的标识符具有局部生命
期, 其生命期开始于执行到该函数或块的标识符声
明处, 结束于该函数或块的结束处 。 具有局部生命
期的标识符存放在栈区 。 具有局部生命期的标识符
如果未被初始化, 其内容是随机的, 不可用 。
具有局部生命期的标识符必定具有局部作用域;
但反之不然, 静态局部变量具有局部作用域, 但却
具有静态生命期 。
( 3) 动态生命期
具有动态生命期的标识符由特定的函数调
用或运算来创建和释放,如调用 malloc()或用
new运算符为变量分配存储空间时,变量的生命
期开始,而调用 free()或用 delete运算符释放
空间或程序结束时,变量生命期结束。具有动
态生命期的变量存放在堆区。关于 new运算和
delete运算将在指针一章中介绍。
2 可见性
可见性 从另一个角度说明标识符的有效性,可见性与作
用域具有一定的一致性。 标识符的作用域包含可见范围
,可见范围不会超过作用域 。可见性在理解同名标识符
的 作用域嵌套 时十分直观。对于外层块与内层块定义了
同名标识符的,在外层作用域中,内层所定义的标识符
是不可见的,即外层引用的是外层所定义的标识符;同
样,在内层作用域中,外层的标识符将被内层的同名标
识符屏蔽,变得不可见,即外层中同名标识符的可见范
围为作用域中挖去内层块的范围。图 4.6显示下面程序
段中变量的作用域与可见性。
2 可见性
下面的程序段和图示显示作用域与
可见性 。
int m=1;
float x;
{
float m=3.5;
X=5.5;
}
m++;
int m,float x作用域
int m可见
float m不可见
x可见
float m作用域
float m可见
int m不可见
x可见
4.6 函数的一些高级议题
4,6,1 缺省变元
4,6,2 内联函数
一般情况下,函数调用时的实参个数应与形参相同,但
为了更方便地使用函数,C++也允许定义具有缺省参数的函数
,这种函数调用时实参个数可以与形参不相同。
缺省参数指在定义函数时为形参指定缺省值(默认值)
。这样的函数在调用时,对于缺省参数,可以给出实参值,
也可以不给出参数值。如果给出实参,将实参传递给形参进
行调用,如果不给出实参,则按缺省值进行调用。
定义方式:类型 函数名(参数 1,参数 2,……
参数 3=缺省值,参数 4=缺省值)
缺省参数可以有多个,但所有缺省参数必须放在参数表的
右侧,即先定义所有的非缺省参数,再定义缺省参数。这是
因为在函数调用时,参数自左向右逐个匹配,当实参和形参
个数不一致时只有这样才不会产生二义性。
4.6.1 缺省变元
【 例 4,20】 缺省变元 。
#include<iostream.h>
void delay(int loops=5)
{ //延时函数,默认延时 5个时间单位
for (; loops>0; loops--);
}
void main()
{
delay(3);
cout<<“延时 3个时间单位 "<<endl;
delay(); //等同于 delay(5)
cout<<“延时 5个时间单位 "<<endl;
}
4.6.1 缺省变元
1 2 3
延时 3个时间单位
1 2 3 4 5
延时 5个时间单位
4.6.2 内联函数
当程序执行函数调用时,系统要建立栈空间,保护现场
,传递参数以及控制程序执行的转移等等,这些工作需要系
统时间和空间的开销。有些情况下,函数本身 功能简单,代
码很短,但使用 频率 却很 高, 程序频繁调用该函数所花费的
时间却很多,从而使得程序执行效率降低。为了提高效率,
一个解决办法就是不使用函数,直接将函数的代码嵌入到程
序中。但这个办法也有缺点,一是相同代码重复书写,二是
程序可读性往往没有使用函数的好。 为了协调好效率和可读
性之间的矛盾, C++提供了另一种方法,即定义 内联函数,
方法是在定义函数时用修饰词 inline。
4.6.2 内联函数
请看如下程序段,读入一行字符串,逐个判断是否为数字字符
:
# include <iostream.h>
inline IsNumber(char ch) {
return(ch>=′0′&&ch<=′9′?1:0);
}
void main()
{ char ch;
while(cin.get(ch),ch!= ′\n′) {
if (IsNumber(ch)) cout<<″是数字字符 ″<<endl;
else cout<<″不是数字字符 ″<<endl;
}
}
因使用频度很高,说明为内联函数。
4.6.2 内联函数
使用内联函数应注意的事项:
A.内联函数体内不允许有循环语句和开关语
句, 如果含有这些语句系统自动按普通函
数处理
B.内联函数的函数体内语句不能过多, 一般以 1到
10行
C,在类内部定义的成员函数, 一般是以内联函数
进行处理, 所以不需要加 inline说明, 但在类外
面一类的成员函数时, 如果想说明为内联函数,
则需要加上 inline
4.7 C++的系统库函数
C++提供了一个很大的常用函数库,该函数库本身并不是
C++语言的组成部分,所有库中的函数用户都可以自己定
义,但直接使用库函数能给编程带来很大方便。 系统函数库
实际上是一系列源程序文件,每个文件中定义了若干常用函
数及标识符,具有相同或相似功能的函数和标识符集中放在
一个文件中。这些文件均以,h的形式命名,存放在系统目录
的 include子目录 下。例如文件 iostream.h中定义了与控制
台输入输出和文件输入输出相关对象和成员函数如 cin和
cout函数,math.h中定义了大量数学函数,string.h中定
义了大量与字符串操作相关的函数。
4.7 C++的系统库函数
在利用库函数时,要注意三点:
1、函数的功能
2、函数调用时要注意函数的原型 —函数名,
参数个数及类型,返回类型,以便能正确
调用
3、将要调用函数的头文件用 #include指令
在文件头部进行说明,而不必对该函数的
原型进行说明。
4.7 C++的系统库函数
#include <iostream.h>
#include <math.h>
Void main()
{ float f;
cin >> f;
cout <<f<<,的平方根为:,
<<sqrt(f)<<endl;
}
还有其他的库函数见书上 P27
4,8 头文件与多文件结构
4,8,1 头文件
在将一个程序分解成若干个文件时,需要考虑标识符在其他文件中的
可见性。使用头文件是一个很有效的方法。如:
# include<iostream.h>
其中的 iostream.h是系统定义的一个文件,这种以,.h”命名的文件称为
头文件,系统定义的头文件 中定义了一些常用的公用标识符和函数,用户
只要将头文件包含进自己的文件,就可使头文件中定义的标识符在用户文
件中变得可见,也就可以直接使用头文件中定义的标识符和函数。
除了系统定义的头文件外,用户还可以 自定义头文件 。对于具有外部
存储类型的标识符,可以在其他任何一个源程序文件中经声明后引用,因
此用户完全可以将一些具有外部存储类型的标识符的声明放在一个头文件
中。具体地说,头文件中可以包括:用户构造的数据类型(如枚举类型)
,外部变量,外部函数、常量和内联函数等具有一定 通用性或常用的量,
而一般性的变量和函数定义不宜放在头文件中。
4,8,2 多文件结构
在开发较大程序时,通常将其分解为多个源程序文件
,每个较小的程序用一个源程序文件建立。程序经过建立
、编译、连接,成为一个完整的可执行程序。 多文件结构
通过工程进行管理,在工程中建立若干用户定义的头文件
.h和源程序文件,cpp。头文件中定义用户自定义的数据类
型,所有的程序实现则放在不同的源程序文件中 。编译时
每个源程序文件单独编译,如果源程序文件中有编译预处
理指令,则首先经过编译预处理生成临时文件存放在内存
,之后对临时文件进行编译生成目标文件,obj,编译后临
时文件撤销。所有的目标文件经连接器连接最终生成一个
完整的可执行文件,exe。
下图是一个多文件系统的开发过程。
4,8,2 多文件结构
编译
预编译
编译
预编译 预编译
编译
图 4.6 C++程序开发过程
file1.hfile1.cpp file2.hfile2.cpp filen.hfilen.cpp?
临时文件 1 临时文件 2 临时文件 n?
file1.obj file2.obj filen.obj?
Filename.
exe
.lib
C++标准类库
连接
运行
4.9 编译预处理
4,9,1 宏定义指令
4,9,2 文件包含指令
4,9,3 条件编译指令
4.9.1 宏定义指令 #define
1 不带参宏定义
用来产生与一个字符串对应的常量字符串,格式为:
#define 宏名 常量串
预处理后文件中凡出现该字符串处均用其对应的常量
串代替。替换过程称为宏替换或宏展开。例如,如果
使用指令
#define PI 3.1415926
则程序中可以使用标识符 PI,编译预处理后产生一个
中间文件,文件中所有 PI被替换为 3.1415926。
宏替换只是字符串和标识符之间的简单替换,预处理
本身不做任何数据类型和合法性检查,也不分配内存
单元 。
4.9.1 宏定义指令 #define
2 带参数的宏定义
带参宏定义的形式很象定义一个函数,格式为:
#define 宏名 ( 形参表 ) 表达式串
例如作如下宏定义:
#define S(a,b) (a)*(b)/2
程序中可使用 S(a,b),预处理后产生中间文件,其中 S(a,b)被
替换成 (a)*(b)/2。注意,宏定义时形参通常要用括号括起来
,否则容易导致逻辑错误。例如,如果定义:
#define S(a,b) a*b/2
那么程序中的 S(3+5,4+2)就会被宏展开为 3+5*4+2/2,不
符合定义的真正的意图。
带参宏定义形式上象定义函数,但它与函数的本质不同,宏定
义仍然只是产生字符串替代,不存在分配内存和参数传递。
4.9.2 文件包含(嵌入)指令 #include
文件包含 用 #include指令, 预处理后将指令中指明的源程序
文件嵌入到当前源程序文件的指令位置处 。 格式为:
#include <文件名 >
或
#include ″文件名 ″
第一种 方式称为 标准方式, 预处理器将在 include子目录下
搜索由文件名所指明的文件 。 这种方式适用于嵌入 C++提供
的头文件, 因为这些头文件一般都存在 C++系统目录的
include子目录下 。 而 第二种方式 编译器将首先在当前文件
所在目录下搜索, 如果找不到再按标准方式搜索 。 这种方式
适用于嵌入用户自己建立的头文件 。
一个被包含的头文件中还可以有 #include指令,
即 include指令可以嵌套,但是,如果同一个头文
件在同一个源程序文件中被 重复包含,就会出现 标
识符重复定义的错误 。例如:头文件 f2.h中包含了
f1.h,如果文件 f3.cpp中既包含 f1.h,又包含 f2.h,
那么编译将提示错误,原因是 f1.h被包含了两次,
那么其中定义的标识符在 f3.cpp中就被重复定义。
避免重复包含可以用 条件编译指令 。
4.9.2 文件包含(嵌入)指令 #include
4.9.3 条件编译指令
1 用 宏名 作为编译的条件
格式为:
#ifdef<宏名 >
<程序段 1>
[#else
<程序段 2>]
#endif
2 表达式的值 作为编译条件
格式为:
#if <表达式 >
<程序段 1>
[#else
<程序段 2>]
#endif
当希望在不同条件下编译程序的不同部分。这种情况就要使
用条件编译指令。
其中程序段可以是程序也可以是编译预处理指令。可以通过
在该指令前面安排宏定义来控制编译不同的程序段。
例:在调试程序时常常要输出调试信息,而调试完后不需要
输出这些信息,则可以把输出调试信息的语句用条件编译指
令括起来。形式如下:
#ifdef DEBUG
cout<<″a=″<<a<<′\t′<<″x=″<<x<<endl;
#endif
在程序调试期间,在该条件编译指令前增加宏定义:
#define DEBUG
调试好后,删除 DEBUG宏定义,将源程序重新编译一次。
条件编译指令包括,#if,#else,#ifdef,#ifndef、
#endif,#undef等。
#ifndef与 #ifdef作用一样,只是选择的条件相反。
#undef指令用来取消 #define指令所定义的符号,这样
可以根据需要打开和关闭符号。
或经常使用的操作或运算抽象出来,定义为函数。使用时只
要考虑其功能和使用接口即可。
在结构化程序设计中,函数是将任务进行模块划分的基
本单位。
在面向对象的程序设计中,类中所封装的操作是用函数
进行描述的,因此函数在 C++程序中具有非常重要的意义。
要掌握函数的使用,必须理解函数调用时的内部实现机
制,以及与此相关的内存分配机制、变量生命期和作用域。
本章还将介绍关于函数重载的概念,介绍递归算法、内
联函数、默认参数函数以及多文件组织、编译预处理、工程
文件的概念和运行库函数。
第四章 函数
第四章 函数
4,1 函数的定义与调用
4,5 作用域与存储类型
4,4 函数调用机制
4,3 全局变量和局部变量
4,2 函数的参数传递,
返回值及函数原型说明
4,9 编译预处理
4,8 头文件与多文件结构
4,7 C++的系统库函数
4,6 函数的一些高级议题
4.1 函数的定义与调用
4.1.1 函数概述
4.1.2 函数的定义
4.1.3 函数的调用
4.1.1 函数概述
函数是 C++程序的基本组成模块 。
通过函数, 可以把一个复杂任务分解成为若干
个易于解决的小任务 。 充分体现结构化程序设计由
粗到精, 逐步细化的设计思想 。
组成 C++程序的若干函数中, 有一个称为 main()
( Winmain()) 函数, 是程序执行的入口, 它可以调
用其他函数 。 而其他一般函数既可以调用也可以被调
用 。 函数之间的调用关系见下图:
4.1.1 函数概述
main ( )
fun2( )fun1( ) fun3( )
fun1_1( ) fun2_1( ) fun2_2( )
图 4.1 函数调用层次关系
4.1.1 函数概述
4.1.1
结束
函数按其是否系统预定义分为两类,一类是编译
系统预定义的,称为 库函数 或 标准函数,如一些常
用的数学计算函数、字符串处理函数、图形处理函
数、标准输入输出函数等。这些 库函数都按功能分
类,集中说明在不同的头文件中 。用户只需在自己
的程序中包含某个头文件,就可直接使用该文件中
定义的函数。另一类是用户 自定义函数,用户可以
根据需要将某个具有相对独立功能的程序定义为函
数。
4.1.2 函数的定义
函数定义格式如下:
数据类型 函数名(形式参数列表)
{
函数体;
}
说明:
1、函数的数据类型规定函数执行后所返回值的类型,如果没有值需要返回
,则定义为 void;
2、函数名是函数的标识,必须应该符合 C++标识符的定义,而且取名应做
到“见名知意”
3、形式参数列表中,如果有多个参数,用逗号分隔,并且每个参数要说明
数据类型,这些参数在调用之前没有实际的存储空间,也就没有具体的内
容,只有在在调用时候,才临时分配空间,并从调用函数中接收数据的实
际内容。一个函数也可以没有任何参数。
4、函数体的语句来描述了函数功能的实现的代码,函数执行完需要返回的
值是通过 return语句来实现。
如定义一个求最大值的函数 max
float max(float x,float y)
{ float z;
if(x > y) z = x;
else z = y;
return z;
}
定义函数时可能会涉及若干个变量,究竟哪些变量应当
作为函数的参数?哪些应当定义在函数体内?这有一个原则:
作为一个相对独立的模块,函数在使用时完全可以被看成
,黑匣子,,除了输入输出外,其他部分可不必关心 。从函
数的定义看出,函数头正是用来反映函数的功能和使用接口,
它所定义的是, 做什么,,在这部分必须明确, 黑匣子, 的
输入输出部分,输出就是函数的返回值,输入就是参数 。因
此,只有那些功能上起自变量作用的变量才必须作为参数定
义在参数表中;函数体中具体描述, 如何做,,因此除参数
之外的为实现算法所需用的变量应当定义在函数体内。
C++中不允许函数的嵌套定义,即在一个函数中定义另一
个函数。
提示
4.1.3 函数的调用
在 C++中, 除了主函数外, 其他任何函数都不能单独作为程
序运行 。 任何函数功能的实现都是通过被主函数直接或间接
调用进行的 。 所谓函数调用, 就是使程序转去执行函数体 。
无参函数的调用格式为:
函数名 ( );
有参函数的调用格式为:
函数名 (实际参数表 );
其中实际参数简称实参, 用来将实际参数的值传递给形参,
因此可以是常量, 具有值的变量或表达式 。
4.1.3 函数的调用
main( )
函数
调用
max(2.5,4.7 )
函数
max(2.5,4.7 )
return 4.7主程序后
续语句
【 例 4,1】 输入两个实数, 输出其中较大的数 。 其中求两个实
数中的较大数用函数完成 。
程序如下,
#include<iostream.h>
float max(float x,float y)
{ return(x>=y?x:y);
}
void main()
{ float x,y;
cout<<"输入两个实数,"<<endl;
cin>>x>>y;
cout<<x<<“和, <<y<<“中较大数为:,
<<max(x,y)<<endl;
}
4.2 函数的参数传递、返回值及
函数原型说明
4,2,1 参数传递及传值调用和引用概念
4,2,3 函数原型说明
4,2,2 函数返回值
函数调用首先要进行参数传递,参数传递的方向是由实参
传递给形参。传递过程是,先计算实参表达式的值,再将
该值传递给对应的形参变量 。一般情况下,实参和形参的
个数和排列顺序应一一对应,并且对应参数应类型匹配
(赋值兼容),即实参的类型可以转化为形参类型。而对
应参数的参数名则不要求相同。某些特殊情况下也允许参
数不对应,这将在函数高级议题中讨论。
按照参数形式的不同,C++有两种调用方式,传值调用 和
引用调用 。顾名思义,传值调用传递的是实参的值,本章
主要介绍传值调用。关于引用调用,将在第五章类与对象
中介绍。
4.2.1 函数的参数传递
传值调用和引用调用
【 例 4,2】 说明实参和形参对应关系的示例 。
#include <iostream.h>
#include <math.h>
float max(float x,float y)
{ int z;
z = x>y?x:y;
return z;
}
void main()
{ float a,b;
a = 10;
b = 5;
cout<<“最大值,"<<max(a,b)<<endl;
cout<<“最大值,"<<max(2.6,8.5)<<endl;
}
4.2.1.1 函数的传值调用
调用
max(10,5 )
函数
max(10,5 )
return
10
主程序后续语
句
a=10
b= 5
【 例 4,2】 说明实参和形参对应关系的示例 。
#include <iostream.h>
#include <math.h>
float max(float x,float y)
{ int z;
z = x>y?x:y;
return z;
}
void main()
{ float a,b;
a = 10;
b = 5;
cout<<“最大值,"<<max(a,b)<<endl;
cout<<“最大值,"<<max(2.6,8.5)<<endl;
}
4.2.1.1 函数的传值调用
调用
max(2.6,8.5 )
函数
max(2.6,8.5 )
return
8.5
主程序后续语
句
4.2.1.2 引用概念
引用类型,引用是 C++引进的,C没有这个概念。 C++引进引
用概念的主要用它做函数的参数和返回值。
什么是引用,引用其实是变量或对象的别名,这跟我们很多
作家有笔名是一样,如周树人,其笔名是鲁迅,其实是同
一个人,引用也是一样,它自身没有存储单位,引用的值
就是被它引用的变量或对象的值,引用的地址值也是被它
引用的变量或对象的地址值。
引用定义和声明,类型 &引用名 = 变量名
&是说明符,说明后面的标识符是个引用名
如,int I = 5;
int &ri = I;那么 ri就是被声明为变量 I的引用,
不能够是 int &ri
4.2.1.2 引用概念
引用变量的例子:
#include <iostream.h>
int main()
{ int i = 5,&ri = i;
cout<<“I=”<<i<<,RI=”<<ri<<endl;
i *= 3;
cout<<“I=”<<I<<,RI=”<<ri<<endl;
ri += 5;
cout<<“I=”<<I<<,RI=”<<ri<<endl;
cout<<“I Addr=”<<&I
<<“RI Addr=”<< &ri<<endl;
return 0;
} 结果是,I=5 RI=5
I=15 RI=15
I=20 RI=20
I Addr=0x8f8bfff4 RI Addr=0x8f8bfff4
4.2.1.2 引用概念
从程序中可以看出:
( 1)、对引用的操作就是对变量的操作,反之也一样
( 2)、引用和被引用的实体具有相同的地址
( 3)、程序中有一个求地址符号的 &和引用声明中的 &不一样
( 4)、指针和引用的区别:指针是变量,引用不是变量;指
针可以初始化,也可以不初始化,而引用必须初始化;指针
可以做数组元素,而引用不可以;指针也可以引用,而引用
不可以引用。 int &&ri=i; // 这样是不行的
int *p;
int *&rp = p;
int m = 5;
rp = &m;
( 5)、创建常量引用时候,系统会建立一个临时变量
const int i = 5;
const int &ri = i;
4.2.1.2 引用概念
( 6)、引用做参数,这是引用很重要的使用功能
#include <iostream.h>
void main()
{ int x = 3,y = 5;
void swap(int &,int &);
swap(x,y);
cout<<“x=“<<x<<“,y=“<<y<<endl;
}
void swap(int &n1,int &n2)
{ int temp = n1;
n1 = n2;
n2 = temp;
}
结果是 x=5,y=3
从运行的结果可以看出,函数之间的参数传递采用引用参数后,成功地 实现数据调用。
4.2.2 函数返回值
return语句的一般格式为:
return 表达式;
函数的计算结果通过该语句传递回主调函数 。
【 例 4,3】 设计函数,根据三角形的三边长求面积。如
果不能构成三角形,给出提示信息。
分析:函数为计算三角形面积,一般三角形返回面积值,
若不能构成三角形则返回 -1。设计一个主函数完成函数
测试。根据返回值情况输出相应结果。
程序见下页:
4.2.2 函数返回值
#include<iostream.h>
#include<math.h>
float TriangleArea(float a,float b,float c){
if ((a+b<=c)||(a+c<=b)||(b+c<=a)) return -1;
float s;
s=(a+b+c)/2;
return sqrt(s*(s-a)*(s-b)*(s-c));
}
void main(){
float a,b,c,area;
cout<<"输入三角形三边 a,b,c:"<<endl;
cin>>a>>b>>c;
area=TriangleArea(a,b,c);
if(area==-1) cout<<'('<<a<<','<<b<<',' <<c
<<')'<<"不能构成三角形! "<<endl;
else cout<<"三角形 ("<<a<<','<<b<<','<<c
<<")面积为,"<<area<<endl;
}
4.2.2 函数返回值
函数可以有返回值, 也可以没有返回值 。 对
于没有返回值的函数, 功能只是完成一定操作, 应
将返回值类型定义为 void, 函数体内可以没有
return语句, 当需要在程序指定位置退出时, 可
以在该处放置一个:
return ;
4.2.2
结束
4.2.3 函数原型说明
函数原型是一条以分号结束的语句, 实际上
就是所定义函数的函数头, 形如:
,函数返回值类型, 函数名 (,形参表, )
语法上对程序文件中函数的排列次序是没有固定要求的,
只要满足 先定义后使用 即可 。 但从结构化程序设计的角度,
通常是先调用后定义 。 使用函数原型, 则既符合由粗到精
的思维方式, 又满足了语法要求 。
其中形参表可以逐个列出每个参数的类型和参数名,
也可以列出每个形参的类型,参数名可省略,各形参之间
以逗号分隔。函数原型和所定义的函数必须在返回值类型、
函数名、形参个数和类型及 次序 等方面完全对应一致,否
则将导致编译错误。
下面是一个使用结构化程序设计思想开发的企业管理
报表程序的框架 。 它使用了函数原型说明 。
#include <iostream.h>
void menu_print();
void account_report();
void engineering_report();
float marketing_report(float x,int n);
void main()
{ int choice;
do{
menu_print();
cin>>choice;
} while(choice<=0||choice>=4);
switch(choice){
case 1,account_report(); break;
case 2,engineering_report(); break;
case 3,marketing_report(); break;
}
}
void menu_print() {
cout<<”系统功能:” <<endl;
cout<<”1 财务报表” <<endl;
cout<<”2 工程报表” <<endl;
cout<<”3 销售情况” <<endl;
cout<<”选择业务序号:” ;
}
void account_report(){
//生成财务报表
}
void engineering_report(){
//生成工程报表
}
float marketing_report(float x,int n) {
//生成市场报表;
}
4.3 变量的存储属性
4,3,1 变量的存储机制与 C++的内存布局
4,3,2 全局变量
4,3,3 局部变量
系统核心区( OS)
代码区(程序代码)
全局数据区 (全局、静态变量 )
栈区(函数局部数据)
堆区 (动态数据 )
4.3.1 C++的内存布局与变量的存储机制
操作系统为一个 C++程序的运行所分配的内
存分为四个区域, 如图 4.3 程序在内存中的
区域 所示:
动态存储区
静态存储区
( 1)代码区( Code area):存放程序代码,
即程序中各个函数的代码块;
( 2)全局数据区( Data area):存放全局数
据和静态数据;分配该区时内存全部清零。
( 3)栈区( Stack area):存放局部变量,如
函数中的变量等;分配栈区时内存不处理。
( 4)堆区( Heap area):存放与指针相关的
动态数据。分配堆区时内存不处理。参见第七
章。
4.3.1 C++的内存布局与变量的存储机制
4.3.2 全局变量
在所有函数之外定义的变量称为 全局变量 。
全局变量在编译时建立在全局数据区, 在未给
出初始化值时系统自动 初始化为全 0。
全局变量可定义在程序开头, 也可定义在中间
位置, 该全局变量 在定义处之后 的任何位置都是可
以访问的, 称为可见的 。
请看下例:
4.3.2 全局变量
打印
200
调用
func( )
函数
func( )
200*2
=400打印400
n=100
n=100*2
=200
【 例 4,5】 多个函数使用全局变量的例子 。
#include<iostream.h>
int n=100;
void func(){
n*=2;
}
void main(){
n*=2;
cout<<n<<endl;
func();
cout<<n<<endl;
}
4.3.3 局部变量
定义在函数内或块内的变量称为 局部变量 。
程序中使用的绝大多数变量都是局部变量 。
局部变量在程序运行到它所在的块时建立在栈中,
该块执行完毕局部变量占有的空间即被释放 。
局部变量在定义时可加修饰词 auto,但通常省略 。
局部变量在定义时若未初始化, 其值为随机数 。
4.3.3 局部变量
打印 main()
中的 t=3.5
调用
fun( )
函数
fun( )
打印 fun()
中的 t=5打印 main()中的 t=3.5
t= 3.5
t = 5
【 例 4,9】 使用局部变量的例子 。
#include<iostream.h>
void fun(){
auto int t=5;
// fun()中的局部变量, auto可省略
cout<<"fun()中的 t="<<t<<endl;
}
void main(){
float t=3.5;
//main()函数中的局部变量
cout<<"main()中的 t="<<t<<endl;
fun();
cout<<"main()中的 t="<<t<<endl;
}
4.4 函数调用机制
局部变量占用的内存是在程序执行过程中, 动态, 地建立
和释放的 。 这种, 动态, 是通过栈由系统自动管理进行的 。 当
任何一个函数调用发生时, 系统都要作以下工作:
( 1) 建立栈空间;
( 6) 恢复现场:取主调函数运行状态及返回地址, 释放栈空间;
( 7) 继续主调函数后续语句 。
( 5) 释放被调函数中局部变量占用的栈空间;
( 4) 执行被调函数函数体;
( 3) 为被调函数中的局部变量分配空间, 完成参数传递;
( 2) 保护现场:主调函数运行状态和返回地址入栈;
4.4 函数调用机制
void fun1(int,int);
void fun2(float);
void main(){
int x=1;y=2;
fun1(x,y);
}
void fun1(int a,int b){
float x=3;
fun2(x);
}
void fun2(float y) {
int x;
…
}
x 栈顶
栈底
y 3
fun2() fun1()运行状态及返回地址
x 3
b 2
a 1
fun1() main()运行状态及返回地址
y 2
x 1
main() 操作系统运行状态及返回地址
此图例说明 在程序执行过程中怎样通过栈,动态,地建立和
释放局部变量占用的内存的
4.5 作用域与存储类型
4.5.1 作 用 域
4.5.2 变量的存储类型
4.5.3外部存储类型与静态存储类型
4.5.4 生命期与可见性
4.5.1 作用域
1 块作用域 3 文件作用域2 函数原型作用域
作用域 指标识符能够被使用的范围 。 只有在作用
域内标识符才可以被访问 ( 称为可见 ) 。
本节只讨论 局部域 和文件域 ( 全局域 ), 其中局
部域包括 块域 和 函数原型域 。 任何 标识符 作用域的起
始点均为 标识符说明 处 。
下面分别介绍,
参和函数体中定义的局部变量, 作用域都在该函数内,
也称作 函数域 。
1,块域
块 指一对大括号括起来的程序段 。 块中定义的标
识符, 作用域在块内 。
复合语句是一个块 。
函数也是一个块 。
复合语句中定义的标识符,
作用域仅在该复合语句中 。
函数中定义的标识符, 包括形
1,块域
a= 3
b= 53
5
a=3 b=5
a=5 b=3
【 例 4,7】 输入两数, 将两数按从大到小的顺序保存, 并输出结果 。
结果
栈
t = 3
#include<iostream.h>
void main(){
int a,b; //具有函数域
cout<<"输入两整数,"<<endl;
cin>>a>>b;
cout<<“a="<<a<<'\t'<<"b="<<b<<endl;
if(b>=a){
int t; //具有块域
t=a; a=b; b=t; //交换 a,b的值
}
cout<<"a="<<a<<'\t'<<"b="<<b<<endl;
}
【 例 4,8】 设计函数完成两数交换, 用主函数进行测试, 看结果如何 。
#include<iostream.h>
void swap(int,int);
void main(){
int a,b; //a,b作用域为 main()
cout<<"输入两整数,"<<endl;
cin>>a>>b;
cout<<"调用前:实参 a="<<a<<',' <<"b="<<b<<endl;
swap(a,b); //传值
cout<<"调用后:实参 a="<<a<<','<<"b="<<b<<endl;
}
void swap(int a,int b){ //a,b作用域为 swap()
cout<<"调用中 …"<<endl;
cout<<"交换前:形参 a=,<<a<<','<<"b="<<b<<endl;
int t;
t=a; a=b; b=t; //交换 swap()中的 a,b的值
cout<<"交换后:形参 a="<<a<<','<<"b="<<b<<endl;
}
局部变量具有局部作用域使得程序在不同块中可以使用同名变量。这些
同名变量各自在自己的作用域中可见,在其它地方不可见 。
1,块作用域
由 VC++平台运行,结果如下:
输入两整数:
3 5
调用前:实参 a=3,b=5
调用中 …
交换前:形参 a=3,b=5
交换后:形参 a=5,b=3
调用后:实参 a=3,b=5
交换失败
对于块中 嵌套 其它块的情况,如果嵌套块中有同名局部变量,服从
局部优先原则,即在内层块中 屏蔽 外层块中的同名变量,换句话说,内
层块中局部变量的作用域为内层块;外层块中局部变量的作用域为外层
除去包含同名变量的内层块部分。
如果块内定义的局部变量与全局变量同名,块内仍然局部变量优先
,但与块作用域不同的是,在块内可以通过域运算符,::,访问同名
的全局变量。
全局 n= 100
100 200 300
内 i= 500
内 j= 600
内 n=500+600
=1100
1100 500 600
100
200+300=500 500
500 200 300
外部 i=200
外部 j=300
【 例 4,9】 显示同名变量可见性 。
int n=100;
#include<iostream.h>
void main(){
int i=200,j=300;
cout<< n<<'\t'<<i<<'\t'<<j<<endl;
{ //内部块
int i=500,j=600,n;
n=i+j;
cout<< n<<'\t'<<i<<'\t'<<j<< endl;
//输出局部变量 n
cout<<::n<<endl;//输出全局变量 n
}
n=i+j; //修改全局变量
cout<< n<<'\t'<<i<<'\t'<<j<< endl;
}
2 函数原型作用域
函数原型不是定义函数, 在作函数原型声
明时, 其中的形参作用域只在原型声明中,
即 作用域结束于右括号 。 正是由于形参不能
被程序的其他地方引用, 所以通常只要声明
形参个数和类型, 形参名可省略 。
3 文件作用域
文件作用域 也称全局作用域 。 定义在所有函
数之外的标识符, 具有文件作用域, 作用域
为从定义处到整个源文件结束 。 文件中定义
的全局变量和函数都具有文件作用域 。
如果某个文件中说明了具有文件作用域的标
识符, 该文件又被另一个文件包含, 则该标
识符的作用域延伸到新的文件中 。 如 cin和
cout是在头文件 iostream.h中说明的具有文
件作用域的标识符, 它们的作用域也延伸到
嵌入 iostream.h的文件中 。
存储类型决定了变量的 生命期, 变量生命期指从
获得空间到空间释放之间的时期 。
4.5.2 变量的存储类型
存储类型的说明符有四个,auto,register,static和
extern。 前两者称为自动类型, 后两者分别为静态和外
部类型 。
本节重点掌握 static和 extern这两种类型的使用和
区别 。 具体说, 区分 局部变量和静态局部变量, 全局
变量和静态全局变量 。
auto,前面提到的局部变量都是自动类型 。 其空间分配
于块始, 空间释放于块终, 且由系统自动进行 。 自
动变量保存在栈中, 且是在程序运行过程中获得和
释放空间, 未初始化时值为随机数 。
4.5.2 变量的存储类型
register:为提高程序运行效率, 可以将某些变量保存在
寄存器中, 即说明为寄存器变量, 但不提倡使用 。
static,静态变量 。 根据被修饰变量的位置不同, 分为
局部 ( 内部 ) 静态变量和全局 ( 外部 ) 静态变量 。
所有静态变量均存放在全局数据区, 编译时获得存
储空间, 未初始化时自动全 0,且只初始化一次 。
局部静态变量的 作用域 为 块域, 但 生命期 为 整个
文件 。 即当块结束时, 局部静态变量空间仍然保持,
直到整个程序文件结束时该局部静态变量空间才释放,
生命期结束 。
局部静态变量
【 例 4,10】 自动变量与局部静态变量的区别 。 ( 演
示 )
#include <iostream.h>
st(){
static int t=100; //局部静态变量
t++; return t;
}
at(){
int t=100; //自动变量
t++;return t;
}
void main(){
int i;
for(i=0;i<5;i++) cout<<at()<<'\t';
cout<<endl;
for(i=0;i<5;i++) cout<<st()<<'\t';
cout<<endl;
}
4.5.2 变量的存储类型
i= 0
t= 100
12345
101
4.5.2 变量的存储类型
i= 0
t=100
12
101
345
102103104105
#include <iostream.h>
st(){
static int t=100; //局部静态变量
t++; return t;
}
at(){
int t=100; //自动变量
t++;return t;
}
void main(){
int i;
for(i=0;i<5;i++) cout<<at()<<'\t';
cout<<endl;
for(i=0;i<5;i++) cout<<st()<<'\t';
cout<<endl;
}
全局静态变量
全局静态变量是指用 static修饰的全局
变量 。 有关内容在下节静态存储类型中介绍 。
4.5.3 外部存储类型与静态存储类型
1,外部存储类型
2,静态存储类型
一个 C++程序可以由多个源程序文件组成,编译系统
将这若干个文件连接在一起,产生可执行程序。外部
存储类型和静态存储类型确定了变量和函数在多文件
程序中的联络关系。
1 外部存储类型
外部存储类型包括外部变量和外部函数 。 在由多个源程序
文件组成的程序中, 如果一个文件要使用另一个文件中定
义的全局变量或函数, 这些源程序文件之间通过外部类型
的变量和函数进行沟通 。
在一个文件中定义的全局变量和函数都缺省为外部的,即
其作用域可以延伸到程序的其他文件中。但其他文件如果
要使用这个文件中定义的全局变量和函数,必须在使用前
用, extern”作外部声明,外部声明通常放在文件的开头。
变量定义时编译器为其分配存储空间,而变量声明指明该
全局变量已在其他地方说明过,编译系统不再分配存储空
间,直接使用变量定义时所分配的空间。
函数声明缺省为外部的,因此修饰词 extern通常省略。
1 外部存储类型
【 例 4.11】 外部存储类型的例子。假定程序包含两个源程序文件
Ex4_11_1.cpp和 Ex4_11_2.cpp,程序结构如下:
/* Ex4_11_1.cpp,由 main()组成 */
# include <iostream.h>
void fun2(); //外部函数声明,等价于 extern void fun2();
int n; //全局变量定义
void main(){
n=1;
fun2(); // fun2()定义在文件 Ex4_11_2.cpp中
cout<<″n=″<<n<<endl;
}
/* Ex4_11_2.cpp,由 fun2()组成 */
extern int n; //外部变量声明,n定义在文件 Ex4_11_1.cpp
中
void fun2() { //fun2()被文件 Ex4_11_1.cpp中的函数调用
n=3;
}
运行结果,n=3
2 静态存储类型
静态存储类型包括静态全局变量和静态函数。在定义全局
变量或函数时加说明符 static,就成为静态变量或静态函
数。静态存储类型的作用域与外部存储类型相反,一旦定
义为静态存储类型,就限制该变量或函数只能在定义它的
文件中使用。静态全局变量在编译时分配存储空间,如果
定义时不指定初值,则编译系统将其初始化为全 0。
一个全局变量和一个静态全局变量在使用上是不同的,其
他文件通过外部变量声明可以使用一个全局变量,但却无
法使用静态全局变量,静态全局变量只能被定义它的文件
所独享 。函数与静态函数之间的区别是相同的。
4.5.4 生命期与可见性
1,生命期
2,可见性
1 生命期
( 1)静态生命期
( 2)局部生命期
( 3)动态生命期
生命期( Life time)也叫生存期。生命期与存储区域相关,
存储区域分为代码区、静态数据区、栈区和堆区,相应地,
生命期分为 静态生命期、局部生命期和动态生命期 。
( 1) 静态生命期
静态生命期 指的是标识符从程序开始运行时
存在, 即具有存储空间, 到程序运行结束时消亡,
即释放存储空间 。 具有静态生命期的标识符存放
在静态数据区, 属于静态存储类型, 如全局变量,
静态全局变量, 静态局部变量 。 具有静态生命期
的标识符在未被用户初始化的情况下, 系统会自
动将其初始化为全 0。
函数驻留在代码区, 也具有静态生命期 。 所
有具有文件作用域的标识符都具有静态生命期 。
( 2) 局部生命期
在函数内部或块中定义的标识符具有局部生命
期, 其生命期开始于执行到该函数或块的标识符声
明处, 结束于该函数或块的结束处 。 具有局部生命
期的标识符存放在栈区 。 具有局部生命期的标识符
如果未被初始化, 其内容是随机的, 不可用 。
具有局部生命期的标识符必定具有局部作用域;
但反之不然, 静态局部变量具有局部作用域, 但却
具有静态生命期 。
( 3) 动态生命期
具有动态生命期的标识符由特定的函数调
用或运算来创建和释放,如调用 malloc()或用
new运算符为变量分配存储空间时,变量的生命
期开始,而调用 free()或用 delete运算符释放
空间或程序结束时,变量生命期结束。具有动
态生命期的变量存放在堆区。关于 new运算和
delete运算将在指针一章中介绍。
2 可见性
可见性 从另一个角度说明标识符的有效性,可见性与作
用域具有一定的一致性。 标识符的作用域包含可见范围
,可见范围不会超过作用域 。可见性在理解同名标识符
的 作用域嵌套 时十分直观。对于外层块与内层块定义了
同名标识符的,在外层作用域中,内层所定义的标识符
是不可见的,即外层引用的是外层所定义的标识符;同
样,在内层作用域中,外层的标识符将被内层的同名标
识符屏蔽,变得不可见,即外层中同名标识符的可见范
围为作用域中挖去内层块的范围。图 4.6显示下面程序
段中变量的作用域与可见性。
2 可见性
下面的程序段和图示显示作用域与
可见性 。
int m=1;
float x;
{
float m=3.5;
X=5.5;
}
m++;
int m,float x作用域
int m可见
float m不可见
x可见
float m作用域
float m可见
int m不可见
x可见
4.6 函数的一些高级议题
4,6,1 缺省变元
4,6,2 内联函数
一般情况下,函数调用时的实参个数应与形参相同,但
为了更方便地使用函数,C++也允许定义具有缺省参数的函数
,这种函数调用时实参个数可以与形参不相同。
缺省参数指在定义函数时为形参指定缺省值(默认值)
。这样的函数在调用时,对于缺省参数,可以给出实参值,
也可以不给出参数值。如果给出实参,将实参传递给形参进
行调用,如果不给出实参,则按缺省值进行调用。
定义方式:类型 函数名(参数 1,参数 2,……
参数 3=缺省值,参数 4=缺省值)
缺省参数可以有多个,但所有缺省参数必须放在参数表的
右侧,即先定义所有的非缺省参数,再定义缺省参数。这是
因为在函数调用时,参数自左向右逐个匹配,当实参和形参
个数不一致时只有这样才不会产生二义性。
4.6.1 缺省变元
【 例 4,20】 缺省变元 。
#include<iostream.h>
void delay(int loops=5)
{ //延时函数,默认延时 5个时间单位
for (; loops>0; loops--);
}
void main()
{
delay(3);
cout<<“延时 3个时间单位 "<<endl;
delay(); //等同于 delay(5)
cout<<“延时 5个时间单位 "<<endl;
}
4.6.1 缺省变元
1 2 3
延时 3个时间单位
1 2 3 4 5
延时 5个时间单位
4.6.2 内联函数
当程序执行函数调用时,系统要建立栈空间,保护现场
,传递参数以及控制程序执行的转移等等,这些工作需要系
统时间和空间的开销。有些情况下,函数本身 功能简单,代
码很短,但使用 频率 却很 高, 程序频繁调用该函数所花费的
时间却很多,从而使得程序执行效率降低。为了提高效率,
一个解决办法就是不使用函数,直接将函数的代码嵌入到程
序中。但这个办法也有缺点,一是相同代码重复书写,二是
程序可读性往往没有使用函数的好。 为了协调好效率和可读
性之间的矛盾, C++提供了另一种方法,即定义 内联函数,
方法是在定义函数时用修饰词 inline。
4.6.2 内联函数
请看如下程序段,读入一行字符串,逐个判断是否为数字字符
:
# include <iostream.h>
inline IsNumber(char ch) {
return(ch>=′0′&&ch<=′9′?1:0);
}
void main()
{ char ch;
while(cin.get(ch),ch!= ′\n′) {
if (IsNumber(ch)) cout<<″是数字字符 ″<<endl;
else cout<<″不是数字字符 ″<<endl;
}
}
因使用频度很高,说明为内联函数。
4.6.2 内联函数
使用内联函数应注意的事项:
A.内联函数体内不允许有循环语句和开关语
句, 如果含有这些语句系统自动按普通函
数处理
B.内联函数的函数体内语句不能过多, 一般以 1到
10行
C,在类内部定义的成员函数, 一般是以内联函数
进行处理, 所以不需要加 inline说明, 但在类外
面一类的成员函数时, 如果想说明为内联函数,
则需要加上 inline
4.7 C++的系统库函数
C++提供了一个很大的常用函数库,该函数库本身并不是
C++语言的组成部分,所有库中的函数用户都可以自己定
义,但直接使用库函数能给编程带来很大方便。 系统函数库
实际上是一系列源程序文件,每个文件中定义了若干常用函
数及标识符,具有相同或相似功能的函数和标识符集中放在
一个文件中。这些文件均以,h的形式命名,存放在系统目录
的 include子目录 下。例如文件 iostream.h中定义了与控制
台输入输出和文件输入输出相关对象和成员函数如 cin和
cout函数,math.h中定义了大量数学函数,string.h中定
义了大量与字符串操作相关的函数。
4.7 C++的系统库函数
在利用库函数时,要注意三点:
1、函数的功能
2、函数调用时要注意函数的原型 —函数名,
参数个数及类型,返回类型,以便能正确
调用
3、将要调用函数的头文件用 #include指令
在文件头部进行说明,而不必对该函数的
原型进行说明。
4.7 C++的系统库函数
#include <iostream.h>
#include <math.h>
Void main()
{ float f;
cin >> f;
cout <<f<<,的平方根为:,
<<sqrt(f)<<endl;
}
还有其他的库函数见书上 P27
4,8 头文件与多文件结构
4,8,1 头文件
在将一个程序分解成若干个文件时,需要考虑标识符在其他文件中的
可见性。使用头文件是一个很有效的方法。如:
# include<iostream.h>
其中的 iostream.h是系统定义的一个文件,这种以,.h”命名的文件称为
头文件,系统定义的头文件 中定义了一些常用的公用标识符和函数,用户
只要将头文件包含进自己的文件,就可使头文件中定义的标识符在用户文
件中变得可见,也就可以直接使用头文件中定义的标识符和函数。
除了系统定义的头文件外,用户还可以 自定义头文件 。对于具有外部
存储类型的标识符,可以在其他任何一个源程序文件中经声明后引用,因
此用户完全可以将一些具有外部存储类型的标识符的声明放在一个头文件
中。具体地说,头文件中可以包括:用户构造的数据类型(如枚举类型)
,外部变量,外部函数、常量和内联函数等具有一定 通用性或常用的量,
而一般性的变量和函数定义不宜放在头文件中。
4,8,2 多文件结构
在开发较大程序时,通常将其分解为多个源程序文件
,每个较小的程序用一个源程序文件建立。程序经过建立
、编译、连接,成为一个完整的可执行程序。 多文件结构
通过工程进行管理,在工程中建立若干用户定义的头文件
.h和源程序文件,cpp。头文件中定义用户自定义的数据类
型,所有的程序实现则放在不同的源程序文件中 。编译时
每个源程序文件单独编译,如果源程序文件中有编译预处
理指令,则首先经过编译预处理生成临时文件存放在内存
,之后对临时文件进行编译生成目标文件,obj,编译后临
时文件撤销。所有的目标文件经连接器连接最终生成一个
完整的可执行文件,exe。
下图是一个多文件系统的开发过程。
4,8,2 多文件结构
编译
预编译
编译
预编译 预编译
编译
图 4.6 C++程序开发过程
file1.hfile1.cpp file2.hfile2.cpp filen.hfilen.cpp?
临时文件 1 临时文件 2 临时文件 n?
file1.obj file2.obj filen.obj?
Filename.
exe
.lib
C++标准类库
连接
运行
4.9 编译预处理
4,9,1 宏定义指令
4,9,2 文件包含指令
4,9,3 条件编译指令
4.9.1 宏定义指令 #define
1 不带参宏定义
用来产生与一个字符串对应的常量字符串,格式为:
#define 宏名 常量串
预处理后文件中凡出现该字符串处均用其对应的常量
串代替。替换过程称为宏替换或宏展开。例如,如果
使用指令
#define PI 3.1415926
则程序中可以使用标识符 PI,编译预处理后产生一个
中间文件,文件中所有 PI被替换为 3.1415926。
宏替换只是字符串和标识符之间的简单替换,预处理
本身不做任何数据类型和合法性检查,也不分配内存
单元 。
4.9.1 宏定义指令 #define
2 带参数的宏定义
带参宏定义的形式很象定义一个函数,格式为:
#define 宏名 ( 形参表 ) 表达式串
例如作如下宏定义:
#define S(a,b) (a)*(b)/2
程序中可使用 S(a,b),预处理后产生中间文件,其中 S(a,b)被
替换成 (a)*(b)/2。注意,宏定义时形参通常要用括号括起来
,否则容易导致逻辑错误。例如,如果定义:
#define S(a,b) a*b/2
那么程序中的 S(3+5,4+2)就会被宏展开为 3+5*4+2/2,不
符合定义的真正的意图。
带参宏定义形式上象定义函数,但它与函数的本质不同,宏定
义仍然只是产生字符串替代,不存在分配内存和参数传递。
4.9.2 文件包含(嵌入)指令 #include
文件包含 用 #include指令, 预处理后将指令中指明的源程序
文件嵌入到当前源程序文件的指令位置处 。 格式为:
#include <文件名 >
或
#include ″文件名 ″
第一种 方式称为 标准方式, 预处理器将在 include子目录下
搜索由文件名所指明的文件 。 这种方式适用于嵌入 C++提供
的头文件, 因为这些头文件一般都存在 C++系统目录的
include子目录下 。 而 第二种方式 编译器将首先在当前文件
所在目录下搜索, 如果找不到再按标准方式搜索 。 这种方式
适用于嵌入用户自己建立的头文件 。
一个被包含的头文件中还可以有 #include指令,
即 include指令可以嵌套,但是,如果同一个头文
件在同一个源程序文件中被 重复包含,就会出现 标
识符重复定义的错误 。例如:头文件 f2.h中包含了
f1.h,如果文件 f3.cpp中既包含 f1.h,又包含 f2.h,
那么编译将提示错误,原因是 f1.h被包含了两次,
那么其中定义的标识符在 f3.cpp中就被重复定义。
避免重复包含可以用 条件编译指令 。
4.9.2 文件包含(嵌入)指令 #include
4.9.3 条件编译指令
1 用 宏名 作为编译的条件
格式为:
#ifdef<宏名 >
<程序段 1>
[#else
<程序段 2>]
#endif
2 表达式的值 作为编译条件
格式为:
#if <表达式 >
<程序段 1>
[#else
<程序段 2>]
#endif
当希望在不同条件下编译程序的不同部分。这种情况就要使
用条件编译指令。
其中程序段可以是程序也可以是编译预处理指令。可以通过
在该指令前面安排宏定义来控制编译不同的程序段。
例:在调试程序时常常要输出调试信息,而调试完后不需要
输出这些信息,则可以把输出调试信息的语句用条件编译指
令括起来。形式如下:
#ifdef DEBUG
cout<<″a=″<<a<<′\t′<<″x=″<<x<<endl;
#endif
在程序调试期间,在该条件编译指令前增加宏定义:
#define DEBUG
调试好后,删除 DEBUG宏定义,将源程序重新编译一次。
条件编译指令包括,#if,#else,#ifdef,#ifndef、
#endif,#undef等。
#ifndef与 #ifdef作用一样,只是选择的条件相反。
#undef指令用来取消 #define指令所定义的符号,这样
可以根据需要打开和关闭符号。