类型,转换,数组,协变及其他

类型,转换,数组,协变及其他

 

为了让叙述简化,先定义几个用到的术语: 

 

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

 

类型系统是现在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,其实也就是所有支持反射的语言的基本机制,这个东西一般被认为是不合规矩的:),但它确实在某种程度上增加了我们的表达能力。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值