`

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

 
阅读更多

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

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

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

分享到:
评论

相关推荐

    C#语言规范(4.0版本)

    6.5.2 匿名函数转换为表达式树类型的计算 124 6.5.3 实现示例 124 6.6 方法组转换 126 7. 表达式 129 7.1 表达式的分类 129 7.1.1 表达式的值 130 7.2 静态和动态绑定 130 7.2.1 绑定时间 131 7.2.2 动态绑定 131 ...

    微软C#语言规范,C#语言教程中文版

    6.5.2 匿名函数转换为表达式树类型的计算 124 6.5.3 实现示例 124 6.6 方法组转换 126 7. 表达式 129 7.1 表达式的分类 129 7.1.1 表达式的值 130 7.2 静态和动态绑定 130 7.2.1 绑定时间 131 7.2.2 动态绑定 131 ...

    C#语言规范4.0

    6.5.2 匿名函数转换为表达式树类型的计算 124 6.5.3 实现示例 124 6.6 方法组转换 126 7. 表达式 129 7.1 表达式的分类 129 7.1.1 表达式的值 130 7.2 静态和动态绑定 130 7.2.1 绑定时间 131 7.2.2 动态绑定 131 ...

    C#语言规范(2.0,3.0,4.0合集)

    6.5.2 匿名函数转换为表达式树类型的计算 124 6.5.3 实现示例 124 6.6 方法组转换 126 7. 表达式 129 7.1 表达式的分类 129 7.1.1 表达式的值 130 7.2 静态和动态绑定 130 7.2.1 绑定时间 131 7.2.2 动态绑定 131 ...

    C#_语言规范_4.0_中文版

    6.5.2 匿名函数转换为表达式树类型的计算 124 6.5.3 实现示例 124 6.6 方法组转换 126 7. 表达式 129 7.1 表达式的分类 129 7.1.1 表达式的值 130 7.2 静态和动态绑定 130 7.2.1 绑定时间 131 7.2.2 动态绑定 131 ...

    CLR.via.C#.(中文第3版)(自制详细书签)Part2

    4.2 类型转换 4.2.1 使用C#的is和as操作符来转型 4.3 命名空间和程序集 4.4 运行时的相互联系 第5章 基元类型、引用类型和值类型 5.1 编程语言的基元类型 5.1.1 checked和unchecked基元类型操作 5.2 引用...

    C#教程(语言规范)

    6.1.9 涉及类型形参的隐式转换. 101 6.1.10 用户定义的隐式转换 102 6.1.11 匿名函数转换和方法组转换 ... 102 6.2 显式转换 102 6.2.1 显式数值转换. 102 6.2.2 显式枚举转换. 104 6.2.3 可以为 null 的显式...

    快学 scala 中文版 带完整目录

    18.10 协变和逆变点 305 18.11 对象不能泛型 307 18.12 类型通配符 308 练习 309 第19章 高级类型 L2 313 19.1 单例类型 313 19.2 类型投影 315 19.3 路径 316 19.4 类型别名 317 19.5 结构类型 318 19.6 ...

    CLR.via.C#.(中文第3版)(自制详细书签)Part1

    4.2 类型转换 4.2.1 使用C#的is和as操作符来转型 4.3 命名空间和程序集 4.4 运行时的相互联系 第5章 基元类型、引用类型和值类型 5.1 编程语言的基元类型 5.1.1 checked和unchecked基元类型操作 5.2 引用...

    CLR.via.C#.(中文第3版)(自制详细书签)

    4.2 类型转换 4.2.1 使用C#的is和as操作符来转型 4.3 命名空间和程序集 4.4 运行时的相互联系 第5章 基元类型、引用类型和值类型 5.1 编程语言的基元类型 5.1.1 checked和unchecked基元类型操作 5.2 引用类型...

    CLR.via.C#.(中文第3版)(自制详细书签)Part3

    4.2 类型转换 4.2.1 使用C#的is和as操作符来转型 4.3 命名空间和程序集 4.4 运行时的相互联系 第5章 基元类型、引用类型和值类型 5.1 编程语言的基元类型 5.1.1 checked和unchecked基元类型操作 5.2 引用...

    C#5.0本质论第四版(因文件较大传的是百度网盘地址)

    2.6.3 不使用转型操作符的类型转换 42 2.7 数组 43 2.7.1 数组的声明 44 2.7.2 数组的实例化和赋值 45 2.7.3 数组的使用 48 2.7.4 字符串作为数组使用 52 2.7.5 常见数组错误 53 2.8 ...

    AspNet MVC 开发技术

    5.1 类型转换 81 5.1.1 隐式转换 82 5.1.2 显式转换 83 5.1.3 使用Convert命令进行显式转换 86 5.2 复杂的变量类型 89 5.2.1 枚举 89 5.2.2 结构 93 5.2.3 数组 96 5.3 字符串的处理 102 5.4 小结 106 5.5 练习 107 ...

Global site tag (gtag.js) - Google Analytics