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

Java的实现方式区分了内部类型和用户定义的类型,对内部类型采用值模型,对用户定义的类型采用则采用引用模型,C#的默认方式与Java类似,另外还提供一些附加的语言特性,比如“unsafe”可以让程序员在程序中使用指针。

悬空引用

在前两篇的名字、作用域和约束中我们列举了对象的3种存储类别:静态、栈和堆。静态对象在程序的执行期间始终是活动的,栈对象在它们的声明所在的子程序执行期间是活动的,而堆对象则没有明确定义活动时间。

在对象不在活动时,长时间运行的程序就需要回收该对象的空间,栈对象的回收将作为子程序调用序列的一部分被自动执行。而在堆中的对象,由程序员或者语言的自动回收机制负责创建或者释放,那么如果一个活动的指针并没有引用合法的活动对象,这种情况就是悬空引用。比如程序员显示的释放了仍有指针引用着的对象,就会造成悬空指针,再进一步假设,这个悬空指针原来指向的位置被其他的数据存放进去了,但是实际却不是这个悬空指针该指向的数据,如果对此存储位置的数据进行操作,就会破坏正常的程序数据。

那么如何从语言层面应对这种问题呢?Algol 68的做法是禁止任何指针指向生存周期短于这个指针本身的对象,不幸的是这条规则很难贯彻执行。因为由于指针和被指对象都可能作为子程序的参数传递,只有在所有引用参数都带有隐含的生存周期信息的情况下,才有可能动态的去执行这种规则的检查。

废料收集

对程序员而已,显示释放堆对象是很沉重的负担,也是程序出错的主要根源之一,为了追踪对象的生存轨迹所需的代码,会导致程序更难设计、实现,也更难维护。一种很有吸引力的方案就是让语言在实现层面去处理这个问题。随着时间的推移,自动废料收集回收都快成了大多数新生语言的标配了,虽然它的有很高的代价,但也消除了去检查悬空引用的必要性了。关于这方面的争执集中在两方:以方便和安全为主的一方,以性能为主的另一方。这也说明了一件事,编程中的很多地方的设计,架构等等方面都是在现实中做出权衡。

废料收集一般有这两种思想,就不详细说了。

引用计算

追溯式收集

表具有递归定义的结构,它或者是空表,或者是一个有序对,有序对由一个对象和另一个表组成。表对于函数式或者逻辑式语言程序设计非常适用,因为那里的大多数工作都是通过递归函数或高阶函数来完成的。

在Lisp中:

(cons 'a '(b)) => (a b) (car '(a b)) => a (cdr '(a b c)) => (b c)

在Haskell和Python还由一个非常有用的功能,叫做列表推导。在Python中可以这样推导出一个列表

[i * i for i in range(1, 100) if i % 2 == 1] 文件和输入/输出

输入/输出(I/O)功能使程序可以与外部世界通信。在讨论这种通信时,将交互式I/O和文件I/O分开可能有些帮助。交互式IO通常意味着与人或物理设备通信,人或设备都与运行着的程序并行工作,送给程序的输入可能依赖程序在此之前的输出。文件通常对应于程序的地址空间之外的存储器,由操作系统实现。

有些语言提供了内置的File数据类型,另外一些语言将IO工作完全委托给库程序包,这些程序包导出一个file类型。所以IO也算作是一种数据类型

相等检测和赋值

对于简单的基本数据类型,如整数、浮点数和字符,相等检测和赋值相对来说都是直截了当的操作。其语义和实现也很明确,可以直接按照二进制位方式比较或复制,但是,对于更加复杂或抽象的数据类型,就可能还需要其它的比较方式

相互是别名?

二进制位是否都相等?

包含同样的字符序列?

如果打印出来,看起来完全一样?

就许多情况下,当存在引用的情况下,只有两个表达式引用相同的对象时它们才相等,这种称为浅比较。而对于引用的对象本身存在相等的含义时,这种比较称为深比较。对于复杂的数据结构,进行深比较可能要进行递归的遍历。所以相对来说,赋值也有深浅之分。深赋值时是进行完整的拷贝。

大多数的语言都使用浅比较和浅赋值

小结

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

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