[TOC] # 面试真题 ## 1\. 动态语言和静态语言的区别? ## 2\. 弱类型语言和强类型语言的区别? ## 3\. JavaScript是强类型还是弱类型语言? ## 4\. JavaScript是动态语言还是静态语言? # 帮助理解 我们在学习编程语言的类型系统时,经常听说”静态语言“、”动态语言“、”强类型语言“、”弱类型语言“这些概念,它们究竟是什么意思呢?各个概念之间又有什么区别呢? 没看这篇文章之前可能会认为静态语言 = 强类型语言,或者动态语言 = 弱类型语言,但它们其实不是一回事 。 <span class="vip">静态和动态语言应该放在一起提及</span>,它们是从同一种维度来评价的;而<span class="vip">强类型/弱类型语言应该放在一起提及</span>,它们是从另外一种不同的维度来评价的。 静态语言并不意味着强类型,动态语言也不意味着弱类型,<span class="vip">一种编程语言的类型可以是静态/动态和强/弱的任意组合</span>,例如: * `C/C++/Java` 是静态语言 + 强类型 * `JavaScript/PHP` 是动态语言 + 弱类型 * `Python` 是动态语言 + 强类型 在很多编程语言(比如 `C`、`C++`、`Java`、`C#` 等)中,定义变量时除了需要指明变量的名字,还需要告诉计算机它是什么类型,比如简单的整数、浮点数、字符串,还有复杂的类、结构体、数组等。编程语言中的数据最终都要放到内存中,在内存中存取数据要明确三件事情:<span class="vip">数据存储地址、数据的长度以及数据的处理方式</span>。 * 数据存储地址决定了数据放在哪里; * 数据长度决定了当前数据使用了多少个字节的内存; * 数据处理方式不仅让计算机能够正确转换数据的内容,不至于导致“乱码”,还让计算机知道如何处理基于该数据的各种运算,比如加减乘除。 变量名不仅仅是为数据起了一个好记的名字,还告诉我们数据的存储地址,使用数据时,只要提供变量名即可,变量名会自动转换成数据在内存中的地址。而数据类型则指明了数据的长度和处理方式,它确定了除地址以外的其它所有信息。 例如:`int n`、`char s`、`float money`等这样的变量定义形式就确定了数据在内存中的所有要素 每种编程语言都有一个数据类型系统,没有它的话,计算机将不知道如何处理我们程序中的数据,这包括: * 计算机不知道数据的长度,不知道该操作多少个字节; * 计算机不知道如何将值存储到内存,不知道如何解析内存中的值; * 计算机不知道如何运算数据; 我们可以在不了解底层类型系统的情况下还可以编程,这就是高级语言的美妙之处。但是,了解我们程序中的数据将给编程带来巨大的优势,如下所示: **可以更少地使用内存资源** 当数据量比较大时,采用长度较小且够用的数据类型,将节省成百上千个字节的内存空间。 **可以读懂报错信息** 比如将两份数据相加时可能报错,因为其中一个是数字类型,一个是字符串类型,虽然人类眼睛看起来都是数字,但是计算机不允许对字符串类型执行相加操作,这告诉我们,不能使用引号来包围数字。 **改变规则** 一旦你知道数据类型是如何工作的,知道如何改变规则。比如将一个字符和一个整数相加,就可以得到另一个字符。 ## 静态类型和动态类型语言 ### 静态类型语言 <span class="vip">静态类型指的是在创建数据(变量、参数、返回值等)时需要显式指明该数据的类型</span>。通常情况下,如果不经过强制转换,这些数据的类型一旦被定义,在程序的整个生命周期也不再改变。 ~~~java int m = 7; // 整数类型 string name = "小漂亮"; // 字符串类型 final double PI = 3.1415926; // 双精度浮点类型 // sum() 函数包含两个整数类型的参数,返回值也是整数类型 public int sum(int m1, int m2) { return m1 + m2; } ~~~ 这是一段`Java`代码,它定义了几个变量,以及一个求和函数。在代码中,我们已经明确的指明了要处理的数据类型,包括整数、双精度浮点数、字符串。如果没有给编译器这些提示,编译器将不知道如何处理变量、方法,它只是一个对我们有意义的名称,但是计算机不理解它。 静态类型的编程语言有很多,常见的有 `C/C++`、`Java`、`C#`、`Go`等。 ### 动态类型语言 让我们再对比一下动态类型的语言,请看下面的例子: ~~~javascript let m = 7; // 整数类型 let name = "小漂亮"; // 字符串类型 let PI = 3.1415926; // 单精度浮点类型 // sum() 包含两个参数和一个返回值 function sum(m1, m2) { return m1 + m2; } ~~~ 这是一段 `JS`代码,代码中我们并没有使用任何明确的数据类型,这就是一种典型的动态类型语言。 <span class="vip">动态类型的编程语言在创建数据时往往不需要指明该数据的类型,而且数据的类型也会随着值的改变而改变,给数据赋予不同的值,数据将得到不同的类型</span>。 那么,不给数据指明类型,编程语言是如何知道它的类型的呢?编程语言会根据赋予数据的值进行猜测或者推断。对于例子中的第一行代码,`js`知道 `7` 是一个数字,将`7`赋值给变量 `m`,`js`也会推断出`m`是一个数字类型。 `sum()` 函数包含的参数类型,会根据调用 `sum()` 时传递的值(实参)进行推断;`sum()` 函数包含的返回值类型,会根据`m1 + m2`的运算结果进行推断。这意味着,`sum()` 可以接收两个数字并返回一个数字。对动态类型语言,类型是推断出来的,甚至可以在程序运行期间更改。我们可以给上面的程序中增加一行代码,把`"最编程"`赋值给`m`变量,这样 `js`将把`m`变量切换为字符串类型。 常见的动态类型语言有 `JavaScript`、`PHP`、`Python`、`Ruby`等。 ### 静态类型和动态类型的优势 <span class="vip">对于静态类型语言,我们需要预先明确地告诉编译器要处理的数据的类型,这样在程序部署之前就能发现代码中的错误或者疏忽</span>。比如我们将一个变量定义为整数,将另一个变量定义为字符串,如果我们尝试将两个变量相加,那么编译器在编译期间可能就会捕获这种语法错误,并且不会让我们完成程序的构建。 这很好,<span class="vip">因为越早发现错误,修复它的成本就越低</span>。在部署之前修复问题,要比部署之后修复问题容易得多造成的损失也越小,加加班就搞定了,但是上线之后那可能会涉及到经济损失。 那么,使用静态类型是不是就一定正确呢?<span class="vip">使用静态类型的负担就是,在使用数据之前你必须明确地将它定义好</span>。你必须输入更多的代码,你必须事先知道正在使用的数据的类型,你必须知道你的操作过程中会发生什么。然而在实际开发中由于我们的疏忽或者知识缺陷,想要预判这么多往往是比较困难的。例如在`C/C++` 中,你必须知道`1/3`得到的是整数 `0`,而不是浮点数 `0.3333...`,这对初学者来说是一个坑。 在这方面,<span class="vip">动态类型语言就为我们提供了更多灵活性</span>,程序员经常称动态语言“更具表现力”。例如在 `JS`中,`1/3`将会得到 `0.3333...`,这在很多时候都是我们期望的结果。 然而问题在于,<span class="vip">解释器有时候会对数据类型做出错误的猜测,你必须知道这种情况,否则这将成为一个潜在的危险,因为解释器不会给出错误提示</span>。 由于我们不能捕获所有的潜在危险,因此<span class="vip">使用动态语言编写的代码往往更加脆弱,也更容易出错</span>。 <span class="vip">动态语言中的数据类型通常在程序运行期间才能确定,这使得程序在被部署到生产环境之前很难捕获太多错误</span>。程序可能在你的本地机器上正常工作,但是生产环境可能略有不同,解释器就会产生一些不同的猜测,这将导致潜在的风险。 `JavaScript`被认为是一种典型的动态语言。随着 `TypeScript`的诞生,程序员也想在定义变量时能够声明它的类型,以使 `JavaScript`变得更加静态。 随着 `JavaScript` 越来越流行,人们已经不限于在浏览器上使用它了,大名鼎鼎的 `Node.js` 使得人们可以在脱离浏览器,直接在机器上运行 `JavaScript`。此时程序员意识到为 `JavaScript`增加静态类型的好处,这样能够消除 `JavaScript`在处理数据类型时所做的一些错误猜测。 将动态语言 `JavaScript`变得更像静态语言,可以及时发现错误,并生成更加健壮的代码。在 `Node.js` 的帮助下,`JavaScript`越来越多地渗透到服务器端编程,让 `JavaScript`变得静态就显得尤为重要。 **总结** <span class="vip">静态语言为我们提供了一个更加严格的编程环境,并且通常会生成更加健壮的代码。动态语言为编写代码带来了灵活性,同时也提高了编写代码的效率,但是如果你不注意数据的类型,可能就会导致代码更容易出错</span>。 ## 强类型语言和弱类型语言 正如前面提到的,经常混淆静态/动态类型和强/弱类型的概念,倾向于将<span class="vip">静态类型等同于强类型,将动态类型等同于弱类型,实际上这是不对的</span>。 <span class="vip">强/弱类型指的是编程语言如何处理运算过程中的值</span>。当值的类型不符合运算规则时,编程语言究竟是做出一些猜测临时转换值的类型以帮助人们解决问题,还是停止运行。 ### 强类型语言 ~~~cpp int main() { int m1 = 7; std::string m2 = "7"; // 编译器会报错 std::cout << (m1 + m2); return 0; } ~~~ 这是一段 `C++` 代码,我们尝试将一个整数和一个字符串相加,很显然这是没有意义的,编译器会停止构建,并抛出一条错误信息。在这个例子中,我们将变量 `m2` 定义为字符串类型,但是在使用 `m2` 的时候,我们对它施加了一种不匹配的运算(字符串类型不支持算数求和运算)。`C++` 也可以在后台默默地做一些猜测,临时将字符串`"7"`转换成整数`7`,这样以上代码就能行得通;但是 `C++` 并不会这样做,它会立即标记此处的问题(报错),并放弃构建,交给程序员自己处理。这就是<span class="vip">强类型语言的典型特征,它们不会处理与类型定义明显矛盾的运算,而是把它标记为一个问题,并作为错误抛出</span>。通常人们认为 `C/C++`、`Java`、`C#`、`Python`、`Go`都是强类型语言,它们都不允许上述代码中的行为。 现在虽然有办法削弱 `C++` 的强类型系统,让 `C++` 能够容忍一些与类型定义不匹配的运算,但是这超出了本文的讨论范围,所以不再赘述 ### 弱类型语言 和强类型语言不一样,<span class="vip">当我们执行一些与类型定义不匹配的运算时,弱类型语言尝试提供帮助,它可能会临时转换值的类型,让它符合当前运算</span>。 下面的例子中,我们使用 `JavaScript` 代码执行与上述 `C++` 代码类似的操作: ~~~javascript var m1 = 7; var m2 = "7"; console.log( m1 + m2 ); ~~~ 如果你对 `JavaScript` 特性有所了解,就会知道这段代码不会报错,而是正常运行,它会在控制台上显示 `14`,这也许正是你所期望的。 对于相加运算,如果`+`两边的值的类型不一样,那么 `JavaScript`将尝试转换其中一个值的类型,让它和另一个值的类型相同,然后再进行运算,并给出运算结果。这里之所以显示 `14`,是因为 `JavaScript`将变量 `m2` 的值转换为整数类型,得到一个数字 `7`,然后再和变量 `m1` 相加,结果就是 `14`。 `PHP` 也是一种典型的弱类型语言,对 `PHP` 执行类似的操作,它的结果是怎样的呢?请看下面的代码: ~~~php $m1 = 7; $m2 = "7"; echo $m1 + $m2; // 77 ~~~ 你对这个结果可能会比较惊讶,认为它和预期的不一样,这是因为 `PHP`对值的类型进行了不同的转换。`PHP`会将变量 `$m1` 的值转换为字符串类型,然后再和变量 `$m2` 的字符串拼接起来,得到一个新的字符串。 字符串拼接工作非常简单,就是将字符串中的内容直接连接起来。 `PHP` 和 `JavaScript`虽然都是弱类型语言,但是当它们遇到和类型定义明显矛盾的运算时,可能会做出不同的猜测或者转换,这种猜测有时让人匪夷所思。 <span class="vip">类型系统的“强/弱”指的是当编程语言遇到与类型定义不匹配的运算时,尝试猜测或者转换的力度/程序。它不是一条明确的界限,而是一个范围</span>。 * 强类型语言在遇到与类型定义明显矛盾的运算时,一般会当做一种语法错误,而不会尝试对值的类型进行转换。 * 弱类型语言恰好相反,会猜测程序员的意图,并对其中一些值的类型进行转换,以让程序继续执行。 <span class="vip">强/弱类型是一个相对概念,将两种语言放在一起对比时,才更容易发现明显的差异</span>。 ### 强类型和弱类型各有什么优势 <span class="vip">强类型语言是严谨的,它要确保程序员明确地知道自己在做什么</span>。如果发生错误,强类型语言认为这是程序员不了解当前的运算规则导致的,或者程序员对正在处理的问题含混不清。 例如,将整数`7`和字符串`"7"`相加对计算机来说是一种没有意义的操作,如果你没有意识到,那么强类型语言就明确地提示你。强类型语言会强迫你必须了解运算规则,并明确地告诉计算机你想做什么(通常使用强制类型转换),不能含混不清。 <span class="vip">强类型使得程序被部署到生产环境时更加健壮,不易出错</span>。但是,<span class="vip">我们需要付出更多成本,这包括花费更多时间编写合理规范的代码,以及解决更多细枝末节的问题</span>。 相反的,<span class="vip">弱类型语言消除了强类型语言的严格性,在较大程度上允许程序构建并正常运行,因此弱类型语言更有灵活性和表达性</span>。 弱类型语言不利的一方面是,<span class="vip">它可能错误地猜测程序员的意图,并得到不被期望的运行结果</span>。 `JavaScript`是一种弱类型语言(恰好也是动态语言),但是可以使用 `TypeScript`增强,以使程序更加健壮。`TypeScript`改变了类型系统,它允许程序员在定义变量时增加显式类型声明,这样 `JavaScript`就能获得强类型语言的好处,在编译时捕获更多错误,并防止一些模糊的猜测被带入到生产环境。 此外,支持 `TypeScript`的编辑器基本也可以在你编写代码时就给出错误提示。 # 真题解答 ## 1\. 动态语言和静态语言的区别 静态类型指的是在创建数据(变量、参数、返回值等)时需要显式指明该数据的类型。通常情况下,如果不经过强制转换,这些数据的类型一旦被定义,在程序的整个生命周期也不再改变。静态语言为我们提供了一个更加严格的编程环境,并且通常会生成更加健壮的代码。 动态类型的编程语言在创建数据时往往不需要指明该数据的类型,而且数据的类型也会随着值的改变而改变,给数据赋予不同的值,数据将得到不同的类型。动态语言为编写代码带来了灵活性,同时也提高了编写代码的效率,但是如果你不注意数据的类型,可能就会导致代码更容易出错。 ## 2\. 弱类型语言和强类型语言的区别 强/弱类型指的是编程语言如何处理运算过程中的值。当值的类型不符合运算规则时,编程语言究竟是做出一些猜测临时转换值的类型以帮助人们解决问题,还是停止运行。 强类型语言的典型特征就是它们不会处理与类型定义明显矛盾的运算,而是把它标记为一个问题,并作为错误抛出。强类型使得程序被部署到生产环境时更加健壮,不易出错;但是,我们需要付出更多成本,这包括花费更多时间编写合理规范的代码,以及解决更多细枝末节的问题。 弱类型语言就是当我们执行一些与类型定义不匹配的运算时,弱类型语言尝试提供帮助,它可能会临时转换值的类型,让它符合当前运算。弱类型语言消除了强类型语言的严格性,在较大程度上允许程序构建并正常运行,因此弱类型语言更有灵活性和表达性。弱类型语言不利的一方面是,它可能错误地猜测程序员的意图,并得到不被期望的运行结果。 ## 3\. JavaScript是强类型还是弱类型语言? JavaScript是弱类型语言 ## 4\. JavaScript是动态语言还是静态语言? JavaScript既是弱类型语言同时也是动态语言