第 10 章 成 员 访 问 控 制
在 C++ 中 , 用 户 可 以 说 明 成 员 数 据 和 成 员 函 数 的 访 问 级 别 。 共 有 三 种 访 问 级 别 : 公 共 的 、 保 护 的 和 私 有 的 。 这 一 章 解 释 访 问 控 制 如 何 运 用 于 类 类 型 对 象 以 及 派生 类 。
本 章 包 括 如 下 主 题 :
-
类 成 员 的 访 问 控 制
-
访 问 指 示 符
-
基 类 的 访 问 指 示 符
-
友 元
-
保 护 的 成 员 访 问
-
虚 拟 函 数 的 访 问
-
多 重 访 问
类 成 员 的 访 问 控 制
通 过 对 类 成 员 数 据 或 函 数 的 访 问 控 制 , 用 户 可 以 增 加 用 C++ 编 制 的 软 件 的 完 整性 。 类 成 员 可 以 说 明 为 具 有 私 有 的 、 保 护 的 或 公 共 的 访 问 属 性 , 如 表 10.1 所 示 。
表 10.1 成 员 访 问 控 制
访问类型 意义
私有的 说 明 为 私 有 的 类 成 员 只 能 由 该 类 的 类 成 员 函 数 或 友 元 ( 类 或 函数 ) 来使用
保护的 说 明 为 保 护 的 类 成 员 能 由 该 类 的 类 成 员 函 数 或 友 元 ( 类 或 函 数 ) 来使用 , 并且也可以在该类的派生类中使用
公共的 说明为公共成员可以在任何函数中使用
访 问 控 制 可 以 阻 止 用 户 对 对 象 进 行 的 无 规 划 的 使 用 。 在 你 显 式 地 使 用 类 型 转 换时 , 这 种 保 护 也 会 失 去 作 用 。
注 意 : 访 问 控 制 对 所 有 的 名 称 都 是 等 同 适 用 的 。 包 括 : 成 员 函 数 、 成 员 数 据 、 嵌套 类 和 枚 举 。
用 class 关 键 字 说 明 的 类 类 型 成 员 , 类 缺 省 的 访 问 是 私 有 的 , 而 struct 和 union 的 成 员 的 缺 省 访 问 是 公 共 的 。 对 于 上 面 的 几 种 情 况 , 当 前 的 访 问 级 别 可 以 用public,private 和 protected 关 键 字 来 改 变 。
访 问 指 示 符
在 类 说 明 中 , 成 员 可 以 有 访 问 指 示 符 。
语 法
访 问 指 示 符 : 成 员 表 opt
访 问 指 示 符 决 定 了 跟 在 它 后 面 的 名 称 的 访 问 级 别 , 一 直 影 响 到 下 一 个 访 问 指 示 符
出 现 或 类 说 明 结 束 为 止 。 如 图 10.1 显 示 了 这 一 概 念 。
访问指示符影响以后所有的成员直到下一个访问指示符出现为止
图 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 PublicFun c (); // 说 明 公 共 成 员protected:
int Pro t ctedFun c (); // 说 明 一 个 保 护 成 员private:
int PrivateFunc();// 说 明 一 个 私 有 成 员
};
// 说 明 两 个 从 BaseClass 派 生 出 的 类
class DerivedClass1:public BaseClass
{ };
class DerivedClass2:private BaseClass
{ };
在 DerivedClass1 中 , 成 员 函 数 PublicFunc 是 一 个 公 共 函 数 而 ProtectedFunc 是 一 个 保 护 的 成 员 函 数 , 因 为 BaseClass 是 一 个 公 共 的 基 类 。 PrivateFunc 是BaseClass 的 私 有 成 员 , 并 且 它 在 任 何 派 生 类 中 都 是 不 可 访 问 的 。
在 DerivedClass2 中 , 函 数 PublicFun c 和 ProtectedFunc 是 私 有 成 员 , 因 为BaseClass 是 私 有 基 类 。 而 函 数 PrivateFunc 是 BaseClass 的 私 有 成 员 , 它 在 任何 派 生 类 中 都 是 不 可 访 问 的 。
当 然 你 可 以 不 用 基 类 访 问 指 示 符 说 明 一 个 派 生 类 , 在 这 种 情 况 下 , 如 果 派 生 类 的说 明 使 用 了 class 关 键 字 , 则 派 生 将 视 为 是 私 有 派 生 。 如 果 使 用 struct 关 键 字说 明 派 生 类 , 则 该 派 生 是 公 共 派 生 , 如 下 面 的 代 码 :
class Derived:Base
...
等 价 于 :
class Derived:Private Base
...
同 样 下 面 的 代 码 : struc t 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 <iostream.h>
// 说 明 类 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 是 一 个 私 有 成 员
图 10.2 友 元 关 系 的 牵 连
在 前 面 的 例 子 中 , 只 有 函 数 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 know class HasFriends
{
friend int ForwardDeclar e d::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::GetS t ate, 并 且 GetState 是 作 为 公 共 的 , 然 而 用 一 个 指 向 类型 VFuncDerived 的 指 针 调 用 GetState 破 坏 了 访 问 控 制 , 因 为 GetState 在 类VFunDerived 中 说 明 为 私 有 的 。
警 告 : 虚 拟 函 数 GetState 可 以 用 一 个 指 向 基 类 VFuncBase 的 指 针 调 用 , 这 并 不 意味 着 调 用 的 函 数 是 基 类 中 的 哪 个 版 本 的 函 数 。
多 重 访 问
在 多 重 继 承 的 框 架 中 涉 及 虚 拟 基 类 。 一 个 给 定 的 名 称 可 以 由 多 个 路 径 到 达 。 因为 沿 不 同 的 这 样 路 径 , 适 用 不 同 的 访 问 控 制 , 编 译 器 选 择 最 能 取 得 存 储 的 路 径 , 如图 10.3 所 示 。
图 10.3 沿 继 承 图 中 不 同 路 径 的 访 问
在 图 10.3 中 , 一 个 在 类 VBase 中 说 明 的 名 称 总 是 可 以 由 类 RightPath 到 达 。 右边 的 路 径 总 是 更 可 访 问 的 , 因为 RightPath 把 VBase 说 明 为 公 共 基 类 , 而 LeftPath 把 Vbase 说 明 为 私 有 基 类 。