资讯专栏INFORMATION COLUMN

通俗的方式理解动态类型,静态类型;强类型,弱类型

周国辉 / 1314人阅读

摘要:不允许隐式转换的是强类型,允许隐式转换的是弱类型。拿一段代码举例在使用调用函数的时候会先生成一个类模板运行时生成,执行的时候会生成类模板,执行的时候会生成类模板。

0 x 01 引言

今天和一个朋友讨论 C++ 是强类型还是弱类型的时候,他告诉我 C++ 是强类型的,他和我说因为 C++ 在写的时候需要 int,float 等等关键字去定义变量,因此 C++ 是强类型的,我告诉他 C++ 是弱类型的他竟然还嘲笑我不懂基础。

我又尝试去问了另外一个同学 Python 是强类型还是弱类型的时候,得到的竟然是弱类型,就因为定义变量没有 int,float!

然后我想找一些网上的资料试图告诉他们他们是错的(我是对的),结果发现网上的资料大多为了严谨结果把简单的问题(其实并不简单)说的很复杂。比如:知乎上的一些 回答。所以用通俗的方式,以大多数程序猿(媛)所需要了解的知识去介绍类型系统,但是又不丧失严谨性就是这篇文章写的意义。

0 x 02 什么是动态(静态)类型,强(弱)类型

基础版本

编译时就知道变量类型的是静态类型;运行时才知道一个变量类型的叫做动态类型。比如:

编译器在将 int age = 18; 这段代码编译的时候就会把 age 的类型确定,换言之,你不能对他进行除以 0 的操作等等,因为类型本身就定义了可操作的集合;但是像 C++ 里常见的 auto ite = vec.iterator(); 这种也属于静态类型,这种叫做类型推导,通过已知的类型在编译时期推导出不知道的变量的类型。在静态类型语言中对一个变量做该变量类型所不允许的操作会报出语法错误

但是像 var name = student.getName(); 这行 JavaScript 代码就是动态类型的,因为这行代码只有在被执行的时候才知道 name 是字符串类型的,甚至是 null 或 undefined 类型。你也没办法进行类型推导,因为 student.getName 函数签名根本不包含返回值类型信息。后面会介绍通过一些其他手段来给函数签名加上类型。在动态类型中对一个变量做该变量类型所不允许的操作会报出运行时错误

不允许隐式转换的是强类型,允许隐式转换的是弱类型。比如:

在 Python 中进行 "666" / 2 你会得到一个类型错误,这是因为强类型语言中是不允许隐式转换的,而在 JavaScript 中进行 "666" / 2 你会得到整数 333,这是因为在执行运算的时候字符串 "666" 先被转换成整数 666,然后再进行除法运算。

高级版本

需要先介绍一些基本概念:

Program Errors(程序错误)

trapped errors:导致程序终止执行(程序意识到出错,使用对应的错误处理机制),如除 0,Java 中数组越界访问

untrapped errors:程序出错后继续执行(其实并不一定保证继续执行,程序本身并不知道出错,也没有对应的错误处理机制),如 C 语言里的缓冲区溢出,Jmp 到错误地址

Forbidden Behaviors(禁止行为)

程序在设计的时候会定义一组 forbidden behaviors,包括了所有的 untrapped errors,可能包括 trapped errors。

Well behaved、ill behaved

well behaved: 如果程序的执行不可能出现 forbidden behaviors,则称为 well behaved

ill behaved: 只要有可能出现 forbidden behaviors,则称为 ill behaved

他们之间的关系可以用下图来表达:

从图中可以看出,绿色的 program 表示所有程序(所有程序,你能想到和不能想到的),error 表示出错的程序,error 不仅仅包括 trapped error 和 untrapped error。

根据图我们可以严格的定义动态类型,静态类型;强类型,弱类型

强类型:如果一门语言写出来的程序在红色矩形外部,则这门语言是强类型的,也就是上面说的 well behaved

弱类型:如果一门语言写出来的程序可能在红色矩形内部,则这门语言是弱类型的,也就是上面说的 ill behaved

静态类型:一门语言在编译时排除可能出现在红色矩形内的情况(通过语法报错),则这门语言是静态类型

动态类型:一门语言在运行时排除可能出现在红色矩形内的情况(通过运行时报错,但如果是弱类型可能会触发 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。但是强类型,静态类型的语言写起来往往是最安全的。

0 x 03 动态类型与静态类型的区别,如何利用好动态类型

静态类型由于在编译期会进行优化,所以一般来说性能是比较高的。而动态语言在进行类型操作的时候(比如字符串拼接,整数运算)还需要解释器去猜测其类型,因此性能很低;但是现代的解释器一般会有一些优化措施来提升速度,拿 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 总结的优化方案:

不要轻易的增加删除一个对象的属性,对于已有的属性尽量做到保证类型的不变,保证隐藏类尽可能被复用

实例化属性的时候尽可能保证属性添加的顺序一致性,保证隐藏类和优化代码可以被复用

尽可能重复调用方法,传的参数的个数和类型要在多次调用时要保持一致

对于数组,最好使用 push,unshift 等方法去改变数组大小,紧密的数组在 V8 中是以连续的地址存的,不要随意去删除数组中的元素,因为稀疏数组在 V8 中是一个 hash 表

V8 存储整数用的是 4 个字节,出现大整数时将会涉及到隐式类型转换,性能降低,因此尽量不要让整数超过 32 bit

0 x 04 如何避免弱类型语言所带来的问题

弱类型语言由于在运行时缺乏类型系统,因此很容易出现类型操作上的 untrapped error;C 语言中我们前面介绍了数组访问越界的情况,这里我们以弱类型语言 JavaScript 为例:

尽量使用严格比较符号,如:===

尽量不要让字符串与其他类型的变量进行运算操作

复杂对象不要在运算符上进行操作

0 x 05 语言类型静态化的方案

像 JavaScript 这种动态类型的语言静态化后对运行时的安全性,效率肯定会有很大的提升的,目前有 TypeScript 这种预编译的方案;还有就是像 flow 这样的通过注释来标识类型的方案。

0 x 06 总结

写到最后,我才发现文章的标题没取好,就这样吧。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92387.html

相关文章

  • 做一个好前端必须要知道事——JS语言

    摘要:准确的理解,是编译型语言,源代码整个编译成字节码,字节码,是解释型语言。是一个非常灵活的语言,支持命令式和函数式编程。编译型语言通常会用做配置文件,因为我们通常不会改编译后的字节码。 编程语言按各种方法可以分为各种类型,现在让我们来看看JS属于什么类型语言 解释型语言 按编译执行过程,可以分为编译型语言和解释型语言。比如 c 语言,必须先经过编译生成目标文件,然后链接各个目标文件和库...

    Near_Li 评论0 收藏0
  • 类型类型静态语言、动态语言区别?

    摘要:弱类型强类型会报错静态类型以上是的代码,静态类型语言在编译时遇到错误就会立即提醒。备注意思是陷阱,也被称为异常或故障。 弱类型: 1+2 12 强类型: 1+2 会报错 静态类型: public void ShowHi() { int a = Hi! string b = a; } 以上是c#的代码,静态类型语言在编译时遇到trap错误就会立即提醒。 动态类型:...

    edagarli 评论0 收藏0
  • 基础冲刺

    摘要:事实上,确实存在多种解释器。在命令行下运行就是启动解释器。最常用的,自带的就是这个是基于之上的一个交互式解释器,也就是说,只是在交互方式上有所增强,但是执行代码的功能和是完全一样的。但是的解释器很多,但使用最广泛的还是。 以下资料大多参考的此篇博客:http://www.cnblogs.com/alex37... 基础冲刺 编译型语言:C、C++ 运行之前都需要一次编译,编译成可以...

    sunny5541 评论0 收藏0

发表评论

0条评论

周国辉

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<