谈变量的类型

#Technomous #PL

为什么需要类型

我们先从类型系统谈起,其实在计算机运算时,并不会关心类型。比如 'C' + 25,从类型上来看,字符串无法和整数进行相加,因为这么做没有意义。但是如果抛开类型的话,其实在计算机底层运算的时候,'C' 是 0x43 这个数字,25 是0x19 这个数字,所以从底层来看它们进行相加并没有什么问题。所以类型是我们自己约定出来的一套规则。因为我们写出来的程序需要在日常生活中能够使用。所以需要一个框架的约定来理解这个数字。比如我们一起约定一个框架叫 "uint8",这个框架的作用是状态的数字一对一到整数集合 0 到 255 之间的数字。在这个计算框架下,计算 0x02 + 0x03 得到了 5,就是有意义的。如果计算字符串 'C' 与 25 的和,得到的结果就是没有意义的,因为无法通过这个约定的框架来解释。之后为了工程上的完备,我们又约定了浮点数,定点数,不同的编码(UTF-8、UTF-16),来把自动机接受的状态映射到不同的内容上。这就是类型做的事情。我们在编写程序的时候不会面向内存思考它到底在内存里是怎么表示的(况且部分语言还不是一一对应内存的,包括绝大部分脚本语言,并且部分语言禁止你直接操控内存,必须约定的类型来间接修改内存),通过类型这个抽象层来减少我们的思考,把重心放在编写逻辑上是很有必须要的,这么做就需要类型系统的介入。如果没有了类型系统,计算机运行既不直观也不容易预测结果,很容易出现 untrapped error。在强类型类型系统可以在编译时(静态语言)和运行时(动态语言)提前发现以减少 untrapped error 问题的出现,提高程序的健壮性。

数据的位置和长度:针对类型设计的类型系统是从语法层面定义了一组操作,而从底层来看,类型也提供了数据的位置和长度。也就说,数据在哪就是地址,数据长度其实某方面就是取决于类型。指针的本质就是存储别的变量的地址,那么指针的类型是干什么用的呢?是的,就是去访问指针所指向的地址的时候,指针的类型和指针的值(变量地址),提供了一条汇编指令最关键的两点:数据的长度和数据地址。

编译型和解释型

为什么 C/C++、Java 这类编译型语言就需要显示声明类型,而 Python 这类解释型语言就不需要呢?所以是所有的编译型都需要申明类型?所有的解释型都不需要?

这个理解是不对的。首先,"编译"和"解释"是语言实现的特征,语言本身可以以任何一种方式实现。可能我们大多只了解过C编译器(如GCC、Clang),却很少听过 C 解释器,如 Ch。但是,即使使用 Ch 解释器,我们仍然需要在 Ch 解释的 C 代码中申明类型。Python 的对象模型本身在运行时自带了"类型"这个信息。在 Python 中所有变量的本质上都是一个对象的"指针",也正因为如此,Python 中的各种变量直接才能随意赋值,因为赋值的本质其实就是改变一下变量所指向的对象。而真正到了解释执行的时候,Python 解释器就会取出这个类型字段,来判断操作是否符合语法,不符合的话就会运行时报错。

如何看待变量

值得注意的是静态编译和动态编译与强类型和弱类型之间没有必然的联系。强类型与弱类型区分的根源是变量与数据类型的关系,在所有的计算机语言中,大体有两种看待变量的方式:变量是保存数据的容器和变量是访问数据的入口。

强类型和弱类型

最后,我们来比较一下强弱两种类型的语言。早期,强类型语言,都是编译(静态)语言,例如:C/C++,Fortran,Pascal 等,这类语言,没有强大的运行时来支持对象池,因此只能采用第一种方式。而早期,弱类型语言,都是解释(动态)语言,例如:Lisp,Scheme 等,它们有强大的解释器,其中包括对象池,因此可以采用第二种方式。

强类型语言,有一个非常大的优势,那就是:编译器知道变量的类型,可以提前检查赋值错误,再加上,编译语言的运行性能优势,这使得,强类型语言,在上世纪中叶很快成了主流。但是,强类型的优势也是缺陷,这就是:由于变量带有类型,所以代码和类型强关联,很难写出同时适用于多种类型的代码,为了修补这个缺陷,几乎同时出现了两种解决方案:从宏(模板)发展出来的泛型和面向对象(OOP)。泛型使得类型可以成为某个代码块的参数,在使用该代码块时被具体制定。面向对象利用继承让子类对象复用父类对象的代码块。在经过,泛型和 OOP 改造后,强类型语言在千禧年后,到达了顶峰,以至于这时,出现的 Java 和 C# 这样的动态编译语言,也采用强类型。

但是弱类型语言并非一无是处:没有变量类型是天然的泛型、OOP 也可以引入和适用于脚本代码。因此,才有 JavaScript 和 Python 这样的弱类型语言,随着计算机性能的飞速发生使得强类型语言的性能优势慢慢削弱,而弱类型语言的简单灵活慢慢凸显,这使得,如今的它们也是如日中天。

计算机源于数学,早在第一台计算机出现之前,数据家就对可行性计算问题进行了深入的研究,先后出来了:递归函数、lambda演算、图灵机,之后图灵机称为了计算机体系结构的数学原理,而 lambda演算正是函数式编程的本质。因此,我们可以从数学角度来稍微看一下变量(常量)类型。

变量是否有类型,仅仅是计算机语言的类型系统的一部分,即便是同为强或弱类型语言,其类型系统也差距较大,以下是一些类型系统具有代表性的语言: