PL真有意思(五):数据类型 (3)

在静态类型的语言中,如果“a=b”,那么我们会期望b的类型和a的相同;现在假定所提供的类型和期望的类型和所提供的类型相同,那么我们在要求某个类型的上下文中使用另外一个类型时就需要显示的写出类型变换(或称为类型转换)。根据具体的变换的具体情况,在运行时执行这种变化会有以下三种主要的情况出现:

所涉及的类型可以认为是结构等价的,这种情况里面因为涉及的类型采用了相同的底层的表示,则这种变换纯粹就是概念上的操作,不需要运行时执行任何代码。

所涉及的类型具有不同的值集合,但它们的值集合具有相同的表示形式。比如一个类型和它的子类型,一个整数和一个无符号的整数。拿无符号整数变换为整数来说,由于无符号整数的最大值是整数类型所容纳不了的,则运行时就必须执行一些代码来保证这种变换的合法性,如果合法则继续下去,否则会产生一个动态语义错误。

所涉及的类型具有不同的底层表示,但是我们可以在它们的值之间定义某种对应关系。比如32位整数可以变换到IEEE的双精度浮点数,且不会丢失精度。浮点数也可以通过舍入或割断的形式变换成整数,但是会丢失小数部分。

非变换的类型转换

有这么一种情况,我们需要改变一个值,但是不需要改变它的二进制表示形式,更通俗点说就是我们希望按照另外一个类型的方式去解释某个类型的二进制位,这种情况称为非变换类型转换。最简单的一个例子比如说,一个byte类型的数值65,按byte类型来解释它是65,如果按照char类型来解释它就是字符“A”。比如C++中的static_cast执行类型变换,reinterpret_cast执行非变换的类型转换。c中出现的union形式的结构,就可以认为是这种非变换的类型转换的合法的安全的语言结构。在比如下面C中一般性非变换类型转换代码:

r=*((float *) &n);

任何非变换的类型转换都极其危险的颠覆了语言的类型系统。在弱类型系统的语言中,这种颠覆可能很难发现,在强类型系统的语言中显示的使用这种非变换的类型转换,起码从代码上可以看得出来它是这么一回事,或多或少的有利于排查问题。

类型相容

大多数语言的上下文中并不要求类型等价,相应的一般都是实施较为“宽松”的类型相容规则。比如赋值语句要求右值相容与左值、参数类型相容,实际返回类型与指定的返回类型相容。在语言中,只要允许把一个类型的值用到期望的另外一个类型的上下文中,语言都必须执行一个到所期望类型的自动隐式变换,称为类型强制(比如int b;double a=b;)。就像前面说的显示的类型变换一样,隐式的类型变换也可能需要执行底层代码或者做一些动态类型检查。

重载

一个重载的名字可能引用不同类型的对象,这种歧义性需要通过上下文信息进行解析。比如a+b这个表达式可以表示整数或者浮点数的加法运算,在没有强制的语言中,a和b必须都是整数或都是浮点数。如果是有强制的语言,那么在a或者b有一个是浮点数的情况下,编译器就必须使用浮点数的加法运算(另外一个整数强制转换为浮点数)。如果语言中+只是进行浮点数运算,那么即使a和b都是整数,也会被全部转成浮点数进行运算(这代价就高了好多了)。

通用引用类型

通用引用类型:一些语言根据实习需求,设计有通用的引用类型,比如C中的void*、C#中的Object,任意的值都可以赋值给通用引用类型的对象。但是问题是存进去容易取出来难,当通用引用类型是右值的时候,左值的类型可能支持某些操作,然而这些操作右值对象是不具备的。为了保证通用类型到具体类型的赋值安全,一种解决办法是让对象可以自描述(也就是这个对象包含其真实类型的描述信息),C++,JAVA,C#都是这种方式,C#中如果赋值的类型不匹配则会抛出异常,而C++则是使用dynamic_cast做这种赋值操作,具体的后果呢,也是C++程序员负责。

类型推理

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjysj.html