当前位置: 首页 > >

在结构化程序设计中函数是将任务进行模块划分的基本单位_图文

发布时间:

第四章 函数
在结构化程序设计中,函数是将任务进行模块划分的基 本单位。 要掌握函数的使用,必须理解函数调用时的内部实现机 制,以及与此相关的内存分配机制、变量生命期和作用域。 本章还将介绍关于函数重载的概念,介绍递归算法 、内联函数、默认参数函数以及多文件组织、编译预处 理、工程文件的概念和运行库函数。

第四章 函数
4.1 函数的定义与调用 4. 6 函数的递归调用

4. 2 函数的参数传递, 返回值及函数原型说明
4.3 全局变量和局部变量

4. 7 函数的一些高级议题

4. 8 头文件与多文件结构
4.4 函数调用机制 4.9 编译预处理

4. 5 作用域与存储类型

4.1

函数的定义与调用

4.1.1 函数概述

4.1.2 函数的定义

4.1.3 函数的调用

4.1C++的系统库函数
C++提供了一个很大的常用函数库,该函数库本身并不是 C++语言的组成部分,所有库中的函数用户都可以自己定 义,但直接使用库函数能给编程带来很大方便。系统函数库 实际上是一系列源程序文件,每个文件中定义了若干常用函 数及标识符,具有相同或相似功能的函数和标识符集中放在 一个文件中。这些文件均以.h的形式命名,存放在系统目录 的include子目录下。例如文件iostream.h中定义了与控制 台输入输出和文件输入输出相关对象和成员函数,math.h 中定义了大量数学函数,string.h中定义了大量与字符串操 作相关的函数。

4.1C++的系统库函数
C++提供了一个很大的常用函数库,该函数库本身并不是 C++语言的组成部分,所有库中的函数用户都可以自己定 义,但直接使用库函数能给编程带来很大方便。系统函数库 实际上是一系列源程序文件,每个文件中定义了若干常用函 数及标识符,具有相同或相似功能的函数和标识符集中放在 一个文件中。这些文件均以.h的形式命名,存放在系统目录 的include子目录下。例如文件iostream.h中定义了与控制 台输入输出和文件输入输出相关对象和成员函数,math.h 中定义了大量数学函数,string.h中定义了大量与字符串操 作相关的函数。

math.h中几个常用的数学函数
函数原型 int abs( int n ); double cos( double x ); double exp( double x ); double fabs( double x ); 说明

n的绝对值 x(弧度)的余弦
指数函数ex

x的绝对值

double fmod( double x, double y );
double log( double x ); double log10( double x );

x/y的浮点余数
x的自然对数(以e为底) x的对数(以10为底)
x的y次方(xy)

double pow( double x, double y );
double sin( double x ); double sqrt( double x );

x(弧度)的正弦 x的平方根

double tan( double x );

x(弧度)的正切

4.1.1 函数概述
main ( )

fun1( )

fun2( )

fun3( )

fun1_1( )

fun2_1( )

fun2_2( )

图4.1

函数调用层次关系

4.1.1 函数概述
函数按其是否系统预定义分为两类: 一类是编译系统预定义的,称为库函数或标准函数, 如一些常用的数学计算函数、字符串处理函数、图 形处理函数、标准输入输出函数等。这些库函数都 按功能分类,集中说明在不同的头文件中。用户只 需在自己的程序中包含某个头文件,就可直接使用 该文件中定义的函数。 另一类是用户自定义函数,用户可以根据需要将某 个具有相对独立功能的程序定义为函数。 函数按是否带有参数,分为: 无参函数和有参函数
4.1.1 结束

4.1.2 函数的定义

1. 无参函数

2. 有参函数

1
定义格式为:

无参函数

《数据类型》函数名(《void》){函数体} 例: 下面函数的功能是打印一个表头 void TableHead ( ) { cout<<″****************″<<endl; cout<<″* example *″<<endl; cout<<″****************″<<endl; }

2 有参函数
有参函数的定义格式为 《数据类型》函数名 (参数类型1 形式参数2,…》{函数体} 形式参数1《,参数类型2

例: 下面函数的功能是返回两个整数中较大一个的值

int max (int a, int b){ return(a>=b?a:b); }

提示
定义函数时可能会涉及若干个变量,究竟哪些变量应当 作为函数的参数?哪些应当定义在函数体内?这有一个原则: 作为一个相对独立的模块,函数在使用时完全可以被看成 “黑匣子”,除了输入输出外,其他部分可不必关心。 从函数的定义看出,函数头正是用来反映函数的功能和 使用接口,它所定义的是“做什么”,在这部分必须明确 “黑匣子”的输入输出部分,输出就是函数的返回值,输入 就是参数。因此,只有那些功能上起自变量作用的变量才必 须作为参数定义在参数表中;函数体中具体描述“如何做”, 因此除参数之外的为实现算法所需用的变量应当定义在函数 体内。 C++中不允许函数的嵌套定义,即在一个函数中定义另一 个函数。

4.1.3

函数的调用

在 C++ 中,除了主函数外,其他任何函数都不能单独作为程 序运行。任何函数功能的实现都是通过被主函数直接或间接 调用进行的。所谓函数调用,就是使程序转去执行函数体。

无参函数的调用格式为: 函数名( ); 有参函数的调用格式为: 函数名(实际参数表);
其中实际参数简称实参,用来将实际参数的值传递给形参, 因此可以是常量、具有值的变量或表达式。

4.1.3

函数的调用

【例4.1】 输入两个实数,输出其中较大的数。其中求两个实 数中的较大数用函数完成。 程序如下: main( ) #include<iostream.h> 函数 float max(float x,float y){ 函数 调用 return(x>=y?x:y);} max(2.5,4.7 ) max(2.5,4.7 ) void main(){ float x,y; 主程序后 return 4.7 cout<<"输入两个实数:"<<endl; 续语句 cin>>x>>y; cout<<x<<"和"<<y<<"中较大数为"<<max(x,y)<<endl; }

4.2 函数的参数传递、返回值及 函数原型说明

4.2.1 函数的参数传递及传值调用

4.2.2

函数返回值

4.2.3

函数原型说明

4.2.1 函数的参数传递及传值调用
函数调用首先要进行参数传递,参数传递的方向是由实参 传递给形参。传递过程是,先计算实参表达式的值,再将 该值传递给对应的形参变量。一般情况下,实参和形参的 个数和排列顺序应一一对应,并且对应参数应类型匹配 (赋值兼容),即实参的类型可以转化为形参类型。而对 应参数的参数名则不要求相同。 按照参数形式的不同,C++有两种调用方式:传值调用 和引用调用。顾名思义,传值调用传递的是实参的值, 本章主要介绍传值调用。

4.2.1 函数的参数传递及传值调用
【例4.2】 说明实参和形参对应关系的示例。 #include <iostream.h> n= 3 #include<math.h> x= 4.6 float power(float x,int n){ c= ‘a’ //求x的n次幂 float pow=1; while(n--) pow*=x; 调用 函数 power(4.6,3 ) power(4.6,3 ) return pow; } void main(){ int n=3; return 主程序后续语 float x=4.6; 97.336 句 char c='a'; cout<<"power("<<x<<','<<n<<")="<<power(x,n)<<endl; cout<<"power("<<c<<','<<n<<")="<<power(c,n)<<endl; cout<<"power("<<n<<','<<x<<")="<<power(n,x)<<endl; }

4.2.1 函数的参数传递及传值调用
【例4.2】 说明实参和形参对应关系的示例。 #include <iostream.h> n= 3 #include<math.h> x= 4.6 float power(float x,int n){ c= ‘a’ //求x的n次幂 float pow=1; while(n--) pow*=x; 调用 函数 power('a',3 ) power('a',3 ) return pow; } void main(){ int n=3; 主程序后续语 return float x=4.6; 句 912673 char c='a'; cout<<"power("<<x<<','<<n<<")="<<power(x,n)<<endl; cout<<"power("<<c<<','<<n<<")="<<power(c,n)<<endl; cout<<"power("<<n<<','<<x<<")="<<power(n,x)<<endl; }

4.2.1 函数的参数传递及传值调用
【例4.2】 说明实参和形参对应关系的示例。 #include <iostream.h> n= 3 #include<math.h> x=4.6 float power(float x,int n){ c= ‘a’ //求x的n次幂 float pow=1; 函数 while(n--) pow*=x; 调用 power(3,4.6 ) power(3,4.6 ) return pow; } void main(){ int n=3; 主程序后续语 return float x=4.6; 句 81 char c='a'; cout<<"power ("<<x<<','<<n<<")="<<power(x,n)<<endl; cout<<"power ("<<c<<','<<n<<")="<<power(c,n)<<endl; cout<<"power ("<<n<<','<<x<<")="<<power(n,x)<<endl; }

4.2.2 函数返回值
return语句的一般格式为:

return 表达式;
函数的计算结果通过该语句传递回主调函数。
【例4.3】设计函数,根据三角形的三边长求面积。如 果不能构成三角形,给出提示信息。 分析:函数为计算三角形面积,一般三角形返回面积值, 若不能构成三角形则返回-1。设计一个主函数完成函数 测试。根据返回值情况输出相应结果。 程序见下页:

#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(); void marketing_report(); 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(){ //生成工程报表 } void marketing_report(){ //生成市场报表; }

4.2.3 函数原型说明
【 例 4 . 4】 输 出 所 有 满 足 下 列 条 件 的 正 整 数 m : 10<m<1000且m、m2、m3均为回文数。 分析:回文指左右对称的序列。如121、353等就是回 文数。判断整数是否回文数用函数实现,其思想是将 该数各位拆开后反向组成新的整数,如果该整数与原 数相等则为回文数。 程序如下: #include<iostream.h> #include<iomanip.h> bool palindrome(int); //函数原型

void main(){ cout<<setw(10)<<'m'<<setw(20)<<"m*m“ <<setw(20)<<"m*m*m"<<endl; for(int m=11;m<1000;m++) if(palindrome(m)&&palindrome(m*m) &&palindrome(m*m*m)) cout<<setw(10)<<m<<setw(20)<<m*m <<setw(20)<<m*m*<<endl;} bool palindrome(int n){ int digit[10]; int m=n,i=0; do{ digit[i]=n%10; n/=10;i++; }while(n>0); for(intj=0;j<i;j++) n=n*10+digit[j]; return (n==m);}

4.2.3 函数原型说明
运行结果:
m 11 101 111 m*m 121 10201 12321 m*m*m 1331 1030301 1367631

4.3 全局变量和局部变量
4.3.1 变量的存储机制与C++的内存布局 4.3.2 全局变量 4.3.3 局部变量

4.3.1 变量的存储机制与C++的内存布局
操作系统为一个 C++ 程序的运行所分配的内 存分为四个区域,如图,程序在内存中的区 域所示:
堆区 (动态数据) 栈区(函数局部数据)

(main()函数局部数据)
全局数据区(全局、静态) 代码区(程序代码)

4.3.1 变量的存储机制与C++的内存布局
(1)代码区(Code area):存放程序代码, 即程序中各个函数的代码块; (2)全局数据区(Data area):存放全局数 据和静态数据;分配该区时内存全部清零。 (3)栈区(Stack area):存放局部变量,如 函数中的变量等;分配栈区时内存不处理。 (4)堆区(Heap area):存放与指针相关的 动态数据。分配堆区时内存不处理。

4.3.2 全局变量
在所有函数之外定义的变量称为全局变量。 全局变量在编译时建立在全局数据区,在未给 出初始化值时系统自动初始化为全0。 全局变量可定义在程序开头,也可定义在中间 位置,该全局变量在定义处之后的任何位置都是可 以访问的,称为可见的。 请看下例:

4.3.2 全局变量
【例4.5】 多个函数使用全局变量的例子。 #include<iostream.h> int n=100; void func(){ n*=2; } void main(){ n*=2; cout<<n<<endl; func(); cout<<n<<endl; }
n=100 n=100*2 =200 打印 200 调用 func( ) 打印 400 函数 func( ) 200*2 =400

4.3.3 局部变量
定义在函数内或块内的变量称为局部变量。 局部变量在程序运行到它所在的块时建立在栈中, 该块执行完毕局部变量占有的空间即被释放。 局部变量在定义时可加修饰词 auto,但通常省略。 局部变量在定义时若未初始化,其值为随机数。 程序中使用的绝大多数变量都是局部变量。

4.3.3 局部变量
【例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; }
t=5 t= 3.5

打印main() 中的t=3.5 调用 fun( ) 函数 fun( ) 打印fun() 中的t=5

打印main() 中的t=3.5

4.4 函数调用机制
局部变量占用的内存是在程序执行过程中“动态”地建立 和释放的。这种“动态”是通过栈由系统自动管理进行的。当 任何一个函数调用发生时,系统都要作以下工作: (1)建立栈空间; (2)保护现场:主调函数运行状态和返回地址入栈; (3)为被调函数中的局部变量分配空间,完成参数传递; (4)执行被调函数函数体; (5)释放被调函数中局部变量占用的栈空间; (6)恢复现场:取主调函数运行状态及返回地址,释放栈空间; (7)继续主调函数后续语句。

4.4 函数调用机制
此图例说明在程序执行过程中怎样通过栈“动态”地建立和 释放局部变量占用的内存的 void fun1(int, int); void fun2(float); x 栈顶 void main(){ y 3 int x=1;y=2; fun2() fun1()运行状态及返回地址 fun1(x, y); x 3 } b 2 void fun1(int a,int b){ float x=3; a 1 fun2(x); fun1() main()运行状态及返回地址 } y 2 void fun2(float y) { x 1 int x; main() 操作系统运行状态及返回地址 栈底 … }

4.5 作用域与存储类型
4.5.1 作 用 域

4.5.2

变量的存储类型

4.5.3外部存储类型与静态存储类型

4.5.4 生命期与可见性

4.5.1

作用域

作用域指标识符能够被使用的范围。只有在作用 域内标识符才可以被访问(称为可见)。 本节只讨论局部域和文件域(全局域),其中局 部域包括块域和函数原型域。任何标识符作用域的起 始点均为标识符说明处。 下面分别介绍:
1 块作用域 2 函数原型作用域 3 文件作用域

1. 块域
块指一对大括号括起来的程序段。块中定义的标 识符,作用域在块内。

复合语句是一个块。复合语句中定义的标识符, 作用域仅在该复合语句中。
函数也是一个块。 函数中定义的标识符,包括形 参和函数体中定义的局部变量,作用域都在该函数内, 也称作函数域。

1. 块域
【例4.7】 输入两数,按从大到小的顺序保存,并输出结果。
#include<iostream.h> void main(){ 3 栈 a= 5 int a,b; //具有函数域 b= 3 5 cout<<"输入两整数:"<<endl; cin>>a>>b; t= 3 cout<<“a="<<a<<'\t'<<"b="<<b<<endl; if(b>=a){ 结果 int t; //具有块域 a=3 b=5 t=a; a=b; b=t; //交换a,b的值 a=5 b=3 } 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++平台运行,结果如下: 输入两整数: 35 调用前:实参a=3,b=5 调用中… 交换前:形参a=3,b=5 交换后:形参a=5,b=3 调用后:实参a=3,b=5 交换失败

局部变量具有局部作用域使得程序在不同块中可以 使用同名变量。这些同名变量各自在自己的作用域 中可见,在其它地方不可见。

1. 块作用域
对于块中嵌套其它块的情况,如果嵌套块中有同 名局部变量,服从局部优先原则,即在内层块中屏 蔽外层块中的同名变量,换句话说,内层块中局部 变量的作用域为内层块;外层块中局部变量的作用 域为外层除去包含同名变量的内层块部分。
如果块内定义的局部变量与全局变量同名,块内仍 然局部变量优先,但与块作用域不同的是,在块内 可以通过域运算符“::”访问同名的全局变量。

【例4.9】 显示同名变量可见性。

100

200

300

1100 500 600 int n=100; 100 #include<iostream.h> 500 200 300 void main(){ int i=200,j=300; cout<< n<<'\t'<<i<<'\t'<<j<<endl; { //内部块 int i=500,j=600,n; 内n=500+600 =1100 n=i+j; cout<< n<<'\t'<<i<<'\t'<<j<< endl; 内 j= 600 //输出局部变量n 内 i= 500 cout<<::n<<endl;//输出全局变量n 外部 j=300 } n=i+j; //修改全局变量 外部 i=200 cout<< n<<'\t'<<i<<'\t'<<j<< endl; 全局n= 500 100 200+300=500 }

2

函数原型作用域

函数原型不是定义函数,在作函 数原型声明时,其中的形参作用域 只在原型声明中,即作用域结束于 右括号。正是由于形参不能被程序 的其他地方引用,所以通常只要声 明形参个数和类型,形参名可省略。

3 文件作用域
文件作用域也称全局作用域。定义在所有函数之外的 标识符,具有文件作用域,作用域为从定义处到整个 源文件结束。文件中定义的全局变量和函数都具有文 件作用域。

如果某个文件中说明了具有文件作用域的标识符,该 文件又被另一个文件包含,则该标识符的作用域延伸 到新的文件中。如 cin 和 cout 是在头文件 iostream.h 中说 明的具有文件作用域的标识符,它们的作用域也延伸 到嵌入iostream.h的文件中。

4.5.2 变量的存储类型
存储类型决定了变量的生命期,变量生命期指从 获得空间到空间释放之间的时期。 存储类型的说明符有四个 :auto, register, static 和 extern。前两者称为自动类型,后两者分别为静态和外 部类型。 本节重点掌握 static 和 extern 这两种类型的使用和 区别。具体说,区分局部变量和静态局部变量,全局 变量和静态全局变量。

4.5.2 变量的存储类型
auto:前面提到的局部变量都是自动类型。其空间分配 于块始,空间释放于块终,且由系统自动进行。自 动变量保存在栈中,且是在程序运行过程中获得和 释放空间,未初始化时值为随机数。 register: 为提高程序运行效率,可以将某些变量保存在 寄存器中,即说明为寄存器变量,但不提倡使用。 static : 静态变量。根据被修饰变量的位置不同,分为 局部(内部)静态变量和全局(外部)静态变量。 所有静态变量均存放在全局数据区,编译时获得存 储空间,未初始化时自动全0,且只初始化一次。

局部静态变量

局部静态变量的作用域为块域,但生命期为整个 文件。即当块结束时,局部静态变量空间仍然保持, 直到整个程序文件结束时该局部静态变量空间才释放, 生命期结束。

【例4.10】 自动变量与局部静态变量的区别。(演 示)

4.5.2 变量的存储类型
#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; }

i=

0 4 2 1 3 5

t= 100 101

4.5.2 变量的存储类型
#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; }
i= 0 2 1 5 4 3 t=100 101 104 103 102 105

全局静态变量
全局静态变量是指用 static 修饰的全局 变量。有关内容在下节静态存储类型中介绍。

4.5.3 外部存储类型与静态存储类型
一个C++程序可以由多个源程序文件组成,编译系统 将这若干个文件连接在一起,产生可执行程序。外部 存储类型和静态存储类型确定了变量和函数在多文件 程序中的联络关系。

1. 外部存储类型

2. 静态存储类型

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 生命期
生命期(Life time)也叫生存期。生命期与存储区域相关, 存储区域分为代码区、静态数据区、栈区和堆区,相应地, 生命期分为静态生命期、局部生命期和动态生命期。

(1)静态生命期 (2)局部生命期 (3)动态生命期

(1)静态生命期
静态生命期指的是标识符从程序开始运行时 存在,即具有存储空间,到程序运行结束时消亡, 即释放存储空间。具有静态生命期的标识符存放 在静态数据区,属于静态存储类型,如全局变量、 静态全局变量、静态局部变量。具有静态生命期 的标识符在未被用户初始化的情况下,系统会自 动将其初始化为全0。 函数驻留在代码区,也具有静态生命期。所 有具有文件作用域的标识符都具有静态生命期。

(2)局部生命期
在函数内部或块中定义的标识符具有局部生命 期,其生命期开始于执行到该函数或块的标识符声 明处,结束于该函数或块的结束处。具有静态生命 期的标识符存放在栈区。具有局部生命期的标识符 如果未被初始化,其内容是随机的,不可用。

具有局部生命期的标识符必定具有局部作用域; 但反之不然,静态局部变量具有局部作用域,但却 具有静态生命期。

(3)动态生命期
具有动态生命期的标识符由特定的函数调 用或运算来创建和释放,如调用malloc()或用 new运算符为变量分配存储空间时,变量的生命 期开始,而调用free()或用delete运算符释放空间 或程序结束时,变量生命期结束。具有动态生 命期的变量存放在堆区。关于new运算和delete 运算将在指针一章中介绍。

2

可见性

可见性从另一个角度说明标识符的有效性,可见性与 作用域具有一定的一致性。标识符的作用域包含可见 范围,可见范围不会超过作用域。可见性在理解同名 标识符的作用域嵌套时十分直观。对于外层块与内层 块定义了同名标识符的,在外层作用域中,内层所定 义的标识符是不可见的,即外层引用的是外层所定义 的标识符;同样,在内层作用域中,外层的标识符将 被内层的同名标识符屏蔽,变得不可见,即外层中同 名标识符的可见范围为作用域中挖去内层块的范围。 图4.6显示下面程序段中变量的作用域与可见性。

2

可见性
int m, float x作用域 int m可见 float m不可见 x可见 float m作用域 float m可见 int m不可见 x可见

下面的程序段和图示显示作用域与 可见性。

int m=1; float x; { float m=3.5; X=5.5; } m++;

4.6 函数的递归调用
递归是一种描述问题的方法,或称算法。递 归的思想可以简单地描述为“自己调用自己”。 例如用如下方法定义阶乘:

? 1 ? n!? ? 1 ? n * (n - 1)! ?

n?0 n ?1 n ?1

可以看出是用阶乘定义阶乘,这种自 己定义自己的方法称为递归定义。

4.6 函数的递归调用
在函数调用中,有这样两种情况,一种是在函数A的定 义中有调用函数A的语句,即自己调用自己;另一种是函数 A的定义中出现调用函数B的语句,而函数B的定义中也出 现调用函数A的语句,即相互调用。前者称直接递归,后者 称间接递归。本节只介绍直接递归。递归函数必须定义递 归终止条件(Stopping condition),避免无穷递归( Infinite Recursion)。
递归定义的阶乘算法用函数描述为: fac(int n){ if (n==0||n==1) return 1; else return n*fac(n-1); } 只要设计主函数调用阶乘函数,即可实现计算阶乘。

【例4.12】 求4! #include <iostream.h> int fac(int n){ int y; cout<<n<<'\t'; if(n==0||n==1) y=1; else y=n*fac(n-1); cout<<y<<'\t'; return y; } void main(){ cout<<"\n4!="<<fac(4)<<endl; }
n=4 fac(4)= 24 cout<<4; y=4*fac(3); n=3 cout<<3; y=3*fac(2); n=2 cout<<2; y=2*fac(1); n=1 cout<<1; y=1; cout<<1; return 1;

cout<<24; return 24;

cout<<6; return 6;

cout<<2; return 2;

4.6 函数的递归调用
递归函数的执行分为“递推”和“回归”两个过程 ,这两个过程由递归终止条件控制,即逐层递推, 直至递归终止条件,然后逐层回归。每次调用发生 时都首先判断递归终止条件。递归调用同普通的函 数调用一样,每当调用发生时,在栈中分配单元保 存返回地址以及参数和局部变量;而与普通的函数 调用不同的是,由于递推的过程是一个逐层调用的 过程,因此存在一个逐层连续的参数入栈过程,直 至遇到递归终止条件时,才开始回归,这时才逐层 释放栈空间,返回到上一层,直至最后返回到主调 函数。

4.6 函数的递归调用
【例4.13】 汉诺塔问题。有A、B、C三根柱子,A柱 上有 n个大小不等的盘子,大盘在下,小盘在上。要求 将所有盘子由 A 柱搬动到 C 柱上,每次只能搬动一个盘 子,搬动过程中可以借助任何一根柱子,但必须满足大 盘在下,小盘在上。打印出搬动的步骤。

A柱

B柱

C柱

4.6 函数的递归调用
分析: 1 A柱只有一个盘子的情况: A柱?C柱; 2 A柱有两个盘子的情况:小盘A柱?B柱,大盘A柱?C柱, 小盘B柱?C柱。 3 A柱有n个盘子的情况:将此问题看成上面n-1个盘子和 最下面第n个盘子的情况。n-1个盘子A柱?B柱,第n个盘 子A柱 ?C 柱,n-1个盘子B 柱?C柱。问题转化成搬动 n-1 个盘子的问题,同样,将 n-1个盘子看成上面 n-2个盘子 和下面第 n-1 个盘子的情况,进一步转化为搬动 n-2个盘 子的问题,……,类推下去,一直到最后成为搬动一个 盘子的问题。 这是一个典型的递归问题,递归结束于只搬动一个盘子。

4.6 函数的递归调用
算法可以描述为: 1 n-1个盘子A柱?B柱,借助于C柱; 2 第n个盘子A柱?C柱; 3 n-1个盘子B柱?C柱,借助于A柱; 其中步骤1和步骤3继续递归下去,直至搬动一个 盘子为止。由此,可以定义两个函数,一个是递归 函数,命名为hanoi(int n, char source, char temp, char target),实现将n个盘子从源柱 source借助中间柱temp搬到目标柱target;另一 个命名为move(char source, char target),用 来输出搬动一个盘子的提示信息。

#include <iostream.h> void move(char source,char target){ cout<<source<<"->"<<target<<endl;} void hanoi(int n,char source,char temp,char target){ if(n==1) move(source,target); else{ hanoi(n-1,source,target,temp); //将n-1个盘子搬到中间柱 move(source,target); //将最后一个盘子搬到目标柱 hanoi(n-1,temp,source,target); }} //将n-1个盘子搬到目标柱 void main(){ int n; cout<<"输入盘子数:"<<endl; cin>>n; hanoi(n,'A','B','C');}

【例4.14】 输入一个整数,用递归算法将整数倒序输出。 分析:在递归过程的递推步骤中用求余运算将整数的各个位分 离,并打印出来。 #include<iostream.h> void backward(int n){ cout<<n%10; if(n<10) return; else backward(n/10); } void main(){ int n; cout<<"输入整数:"<<endl; cin>>n; cout<<"原整数:"<<n<<endl<<"反向数:"; backward(n); cout<<endl; }

4.6 函数的递归调用
n=247 backward(247) cout<<endl;
cout<<7; backward(24); return;

n=24
cout<<4; backward(2); return;

n=2
cout<<2; return;

求余总是取当前整数的最右一位,所以先输出余数后递 归可实现倒序输出。如果先递归后输出余数,则是在回归的 过程中输出,实现的就是正序输出。

从以上几例可以看出,递归算法一般不需要借助循环,但通 过不断递推和回归的过程实现了其他算法用循环完成的功能 。因此,递归的终止条件非常重要,否则将会无休止地递归 下去,陷入死循环状态。

4.6 函数的递归调用
【例4.15】在【例3.11】中采用递推法求解Fibonacii数列,

本例用递归法求解。本例的递归调用过程参见图4.11。 #include<iostream.h> #include<iomanip.h> int fib(int n){ if(n==0) return 0; else if(n==1) return 1; else return fib(n-1)+fib(n-2);} void main(){ for(int i=0;i<=19;i++){ //将19改为69,可以看出计算到后面越来越缓慢。 if(i%5==0) cout<<endl; cout<<setw(6)<<fib(i);} cout<<endl; }

4.6 函数的递归调用

图4.11 递归求解斐波那契数列调用树

同其他算法相比,用递归算法编制的程序非常简洁易读,但 缺点是增加了内存的开销,在递推的过程中会占用大量栈空 间,且连续的调用返回操作占用较多CPU时间。因此是否选 择使用递归算法取决于所解决的问题及应用的场合。

函数的嵌套调用
C++不允许对函数作嵌套定义,也就是说在一个函数 中不能完整地包含另一个函数。在一个程序中每一个 函数的定义都是互相平行和独立的。 虽然C++不能嵌套定义函数,但可以嵌套调用函数, 也就是说,在调用一个函数的过程中,又调用另一个 函数。见下图4。

在程序中实现函数嵌套调用时,需要注意的是: 在 调用函数之前,需要对每一个被调用的函数作声明 (除非定义在前,调用在后)。 例4.9 用弦截法求方程f(x)=x3-5x2+16x-80=0的根。 这是一个数值求解问题,需要先分析用弦截法求根的 算法。根据数学知识,可以列出以下的解题步骤: (1) 取两个不同点x1,x2,如果f(x1)和f(x2)符号相反, 则(x1,x2)区间内必有一个根。如果f(x1)与f(x2)同符 号,则应改变x1,x2,直到f(x1), f(x2)异号为止。注意x1、 x2的值不应差太大,以保证(x1,x2)区间内只有一个根。 (2) 连接(x1, f(x1))和(x2, f(x2))两点,此线(即弦)交 x轴于x,见图4.7。

x点坐标可用下式求出: x=x1· f(x2)-x2· f(x1) f(x2)-f(x1) 再从x求出f(x)。 (3) 若f(x)与f(x1)同符号,则根必在(x, x2)区间内,此时 将x作为新的x1。如果f(x)与f(x2)同符号,则表示根 在( x1,x)区间内,将x作为新的x2。 (4) 重复步骤 (2) 和 (3), 直到 |f(x)|<ξ为止, ξ为一 个很小的正数, 例如10-6。此时认为 f(x)≈0。 这就是弦截法的算法,在程序中分别用以下几个函数 来实现以上有关部分功能: (1) 用函数f(x)代表x的函数:x3-5x2+16x-80。

(2) 用函数xpoint (x1,x2)来求(x1,f(x1))和(x2,f(x2))的连 线与x轴的交点x的坐标。 (3) 用函数root(x1,x2)来求(x1,x2)区间的那个实根。显 然,执行root函数的过程中要用到xpoint函数,而执行 xpoint函数的过程中要用到f函数。 根据以上算法,可以编写出下面的程序:
#include <iostream> #include <iomanip> #include <cmath> using namespace std; double f(double); //函数声明 double xpoint(double, double); //函数声明 double root(double, double); //函数声明
int main( ) { double x1,x2,f1,f2,x;

do {cout<<″input x1,x2:″; cin>>x1>>x2; f1=f(x1); f2=f(x2); } while(f1*f2>=0); x=root(x1,x2); cout<<setiosflags(ios∷fixed)<<setprecision(7); //指定输出7位小数 cout<<″A root of equation is ″<<x<<endl; return 0; } double f(double x) //定义f函数,以实现f(x) {double y; y=x*x*x-5*x*x+16*x-80; return y; }

double xpoint(double x1, double x2) //定义xpoint函数,求出弦与x轴交 点 {double y; y=(x1*f(x2)-x2*f(x1))/(f(x2)-f(x1)); //在xpoint函数中调用f函数 return y; } double root(double x1, double x2) //定义root函数,求近似根 {double x,y,y1; y1=f(x1); do {x=xpoint(x1,x2); //在root函数中调用xpoint函数 y=f(x); //在root函数中调用f函数 if (y*y1>0) {y1=y; x1=x; } else x2=x; }while(fabs(y)>=0.00001); return x; }

运行情况如下:
input x1,x2:2.5 6.7↙ A root of equation is 5.0000000

对程序的说明: (1) 在定义函数时,函数名为f,xpoint和root的3个函 数是互相独立的,并不互相从属。这3个函数均定为 双精度型。 (2) 3个函数的定义均出现在main函数之后,因此在 main函数的前面对这3个函数作声明。 习惯上把本程序中用到的所有函数集中放在最前面声 明。 (3) 程序从main函数开始执行。函数的嵌套调用见图 4.8。

图4.8 (4) 在root函数中要用到求绝对值的函数fabs, 它是对双精度数求绝对值的系统函数。它属于数学 函数库,故在文件开头用#include <cmath> 把有关的头文件包含进来。

4.7 函数的一些高级议题
4.7.1 函数重载

4.7.2 函数模板
4.7.3 缺省变元

4.7.1 函数重载
在C++中,如果需要定义几个功能相似,而参数类型不同的函 数,那么这样的几个函数可以使用相同的函数名,这就是函数 重载。例如求和函数,对应不同的参数类型,可以定义如下几 个重载函数: sum(int a,int b) //不写返回类型,返回整型 double sum(double a,double b) float sum(floata,float b,float c) 当某个函数中调用到重载函数时,编译器会根据实参的类型去 对应地调用相应的函数。匹配过程按如下步骤进行: (1)如果有严格匹配的函数,就调用该函数; (2)参数内部转换后如果匹配,调用该函数; (3)通过用户定义的转换寻求匹配。 因此在定义重载函数时必须保证参数类型不同或参数个数不同 ,仅仅返回值类型不同是不行的。函数重载的好处在于,可以 用相同的函数名来定义一组功能相同或类似的函数,程序的可 读性增强。

4.7.1 函数重载
【例4.16】 重载函数的应用。
#include<iostream.h> sum(int a,int b){ return a+b; } Double sum(double a,double b){ return a+b; } float sum(float a,float b,float c){ return a+b+c; } void main(){ cout<<"3+5="<<sum(3,5) <<endl; cout<<"2.2+5.6=“ <<sum(2.2,5.6)<<endl; cout<<"3.5+4+8=“ <<sum(3.5,4,8)<<endl;}

3+5= 8
调用 sum(3,5 )
2.2+5.6= 7.8

函数 sum(3,5 ) return 8
函数double sum(2.2,5.6 )

调用 sum(2.2,5.6 )

3.5+4+8= 15.5 调用 sum(3.5, 4, 8 )

return 7.8 函数float sum(3.5, 4, 8 ) return 15.5

结束

4.7.2

模板

为了代码重用,代码就必须是通用的;通用 的代码就必须不受数据类型的限制。那么我们 可以把数据类型改为一个设计参数。这种类型 的程序设计称为参数化(parameterize) 程序 设计。软件模块由模板(template) 构造。包 括 函 数 模 板 (function template) 和 类 模 板 (class template)。
4.7.3 函数模板及应用

4.7.2 函数模板及应用
函数模板可以用来创建一个通用功能的函数,以支持多 种不同形参,简化重载函数的设计。函数模板定义如下: template<模板参数表>返回类型 函数名(形式参数表) {……;}//函数体 <模板参数表>(template parameter list)尖括号中不能 为空,参数可以有多个,用逗号分开。模板参数主要是模板 类型参数。 模板类型参数(template type parameter)代表一种类型, 由关键字 class 或 typename(建议用typename ) 后加 一个标识符构成,在这里两个关键字的意义相同,它们表示 后面的参数名代表一个潜在的内置或用户定义的类型。

#include <iostream> using namespace std; template<typename T> //模板声明,其中T为类型参数 T max(T a,T b,T c) //定义一个通用函数,用T作虚拟的类型名 {if(b>a) a=b; if(c>a) a=c; return a; } int main( ) {int i1=185,i2=-76,i3=567,i; double d1=56.87,d2=90.23,d3=-3214.78,d; long g1=67854,g2=-912456,g3=673456,g; i=max(i1,i2,i3); //调用模板函数,此时T被int取代 d=max(d1,d2,d3); //调用模板函数,此时T被double取代 g=max(g1,g2,g3); //调用模板函数,此时T被long取代 cout<<″i_max=″<<i<<endl; cout<<″f_max=″<<f<<endl; cout<<″g_max=″<<g<<endl; return 0; }

程序第3~8行是定义模板。定义函数模板的一 般形式为

template < typename T> 或 template <class T>
通用函数定义 通用函数定义

在建立函数模板时,用虚拟的类型名T代替具 体的数据类型。在对程序进行编译时,遇到第13行 调用函数max(i1,i2,i3),编译系统会将函数名max 与模板max相匹配,将实参的类型取代了函数模板 中的虚拟类型T。此时相当于已定义了一个函数:

int max(int a,int b,int c) {if(b>a) a=b; if(c>a) a=c; return a; } 然后调用它。后面两行(14,15行)的情况类似。 类型参数可以不只一个,可以根据需要确定个数。 如 template <class T1,typename T2> 可以看到,用函数模板比函数重载更方便,程序更 简洁。但应注意它只适用于函数的参数个数相同而 类型不同,且函数体相同的情况,如果参数的个数 不同,则不能用函数模板。

4.7.3 缺省参数
一般情况下,函数调用时的实参个数应与形参相同,但为 了更方便地使用函数,C++也允许定义具有缺省参数的函数, 这种函数调用时实参个数可以与形参不相同。 缺省参数指在定义函数时为形参指定缺省值(默认值)。这 样的函数在调用时,对于缺省参数,可以给出实参值,也可 以不给出参数值。如果给出实参,将实参传递给形参进行调 用,如果不给出实参,则按缺省值进行调用。 缺省参数的函数调用:缺省实参并不一定是常量表达式, 可以是任意表达式,甚至可以通过函数调用给出。如果缺 省实参是任意表达式,则函数每次被调用时该表达式被重 新求值。但表达式必须有意义;

例4.8 求2个或3个正整数中的最大数,用带有默认参 数的函数实现。
#include <iostream> using namespace std; int main( ) {int max(int a, int b, int c=0);//函数声明,形参c有默认值 int a,b,c; cin>>a>>b>>c; cout<<″max(a,b,c)=″<<max(a,b,c)<<endl; //输出3个数中的最大者 cout<<″max(a,b)=″<<max(a,b)<<endl; //输出2个数中的最大者 return 0; } int max(int a,int b,int c) //函数定义 {if(b>a) a=b; if(c>a) a=c; return a; }

4.7.3 缺省参数
缺省参数通过表达式给出,所以可以使用函数调用,如: int fun1(int a=rand()); 参数a缺省时,可由随机数发生函数当场产生,编译时定的是 调什么函数。 缺省参数可以有多个,但所有缺省参数必须放在参数表 的右侧,即先定义所有的非缺省参数,再定义缺省参数。这 是因为在函数调用时,参数自左向右逐个匹配,当实参和形 参个数不一致时只有这样才不会产生二义性。 一个参数只能在一个文件被指定一次缺省实参,习惯上 ,缺省参数在公共头文件包含的函数声明中指定,否则缺省 实参只能用于包含该函数定义的文件中的函数调用。例如: int fun2 (int, int =10, int =20); //函数原型中给出缺省值。参数名也可省略 void fun1(){…} int fun2(int a, int b, int c) {…} //定义中不再给出缺省值

4.9 头文件与多文件结构
除了系统定义的头文件外,用户还可以自定义头文件 。对于具有外部存储类型的标识符,可以在其他任何 一个源程序文件中经声明后引用,因此用户完全可以 将一些具有外部存储类型的标识符的声明放在一个头 文件中。具体地说,头文件中可以包括:用户构造的 数据类型(如枚举类型),外部变量,外部函数、常 量和内联函数等具有一定通用性或常用的量,而一般 性的变量和函数定义不宜放在头文件中。

4.9.2 多文件结构
在开发较大程序时,通常将其分解为多个源程序文件 ,每个较小的程序用一个源程序文件建立。程序经过建立 、编译、连接,成为一个完整的可执行程序。多文件结构 通过工程进行管理,在工程中建立若干用户定义的头文件 .h和源程序文件.cpp。头文件中定义用户自定义的数据类 型,所有的程序实现则放在不同的源程序文件中。编译时 每个源程序文件单独编译,如果源程序文件中有编译预处 理指令,则首先经过编译预处理生成临时文件存放在内存 ,之后对临时文件进行编译生成目标文件.obj,编译后临 时文件撤销。所有的目标文件经连接器连接最终生成一个 完整的可执行文件.exe。 图4.12是一个多文件系统的开发过程。

4.9.2 多文件结构
file1.cpp
预编译

file1.h

file2.cpp
预编译

file2.h

?

filen.cpp
预编译

filen.h

临时文件1
编译

临时文件2
编译

?

临时文件n
编译

file1.obj

file2.obj

?

filen.obj

连接 运行

.lib C++标准类库

Filename. exe

图4.6 C++程序开发过程

4.10 编译预处理
4.10.1 宏定义指令

4.10.2 文件包含指令 4.10.3 条件编译指令

4.10.1 宏定义指令#define
1 不带参宏定义 用来产生与一个字符串对应的常量字符串,格式为: #define 宏名 常量串 预处理后文件中凡出现该字符串处均用其对应的常量 串代替。替换过程称为宏替换或宏展开。例如,如果 使用指令 #define PI 3.1415926 则程序中可以使用标识符PI,编译预处理后产生一个 中间文件,文件中所有PI被替换为3.1415926。 宏替换只是字符串和标识符之间的简单替换,预处理 本身不做任何数据类型和合法性检查,也不分配内存 单元。

4.10.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.10.2 文件包含(嵌入)指令#include
文件包含用#include指令,预处理后将指令中指明的源程序 文件嵌入到当前源程序文件的指令位置处。格式为: #include <文件名> 或 #include ″文件名″ 第一种方式称为标准方式,预处理器将在 include 子目录下 搜索由文件名所指明的文件。这种方式适用于嵌入C++提供 的 头 文 件 , 因 为 这 些 头 文 件 一 般 都 存 在 C++ 系 统 目 录 的 include 子目录下。而第二种方式编译器将首先在当前文件 所在目录下搜索,如果找不到再按标准方式搜索。这种方式 适用于嵌入用户自己建立的头文件。

4.10.2 文件包含(嵌入)指令#include
一个被包含的头文件中还可以有#include指令, 即include指令可以嵌套,但是,如果同一个头文 件在同一个源程序文件中被重复包含,就会出现标 识符重复定义的错误。例如:头文件f2.h中包含了 f1.h,如果文件f3.cpp中既包含f1.h,又包含f2.h, 那么编译将提示错误,原因是f1.h被包含了两次, 那么其中定义的标识符在f3.cpp中就被重复定义。 避免重复包含可以用条件编译指令。

4.10.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指令所定义的符号,这样 可以根据需要打开和关闭符号。



相关推荐


友情链接: