今天和一个朋友讨论 C++ 是强类型仍是弱类型的时候,他告诉我 C++ 是强类型的,他和我说由于 C++ 在写的时候须要 int,float 等等关键字去定义变量,所以 C++ 是强类型的,我告诉他 C++ 是弱类型的他居然还嘲笑我不懂基础。javascript
我又尝试去问了另一个同窗 Python 是强类型仍是弱类型的时候,获得的居然是弱类型,就由于定义变量没有 int,float!java
而后我想找一些网上的资料试图告诉他们他们是错的(我是对的),结果发现网上的资料大多为了严谨结果把简单的问题(其实并不简单)说的很复杂。好比:知乎上的一些 回答。因此用通俗的方式,以大多数程序猿(媛)所须要了解的知识去介绍类型系统,可是又不丧失严谨性就是这篇文章写的意义。segmentfault
基础版本数组
编译时就知道变量类型的是静态类型;运行时才知道一个变量类型的叫作动态类型。好比:安全
编译器在将 int age = 18;
这段代码编译的时候就会把 age 的类型肯定,换言之,你不能对他进行除以 0 的操做等等,由于类型自己就定义了可操做的集合;可是像 C++ 里常见的 auto ite = vec.iterator();
这种也属于静态类型,这种叫作类型推导,经过已知的类型在编译时期推导出不知道的变量的类型。在静态类型语言中对一个变量作该变量类型所不容许的操做会报出语法错误。app
可是像 var name = student.getName();
这行 JavaScript 代码就是动态类型的,由于这行代码只有在被执行的时候才知道 name 是字符串类型的,甚至是 null 或 undefined 类型。你也没办法进行类型推导,由于 student.getName 函数签名根本不包含返回值类型信息。后面会介绍经过一些其余手段来给函数签名加上类型。在动态类型中对一个变量作该变量类型所不容许的操做会报出运行时错误。函数
不容许隐式转换的是强类型,容许隐式转换的是弱类型。好比:性能
在 Python 中进行 '666' / 2
你会获得一个类型错误,这是由于强类型语言中是不容许隐式转换的,而在 JavaScript 中进行 '666' / 2
你会获得整数 333,这是由于在执行运算的时候字符串 '666' 先被转换成整数 666,而后再进行除法运算。 优化
高级版本this
须要先介绍一些基本概念:
Program Errors(程序错误)
Forbidden Behaviors(禁止行为)
程序在设计的时候会定义一组 forbidden behaviors,包括了全部的 untrapped errors,可能包括 trapped errors。
Well behaved、ill behaved
他们之间的关系能够用下图来表达:
从图中能够看出,绿色的 program 表示全部程序(全部程序,你能想到和不能想到的),error 表示出错的程序,error 不只仅包括 trapped error 和 untrapped error。
根据图咱们能够严格的定义动态类型,静态类型;强类型,弱类型
举个栗子:
在 Python 中执行 test = '666' / 3
你会在运行时获得一个 TypeError 错误,至关于运行时排除了 untrapped error,所以 Python 是动态类型,强类型语言。
在 JavaScript 中执行 var test = '666' / 3'
你会发现 test 的值变成了 222,由于这里发生了隐式转换,所以 JavaScript 是动态类型,弱类型的。更为夸张的是 [] == ![]
这样的代码在 JavaScript 中返回的是 true,这里是具体的 缘由。
在 Java 中执行 int[] arr = new int[10]; arr[0] = '666' / 3;
你会在编译时期获得一个语法错误,这说明 Java 是静态类型的,执行 int[] arr = new int[10]; arr[11] = 3;
你会在运行时获得数组越界的错误(trapped error),这说明 Java 经过自身的类型系统排除了 untrapped error,所以 Java 是强类型的。
而 C 与 Java 相似,也是静态类型的,可是对于 int test[] = { 1, 2, 3 }; test[4] = 5;
这样的代码 C 语言是没办法发现你的问题的,所以这是 untrapped error,所以咱们说 C 是弱类型的。
下图是常见的语言类型的划分:
另外,因为强类型语言通常须要在运行时运行一套类型检查系统,所以强类型语言的速度通常比弱类型要慢,动态类型也比静态类型慢,所以在上述所说的四种语言中执行的速度应该是 C > Java > JavaScript > Python。可是强类型,静态类型的语言写起来每每是最安全的。
静态类型因为在编译期会进行优化,因此通常来讲性能是比较高的。而动态语言在进行类型操做的时候(好比字符串拼接,整数运算)还须要解释器去猜想其类型,所以性能很低;可是现代的解释器通常会有一些优化措施来提高速度,拿 JavaScript 的 V8 解释器举个栗子:
V8 的优化过程(粗略版本)
咱们知道,像 Java / C++ 这样的静态类型语言对于对象通常都会有个类模板(通常调用函数的时候都是去类模板找的)。而像 V8 这种则是会在运行时建立类模板,从而在访问属性或调用方法的时候仅须要计算该属性在类模板中的偏移就能够了;传统的 JavaScript 对象通常是经过 Hash 或 Trie 树实现的,可是查找的效率很低。拿一段代码举例:
function Point(x, y) { this.x = x; this.y = y; } var p1 = new Point(1, 2);
在使用 new 调用 Point 函数的时候会先生成一个 class0 类模板(运行时生成),执行 this.x = x
的时候会生成 class1 类模板,执行 this.y = y
的时候会生成 class2 类模板。具体的转换过程以下图:
为一个对象肯定一个类模板能够极大的提高属性的访问速度,类模板的肯定就是经过走图里的路径(转换路径)。每当你增长或删除对象的属性的时候都会致使对象的类模板发生改变,甚至你增长的顺序不一样也会生成不一样的类模板!
V8 若是发现一个方法被调用(传入相同类型的参数)屡次时,会使用 JIT 将函数编译成二进制代码,从而提高速度。
结合 V8 总结的优化方案:
弱类型语言因为在运行时缺少类型系统,所以很容易出现类型操做上的 untrapped error;C 语言中咱们前面介绍了数组访问越界的状况,这里咱们以弱类型语言 JavaScript 为例:
===
像 JavaScript 这种动态类型的语言静态化后对运行时的安全性,效率确定会有很大的提高的,目前有 TypeScript 这种预编译的方案;还有就是像 flow 这样的经过注释来标识类型的方案。
写到最后,我才发现文章的标题没取好,就这样吧。