概述
第16章 成员访问控制
在C++中,用户可以说明成员数据和成员函数的访问级别。共有三种访问级别:公共的、保护的和私有的。这一章解释访问控制如何运用于类类型对象以及派生类。
本章包括如下主题:
* 类成员的访问控制
* 访问指示符
* 基类的访问指示符
* 友元
* 保护的成员访问
* 虚拟函数的访问
* 多重访问
类成员的访问控制
通过对类成员数据或函数的访问控制,用户可以增加用C++编制的软件的完整性。类成员可以说明为具有私有的、保护的或公共的访问属性,如表10.1所示。
表10.1 成员访问控制
访问类型 | 意义 |
私有的 | 说明为私有的类成员只能由该类的类成员函数或友元(类或函数)来使用 |
保护的 | 说明为保护的类成员能由该类的类成员函数或友元(类或函数)来使用,并且也可以在该类的派生类中使用 |
公共的 | 说明为公共成员可以在任何函数中使用 |
访问控制可以阻止用户对对象进行的无规划的使用。在你显式地使用类型转换时,这种保护也会失去作用。
注意:访问控制对所有的名称都是等同适用的。包括:成员函数、成员数据、嵌套类和枚举。
用class关键字说明的类类型成员,类缺省的访问是私有的,而struct和union的成员的缺省访问是公共的。对于上面的几种情况,当前的访问级别可以用public,private和protected关键字来改变。
访问指示符
在类说明中,成员可以有访问指示符。
语法
访问指示符:成员表opt
访问指示符决定了跟在它后面的名称的访问级别,一直影响到下一个访问指示符出现或类说明结束为止。如图10.1显示了这一概念。
尽管在图10.1中仅显示了两个访问指示符,但在给定的类说明中对于使用访问指示符的个数没有限制。比如:图10.1中的类Point可以自由地用多重访问指示符说明如下:
class Point { public: //说明公共构造函数 Point(int,int); private: //说明私有的状态变量 int _x; public: //说明公共构造函数 Point(); public: //说明公共访问器 int &x(int); private: //说明私有的状态变量 int _y; public: //说明公共的访问器 int &y(int); };
注意:如前面的例子所示,对于成员访问的说明顺序应没有特别的要求。类类型对象的存储分配会受此影响。但在访问指示符之间的成员将保证按存储器地址高端增长的方向分配。
基类的访问指示符
有两个因素控制着哪些基类的成员在派生类中是可访问的,同样的因素也控制着在派生类中对继承成员的访问控制。
* 是否派生类在类头(class-head在第8章“类”的“定义类型”中详述)中用public说明符说明基类。
* 基类中成员所具有的访问控制。
表10.2给出了这些因素的交互影响以及如何决定基类成员的访问。
表10.2 基类中的成员访问
私有的 | 保护的 | 公共的 |
无论怎样派生,总是不可访问的 | 如果用私有派生,在派生类中是私有的 | 如果用私有派生,在派生类中是私有的 |
如果用保护的派生,在派生类中是保护的 | 如果用保护的派生,在派生类中是保护的 | |
如果用公共的派生,在派生类中是保护的 | 如果用公共的派生,在派生类中是公共的 |
下面的例子显示了这一点:
class BaseClass { public: int PublicFunc(); //说明公共成员 protected: int ProtctedFunc(); //说明一个保护成员 private: int PrivateFunc();//说明一个私有成员 };
//说明两个从BaseClass派生出的类
class DerivedClass1:public BaseClass
{ };
class DerivedClass2:private BaseClass
{ };在DerivedClass1中,成员函数PublicFunc是一个公共函数而ProtectedFunc是一个保护的成员函数,因为BaseClass是一个公共的基类。PrivateFunc是BaseClass的私有成员,并且它在任何派生类中都是不可访问的。
在DerivedClass2中,函数PublicFunc和ProtectedFunc是私有成员,因为BaseClass是私有基类。而函数PrivateFunc是BaseClass的私有成员,它在任何派生类中都是不可访问的。
当然你可以不用基类访问指示符说明一个派生类,在这种情况下,如果派生类的说明使用了class关键字,则派生将视为是私有派生。如果使用struct关键字说明派生类,则该派生是公共派生,如下面的代码:
class Derived:Base.
..
等价于:
class Derived:Private Base
...
同样下面的代码:
struct Derived:Base
...
等价于:
struct Derived: public Base
...
注意,说明为私有的成员对于函数或派生类是不可访问的,除非这些函数或者派生类在基类中使用说明为友元。
联合类型不能有基类。
注意:在说明一个私有基类时,建议显式使用private关键字以使派生类的用户能够了解成员的访问。
访问控制和静态成员
当你说明一个基类为private时,它仅影响非静态成员,公共的静态成员在派生类中仍然是可访问的。然而,使用指针引用或对象访问基类成员时,会需要转换,此时访问控制仍然是适用的,考虑如下的代码:
class Base { public: int Print();//非静态成员 static int CountOf(); //静态成员 };
//Derived1把Base说明为私有基类
class Derived1: private Base
{
};
//Derived2把Derived1说明为公共基类
class Derived2: public Derived1 { int ShowCount();//非静态成员 };
//定义Derived2的ShowCount函数
int Derived2::ShowCount() { //显式调用静态成员函数CountOf int cCount=Base::CountOf(); //OK //用指针调用静态成员函数CountOf cCount=this->CountOf(); //错误:不允许把Derived2*转换为Base* return cCount; }
在上面的代码中,访问控制抑制了从Derived2*到Base*的转换。this指针的隐含类型是Derived2*。要选择CountOf函数,this必须转换为Base*。这种转换是不允许的,因为Base是Derived2的非直接的私有基类,把一个派生类的指针转换成指向它的直接私有基类的指针是允许的。因此指针类型Derived1*可以转换为Base*。
注意,显式地调用CountOf函数,而不用指针、引用或对象,意味着没有转换,因而调用是允许的。
派生类T的成员或友员可以把一个指向T的指针转换成指向T的直接基类的指针。
友 元
在某些情况下,把成员级别的访问控制赋于非本类的成员函数或者在另一个单独类中的所有函数时,会更方便一些。用friend关键字,程序员可以指派特别的函数或类(该类中所有的成员函数)不但可以访问公共成员而且也可以访问保护的私有的成员。
友元函数
友元函数并不视为类的成员。它仍只是赋予了特别访问权限的外部函数。友元并不在类的范围中,它们也不用成员选择符(.或->)调用,除非它们是其它类的成员。下面的例子显示了一个Point类和一个重载操作符operator+(这个例子主要是示例友元的,而不是重载操作符。有关重载操作符的详情见第12章“重载”中的“重载操作符”一节)。
#include
//说明类Point
class Point { public: //构造函数 Point() {_x=_y=0;} Point( unsigned x,unsigned y) {_x=x;_y=y;} //访问器 unsigned x() {return _x;} unsigned y() {return _y;} void Print() {cout <<"Point("<<_x<<","<<_y<<")"<<endl;} //友元函数说明 friend Point operator+(Point& pt,int nOffset); friend Point operator+(int nOffset,Point& pt); private: unsigned _x; unsigned _y; };
//友元函数定义
//
//处理Point+int表达式
Point operator+(Point& pt, int nOffset) { Point ptTemp=pt; //直接改变私有成员_x和_y ptTemp._x += nOffset; ptTemp._y += nOffset; return ptTemp; }
//处理int+Point表达式
Point operator+(int nOffset,Point& pt) { Point ptTemp=pt; //直接改变私有成员_x和_y ptTemp._x += nOffset; ptTemp._y += nOffset; return ptTemp; }
//测试重载操作符
void main() { Point pt(10,20); pt.Print(); pt=pt+3; //Point+int pt.Print(); pt=3+pt; //int+Point pt.Print(); }
当编译器在main函数中碰到表达式pt+3时,编译器确认是否有一个合适的用户自定义的operator+存在。在此情形下,函数operator+(Point pt,int nOffset)匹配该操作符,并发出对该函数的调用。在第二种情形(表达式3+pt)函数operator+(Point pt,int nOffset)匹配提供的操作数。因而为operator+提供了两种形式满足了+运算符的可交换性。
用户自定义的operator+函数也可以作为成员函数来定义,但它只能接收一个显式的参数:即加到对象中的值。结果,使用成员函数加法的可交换性无法正确实现。它们必须用友元函数代替才能完成。
注意:两个版本的重载operator+函数在类Point中说明为友元函数。两个说明都是必要的,在友元说明名称重载了函数和操作符时,只有那些由参数表说明的特别的函数才成为友元。假设第三种operator+函数按如下说明:
Point &operator+(Point &pt,Point &pt)
在上面例子中的operator+函数并不是类Point的友元,只不过它有着同其它两个说明为友元的函数有同样的名称。
因为friend说明不受访问指示符的影响,故它们可以在类说明的任何一节中说明。
类成员函数和类成为友元
类成员函数可以在别的类中说明为友元,考虑下面的例子:
class B
class A { int Func1(B& b); int Func2(B& b); }; class B { private int _b; friend int A::Func1(B&) //把友元访问权限赋予类B }; // 类B中的一个函数
int A::Func1(B& b) {return b._b;} //正确:这是友元
int A::Func2(B& b) {return b._b;} //错误:_b是一个私有成员
在前面的例子中,只有函数A::Func1(B& b)赋予了对类B的友元访问权限。因此在类A中的函数Func1中访问私有成员_b是正确的。但Func2则不正确。假设在类B中的友元说明如下:
friend class A;在这种情况下,类A中的所有成员函数都有对类B的友元访问权限。注意“友元关系”是不能被继承的,也没有任何“友元的友元”的访问。图10.2给出了四个类说明:Base,Derived,aFriend和anotherFriend。只有类aFriend有直接
访问类Base的私有成员的权利(以及类Base继承的成员)。
友元的说明
如果你说明了一个友元函数,并且该函数是以前说明过的,则此函数将导出到非类范围的封闭块中。
在友元说明中的函数说明被作为extern的。就像用extern关键字说明过的一样(有关extern的详情见第6章“说明”中的“静态存储类说明符”。
全局范围中的函数可以先于它的类型说明而被说明为友元函数,但是成员函数在其完整的类说明出现之前是不能说明为友元的。下面的代码显示了为什么会失败:
class ForwardDeclared; //class name is knowclass HasFriends{ friend int ForwardDeclared::IsAFriend(); //错误
};
上述的例子,把类名称ForwardDec1ared加入到了范围,但是其完全的定义特别是函数IsAFriend的说明部分还不知道,因此类HasFriends中的友元说明发生了错误。
为了说明两个类彼此为友元,则完整的第二个类必须说明为第一个类的友元。这种限制的原因是编译器仅在第二个类的说明时才有足够的信息说明单个的友元函数。
注意:尽管完整的第二个类必须说明为第一个类的友元,但你可以选择第一个类中的某些函数成为第二个类的友元。
在类说明中定义友元函数
友元函数可以在类说明中定义,这些函数是嵌入函数。就像嵌入的成员函数的行为,尽管它们定义于所见的类成员之后和类范围结束前(类说明的结束)。
在类说明中定义的友元函数被视为在文件范围中,它们并不在封闭的类范围中。
保护的成员访问
说明为保护的类成员仅用于下列情况:
* 最初说明该成员的类成员函数。
* 最初说明该成员的类的友元。
* 由公共派生的类或由最初说明该成员类访问保护的类。
* 直接私有的派生类对于保护的成员有私有的访问权。
保护的成员同私有的成员在私有上是不同的,因为私有的成员只是在说明它们的类中是可访问的。保护的成员同公共的成员在公共上是不同的,因为公共的成员在任何函数中都是访问的。
用static说明的保护成员可被任何友元函数或派生类的成员函数访问。未用static说明的保护的成员也可以被任何友元函数或派生类的成员函数访问,只是要通过派生类的对象,指向派生类对象的指针,或对派生类对象的引用才行。
虚拟函数的访问
适用于虚拟函数的访问控制是由调用该虚拟函数的类型决定。重载函数的说明不会对给定类类型的访问控制有影响,例如:
class VFuncBase { public: virtual int GetState() {return _state;} protected: int _state; }; class VFuncDerived:public VFuncBase { private: int GetState() {return _state;} };
...
VFuncDerived vfd; //派生类对象
VFuncBase *pvfb=&vfd; //指向基类的指针
VFuncDerived *pvfd=&vfd; //指向派生类的指针
int state;
State=pvfb->GetState(); //GetState是公共的
State=pvfd->GetState(); //GetState是私有的,错误。
在上面的例子中,使用一个指向类型VFuncBase的指针调用虚拟函数GetState会激活VFuncBase::GetState,并且GetState是作为公共的,然而用一个指向类型VFuncDerived的指针调用GetState破坏了访问控制,因为GetState在类VFunDerived中说明为私有的。
警告:虚拟函数GetState可以用一个指向基类VFuncBase的指针调用,这并不意味着调用的函数是基类中的哪个版本的函数。
多重访问
在多重继承的框架中涉及虚拟基类。一个给定的名称可以由多个路径到达。因为沿不同的这样路径,适用不同的访问控制,编译器选择最能取得存储的路径,如图10.3所示。
在图10.3中,一个在类VBase中说明的名称总是可以由类RightPath到达。右边的路径总是更可访问的,因为RightPath把VBase说明为公共基类,而LeftPath把Vbase说明为私有基类。
最后
以上就是苹果毛衣为你收集整理的第16章 成员访问控制的全部内容,希望文章能够帮你解决第16章 成员访问控制所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复