2.2 数据共享和成员特性
类的重要特性是使数据封装与隐藏,但同时也给数据共享及外部访问带来了不便。为此, C++提供了静态成员和友元机制来解决这些问题,但同时也要注意它们的副作用。除静态(static)成员之外,C++的成员还可以用const等来修饰,且成员函数中还隐藏一个特殊的this指针。
2.2.1 静态成员
以往实现数据共享的做法是设置全局变量或全局对象,但全局变量或全局对象是有许多局限性的:一来严重破坏类的封装性,二来全局变量或全局对象的滥用会导致程序的混乱,一旦程序变大,维护量就急剧上升。
静态成员能较好地解决上述问题,首先静态成员是类中的成员,是类的一部分,在类外不可访问,从而起到保护作用。其次,静态成员有静态数据成员和静态成员函数之分。静态数据成员与静态变量相似,具有静态生存期,是在类中声明的全局数据成员,能被同一个类的所有对象所共享。而公有静态成员函数不仅可以通过类对象来访问,还可通过“类名::静态成员函数”的形式在程序中直接调用。
1.静态数据成员
使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共享。静态数据成员的值是可以修改的,但它对每个对象都是一样的。
与静态变量相似,静态数据成员是静态存储(static)的,但定义一个静态数据成员与一般静态变量不一样,它必须按下列两步进行:
(1)在类中使用关键字static声明静态数据成员。在类中声明静态数据成员,仅仅是说明了静态数据成员是类中的成员这个关系,即便用该类定义对象时,该静态数据成员也不会分配内存空间。因此可以说,类中声明的静态数据成员是一种形式上的虚的数据成员。静态数据成员的实际定义是由下一步来完成的。
(2)在类外为静态数据成员分配内存空间并初始化。类中数据成员的内存空间是在对象定义时来分配的,但静态数据成员的内存空间为所有该类对象所共享,只能分配一次,因而不能通过定义类对象的方式来分配,必须在类的外部作实际定义才能为所有对象共享,其定义格式如下:
<数据类型><类名>::<静态数据成员名>=<值>
可见,在类外初始化的静态数据成员与全局变量初始化格式相似,只是必须指明它所属的类。由于静态数据成员的静态属性static已在类中声明,因此在类外不可再指定static。
【例Ex_StaticData】 静态数据成员的使用
#include <iostream.h> class CSum { public: CSum(int a=0,int b=0) //A { nSum+=a+b; } int getSum() { return nSum; } void setSum(int sum) { nSum=sum; } private: static int nSum; // 声明静态数据成员 }; int CSum::nSum=0; // 静态数据成员的实际定义和初始化 int main() { CSum one(10, 2), two; cout<<"one: sum = "<<one.getSum()<<endl; cout<<"two: sum = "<<two.getSum()<<endl; two.setSum(5); cout<<"one: sum = "<<one.getSum()<<endl; cout<<"two: sum = "<<two.getSum()<<endl; return 0; }
分析:
(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。
程序运行结果如下:
one: sum = 12
two: sum = 12
one: sum = 5
two: sum = 5
需要说明的是:
(1)由于静态数据成员在类中所作的声明仅仅声明该成员是属于哪个类的,它是形式上的虚的成员,还必须在类的外部作实际定义才能被所有对象共享,正因为如此,静态数据成员的实际定义和初始化本身不受public、private和protected等访问属性的限制。
(2)静态数据成员可看成类中声明、类外定义的静态全局变量,因此它具有静态生存期,在程序中从实际定义时开始产生,到程序结束时消失。也就是说,静态数据成员的内存空间不会随对象的产生而分配,也不会随对象的消失而释放。当然,静态数据成员的内存空间同样不能在类的构造函数中创建或是在析构函数中释放。
(3)静态数据成员是类中的成员,它的访问属性同普通数据成员一样,可以为public、private和protected。当静态数据成员为public时,则在类外对该成员的访问和引用可有两种方式,一是通过对象来引用,二是直接引用。当直接引用时,应使用下列格式:
<类名>::<静态成员名>
例如,有:
class CSum { //… public: static int nSum; // 声明公有型静态数据成员 }; int CSum::nSum=0; // 静态数据成员的实际定义和初始化
则在main函数中可有下列引用:
int main() { CSum one; one.nSum=10; // 通过对象来引用 //… CSum::nSum=12; // 直接引用 cout<<one.nSum<<endl; // 输出12 return 0; }
代码中,引用公有型静态数据成员nSum的两种方式都是合法的,也是等价的。
2.静态成员函数
静态成员函数和静态数据成员一样,它们都属于类的静态成员,但它们都不专属于某个对象的成员,而是所有对象所共享的成员。因此,对于公有型(public)静态成员来说,除可用对象来引用外,还可通过“类名::成员”直接来引用。
在类中,静态数据成员可以被成员函数引用,也可以被静态成员函数所引用。但反过来,静态成员函数却不能直接引用类中说明的非静态成员。假如静态成员函数可以引用类中的非静态成员,例如:
class CSum { public: static void ChangeData(int data) { nSum=data; // 错误:引用类中的非静态成员 } public: int nSum; };则当执行语句:
CSum::ChangeData(5); // 合法的静态成员引用
时必然会出现编译错误,这是因为此时CSum类的任何对象都还没有创建,nSum数据成员根本就不存在。即使是创建了CSum类对象,此时这种形式的静态成员函数调用根本无法确定函数中所引用的nSum是属于哪个对象的,因此静态成员函数只能引用静态数据成员,因为它们都是独立于对象实例之外而为对象所共享的成员。
下面来看一个示例,它用静态成员来实现数据插入、输出和排序操作。
【例Ex_StaticMember】 静态成员的使用
#include <iostream.h> class CData { public: static void Add(int a) // 添加数据a { if ( pCur >= data + 20 ) cout<<"内存空间不足,无法添加!"<<endl; else { *pCur=a; pCur++; } } static void Print(void); static void Sort(void); private: static int data[20]; // 声明静态内存空间 static int*pCur; // 声明静态指针成员 }; int CData::data[20]; // 实际定义,默认的初值为0 int*CData::pCur=data; // 实际定义,设初值为data数组的首地址 void CData::Print(void) // 类外定义的静态成员函数 { for (int i=0; i<(pCur-data); i++) cout<<data[i]<<", "; cout<<endl; } void CData::Sort(void) // 类外定义的静态成员函数 { int n = pCur - data; for (int i=0; i< n -1; i++) for (int j=i+1; j<n; j++) if ( data[i] > data[j] ) { int temp=data[i]; data[i]=data[j]; data[j]=temp; } } int main() { CData::Add(20); CData::Add(40); CData::Add(-50); CData::Add(7); CData::Add(13); CData::Print(); CData::Sort(); CData::Print(); return 0; }
同普通成员函数,类中的静态成员函数也可在类中声明,而在类外实现。由于类CData中所有的成员都声明成了静态的,因此main函数直接通过“类名::成员”的形式来引用其public成员,通过静态成员函数Add在静态数组成员data中设置并添加数组,每添加一次,静态指针成员pCur的指向就向下移动一次,使其指向下一个数组元素的内存空间。静态成员函数Print和Sort分别将data数组中的已有数组输出和排序。
程序运行结果如下:
20, 40, -50, 7, 13,
-50, 7, 13, 20, 40,
可见,若将相同类别的操作均用公有型静态成员函数来实现,则无须通过对象就可引用类中的成员,此时的类成了一种工具集,这在强调程序算法的场合下得到了广泛应用。
需要强调的是:
(1)静态成员中的“静态(static)”与普通静态变量和静态函数中“静态”的含义是不一样的。普通静态变量中的“静态”是使用静态存储内存空间,而类中的静态数据成员的“静态”是对象数据共享的声明,并非具有实际意义的静态存储内存空间。普通静态函数中的“静态”是表示本程序文件的内部函数,而类中的静态成员函数的“静态”表示该成员函数仅能访问静态数据成员,是为所有该类对象共享的声明方式。
(2)类的静态数据成员的内存开辟和释放只能通过静态成员函数来实现,而不能通过类的构造函数和析构函数来完成。C++中也没有静态构造函数和静态析构函数。
静态成员为对象的数据和操作共享提供了方便,但同时也带来了副作用。例如,若在一个对象中,它的某个操作修改了某个静态数据成员,然而这一修改造成了所有该类对象的该静态数据成员进行了更新,便会给程序带来许多潜在的危险,并且不同对象的多次修改还会使静态数据成员的值被任意更新,从而导致程序的混乱,类的封装性也被严重破坏。
那么有没有办法允许在类外只对某个对象的数据成员进行操作呢?于是,友元(friend)的机制便产生了,并且友元还能访问类中private和protected成员。
1.友元概述
通常,类的私有型(private)数据成员和保护型(protected)数据成员只能在类中由该类的成员函数来访问,类的对象及外部函数只能访问类的公有型(public)成员函数,类的私有和保护型数据成员只能通过类的成员函数来访问,如图2.1(a)所示。但是,如果在类中用friend关键字声明一个函数,且该函数的形参中还有该类的对象形参,这个函数便可通过形参对象或通过在函数体中定义该类对象来访问该类的任何私有和保护型数据成员,如图2.1(b)所示。这就好比一个人的秘密可以让“密友”知道一样,用friend声明的这个函数就称为这个类的友元函数。
2.2.2 友元
除友元函数外,友元还可以是类,即一个类可以作为另一个类的友元,称为友元类。例如,当B类作为A类的友元时,就意味着要在B类中通过A类对象来访问A类中的所有成员。
可见,采用友元机制,通过类对象可以访问或引用类中的所有成员。这样,即使通过友元来修改数据成员,修改的也仅仅是某个对象的数据成员,从而既保证了类的封装性,也为外部访问类的私有和保护型成员提供方便。
图2.1 外部函数和友元对类成员的访问关系
2.友元函数
友元函数可分为友元外部函数和友元成员函数。当一个函数f是A类的友元,且f还是另一个类B的成员函数时,则这样的友元称为友元成员函数;若f不属于任何类的成员,则这样的友元称为友元外部函数。通常将友元外部函数直接简称为友元函数。
友元函数在类中定义的格式如下:
friend <函数类型> <函数名>(形参表) {…}
从格式中可以看出,友元函数和类的成员函数定义格式基本一样,只是友元函数前面用一个关键字friend来修饰。由于友元函数与类的关系是一种“友好(friendship)”关系,因此友元函数不属于类中的成员函数,它是在类中声明的一个外部函数。需要说明的是:
(1)友元函数的定义可在类中进行,也可将友元函数在类中声明,而将其实现在类外定义。但在类外定义时,不能像成员函数那样指明它所属的类。
(2)由于友元函数是一个外部函数,因此它对类中的成员访问只能通过类对象来进行,而不能直接去访问。这里的对象可以通过形参来指定,也可在友元函数中进行定义。
(3)由于友元函数是类中声明的外部函数,因而它跟成员的访问权限private、protected和public没有任何关系,因此它的声明可以出现在类中的任何部分,包括在private和public部分。但为了增强程序的可读性,常将友元函数声明在类体的开头或最后。
(4)由于友元函数不是类的成员,因此它在调用时不能指定其所属的类,更不能通过对象来引用友元函数。
(5)大多数外部函数对类中的数据操作是采用形参对象的方式,通过对象的“引用”传递,达到修改对象数据的目的。对于友元函数,也应该采用这种方式,只是友元函数还能修改对象的私有和保护型数据成员。
下面来举一个例子,它是通过友元函数将一个点(Cpoint)的位置发生偏移。
【例Ex_FriendFun】 使用友元函数
#include <iostream.h> class CPoint { friend 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; }
在类CPoint中,Inflate是在类中声明、类外定义的友元函数。它有两个形参:一是引用形参对象pt,二是int形参变量nOffset。在友元函数中,由于对象的所有数据成员可以直接访问,因此可直接将形参对象pt的私有数据成员直接加上nOffset指定的偏移量。由于Inflate指定的pt对象是引用传递,因此对pt内容的修改也就是对实参对象内容的修改。
程序运行结果如下:
Point(10, 20)
Point(13, 23)
3.友元成员函数
友元成员函数在类中定义的格式如下:
friend <函数类型> <类名>::<函数名>(形参表) {…}
由于友元成员函数还是另一个类的成员函数,因此这里的类名是指它作为成员所在的类名。与成员函数一样,友元成员函数的定义既可在类中进行,也可将友元成员函数在类中声明,而将其实现在类外定义。但当在类外定义时,应像成员函数那样指明它所属的类。
【例Ex_FriendMemFun】 使用友元成员函数
#include <iostream.h> class CRect; // 声明类名CRect,以便可以被后面引用 class CPoint { public: void Inflate(CRect&rc,int nOffset); // 成员函数 CPoint( int x = 0, int y = 0 ) { xPos=x; yPos=y; } void Print() { cout<<"Point("<<xPos<<", "<<yPos<<")"<<endl; } private: int xPos, yPos; }; class CRect { friend void CPoint::Inflate(CRect &rc, int nOffset); public: CRect(int x1=0, int y1=0, int x2=0, int y2=0) { xLeft=x1; xRight=x2; yTop=y1; yBottom=y2; } void Print() { cout<<"Rect("<<xLeft<<", "<<yTop<<", "<<xRight<<", "<<yBottom<<")"<< endl; } private: int xLeft, yTop, xRight, yBottom; }; void CPoint::Inflate(CRect&rc,int nOffset) // 友元函数的定义 { xPos+=nOffset; yPos+=nOffset; // 直接改变自己类中的私有数据成员 // 访问CRect类的私有成员 rc.xLeft+=nOffset; rc.xRight+=nOffset; rc.yTop+=nOffset; rc.yBottom+=nOffset; } int main() { CPoint pt( 10, 20 ); CRect rc( 0, 0, 100, 80 ); pt.Print(); rc.Print(); pt.Inflate(rc,3); // 调用友元函数 pt.Print(); rc.Print(); return 0; }
在类CRect中声明了一个友元函数Inflate,由于它还是类CPoint的成员函数,因此Inflate既可以直接访问CPoint的所有成员,也可以通过CRect类对象访问类CRect中的所有成员。由于在类CPoint中的Inflate函数的形参含有CRect对象,而此时CRect类还没有定义,因此需要在类CPoint前先作CRect类的声明,以便后面能使用CRect数据类型。
程序运行结果如下:
Point(10, 20)
Rect(0, 0, 100, 80)
Point(13, 23)
Rect(3, 3, 103, 83)
4.友元类
除一个类的成员函数可以声明成另一个类的友元外,也可以将一个类声明成另一个类的友元,称为友元类。当一个类作为另一个类的友元时,就意味着这个类的所有成员函数都是另一个类的友元成员函数。友元类的声明比较简单,其格式如下:
friend class <类名>;
下面来看一个例子。
【例Ex_ClassFriend】 使用友元类
#include <iostream.h> class CPoint { friend class COther; // 声明友元类 public: CPoint( int x = 0, int y = 0 ) { xPos=x; yPos=y; } void Print() { cout<<"Point("<<xPos<<", "<<yPos<<")"<<endl; } private: int xPos, yPos; void Inflate(int nOffset) { xPos+=nOffset; yPos+=nOffset; // 直接改变自己类中的私有数据成员 } }; class COther { public: COther(int a = 0, int b = 0) { pt.xPos=a; pt.yPos=b; // 通过对象访问类CPoint的私有数据成员 } void Display(void) { pt.Inflate(10); // 通过对象访问类CPoint的私有成员函数 pt.Print(); } private: CPoint pt; }; int main() { COther one(12,18); one.Display(); return 0; }
在类CPoint的定义中,类COther被声明成CPoint的友元类。这样,在类COther中,可通过CPoint对象pt访问类CPoint的所有成员。
程序运行结果如下:
Point(22, 28)
总之,关于友元有下面的一些说明:
(1)友元关系反映了程序中类与类之间、外部函数和类之间、成员函数和另一个类等之间的关系,这个关系是单向的,即当在CPoint中声明COther是CPoint的友元类时,只能在COther类中通过CPoint对象访问CPoint类的所有成员,而在CPoint类中无法访问COther类的私有和保护型成员。
(2)一个类中的友元并非是该类的成员,由于“friend”关系,因而友元只能通过对象来访问声明友元所在类的成员。而静态成员是类的一个成员,它本身具有不同的访问属性,只是对于公有静态成员来说,它可以有对象访问和“类名::静态成员”两种等价的访问方式。
(3)与友元函数相比,静态成员函数只是修改类的静态数据成员,而对于友元来说,由于通过对象可以修改声明友元所在类的所有数据成员,因而友元函数比静态成员函数更加危险,而且友元类会使这种危险扩大。因此,在类程序设计中,静态成员和友元一定要慎用!
2.2.3 常类型
常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。因此,定义或说明常类型时必须进行初始化。
1.常对象
常对象是指对象常量,定义格式如下:
<类名> const <对象名>
定义常对象时,修饰符const可以放在类名后面,也可以放在类名前面。例如:
class COne { public: COne(int a, int b) { x = a; y = b; } //… private: int x, y; }; const COne a(3,4); COne const b(5,6);
其中,a和b都是Cone的对象常量,初始化后就不能再被更新。
2.常指针和常引用
常指针也是使用关键字const来修饰的。但需要说明的是,const的位置不同,其含义也不同,它有3种形式。
第1种形式是将const放在指针变量的类型之前,表示声明一个指向常量的指针。此时,在程序中不能通过指针来改变它所指向的数据值,但可以改变指针本身的值。例如:
int a = 1, b = 2; const int*p1=&a; // 声明指向int型常量的指针p1, 指针地址为a的地址 *p1=2; // 错误,不能更改指针所指向的数据值 p1=&b; // 正确,指向常量的指针本身的值是可以改变的
需要说明的是,用这种形式定义的常量指针,在声明时可以赋初值,也可以不赋初值。
第2种形式是将const放在指针定义语句的指针名前,表示指针本身是一个常量,称为指针常量或常指针。因此,不能改变这种指针变量的值,但可以改变指针变量所指向的数据值。例如:
int a = 1, b = 2; int*const p1=&a; // 声明指向int型常量的指针p1, 指针地址为a的地址 int*const p2; // 错误,在声明指针常量时,必须初始化 *p1=2; // 正确,指针所指向的数据值可以改变 p1=&b; // 错误,指针常量本身的值是不可改变的
第3种形式是将const在上述两个地方都加,表示声明一个指向常量的指针常量,指针本身的值不可改变,而且它所指向的数据的值也不能通过指针改变。例如:
int a = 1, b = 2; const int * const pp = &a; *pp=2; // 错误 pp=&b; // 错误
需要说明的是,用第2种和第3种形式定义的指针常量,在声明时必须赋初值。
使用const修饰符也可用来声明引用,被声明的引用为常引用,该引用所引用的对象不能被更新。其定义格式如下:
const <类型说明符> & <引用名>
例如:
const double & v;
在实际应用中,常指针和常引用往往用来当做函数的形参,这样的参数称为常参数。使用常参数则表明该函数不会更新某个参数所指向或所引用的对象,这样,在参数传递过程中就不需要执行拷贝构造函数,这将会改善程序的运行效率。
【例Ex_ConstPara】有常参数的函数传递
#include <iostream.h> class COne { public: void print(const int*p,int n) // 使用常参数 { cout<<"{"<<*p; for (int i = 1; i<n; i++) cout<<", "<<*(p+i); cout<<"}"<<endl; } }; int main() { int array[6] = {1, 2, 3, 4, 5, 6}; COne one; one.print(array, 6); return 0; }
程序运行结果如下:
{1, 2, 3, 4, 5, 6}
3.常成员函数
使用const关键字进行声明的成员函数,称为常成员函数。只有常成员函数才有资格操作常量或常对象,没有使用const关键字说明的成员函数不能用来操作常对象。常成员函数说明格式如下:
<类型说明符> <函数名> (<参数表>) const;
其中,const是加在函数说明后面的类型修饰符,它是函数类型的一个组成部分,因此,在函数实现部分也要带const关键字。
【例Ex_ConstFunc】 常成员函数的使用
#include <iostream.h> class COne { public: COne(int a, int b) { x = a; y = b; } void print(); void print()const; // 声明常成员函数 private: int x, y; }; void COne::print() { cout<<x<<", "<<y<<endl; } void COne::print() const { cout<<"使用常成员函数:"<<x<<", "<<y<<endl; } int main() { COne one(5, 4); one.print(); const COne two(20, 52); two.print(); return 0; }
程序运行结果如下:
5, 4
使用常成员函数:20, 52
程序中,类COne声明了两个重载成员函数,一个带const,另一个不带。语句“one.print();”调用成员函数“void print();”,而“two.print();”调用常成员函数“void print() const;”。
4.常数据成员
类型修饰符const不仅可以说明成员函数,也可以说明数据成员。由于const类型对象必须被初始化,并且不能更新,因此,当类中声明了const数据成员时,只能通过成员初始化列表的方式来生成构造函数对数据成员进行初始化。
【例Ex_ConstData】 常数据成员的使用
#include <iostream.h> class COne { public: COne(int a) :x(a),r(x) // 常数据成员的初始化 { } void print(); const int&r; // 引用类型的常数据成员 private: const int x; // 常数据成员 static const int y; // 静态常数据成员 }; const int COne::y=10; // 静态数据成员的初始化 void COne::print() { cout<<"x = "<<x<<", y = "<<y<<", r = "<<r<<endl; } int main() { COne one(100); one.print(); return 0; }
程序运行结果如下:
x = 100, y = 10, r = 100
2.2.4 this指针
this指针是一个仅能被类的非静态成员函数所访问的特殊指针。当一个对象调用成员函数时,编译器先将对象的地址赋给this指针,然后调用成员函数。例如,当下列成员函数调用时:
one.copy(two);
它实际上被解释成:
copy( &one, two);
只不过是&one参数被隐藏了。需要说明的是,通过*this可以判断是哪个对象来调用该成员函数或重新指定对象。
【例Ex_This】 this指针的使用
#include <iostream.h> class COne { public: COne() { x=y=0; } COne(int a, int b) { x = a; y = b; } void copy(COne&a); // 对象引用做函数参数 void print() { cout<<x<<" , "<<y<<endl; } private: int x, y; }; void COne::copy(COne &a) { if (this == &a) return; *this = a; } int main() { COne one, two(3, 4); one.print(); one.copy(two); one.print(); return 0; }
程序运行结果如下:
0 , 0
3 , 4
程序中,使用this指针的函数是copy,它在copy函数中出现了2次。“if(this == &a)”中的this是操作该成员函数的对象的地址,从main函数中的“one.copy(two);”可以看出这个对象就是one。copy函数中的语句
*this = a;
是将形参a(对象的引用)赋给操作该成员函数的对象。在本例中,就是将对象two赋给对象one。因此,main函数中最后的语句“one.print();”实际上就是“two.print();”。
事实上,若成员函数的形参名与该类的成员变量名同名,则必须用this指针来显式区分,例如:
class CPoint { public: CPoint( int x = 0, int y = 0) { this->x=x; this->y=y; } void Offset(int x, int y) { (*this).x+=x; (*this).y+=y; } void Print() const { cout<<"Point("<<x<<", "<<y<<")"<<endl; } private: int x, y; };
类CPoint中的私有数据成员x、y和构造函数、Offset成员函数的形参同名,正是因为成员函数体中使用了this指针,从而使函数中的赋值语句合法有效,且含义明确。否则,如果没有this指针,则构造函数中的赋值语句就变为了“x=x; y=y;”,显然是不合法的。
需要说明的是,对于静态成员函数来说,由于它是为所有对象所共享,因此在静态成员函数中使用this指针将无法确定this的具体指向。所以,在静态成员函数中是不能使用this指针的。