资源描述
第第5章章 数据共享和成员特性数据共享和成员特性5.1 静态成员静态成员5.2 友元友元5.3 成员其他特性成员其他特性5.4 this指针指针5.5 综合应用实例:栈类静态操作综合应用实例:栈类静态操作5.1 静态成员静态成员使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共享。静态数据成员的值是可以修改的,但它对每个对象都是一样的。1.定义和使用定义和使用与静态变量相似,静态数据成员是静态存储(static)的,但定义一个静态数据成员与一般静态变量不一样,它必须按下列2步进行:(1)在类中使用关键字在类中使用关键字static声明静态数据成员声明静态数据成员。在类中声明静态数据成员,仅仅是说明了静态数据成员是类中的成员这个关系,即便用该类定义对象时,该静态数据成员也不会分配内存空间。因此可以说,类中声明的静态数据成员是一种形式上形式上的虚虚的数据成员。静态数据成员的实际定义是由下一步来完成。(2)在类外为静态数据成员分配内存空间并初始化在类外为静态数据成员分配内存空间并初始化。类中数据成员的内存空间是在对象定义时来分配的,但静态数据成员的内存空间是为所有该类对象所共享,只能分配一次,因而不能通过定义类对象的方式来分配,即其初值不通过构造函数来制定,必须在类的外部作实际定义实际定义才能为所有对象共享,其定义格式如下::=可见,在类外初始化的静态数据成员与全局变量初始化格式相似,只是须指明它所属的类。由于静态数据成员的静态属性static已在类中声明,因此在类外不可再指定static。例如:5.1.1 静态数据成员静态数据成员5.1.1 静态数据成员静态数据成员程序运行的结果如下:分析:(1)A中,由于使用了默认参数,因而使得默认构造函数和重载构造函数定义成一个构造函数。这种程序方法在实际应用时要小心使用。(2)程序中,类CSum中的私有数据成员nSum被声明成静态的,由于类中声明的nSum是虚虚的,因此它必须在类体外进行实际定义实际定义。若不指定初值,则默认为0。(3)main函数中,对象one初始化后,nSum值变为12。对象two由于调用的是(a=0,b=0)的默认构造函数,故nSum的值没有变化,仍然是12(注意构造函数体的语句“nSum+=a+b;”中的“+=”不是“=”)。因此,main函数中前面两条输出语句的结果都是输出12。当执行“two.setSum(5);”后,nSum值被设为5。由于nSum是所有对象所共享,也就是说,nSum是所有对象的公共成员,因此对象one中的nSum的值也是5。5.1.1 静态数据成员静态数据成员2.几点说明几点说明(1)由于静态数据成员在类中所作的声明仅仅是一种声明该成员是属于哪个类的,它是形式上的虚的成员,还必须在类的外部作实际定义才能为所有对象共享,正因为如此,静态数据成员静态数据成员的实际定义和初始化本身是不受不受public、private和和protected等访问属性的等访问属性的限制限制。(2)静态数据成员可看成是类中声明、类外定义的静态全局变量,因此它具有静态生存期,在程序中从实际定义时开始产生,到程序结束时消失。也就是说,静态数据成员的内存空间不会随对象的产生而分配,也不会随对象的消失而释放。当然,静态数据成员的内存空间同样不能在类的构造函数中创建或是在析构函数中释放。(3)静态数据成员是类中的成员,它的访问属性同普通数据成员一样,可以为public、private和protected。当静态数据成员为public时,则在类外对该成员的访问和引用可有2种方式,一是通过对象来引用,二是直接引用。当直接引用时,应使用下列格式::5.1.1 静态数据成员静态数据成员例如,有:class CSumpublic:static int nSum;/声明公有型静态数据成员;int CSum:nSum=0;/静态数据成员的实际定义和初始化则在main函数中可有下列引用:int main()CSum one;one.nSum=10;/通过对象来引用通过对象来引用CSum:nSum=12;/直接引用直接引用coutone.nSumendl;/输出12return 0;代码中,引用公有型静态数据成员nSum的2种方式都是合法的,也是等价的。5.1.2 静态成员函数静态成员函数静态成员函数和静态数据成员一样,它们都属于类的静态成员,但它们都不专属不专属于某个对象的成员,而是所有对象所共享的成员。因此,对于公有型(public)静态成员来说,除可用对象来引用外,还可通过“类名类名:成员成员”直接来引用。在类中,静态数据成员可以被成员函数引用,也可以被静态成员函数所引用。但反过来,静态成员函数却不能直接引用类中说明的非静态成员静态成员函数却不能直接引用类中说明的非静态成员。假如假如,静态成员函数可以引用了类中的非静态成员,例如:class CSumpublic:static void ChangeData(int data)nSum=data;/错误错误:引用类中的非静态成员public:int nSum;则当执行语句:CSum:ChangeData(5);/合法的静态成员引用5.1.2 静态成员函数静态成员函数时必然会出现编译错误,这是因为此时CSum类的任何对象都还没有创建,nSum数据成员根本就不存在。即使是创建了CSum类对象,此时这种形式的静态成员函数调用根本无法确定函数中所引用的nSum是属于哪个对象,因此静态成员函数只能引用静态数据静态成员函数只能引用静态数据成员成员,因为它们都是独立于对象实例之外而为对象所共享的成员。下面来看一个示例,它是用静态成员来实现数据插入、输出和排序操作。同普通成员函数,类中的静态成员函数也可在类中声明,而在类外实现。由于类CData中所有的成员都声明成了静态的,因此在main函数直接通过“类名:成员”的形式来引用其public成员,通过静态成员函数Add在静态数组成员data中设置并添加数组,每添加一次,静态指针成员pCur的指向就向下移动一次,使其指向下一个数组元素的内存空间。静态成员函数Print和Sort分别将data数组中的已有数组输出和排序。程序运行的结果如下:可见,若将相同类别的操作均用公有型静态成员函数来实现,则无需通过对象就可引用类中的成员,此时的类成了一种工工具具集集,这在强调程序算法的场合下得到了广泛应用。5.1.2 静态成员函数静态成员函数但是,静态成员函数可以把类的引用或指针作为形参以间接访问非静态成员。例如:例例Ex_VisitNonStatic 静态成员函数间接访问非静态成员静态成员函数间接访问非静态成员#include using namespace std;class CSum public:static int getsum(CSum&obj);int addval(int a=1,int b=1)/非静态成员函数return(BaseVal+a+b);private:static int BaseVal;int CSum:BaseVal=10;int CSum:getsum(CSum&obj)int nSum=0;nSum+=obj.addval();/静态成员函数getsum()使用引用obj来访问非静态成员函数addval()return nSum;int main()CSum a;coutCSum:getsum(a)endl;return 0;5.1.2 静态成员函数静态成员函数程序运行的结果如下:这种将引用或者指针作为静态成员函数形参传递的用法,与非静态成员函数里this指针(后面讨论)的功能有异曲同工之妙。需要强调的是:(1)静态成员中的“静态(static)”与普通静态变量和静态函数中的“静态”含义是不一样的。普通静态变量中的“静态”是使用静态存储内存空间,而类中的静态数据成员的“静态”是对象数据共享的声明,并非具有实际意义的静态存储内存空间。普通静态函数中的“静态”是表示本程序文件的内部函数,而类中的静态成员函数的“静态”表示该成员函数仅能访问静态数据成员,是为所有该类对象共享的声明方式。(2)类的静态数据成员的内存开辟和释放只能通过静态成员函数来实现,而不能通过类的构造函数和析构函数来完成。C+中也没有静态构造函数和静态析构函数。5.2 友元友元通常,类的私有型(private)数据成员和保护型(protected)数据成员只能在类中由该类的成员函数来访问,类的对象以及外部函数只能访问类的公有型(public)成员函数,类的私有和保护型数据成员只能通过类的成员函数来访问,如图5.1(a)所示。但是,如果在类中用friend关键字声明一个函数,且该函数的形参中还有该类的对象形参,这个函数便可通过形参对象或通过在函数体中定义该类对象来访问该类的任何私有和保护型数据成员,如图5.1(b)所示。这就好比一个人的私密可以让“密友”知道一样,用friend声明的这个函数就称为这个类的友元函数友元函数。除友元函数外,友元还可以是类,即一个类可以作另一个类的友元,称为友元类友元类。例如,当B类作为A类的友元时,这就意味着在B类中通过A类对象来访问A类中的所有成员。可见,采用友元机制,通过类对象类对象可以访问或引用类中的所有成员。这样,即使通过友元来修改数据成员时,修改的也仅仅是某个对象的数据成员,从而既保证了类的封装性,也为外部访问类的私有和保护型成员提供了方便。5.2.1 友元概述友元概述5.2.1 友元概述友元概述class CPointpublic:void Print();private:int xPos,yPos;f()CPoint X;外部函数外部函数外部函数外部函数不可访问不可访问不可访问不可访问(a)(a)无友元关系无友元关系无友元关系无友元关系class CPoint friend f();public:void Print();private:int xPos,yPos;f()CPoint X;指明友元关指明友元关指明友元关指明友元关系系系系(b)(b)有友元关系有友元关系有友元关系有友元关系图图5.1 外部函数和友元对类成员的访问关系外部函数和友元对类成员的访问关系 5.2.2 友元函数友元函数友元函数可分为友元外部函数友元外部函数和友元成员函数友元成员函数。当一个函数f是A类的友元时,若f还是另一个类B的成员函数时,则这样的友元称为友元成员函数友元成员函数;若f不属于任何类的成员,则这样的友元称为友元外部函数友元外部函数。友元外部函数友元外部函数常直接简称为友元函数友元函数。1.友元函数友元函数友元函数友元函数在类中定义的格式如下:friend (形参表形参表)从格式中可以看出,友元函数和类的成员函数定义格式基本一样,只是友元函数前面用一个关键字friend来修饰。由于友元函数与类的关系是一种“友好友好(friendship)”关系,因此友元函数不属于类中的成员函数友元函数不属于类中的成员函数,它是在类中声明的一个外部函数。需要说明的是:(1)友元函数的定义可在类中进行,也可将友元函数在类中声明,而将其实现在类外定义。但在类外定义时,不能像成员函数那样指明它所属的类。(2)由于友元函数是一个外部函数,因此它对类中的成员访问只能通过类对象类对象来进行,而不能直接去访问。这里的对象可以通过形参来指定,也可在友元函数中进行定义。(3)由于友元函数是类中声明的外部函数,因而它跟成员的访问权限private、protected和public没有任何关系,因此它的声明可以出现在类中的任何部分,包括在private和public部分。但为了程序的可读性,常将友元函数声明在类体的开头或是最后。5.2.2 友元函数友元函数(4)由于友元函数不是类的成员,因此它在调用时不能指定其所属的类,更不能通过对象来引用友元函数。(5)大多数外部函数对类中的数据操作是采用形参对象的方式,通过对象的“引用”传递,达到修改对象数据的目的。对于友元函数,也应该采用这种方式,只是友元函数还能修改对象的私有和保护型数据成员。下面来举一个例子,它是通过友元函数将一个点(CPoint)的位置发生偏移。5.2.2 友元函数友元函数 例例Ex_FriendFun 使用友元函数使用友元函数#include using namespace std;class CPointfriend CPoint Inflate(CPoint&pt,int nOffset);/声明一个友元函数public:CPoint(int x=0,int y=0)xPos=x;yPos=y;void Print()cout Point(xPos ,yPos )endl;private:int xPos,yPos;CPoint Inflate(CPoint&pt,int nOffset)/友元函数的定义 pt.xPos+=nOffset;/直接改变私有数据成员xPos和yPos pt.yPos+=nOffset;return pt;int main()CPoint pt(10,20);pt.Print();Inflate(pt,3);/直接调用友元函数pt.Print();return 0;5.2.2 友元函数友元函数在类CPoint中,Inflate是在类中声明,在类外定义的友元函数。它有两个形参:一是引用形参对象pt,二是int形参变量nOffset。由于在友元函数中,对象的所有数据成员可以直接访问,因此可直接将形参对象pt的私有数据成员直接加上nOffset指定的偏移量。由于Inflate指定的pt对象是引用传递,因此对pt内容的修改也就是对实参对象内容的修改。程序运行的结果如下:2.友元成员函数友元成员函数友元成员函数友元成员函数在类中定义的格式如下:friend :(形参表形参表)由于友元成员函数还是另一个类的成员函数,因此这里的类名类名是指它作为成员所在的类名。同成员函数一样,友元成员函数的定义既可在类中进行,也可将友元成员函数在类中声明,而将其实现在类外定义。但在类外定义时,应像成员函数那样指明它所属的类。例如:5.2.2 友元函数友元函数在类CRect中声明了一个友元函数Inflate,由于它还是类CPoint的成员函数,因此Inflate既可以直接访问CPoint的所有成员,也可以通过CRect类对象访问类CRect中的所有成员。由于在类CPoint中的Inflate函数的形参含有CRect对象,而此时CRect类还没有定义,因此需要在类CPoint前先作CRect类的声明,以便后面能使用CRect数据类型。程序运行的结果如下:下面来看一个例子。在类CPoint的定义中,类COther被声明成CPoint的友元类。这样,在类COther中,可通过CPoint对象pt访问类CPoint的所有成员。程序运行的结果如下:5.2.3 友元类友元类总之:(1)友元关系反映了程序中类与类之间、外部函数和类之间、成员函数和另一个类等之间的关系,这个关系是单向单向的,即当在CPoint中声明COther是CPoint的友元类时,只能在COther类中通过CPoint对象访问CPoint类的所有成员,而在CPoint类中是无法访问COther类的私有和保护型成员。(2)一个类中的友元并非是该类的成员,由于“friend”关系,因而友元只能通过对象来访问声明友元所在类的成员。而静态成员是类的一个成员,它本身具有不同的访问属性,只是对于公有静态成员来说,它可以有对象访问和“类名:静态成员”2种等价的访问方式。(3)与友元函数相比,静态成员函数只是修改类的静态数据成员,而对于友元来说,由于通过对象可以修改声明友元所在类的所有数据成员,因而友元函数比静态成员函数更加危险,而且友元类使这种危险更加扩大。因此,在类程序设计中,静态成员和友元一定要慎用慎用!5.3 成员其他特性成员其他特性关键字const不仅可以修饰数据成员,而且可以修饰成员函数。1.常数据成员常数据成员在类中使用const关键字进行声明的数据成员,称为常数据成员常数据成员,其声明格式如下:数据类型数据类型 const 数据成员名数据成员名;const数据类型数据类型 数据成员名数据成员名;上述两种格式是等价的,都是用来声明const数据成员。const除了修饰一般数据成员外,还可修饰引用数据成员和静态数据成员,分别称为常引用数据成员常引用数据成员和常静态数据成员常静态数据成员。同const变量一样,这些常数据成员的值都是不能被更新的,且必须进行初始化。对于一般常数据成员和常引用数据成员来说,其初始化只能通过构造函数中的成员初始化列表的方式来进行。而对于静态常数据成员来说,则须在类外进行定义并初始化,初始化的方式和一般静态数据成员相同。例如:5.3.1 const成员成员5.3.1 const成员成员例例Ex_ConstData 常数据成员的使用常数据成员的使用#include using namespace std;class COnepublic:COne(int a):x(a),r(x)/常数据成员的初始化 void Print();public:const int&r;/常引用数据成员private:const int x;/常数据成员static const int y;/静态常数据成员;const int COne:y=10;/静态常数据成员的初始化void COne:Print()coutx=x,y=y,r=rendl;int main()COne one(100);one.Print();COne two(200);two.Print();return 0;5.3.1 const成员成员程序中,类COne声明了3个常数据成员,分别是一般常数据成员x、常静态数据成员y和常引用数据成员r。要注意这些常数据成员的初始化方式,常静态数据成员y是在类外进行实际定义,其值设为10,而常数据成员x和常引用数据成员r通过成员初始化列表的方式来进行,r的初值是引用a的内存空间,但它们的值都取决于构造函数的形参a。程序的运行结果如下:需要说明的是,常数据成员和常引用数据成员都是类中的数据成员,它们与其他成员一样,也有private、public和protected访问属性。2.常成员函数常成员函数使用const关键字进行声明的成员函数,称为常成员函数常成员函数,其声明格式如下:()const;说明:(1)常成员函数声明格式中,const是加在函数说明后面的类型修饰符,它是函数类型的一个组成部分,因此,在函数实现部分也要带const关键字。5.3.1 const成员成员(2)由于常成员函数是类中的成员,因此原则上在该函数中可以访问类中的所有成员,但由于有了const限制,因而常成员函数不能修改任何数据成员不能修改任何数据成员,并且由于普通成员函数往往会更新数据,因此在常成员函数中不能调用非不能调用非const成员函数成员函数。(3)只有常成员函数才有资格被常对象调用只有常成员函数才有资格被常对象调用,由于常对象不能调用其他成员函数,因此常成员函数是常对象唯一的对外接口方式。(4)const关键字可以作为重载函数的区分关键字可以作为重载函数的区分,例如:void print();void print()const;是合法的重载函数。5.3.1 const成员成员 例例Ex_ConstFunc 常成员函数的使用常成员函数的使用#include using namespace std;class COnepublic:COne(int a=0,int b=0)x=a;y=b;void print();void print()const;/声明常成员函数private:int x,y;void COne:print()coutx,yendl;void COne:print()const cout使用常成员函数:x,yendl;int main()COne one(5,4);one.print();const COne two(20,52);two.print();return 0;5.3.1 const成员成员程序中,类COne声明了两个重载成员函数,一个带const,一个不带。由于one是一般对象,因此语句“one.print();”调用的是一般成员函数“void print();”,而two是常对象,因此“two.print();”调用的是常成员函数“void print()const;”。程序运行的结果如下:5.3.2 mutable成员成员关键字mutable是“易变的”意思,它与const关键字相对。前面已说过,在一个常成员函数中是不能对数据成员进行更改的。但有些数据成员本身是不需要保护的,且又需要类来封装时,mutable关键字就起了很大的作用。一个用mutable来修饰的数据成员永远处于“可变”的状态,即使在常成员函数中,也可以被修改,例如:5.3.2 mutable成员成员 例例Ex_Mutable Mutable数据成员的使用数据成员的使用#include using namespace std;class COnepublic:COne(int a=0,int b=0)x=a;y=b;void print();void print()const;/声明常成员函数private:mutable int x,y;/mutable修饰的数据成员;void COne:print()coutx,yendl;void COne:print()constx+;y+;/修改数据成员 cout使用常成员函数:x,yendl;int main()COne one(5,4);one.print();const COne two(20,52);two.print();return 0;5.3.2 mutable成员成员程序运行的结果如下:需要说明的是:在ANSI/ISO C+中,mutable仅用作类中的数据成员的存储类型。因此,它不能修饰成员函数,也不能修饰类外的普通变量。当mutable修饰数据成员时,它可被常成员函数修改。同样,也可由常对象修改。如:class Apublic:mutable int x;int y;const A a;/常对象a.x=10;/合法a.y=10;/错误错误,成员成员y不是不是mutable存储类型存储类型mutable还可以修饰const、static数据成员,但不能修饰引用成员,并且当mutable修饰const成员时,const应紧随mutable之后,例如:class X mutable const int*p;/好mutable int*const q;/不好不好;5.3.3 explicit成员成员关键字explicit是“显式的”意思,它用于修饰类的构造函数。在讨论explicit用法之前,先来看看类CData的代码。class CDatapublic:CData(int data=0)m_nData=data;CData()private:intm_nData;当用类CData定义对象并初始化时,可以有下列两种等价形式:CData one(10);/A:显式调用构造函数CData two=10;/B:隐式调用构造函数5.3.3 explicit成员成员对于B方式来说,当“two=10;”时它实际上是隐式调用构造函数“CData(int data)”,从而使two对象的数据成员m_nData等于10。但这种方式有时会引起误解,例如:class CDatapublic:CData(int data=0)m_nData=data;CData(const char*str)coutstrendl;CData()private:intm_nData;此时,若有CData a=a;/隐式调用构造函数5.3.3 explicit成员成员则很容易让人误解为是将对象a中的数据成员m_nData的值等于a,会认为因为类型不一致而导致错误产生。但实际上这种形式的初始化是合法的,由于它是调用构造函数“CData(const char*str)”,从而运行结果是输出字符串“a”。但这恐怕并非是程序程序设计所要的结果,因此为了避免对象初始化采用隐式调用构造函数的方式,在相应的构造函数前面加上关键字explicit,即:class CDataexplicit CData(const char*str)coutstrendl;这样,当CData a=a;/不合法,编译错误不合法,编译错误就会出现编译错误。但显示调用构造函数的方式仍是合法的,即:CData b(a);/合法合法5.4 this指针指针在深入讨论this指针之前,先来看一看类中成员函数的效率问题。1.引类引类CPoint代码代码若有一个CPoint类,其代码如下:类CPoint用来描述一个平面上的点的信息。其中,定义的4个成员函数Add1Add4的作用都是用来实现两个2个点的加法操作,只不过它们的形参个数和返回类型不同而已。2.成员函数的效率分析成员函数的效率分析(1)成员函数Add1的原型如下:CPoint Add1(CPoint one,CPoint two);对于该函数的使用,可有下列代码:CPoint pt1(10,20),pt2(30,40);/语句ACPoint pt=pt.Add1(pt1,pt2);/语句Bpt.Print();/输出Point(40,60)可见,为了在类外调用Add1函数,定义了3个对象pt1、pt2和pt,这样语句A和B共执行了3次CPoint构造函数,且语句B还执行3次CPoint默认拷贝构造函数:pt1和pt2对象传递及函数Add1返回对象赋给pt。5.4.1 成员函数的效率成员函数的效率5.4.1 成员函数的效率成员函数的效率(2)成员函数Add2的原型如下:void Add2(CPoint one,CPoint two);对于该函数的使用,可有下列代码:CPoint pt1(10,20),pt2(30,40),pt;/语句Apt.Add2(pt1,pt2);/语句Bpt.Print();/输出Point(40,60)同样,为了在类外调用Add2函数,定义了3个对象pt1、pt2和pt,这样语句A执行了3次CPoint构造函数,而语句B只执行2次CPoint默认拷贝构造函数:pt1和pt2对象传递。这样,Add2函数的使用比Add1函数的使用少执行了少执行了1次次CPoint默认拷贝构造函数,效率提高了一些。(3)成员函数Add3的原型如下:CPoint Add3(CPoint two);对于该函数的使用,可有下列代码:CPoint pt(10,20),pt2(30,40);/语句Apt=pt.Add3(pt2);/语句Bpt.Print();/输出Point(40,60)5.4.1 成员函数的效率成员函数的效率此时,为了在类外调用Add3函数,定义了2个对象pt和pt2,这样程序运行时执行了2次CPoint构造函数和2次CPoint默认拷贝构造函数。因此Add3函数的使用比Add2函数的使用又少执行了又少执行了1次次CPoint构造函数,效率提高更多了一些。(4)成员函数Add4的原型如下:void Add4(CPoint two);对于该函数的使用,可有下列代码:CPoint pt(10,20),pt2(30,40);/语句Apt.Add4(pt2);/语句Bpt.Print();/输出Point(40,60)这时,为了在类外调用Add4函数,定义了2个对象pt和pt2,这样程序运行时执行了2次CPoint构造函数和1次CPoint默认拷贝构造函数。因此Add4函数的使用比Add3函数的使用又少执行了又少执行了1次次CPoint默认拷贝构造函数,效率最高。5.4.1 成员函数的效率成员函数的效率上述执行分析的结果可用表5.1来表示。表表5.1 成员函数的效率分析成员函数的效率分析成员函数成员函数调用调用需对象的需对象的个数个数调用默认构造函数的调用默认构造函数的次数次数调用默认拷贝构造函调用默认拷贝构造函数的次数数的次数Add1333Add2332Add3222Add4221从表中可以看出:(1)Add1函数的使用效率最低,而Add4函数的使用效率最高。可见,成员函数定义的不同往往直接影响程序的执行效率,因此成员函数的参数和返回值除非得已,应尽可能不使用对象。1.结果分析结果分析5.4.1 成员函数的效率成员函数的效率(2)考察Add4函数,它的形参只有1个CPoint 对象,那么又怎么能够实现2个对象的加法操作呢?这就是C+类中的this指针在起作用。每当一个对象调用成员函数时,编译先将对象的地址赋给this指针,然后调用成员函数。这样,当有下列成员函数调用时:pt.Add4(pt2);它实际上被解释成:Add4(&pt,pt2);只不过,&pt参数被隐藏了。这样,当执行Add4函数下列代码时:void Add4(CPoint two)xPos+=two.xPos;yPos+=two.yPos;就是执行这样的代码:void Add4(CPoint*this,CPoint two)(*this).xPos+=two.xPos;(*this).yPos+=two.yPos;5.4.1 成员函数的效率成员函数的效率因此,由this指针解释的“Add4(&pt,pt2);”实际执行过程如图5.2所示(图中的数字表示执行的次序)。pt.Add4(pt2);Add4(&pt,pt2);void Add4(CPoint*this,CPoint two)(*this).xPos+=two.xPos;(*this).yPos+=two.yPos;tempCPoint(pt2)pt.xPos+=temp.xPos;pt.yPos+=temp.yPos;(1)(2)(3)(4)(5)(6)(7)图图5.2 pt.Add4(pt2)内部执行过程内部执行过程5.4.2 this指针的实质指针的实质1.this指针的指向指针的指向从前面的分析可知,当类实例化时,即用类定义对象时,则this指针的指向总是对象本身。但在类声明时,this指针的指向应是类本身,所以可以在该类成员函数中通过this指针来访问类中的所有成员。简单地说,当类实例化时,当类实例化时,this指针的指向对象自己,而在类的声明时指向类本身指针的指向对象自己,而在类的声明时指向类本身。打个比方,this指针就好比你自己一样,当你在屋子里面(类的声明)时,你只知道“房子”这个概念(类名),而不知道房子是什么样子,但你可以看到里面的一切(可以通过this指针引用所有成员),所谓“不识庐山真面目,只缘身在此山中”,而当你走出屋子外(类的实例),你看到的是一栋具体的房子(this指针指向类的实例)。下面来看一个示例,它是通过this指针用另一个对象直接给对象赋值。5.4.2 this指针的实质指针的实质例例Ex_This 使用使用this指针指针#include using namespace std;class CPointpublic:CPoint(int x=0,int y=0)xPos=x;yPos=y;public:void Copy(CPoint one)*this=one;/直接通过this赋值void Print()coutPoint(xPos,yPos)x=x;this-y=y;void Offset(int x,int y)(*this).x+=x;(*this).y+=y;void Print()constcoutPoint(x,y)endl;private:int x,y;5.4.2 this指针的实质指针的实质代码中,类CPoint中的私有数据成员x、y和构造函数、Offset成员函数的形参同名,正是因为成员函数体中使用了this指针,从而使函数中的赋值语句合法有效,且含义明确。否则,如果没有this指针,则构造函数中的赋值语句就变为了“x=x;y=y;”,显然是不合法的。需要说明的是,对于静态成员函数来说,由于它是为所有对象所共享,因此在静态成员函数中使用this指针将无法确定this的具体指向。所以,在静态成员函数中是不能使用在静态成员函数中是不能使用this指针的指针的。2.this指针的类型指针的类型尽管this指针的类型是一个常指针类型常指针类型,但由于它是隐藏在成员函数中的一个指针,因此在不同的成员函数中,其类型还有一些微小区别。在普通成员函数中,如上述CPoint类的构造函数和Offset成员函数,它们所隐藏的this指针的类型是下列格式的默认定义:*const this=&类名类名;这表明此时的this指针的指向不可更改,但其所指向的内存空间的内容可以更改,即“*this”在类成员函数中可以作左值。因此上述类CPoint中的构造函数和Offset成员函数的this指针访问数据成员的格式都是合法的。但在类常成员函数中,如上述类CPoint中的Print()是一个常成员函数,它所隐藏的this指针的类型是下列格式的默认定义:const *const this=&类名类名;这表明此时的this指针的指向和内存空间的内容都不能被更新。因此,在函数不能将“this-x”和“this-y”作为左值。5.5 综合应用实例:栈类静态操作综合应用实例:栈类静态操作1.CPerson和和CScore类的设计类的设计由于要设计的CPerson类是将CScore类作为自己的友元类,因此CPerson类的私有数据成员的操作不必再在CPerson类进行指定,且也不必重新指定构造函数和拷贝构造函数,因为它们的默认版本已能满足需求。定义的CPerson和CScore类代码如下:2.CStack类的设计类的设计在CStack类的设计中,除了可以像上一章实例一样使用普通成员函数来操作学生成绩数据外,还可以使用静态成员来进行。但由于C+没有静态构造函数和静态析构函数,因此CStack类的栈内存(静态指针成员)的开辟和释放需要重新指定静态成员函数来实现。CStack类的声明代码如下:class CStackpublic:static void Create(int nSize=10);/创建函数static void Release();/释放函数public:/操作static voidPush(CScore a);/压入static CScorePop(void);/弹出private:static CScore*sp;static CScore*buffer;static intm_nSize;5.5 综合应用实例:栈类静态操作综合应用实例:栈类静态操作根据上述分析和设计思想,可有下列程序代码:3.具体程序具体程序程序运行的结果为:
展开阅读全文