小序:
有一阵子没来打扫
Blog
了
……技术这东西,就是走走停停的,一段时间就会遇到一个瓶颈、迷茫一下,然后发现与其因为迷茫而停滞不前,不如瞄准一个大方向勇敢地游下去。
这两天有几个正在学习
C++
的同学问我什么是“友元”、
C#
里有没有“友元”,
C++
里的
friend
关键字是不是就相当于
C#
里的
protected
关键字。
OK
,今天就在这里稍微讲解一下。
正文:
正统的面向对象与革新
从面向对象思想的角度上来看,
C++/Java
所持有的思想是“传统的”或者是“正统的”
OO
思想。
C#
是对传统
OO
思想的改进和创新。比如,
C#
中包含的“属性”(
Property
)就是对传统
OO
思想中“私有变量
+Set/Get
函数对”的一种改进革新;
C#
中的“委托”(
Delegate
)就是对
C/C++
中函数指针的升级——不但安全,而且多播(请大家参阅我的《深入浅出话委托》一文);
C#
中的“事件”本质仍然是委托,因为它就是一个被
event
关键字修饰的委托的实例
,但对于类与类之间的消息传递来说,这绝对称得上是微软的一种创新。总之,微软对
OO
思想的革新使
OO
在语言层面上实现起来更加方便、更加自然。
OO
的三大特征:封装、继承、多态(也有说是“自定义类、继承、多态”的,见《
C++ Primer 4th
》)中,设计类的继承是个很庞杂的工作。其中需要反复思量的一个问题就是类中所封装的变量与方法的可访问性(或者称为“访问级别”)——谁可以访问到?谁不能访问到?这里的“谁”指代的是包括被访问的实体(比如一个变量或一个函数)的宿主(即包含这个实体的类)在内的程序中所有的类。
对于一个被封装的实体来说,谁都访问不到它显然是不行的——那就彻底没用了。那么如果让所有类都能访问得到,岂不美哉?的确美哉!前提是使用这个类的其它程序员一定要像这个类的设计人员一样了解它。这就有两个问题出现了:
l
类的使用者要被迫接受很多信息,知识量大,过于庞杂,不能专注于应该了解的功能。这个问题导致的直接后果就是使开发速度降低。
l
一个好的类,内部设计是非常精巧的(高内聚),每个被封装的实体都可能起着或重要、或微妙的作用,使用者一不小心就会产生“牵一发而动全身”的错误,造成对整个项目的影响——我们称这种错误为类的“用户级错误”(与类的“设计级错误”相区别)。
所以,一个类留给用户程序员的接口并不是功能越多越好,而是越干净越好。换句话说就是:该隐藏的隐藏,该暴露的暴露,不给使用者留有犯错误的机会。
本着这个原则,
C++
给出了三个访问控制级别——
public、
private和protected
——要么所有类都能访问得到(public),要么只有宿主类里的成员可以访问得到(private),要么只有派生类可以访问得到(protected)。这三个访问级别对付大多数项目已经足够了,而
C#
在语言层面上又进行了改进,使之方便了不少:
l
保留
public
关键字:与
C++
一致,表示程序中的所有类均可以访问其修饰目标。
l
保留
private
关键字:与
C++
一致,表示仅宿主类可以访问其修饰目标。
l
保留
protected
关键字:与C++一致,表示可以在宿主类的派生类中访问到其修饰目标。
l
新增
internal
关键字:表示同一个程序集(
Assembly
)中的所有类可以访问到其修饰目标。
注意:程序集是
.NET
技术的一个新概念,又称“托管程序集”,一般情况下一个程序集不是一个托管的
.DLL
文件就是一个托管的
.EXE
文件。所谓“托管”就是这样的文件只能运行在
.NET
平台上,即
Windows
把这些程序“拜托”给
.NET
虚拟机来管理执行了。而且,使用
Visual Studio
,只能写出一个
DLL/EXE
只包含一个程序集的程序来,如果你想在一个
DLL/EXE
里包含多个程序集(就像双黄蛋那样),对不起,你得自己用命令行手动编译。请参阅《
CLR via C#
》或者这本书的上一个版本。
友元
一直到这儿“友元”还没露面儿……
呵呵,不把上面的东西搞明白,怎么能知道“友元”的威力呢?上面提到的都是“规规矩矩”的访问控制。然而,总是规规矩矩办事,难免会有不方便的时候——比如我向我同桌的刘蕾同学借把剪刀也要经过领导进行一下资产借调审批流程、算一算部门折旧与均摊,要么领导疯掉,要么我被勒令去开弱智证明……
好吧,不瞒您说,“友元”就是一个“不规矩”的产物——在
C++
的某个类中,使用了
friend
关键字修饰的实体就称为“友元”——能被
friend
关键字修饰的实体只有两种,要么是个类(称为“友元类”)要么是个函数(称为“友元函数”)。被
friend
修饰之后,朋友吗!那就可以随便一些、不那么规矩啦!所以,友元类
/
友元函数可以访问到将它声明为友元的类的
private
成员——而不去管它有没有继承关系、是不是
public
的。
看下面的例子:
#include
<iostream>
// 友元的宿主类
class Man
{
friend class Thief; // 声明友元类
public :
Man(): money(100){}; // 构造函数,将初始的金钱数设置为100
void SpendMoney()
{
money --;
}
int ReportMoney()
{
return money;
}
private:
int money;
};
// 友元类的定义
class Thief
{
public:
void ThieveMoney(Man& haplessMan)
{
haplessMan.money -= 10;
}
};
int main(int argc, char *argv[])
{
Man man;
man.SpendMoney();
std::cout<<man.ReportMoney()<<std::endl;
Thief thief;
thief.ThieveMoney(man);
std::cout<<man.ReportMoney()<<std::endl;
return 0;
}
// 友元的宿主类
class Man
{
friend class Thief; // 声明友元类
public :
Man(): money(100){}; // 构造函数,将初始的金钱数设置为100
void SpendMoney()
{
money --;
}
int ReportMoney()
{
return money;
}
private:
int money;
};
// 友元类的定义
class Thief
{
public:
void ThieveMoney(Man& haplessMan)
{
haplessMan.money -= 10;
}
};
int main(int argc, char *argv[])
{
Man man;
man.SpendMoney();
std::cout<<man.ReportMoney()<<std::endl;
Thief thief;
thief.ThieveMoney(man);
std::cout<<man.ReportMoney()<<std::endl;
return 0;
}
输出的结果是这样的:
显然,我们这位“盗贼朋友”是偷窃成功了的!请大家注意:
Man
类中的
money
成员是
private
的,而正因为它用
friend
关键字修饰了
class Thief
——这种以贼为“友”、引狼入室的行为,造成了
Thief
类也可以访问
Man
兜里的
monry…
呵呵,其实上面只是举个例子说明友元的工作原理,并不是要说明友元是危险的。恰恰相反,正因为有了友元,所以才使访问变得不但方便而且安全了——
l
方便:想让谁能访问到,在类里把它修饰成
friend
就行了。
l
安全:有限度地向需要访问的类
/
函数开放访问权限,总比声明成
public
要安全得多吧
J
关于C#
C#
中没有友元的概念,只是把访问级别、访问范围划分的更细致了。而且在
C#
中,
private
就是
private
,除了修饰目标的宿主类之外,谁也访问不到
private
级别的封装实体。
protected
也是一种“有限度”的开放
,不过它的开放范围不是面向某一个类(只面向类,不面向函数)而是面向其所有的直接
/
间接派生类。之所以称为
protected
,意思是说这种开放是具有“家族性”的,是“传家宝”而需要保护起来、不让继承链之外的类看到——哪怕是个破笤帚疙瘩也要一代一代传下去!哈哈。
最后再妄自揣测一下
C#
里为什么没有
friend
吧……其实,当你向一个类中添加友元的时候,实际上是添加了这两个类之间的耦合,而且是个别添加而非面向某一组具有特定条件的类——这似乎是一种无规则的、无原则的“感情用事”,很危险,会把程序搞乱。
问题
末了,抛给大家一个问题:如果一个类继承自
Thief
,但这个类并未声名为
Man
的友元,那么它能不能访问
Man
的
money
成员呢?
class
LThief: Thief
{
// 类体为空即可。
};
呵呵……这个问题会引出另一个问题—— public 继承与 private 继承的区别 。咱们有空再侃!
{
// 类体为空即可。
};
呵呵……这个问题会引出另一个问题—— public 继承与 private 继承的区别 。咱们有空再侃!
本文转自 水之真谛 51CTO博客,原文链接:http://blog.51cto.com/liutiemeng/18749,如需转载请自行联系原作者