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

在编译期间,符号表维护者程序中的每个数组的维度和边界信息。对于每个记录,它还维护着每个域的偏移量。如果数组维度的数目和边界是静态已知的,编译器就可以在符号表中找出它们,以便计算数组元素的地址。如果这些值不是静态已知的,则编译器就必须生成代码,在运行时从一个叫内情向量的数据结构来查找它

栈分配

子程序参数是动态形状数组最简单的例子,其中数组的上下界在运行时才确定,调用方都会传递数组的数据和一个适当的内情向量,但是如果一个数组的形状只能到加工时才知道,这种情况下仍可以在子程序的栈帧里为数组分配空间,但是需要多做一层操作

堆分配

在任意时间都可以改变形状的数组,有时被称为是完全动态的。因为大小的变化不会以先进先出的顺序进行,所以栈分配就不够用了。完全动态的数组必须在堆中分配。比如Java中的ArrayList

#### 内存布局

大多数语言的实现里,一个数组都存放在内存的一批连续地址中,比如第二个元素紧挨着第一个,第三个紧挨着第二个元素。对于多维数组而言,则是一个矩阵,会出现行优先和列优先的选择题,这种选择题对于语言使用者而言是透明的,而对语言的实现者则需要考虑底层方面的优化问题了。

在一些语言中,还有另外一种方式,对于数组不再用连续地址分配,也不要求各行连续存放,而是允许放置在内存的任何地方,再创建一个指向各元素的辅助指针数组,如果数组的维数多于两维,就再分配一个指向指针数组的指针数组。这种方式称为行指针布局,这种方式需要更多的内存空间,但是却有两个优点:

首先,可能加快访问数组里单独元素的速度;

其次,允许创建不用长度的行,而且不需要再各行的最后留下对齐所用的空洞空间,这样节省下来的空间有时候可能会超过指针占据的空间。C,C++和C#都支持连续方式或行指针方式组织多维数组,从技术上讲,连续布局才是真正的多维数组,而行指针方式则只是指向数组的指针数组。

字符串

许多语言中,字符串也就是字符的数组。而在另一些语言中,字符串的情况特殊,允许对它们做一些其他数组不能用的操作,比如Icon以及一些脚本语言中就有强大的字符串操作功能。

字符串是编程中非常重要的一个数据类型,故而很多语言都对字符串有特殊的处理以便优化其性能以及存储(比如C#中的字符串不可变性保证了性能,字符串驻留技术照顾了存储方面的需要),由于这些特殊的处理,故而各各语言中为字符串提供的操作集合严重依赖语言设计者对于实现的考虑。

集合

程序设计语言中的一个集合,也就是具有某个公共类型的任意数目的一组值的一种无序汇集。集合的元素所具有的类型叫做元类型或者基类型。现在的大多数程序设计语言都对集合提供了很大的支持,为集合提供了很多相关的操作

指针和递归类型

所谓的递归类型,就是可以在其对象中包含一个或多个本类型对象的引用类型。递归类型用于构造各种各样的“链接”数据结构,比如树。在一些对变量采用引用模型的语言中,很容易在创建这种递归类型,因为每个变量都是引用;在一些对变量采用值模型的语言中,定义递归类型就需要使用指针的概念,指针就是一种变量,其值是对其他对象的引用。

对于任何允许在堆里分配新对象的语言,都存在一个问题:若这种对象不在需要了,何时以及以何种方式收回对象占用的空间?对于那些活动时间很短的程序,让不用的存储留在那里,可能还可以接受,毕竟在它不活动时系统会负责回收它所使用的任何空间。但是大部分情况下,不用的对象都必须回收,以便腾出空间,如果一个程序不能把不再使用的对象存储回收,我们就认为它存在“内存泄漏”。如果这种程序运行很长一段时间,那么它可能就会用完所有的空间而崩溃。许多早期的语言要求程序员显示的回收空间,如C,C++等,另一些语言则要求语言实现自动回收不再使用的对象,如Java,C#以及所有的函数式语言和脚本语言。显示的存储回收可以简化语言的实现,但会增加程序员忘记回收不再使用的对象(造成内存泄漏),或者不当的回收了不该回收的正在使用的对象(造成悬空引用)的可能性。自动回收可以大大简化程序员的工作,但是为语言的实现带来了复杂度。

语法和操作

对指针的操作包括堆中对象的分配和释放,对指针间接操作以访问被它们所指的对象,以及用一个指针给另一个指针赋值。这些操作的行为高度依赖于语言是函数式还是命令式,以及变量/名字使用的是引用模型还是值模型。

函数式语言一般对名字采用某种引用模型(纯的函数式语言里根本没有变量和赋值)。函数式语言里的对象倾向于采取根据需要自动分配的方式。

命令式语言里的变量可能采用值模型或引用模型,有时是两者的某种组合。比如 A=B;

值模型: 把B的值放入A。

引用模型: 使A去引用B所引用的那个对象。

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

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