1、第第5 5章章 程序结构程序结构第第5章章 程序结构程序结构 5.1 作用域与可见性作用域与可见性5.2 生存期生存期 5.3 局部变量和全局变量局部变量和全局变量 5.4 静态成员静态成员 5.5 友元友元 5.6 常类型常类型 5.7 多文件结构多文件结构 5.8 编译预处理编译预处理 第第5 5章章 程序结构程序结构5.1 作用域与可见性作用域与可见性 5.1.1 作用域 作用域是一个标识符在程序正文中有效的区域。C+的作用域有函数原型作用域、块作用域(亦称局部作用域)和文件作用域。 1. 函数原型作用域 函数原型作用域是C+程序中最小的作用域。前面介绍过,在函数原型的声明中一定要包含参
2、数的类型声明。 第第5 5章章 程序结构程序结构 这里参数的声明就属于函数原型作用域,它开始于函数原型声明的左括号“(”,结束于右括号“)”。例如,有如下函数声明: double Area(double length,double width); 由于形参length和width只在括号之内有效,在程序的其它地方无法引用这个标识符,如果要引用,必须重新定义。例如: double Area(double length,double width); /函数声明 length=10; width=5;第第5 5章章 程序结构程序结构 这段代码会引起无定义的标识符编译错误。所以,对于这种情况,标识符l
3、ength和width实际上是可有可无的,省去它,也决不会影响到程序的编译和运行结果。例如: double Area(double,double); 但是,考虑到程序的可读性,还是要在函数原型声明时,给形参指定一个有意义的标识符,而且一般总是与该函数定义时声明的参数标识符一致。 第第5 5章章 程序结构程序结构例如:double Area(double length,double width);/函数声明/.double Area(double length,double width)/函数定义/.第第5 5章章 程序结构程序结构 2块作用域 块作用域又称局部作用域。当标识符的声明出现在由一对
4、花括号“”所括起来的程序(块)内时,则此块中声明的标识符的作用域从声明处开始,一直到块结束的花括号为止。为了理解块作用域,我们来看一个例子: #include void main( ) int n;第第5 5章章 程序结构程序结构for(int i=0;i5;i+)int m;if(i%2) m作用域 i作用域 n作用域n+; m=n/2;/错误,m未定义n=i;第第5 5章章 程序结构程序结构 在这个例子中,函数体main( )是一个块,for语句之后的循环体又是一个较小的块。变量n和i的作用域从声明处开始,到它所在的块,即整个函数体main( )结束处为止。变量m的作用域从声明处开始到它所
5、在的块,即循环体结束为止。因此,编译时m=n/2会出错,因为它试图在m作用域之外引用m。而n=i是允许的,因为此时还在i的作用域内。如果在上面的程序中加入一个函数fun1( ),情况会怎么样呢?第第5 5章章 程序结构程序结构#includevoid main( )void fun1( );/fun1( )函数声明int n;for(int i=0;i5;i+)int m;if(i%2)n+;第第5 5章章 程序结构程序结构m=n/2; /错误,m未定义fun1( );/fun1( )函数调用n=i;void fun1( )couti=iendl; /错误,i未定义第第5 5章章 程序结构程序
6、结构 3文件作用域 具有文件作用域的标识符是在所有函数定义之外声明的,其作用域从声明点开始,一直延伸至文件尾。一般情况下,程序中所声明的全局变量都具有文件作用域,它们在整个文件中都有效。 【例5-1】 文件作用域例题。 #include int k; void main( ) k=5; 第第5 5章章 程序结构程序结构 k+; coutk=kendl; 程序运行结果为 k=6 这个例子中,在主函数之前声明的变量k具有文件作用域,它的有效作用范围是整个源代码文件。第第5 5章章 程序结构程序结构 5.1.2 可见性 作用域指的是标识符有效的范围,可见性从另一个角度表现标识符的有效范围。标识符的可
7、见性是指在程序的某个位置, 该标识符可以被有效地引用,因此, 形象地称为可见性。可见性遵循的一般规则如下: 标识符在引用前必须先声明。 在互相没有包含关系的不同作用域中声明同名的标识符时,两标识符互不影响。 如果在两个或多个具有包含关系的作用域中声明了同名标识符,则外层标识符在内层不可见。第第5 5章章 程序结构程序结构【例5-2】 具有包含关系的作用域中可见性例题。 #include int k; void main( ) k=10; int k=5;coutk=kendl; 第第5 5章章 程序结构程序结构 coutk=kendl; 程序运行结果为 k=5 k=10 在这个例子中,主函数之
8、前声明的变量k具有文件作用域,它的有效作用范围是整个源代码文件;主函数内声明的变量k具有块作用域,它的作用范围在内层的花括号内,k的块作用域被完全包含在k的文件作用域中。第第5 5章章 程序结构程序结构 图5-1描述了k的两个作用域的包含关系。根据作用域可见性的规则,在具有包含关系的作用域中声明同名标识符,外层标识符在内层不可见。程序的运行结果验证了这一点。第第5 5章章 程序结构程序结构图5-1 k的作用域示意图图5-1 k的作用域示意图k的文件作用域k的块作用域第第5 5章章 程序结构程序结构5.2 生存期生存期 5.2.1 静态生存期 此生存期与程序的运行期相同。静态生存期的变量只要程序
9、一开始运行,它就存在,直到程序运行结束,此变量的生存期也就结束了。具有文件作用域的变量具有静态生存期。如果要在函数内部的块作用域中声明具有静态生存期的变量,则要使用关键字static。例如,下列语句声明的变量k便是具有静态生存期的变量,也称为静态变量。 第第5 5章章 程序结构程序结构 static int k; 具有静态生存期的变量在固定的数据区域内分配空间。如果具有静态生存期的变量未初始化,则自动初始化为0。全局变量、静态全局变量和静态局部变量都具有静态生存期。第第5 5章章 程序结构程序结构 5.2.2 局部生存期 在块作用域中声明的变量具有局部生存期。此生存期诞生于声明点,而终止于其作
10、用域的结束处。因此,具有局部生存期的变量都具有块作用域。但反之则不然,一般具有块作用域的变量都具有局部生存期,但当在块作用域内将变量说明为静态变量时,该变量则具有静态生存期。例如: void main( ) static int k; /. 第第5 5章章 程序结构程序结构 5.2.3 动态生存期 动态生存期由程序中特定的函数 (malloc( )和free( ) 调用或由操作符(new和delete)创建和释放,这部分内容将在第6章中介绍。具有动态生存期的变量在内存的堆区分配空间。第第5 5章章 程序结构程序结构5.3 局部变量和全局变量局部变量和全局变量 5.3.1 局部变量 局部变量具有
11、局部作用域。因此,在不同函数体内的局部变量是互相不可见的,这就很好地实现了函数之间的数据隐蔽。这也是结构化程序设计中实现数据隐蔽的唯一办法。局部变量包括自动(auto)变量、内部静态(static)变量和函数参数。自动变量是在函数体或分程序内声明的变量,具有块作用域。声明时,变量前可以加auto,也可以不加,程序中没有特别说明的变量都是自动变量。 第第5 5章章 程序结构程序结构 自动变量以堆栈方式占用内存空间。因此,当程序运行到此类变量声明处时,会立刻为它分配内存空间,而一旦其生存期结束,系统立即收回这个堆栈,此变量也就立即消失。内部静态变量在前面已介绍过,它具有文件作用域和静态生存期,系统
12、在固定的内存区数据区为它分配空间。函数参数实质上就是自动变量。 第第5 5章章 程序结构程序结构 局部变量能够在调用和被调用函数之间通过参数进行数据传递。如果把数据存储在局部变量中,函数在不同的块之间只能通过参数传递来共享数据。我们在前面学习函数的时候曾经深入讨论过这个问题。这种方法比较适合共享少量数据的情况,而且这种共享只能在主调函数与被调函数之间进行。 第第5 5章章 程序结构程序结构 5.3.2 全局变量 全局变量具有文件作用域。在整个程序中,除了在定义有同名局部变量的块中之外,其它地方都可以直接访问全局变量。将数据存放在全局变量中,不同的函数在不同的地方对同一个全局变量进行访问,实现了
13、这些函数之间的数据共享。请看下面的程序: #include int n; void f( ) n=5;第第5 5章章 程序结构程序结构 void g( ) coutn=nendl; void main( ) f( ); g( ); 程序运行结果为 n=5 这样的共享方法,使用相当方便,但是副作用也不可低估。 第第5 5章章 程序结构程序结构【例5-3】 局部变量和全局变量例题。#includeint i=1; void main( )static int a;int b= -10;int c=0;void other(void);cout-main-endl;第第5 5章章 程序结构程序结构c
14、outi=i a=a b=b c=cendl;c=c+8;other( );cout-main-endl;couti=i a=a b=b c=cendl;other( );void other(void)static int a=1;第第5 5章章 程序结构程序结构static int b;int c=5;i=i+2;a=a+3;c=c+5;cout-other-endl;couti=i a=a b=b c=cendl;b=a;第第5 5章章 程序结构程序结构程序运行结果为-main-i=1 a=0 b= -10 c=0-other-i=3 a=4 b=0 c=10-main-i=3 a=0
15、b= -10 c=8-other-i=5 a=7 b=4 c=10第第5 5章章 程序结构程序结构【例5-4】 局部对象和全局对象例题。#includeclass Clock/时钟类声明private:int Hour,Minute,Second;public:Clock( )/构造函数第第5 5章章 程序结构程序结构Hour=0;Minute=0;Second=0;void SetTime(int NewH,int NewM,int NewS);/设置时间函数void ShowTime( );Clock( );void Clock:SetTime(int NewH,int NewM,int
16、NewS)第第5 5章章 程序结构程序结构 Hour=NewH;Minute=NewM;Second=NewS;void Clock:ShowTime( )coutHour:Minute:Secondendl;Clock globClock; /声明具有静态生存期、文件作用域的对象globClockvoid main( )第第5 5章章 程序结构程序结构 coutFirst time output:endl;globClock.ShowTime( );globClock.SetTime(10,30,45);Clock myclock(globClock);/声明具有块作用域的对象myclock
17、coutSecond time output:endl;myclock.ShowTime( );第第5 5章章 程序结构程序结构 程序运行结果为 First time output: 0:0:0 Second time output: 10:30:45 程序中包含了具有各种作用域的变量和对象。其时钟类声明中,函数成员SetTime的三个形参具有函数原型作用域,对象globClock具有文件作用域,对象myclock具有块作用域。在主函数中,这些变量、对象及其公有成员都是可见的。 第第5 5章章 程序结构程序结构5.4 静态成员静态成员 静态成员用于解决同一个类的不同对象之间的数据和函数的共享问
18、题。例如,我们可以抽象出学生的共性,设计如下的学生类: class Student private: int StudentNo; int ID;第第5 5章章 程序结构程序结构 char *name; /字符指针,将在第6章介绍 / ; 如果需要统计学生总数,这个数据存放在什么地方呢?若以类外的变量来存储总数,不能实现数据的隐藏。若在类中增加一个数据成员用以存放总数,必然在每一个对象中都存储一个副本,不仅冗余,而且每个对象分别维护一个“总数”,势必造成数据的不一致。因此,比较理想的方案是使类的所有对象共同拥有一个用于存放总数的数据成员,这就是下面要介绍的静态数据成员。 第第5 5章章 程序结
19、构程序结构 5.4.1 静态数据成员 类的普通数据成员在类的每一个对象中都拥有一个拷贝,也就是说,每个对象的同名数据成员可以分别存储不同的数值,这也是每个对象拥有自身特征的保证。而静态数据成员是类的数据成员的一种特例。 每个类只有一个静态数据成员拷贝,它由该类的所有对象共同维护和使用,从而实现了同一个类的不同对象之间的数据共享。静态数据成员具有静态生存期。在静态数据成员的声明和使用时,应注意以下几点: 第第5 5章章 程序结构程序结构 静态数据成员声明时,应在前面加static关键字来说明。例如:static int n 静态数据成员必须初始化,并且一定要在类外进行。其初始化的形式如下: :=
20、 例如: int point:n=0; 静态数据成员属于类,而不属于任何一个对象,所以,在类外只能通过类名对它进行引用。静态数据成员引用的一般形式如下: :;第第5 5章章 程序结构程序结构 静态数据成员同一般数据成员一样要服从访问控制限制。当静态数据成员被声明为私有成员时,只能在类内直接引用它,在类外无法引用。但当静态数据成员被声明为公有成员或保护成员时,可以在类外通过类名对它进行引用。下面,我们通过一个例题来说明静态数据成员的使用。第第5 5章章 程序结构程序结构【例5-5】 含有静态数据成员例题。#includeclass Testprivate:int k;public:static
21、int n;/静态数据成员Test(int kk)k=kk;n+;void Display( )第第5 5章章 程序结构程序结构coutn=n k=kendl;int Test:n=0;/静态数据成员初始化void main( )Test t1(10);t1.Display( );Test t2(20);t2.Display( );Test:n+;t2.Display( );第第5 5章章 程序结构程序结构 上面的例题中,类Test中声明n为公有静态数据成员,用来给Test类的对象计数,每声明一个新对象,n的值就相应加l。静态数据成员n的定义和初始化在类外进行。n的值在类的构造函数中被引用。t
22、1对象生成时,调用构造函数Test( ),n值由0变为1;当t2对象生成时,又调用构造函数Test( ),这次,n值由1变为2。两次调用均访问的是t1和t2共同维护的该静态成员的拷贝,这样实现了在t1和t2两个对象间数据共享。另外,在主函数中,通过类名对它进行引用,n值又一次被加1。第第5 5章章 程序结构程序结构例5-5的程序运行结果为n=1 k=10n=2 k=20n=3 k=20如果我们保持程序的其它部分不变,只将主函数中前四条语句改为Test t1(10),Test t2(20);t1.Display( );t2.Display( );读者可以想一想,程序运行结果又会怎样。第第5 5章
23、章 程序结构程序结构 5.4.2 静态函数成员 所谓静态函数成员,就是使用static关键字声明的函数成员。同静态数据成员一样,静态函数成员也属于整个类,由同一个类的所有对象共同维护,为这些对象所共享。静态函数成员可以直接引用该类的静态数据和函数成员,而不能直接引用非静态数据成员,如果要引用,必须通过参数传递的方式得到对象名,然后再通过对象名来引用。 作为成员函数,静态函数成员的访问属性可以受到类的严格控制。对于公有的静态函数成员,可以通过类名或对象名来调用;而对于一般的非静态函数成员,只能通过对象名来调用。 第第5 5章章 程序结构程序结构【例5-6】 使用静态函数成员例题。#include
24、class pointprivate:int x,y;static int countP;/静态数据成员public:point(int xx=0,int yy=0)/构造函数x=xx;y=yy;countP+;第第5 5章章 程序结构程序结构 point(point &p);/拷贝构造函数int get_x( ) return x;int get_y( ) return y;static void get_c( ) /静态函数成员coutObject id=countPendl;point:point(point &p)x=p.x;y=p.y;countP+;第第5 5章章 程序结构程序结构
25、int point:countP=0; /静态数据成员初始化void main( )point:get_c( ); /利用类名引用静态函数成员point a(4,5);coutpoint a,a.get_x( ),a.get_y( );a.get_c( );/利用对象名引用静态函数成员point b(a);第第5 5章章 程序结构程序结构 coutpoint b,b.get_x( ),b.get_y( ); point:get_c( );/利用类名引用静态函数成员 在这个例题中,类point的数据成员countP被声明为静态,用来给point类的对象计数,每声明一个新对象,countP的值就相
26、应加l。静态数据成员countP的定义和初始化在类外进行,countP的值是在类的构造函数中引用的。 第第5 5章章 程序结构程序结构 例5-6的程序运行结果为 Object id=0 point a,4,5 Object id=1 point b,4,5 Object id=2 如果上述程序中,采用非静态函数成员get_c( ),程序其余的部分保持不变,程序代码如下: #include class point private: int x,y;第第5 5章章 程序结构程序结构 static int countP;/静态数据成员 public:point(int xx=0,int yy=0)/
27、构造函数x=xx;y=yy;countP+;point(point &p);/拷贝构造函数int get_x( ) return x;int get_y( ) return y;void get_c( ) /非静态函数成员cout Object id=countPendl;第第5 5章章 程序结构程序结构;point:point(point &p)x=p.x;y=p.y;countP+;int point:countP=0;/静态数据成员初始化void main( )第第5 5章章 程序结构程序结构point a(4,5);coutpoint a,a.get_x( ),a.get_y( );a
28、.get_c( );/利用对象名引用非静态函数成员point b(a);coutpoint b,b.get_x( ),b.get_y( );b.get_c( );/利用对象名引用非静态函数成员 仔细比较这两个例子,在类的声明中,只是把原来的静态函数成员get_c( )改写为普通函数成员,其余的部分保持不变。 第第5 5章章 程序结构程序结构 这时要输出静态数据成员countP,只能通过point类的某个对象来调用函数get_c( )。上述程序的运行输出结果与例5-6的完全相同。相比而言,例5-6采用静态函数成员的好处是可以不依赖于任何对象,直接引用静态数据。 如果此时采用类名调用get_c(
29、)会怎么样呢?现在尝试将上述主函数改写如下: void main( ) point a(4,5); coutpoint a,a.get_x( ),a.get_y( );第第5 5章章 程序结构程序结构 a.get_c( );/利用对象名引用静态函数成员point b(a);coutpoint b,b.get_x( ),b.get_y( );point:get_c( );/出错,试图利用类名引用静态函数成员 结果编译时出错。由此可见,对普通函数成员的调用不能用类名,必须用对象名。前面这段程序说明,通过非静态函数成员同样可以输出静态数据成员,但在有些情况下是不行的。 第第5 5章章 程序结构程序结
30、构 例如,在所有对象声明之前,countP的初始值已经为0,这时想要输出这个初始值,只能通过静态函数成员。现在请看下面的程序段: #include class A private: int x; public: static void f(A a); ; void A:f(A a)第第5 5章章 程序结构程序结构 coutx;/对x的引用是错误的 couta.x;/正确 可以看到,通过静态函数成员引用非静态成员是相当麻烦的。一般情况下,静态函数成员主要用来引用全局变量或同一个类中的静态数据成员,特别是和后者一起使用时,可以达到对同一个类中对象之间的共享数据进行维护的目的。第第5 5章章 程序结
31、构程序结构5.5 友元友元 先来看一段程序。#includeclass Aprivate:int x;public:第第5 5章章 程序结构程序结构void display( )coutxendl;int get( )return x;class Bprivate:A a;public:void set(int);void display( );第第5 5章章 程序结构程序结构 这是类组合的情况,B类中内嵌了A类的对象,但是B的成员函数却无法直接访问A的私有成员x。从数据的安全角度来说,这无疑是最安全的,内嵌的部件相当于一个黑盒。但是使用起来多少有些不便。例如,按如下形式实现B的成员函数set
32、( )会引起编译错误: void B:set(int k) a.x=k;第第5 5章章 程序结构程序结构 由于A的对象内嵌于B中,能否让B的函数直接访问A的私有数据呢?这就需要寻 求一种使得类以外的对象或函数能够访问类中的私有成员的方法。C+为上述这种需求提供了语法支持,这就是友元。 友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。也就是说,通过友元的方式,一个普通函数或者类的成员函数可以访问到封装于某一个类中的数据。这相当于给类的封装挖了一个小孔,通过它,外界可以看到类内部的一些属性。 第第5 5章章 程序结构程序结构 5.5.1 友元函数 如果友元是普通
33、函数或类的成员函数,则称为友元函数。友元函数是在类声明中由关键字friend修饰的非成员函数。 普通函数声明为友元函数的形式如下: friend (参数表) 成员函数声明为友元函数的形式如下: friend :(参数表)第第5 5章章 程序结构程序结构 说明: 友元函数的声明可以在类声明中的任何位置,既可在public区,也可在protected区,意义完全一样。 友元函数的定义一般放在类的外部,最好与类的其它成员函数定义放在一起。如果是普通函数作为友元,也可以放在类中。 友元函数不是本类的成员函数,但是它可以通过对象名访问类的所有成员,包括私有和保护成员,我们先来看一个例子。第第5 5章章
34、程序结构程序结构 【例5-7】 普通函数作友元函数例题。 这是使用普通函数作友元函数计算两点距离的例题。这里声明了一个point类,两点的距离用普通函数distance来计算。这个函数需要访问point类的私有数据成员x和y,为此,将distance声明为point类的友元函数。程序代码如下: #include #include class point 第第5 5章章 程序结构程序结构private:double x,y;public:point(double xx=0,double yy=0) x=xx;y=yy;double get_x( ) return x;double get_y(
35、) return y;friend double distance(point &p1,point &p2);/普通函数作point的友元;double distance(point &p1,point &p2)第第5 5章章 程序结构程序结构return (sqrt(p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y);void main( )point myp1(1,1),myp2(4,5);coutThe distance is:distance(myp1,myp2)endl;第第5 5章章 程序结构程序结构 上述程序中, point类在声明友元函
36、数时,只给出友元函数原型,友元函数distance的定义是在类外进行的。在友元函数中通过对象名直接引用point类中的私有数据成员x和y。 程序运行结果为 The distance is:5 本例中的友元函数是一个普通函数。其实这个函数也可以是另外一个类的成员函数。有时候把成员函数作友元的情况称为友元成员。这种友元成员函数的使用和一般友元函数的使用基本相同,只是在使用该友元成员时要通过相应类的对象名来访问。 第第5 5章章 程序结构程序结构 【例5-8】 成员函数作友元函数例题。 现在使用成员函数作友元函数计算两点距离。我们只要将例5-7中相应的地方改为成员函数作友元就可以了。程序代码如下:
37、#include #include class point;/前向声明point类 class A public:double distance(point &p1,point &p2);第第5 5章章 程序结构程序结构/.;class pointprivate:double x,y;public:point(double xx=0,double yy=0) x=xx;y=yy;double get_x( ) return x;double get_y( ) return y;第第5 5章章 程序结构程序结构friend double A:distance(point &p1,point &p
38、2); /A类的成员函数作point的友元; double A:distance(point &p1,point &p2)return (sqrt(p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y);void main( )point myp1(1,1),myp2(4,5);第第5 5章章 程序结构程序结构 A obj;/必须声明一个A类的对象coutThedistance is:obj.distance(myp1,myp2)endl; 上述程序中,distance( )为A类的成员函数,因此,在声明友元函数时,必须在distance( )前加“A:”
39、,以便确认distance( )属于哪个类。另外,在主函数中一定要声明一个A类的对象。只有这样,才能通过对象名调用友元函数。这时,程序的运行结果与例5-7的完全相同。第第5 5章章 程序结构程序结构 5.5.2 友元类 如果友元是一个类,则称为友元类。友元类的声明形式如下: friend class 说明: 友元类的声明同样可以在类声明中的任何位置。 友元类的所有成员函数都成为友元函数。 例如,若A类为B类的友元类,则A类的所有成员函数都是B类的友元函数,都可以访问B类的私有和保护成员。第第5 5章章 程序结构程序结构现在我们用友元类的方法重做例5-8。程序代码如下:#include#incl
40、udeclass point;/前向声明point类class Apublic:double distance(point &p1,point &p2);/.;class point第第5 5章章 程序结构程序结构private:double x,y;public:point(double xx=0,double yy=0) x=xx;y=yy;double get_x( ) return x;double get_y( ) return y;friend class A;/A类为友元类;double A:distance(point &p1,point &p2)return (sqrt(p1
41、.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y);第第5 5章章 程序结构程序结构 void main( ) point myp1(1,1),myp2(4,5);A obj;/必须声明一个A类的对象coutThe distance is:obj.distance(myp1,myp2)endl; 从上面的程序可以看到,这里只需将例5-8中的friend double A:distance(point &p1,point &p2)语句改为friend double A,程序其它部分没有作任何变动。此时,程序运行结果与例5-7和例5-8的都完全相同。第第5 5
42、章章 程序结构程序结构 通过友元类声明,友元类的成员函数可以通过对象名直接访问到隐藏的数据,达到高效协调工作的目的。在较为复杂的程序中,实现不同类之间的数据共享,友元类的使用是很必要的。现在将本节开头部分的程序段修改成如下形式,以便实现B类的成员函数直接访问A类私有成员x的目的。 #include class A private:第第5 5章章 程序结构程序结构int x;public:void display( )coutxendl;int get( )return x;friend class B;class Bprivate:A a;public:第第5 5章章 程序结构程序结构void
43、 set(int);void display( );void B:set(int k)a.x=k;第第5 5章章 程序结构程序结构 关于友元,还有两点需要注意: 友元关系是不能传递的。B类是A类的友元,C类是B类的友元,C类和A类之间如果没有声明,就没有任何友元关系,不能进行数据共享。 友无关系是单向的。如果声明B类是A类的友元,B类的成员函数就可以访问A类的私有和保护数据,但A类的成员函数却不能访问B类的私有和保护数据。第第5 5章章 程序结构程序结构5.6 常类常类 型型 5.6.1 常引用 使用const关键字声明的引用称为常引用。常引用所引用的对象不能被更新。常引用的声明形式如下: c
44、onst & 现在来看下面的例题。第第5 5章章 程序结构程序结构【例5-9】 常引用例题。#includevoid main( )int n=5;const int &m;/声明m为常引用m=10;coutm=mendl;第第5 5章章 程序结构程序结构这段程序编译时有两个错误:error C2530: m : references must be initializederror C2166: l-value specifies const object将上述程序稍作修改,程序代码如下:#includevoid main( )int n=5;const int &m=n;/声明m为常引用c
45、outm=mendl;这时,程序运行结果为m=5第第5 5章章 程序结构程序结构【例5-10】 常引用作形参例题。#includevoid display(const double &r);/常引用作形参void main( )double d(6.5); display(d);void display(const double &r)coutr=rendl;第第5 5章章 程序结构程序结构 常引用做形参时,函数不能更新r所引用的对象,因此,对应的实参就不会被破坏。 程序运行结果为 r=6.5第第5 5章章 程序结构程序结构 5.6.2 常对象 使用const关键字声明的对象称为常对象。常对象
46、的声明形式如下: const 或 const 声明常对象的同时,也要进行初始化,而且该对象以后不能再被更新。现在来看下面的程序: class A private: int x,y;第第5 5章章 程序结构程序结构 public: A(int i=0,int j=0) x=i;y=j; /. ; const A cobj1; A const cobj2(3,4); /. 在上面的程序中,分别用两种形式声明了两个常对象cobj1和cobj2,并在声明的同时对它们进行了初始化。第第5 5章章 程序结构程序结构 那么语法如何保障常对象的值不被改变呢?改变对象的数据成员值有两个途径:一是在类外通过对象名
47、访问其公有数据成员,这时语法会限制不能再赋值;二是在类的成员函数中改变数据成员的值。然而几乎无法预料和统计哪些成员函数会改变数据成员的值。因此,语法只好规定不能通过常对象调用普通的成员函数。可是这样一来,常对象不是没有任何可用的对外接口了吗?其实办法是有的,在下一小节中,我们将介绍专门为常对象定义的常成员函数。第第5 5章章 程序结构程序结构 5.6.3 常成员函数 使用const关键字声明的函数称为常成员函数。常成员函数声明的形式如下: (参数表) const; 说明: const是加在函数声明后面的类型修饰符,它是函数类型的一个组成部分,因此,在实现部分也要带const关键字。第第5 5章
48、章 程序结构程序结构 const关键字可以被用于对重载函数的区分。例如,可以在类中这样声明: void fun( ); void fun( ) const; 常成员函数不能更新对象的数据成员,也不能调用该类中没有用const修饰的成员函数。 如果将一个对象说明为常对象,则通过该对象只能调用它的常成员函数,而不能调用其它成员函数。第第5 5章章 程序结构程序结构【例5-11】 常成员函数例题。#includeclass Aprivate:int x,y;public:A(int i=0,int j=0) x=i;y=j;void fun( )/成员函数第第5 5章章 程序结构程序结构cout成员
49、函数: x=x, y=yendl;void fun( ) const /常成员函数cout常成员函数:x=x, y=yendl;void main( )A obj1(1,2);/A类对象obj1.fun( );const A obj2(3,4);/A类常对象obj2.fun( );第第5 5章章 程序结构程序结构 在A类中说明了两个同名成员函数fun( ),其中一个是常成员函数。在主函数中说明了两个对象obj1和obj2,其中对象obj2是常对象。通过对象obi1调用的是一般的成员函数,而通过对象obj2调用的是用const修饰的常成员函数。 程序运行结果为 成员函数: x=1, y=2 常成
50、员函数:x=3, y=4第第5 5章章 程序结构程序结构 5.6.4 常数据成员 使用关键字const不仅可以说明成员函数,还可以说明数据成员。如果在一个类中说明了常数据成员(包括常引用、常对象等),由于常数据成员不能被更新,因此,在类中说明常数据成员时,只能用成员初始化列表的方式通过构造函数对该数据成员进行初始化。第第5 5章章 程序结构程序结构【例5-12】 常数据成员例题。#includeclass Aprivate:const int x;/常数据成员static const int y;/静态常数据成员public:const int &r; /常引用第第5 5章章 程序结构程序结构