imjacob的专栏

首页博文目录订阅
正 文

类型 转换 数组 协变及其他

(2009/3/4 22:48)
为了让叙述简化,先定义几个用到的术语:

函数:可以给出输出的那种抽象体,变量可以认为是无参数函数。对于成员函数或者更习惯的叫做方法的那种函数,我认为它就是隐含了对象参数的函数。

类型系统是现在OO语言的核心和基石。类型系统是保证正确性的基础,现在的编程语言大多强调静态安全性,其实就是编译时类型正确性。动态类型系统对应着运行期类型检查,保证运行时的类型正确性。经常所说的安全性其实就是类型正确性。

类型转换是对类型系统的公然藐视。它不是安全的。可能会引入很多问题。有时候,我们可能会觉得需要类型转换,但是那是错觉,我们可以定义一个函数,接受某种类型的对象,返回另一种类型的对象。所以类型转换是不必要的。有人会说,你把类型转换看成某种形式奇怪的函数调用得了。我承认,这个想法很好,但是有两个缺陷,一、类型转换比函数更强横霸道,能干很多函数不能干的事。二、类型转换有时候会自动发生。

数组,一个常见的语言构造,大多数语言都支持它。它一般被定义为同一类型的一堆对象,可以通过index来认领这一堆对象中的任意一个。很多语言都把这一堆对象起一个名字,同时通过[index]运算认领。但是,有很多语言都不支持数组作为一个一类构造。原因是他们认为:数组也不过是一个对象而已,它包含多个其他对象,这种包含多个其他对象的对象一般叫做容器。还有另一种观点是,数组不外是函数而已。它完成一个从index到对象的映射。不管是哪种观点,都比原始的数组的观念强大,更容易加上一些辅助的特性如边界检查什么的。当然,这似乎暗示引入[]是无价值的。()就行了。嘻嘻,BASIC 啊。

单单静态类型检查是不够的。最常见的一个例子是list,如果我们要求必须是静态检查,那么有可能要求我们要么放宽list容器容纳的对象类型(也就是通用化、泛化(这是根特化和继承相对的那个概念,不要联系到泛型)),要么强大的限制了list的能力。举例吧:一个list,在它的偶数位置放置鹦鹉,奇数位置放置大象,如果是完全的静态类型检查,那么我们可能会说:我们有一个放置动物的list,但是这容易造成我们放了一只蚂蚁进去!而且类型系统不能够阻止这个行为。实际上造成了不正确性。

协变(Covariance)、抗变(Contravariance)和不变(Nonvariance)是OO理论中最容易引起争论的话题。不过它们是如此重要和基本,非说不可了。它们跟继承多态什么的密切相关。同时,它们也会涉及到如何搞定上面提到的list的问题。

所谓协变,就是随同主类型一起变化。故此称为“协”。还是举例说明比较清楚:

class Animal
{
public:
    marry(Animal another);
};

class Elephant : public Animal
{
public:
    marry(Elephant another);
};

class Fox : public Animal
{
public:
    marry(Fox another);
};

其中,marry的参数another就是协变的。大家可能觉得,咦,这不是挺好么?其实,对于参数的协变还有很多别的说法。就一般情况而言,大多数人认为,参数应该是抗变的,那么,什么是抗变呢?抗变,又叫反变,意思是说,子类型的函数参数应该是父类型的函数参数的父类型,这儿稍微有点绕,仔细看清楚了,呵呵,等你理解了这句话,你会觉得这不符合直觉,对吗。可是,事实上,这句话是对的,在一般意义上是对的。我举个例子:

你是一个打印服务供货商,你向客户承诺,可以接受TXT和PS格式的文件,返回一堆打印纸张,拥有TXT或者PS文件描述的内容。

后来,你想升级换代,你又增加了一种文件格式PDF,你没有错,你可以赢得新的客户,但是如果你说我要取消TXT文件格式,那么,你的客户可能会抱怨你的。也就是说,你只能放宽你接受的参数类型(泛化、父类什么的),但是变窄(特化、子类什么的)会导致原来能够正常工作的东西突然失效。

现在你或许觉得参数抗变是合理的,应该的。可是别急,想想前面的例子,难道说,动物和动物结婚没错,大象和应该和动物或者更上层的类生物结婚么?是啊,这是一个问题,你或许会觉得这可能是个特例,但实际上这是普遍存在的,回想一下我们的成员函数(方法),就会发现,该函数有一个隐含的this参数一直就是协变的。另外,函数的返回值类型也是协变的,这个几乎没有争议。就如同上面那个例子,你给你的客户是一堆打印了内容的纸,如果你改成它的子类,一堆打印了内容的好纸,你的客户不会反对的。对于变量,我说过它也不过是没有参数的函数的返回值,那么这儿似乎暗示它们应该是协变的。

还有更复杂的问题。比如我们上面那个动物类型体系,我们有一个叫做吃的函数,动物吃食物,大象吃草,狐狸吃肉。这个该算做什么?协变吧。这么说,我们没有办法决定究竟该怎么弄了?究竟怎么回事?继续看:)

现在介绍一个概念,分派(Dispatch)。分派其实就是函数调用。不过稍微有点复杂的是:它根据它的参数类型来选择特定的函数实施这个调用。

Motor* m = new ...();
m->run();

这个run就是一个函数。究竟调用那一个run,依赖于m这个参数(m其实就是那个隐含的this参数)的实际类型,这就是分派。很明显,我们这个是OO 里面多态的基础,另外还有一个术语叫做双分派,其实就是根据两个参数来决定调用那个函数,当然,推而广之就有多分派这个说法了。

某位大牛(记不得叫什么了,抱歉啊)经过研究最终发话了:一个函数中,那些决定分派的参数应该是协变的,而其他的参数应该是反变的。看到这儿,手抚额头,恍然大悟啊。原来如此,就应该如此,非如此不可啊。呵呵。

上面啰里啰唆说了一大通,发现没有提到动态类型相关的东西,现在说说吧。C++里面关于动态类型的构造叫做RTTI——运行时类型信息。关于这个的有两个操作,一个叫做dynamic_cast<T*>(pO),一个就是臭名昭著的typeid了。第一个操作是在运行时得到实际的类型信息,当然,有可能失败的。比如:一个动物,其实际类型是蚂蚁,但是我在运行时想通过dynamic_cast变换成大象,这个肯定不可能。但是我们可以进行这种尝试,如果失败,我们也能得到某种信息不是吗。对于typeid,我就不说别的了,它的增强版就是typeof,其实也就是所有支持反射的语言的基本机制,这个东西一般被认为是不合规矩的:),但它确实在某种程度上增加了我们的表达能力。
http://fixopen.javaeye.com/blog/27848
评 论
还没有网友评论,欢迎您第一个评论!
博 主
进入imjacob的首页
博客名称:雅克的一府
日志总数:514
评论数量:901
访问次数:1868543
建立时间:2006/11/23 20:52
导 航
公 告
Locations of visitors to this page 本博客主要用于个人学习与资料收藏。当然大家应该读了之后也能学到不少东西。其中大多数资料都是来自网络,我转载时尽可能地表明文章出处与原作者姓名,但由于很多资料经多人转载,已不清楚原作者信息与出处,所以未表明相关…
评 论
链 接

ARM+LINUX 嵌入式博客
http://blog.chinaunix.net/u1/58780/index.html

嵌入式软件
http://blog.csdn.net/embeddedsoft

诚诚恳恳做人踏踏实实编程
http://blog.sina.com.cn/u/1244756857 

和我风格相似的一个blog
http://blogger.org.cn/blog/m…