静态类型与动态类型

”静态类型“与”动态类型“,则是对一门编程语言是否在编译期就能确定变量类型的区分。例如,考虑下面这段伪代码:

a = 1
a = true

在 Python 这样的动态类型语言中,不规定变量类型必须不变,因此类似这样的代码是正确的。而在 Java、C#、TypeScript 等编程语言中,这样的代码是不能通过编译的。这就是对静态类型语言和动态类型语言的简单区分。

可以注意到,即使在上面区分动态类型与静态类型的例子中,我也没有写下面这种代码:

int a = 1;
a = TRUE;

这是一段 C 语言代码,并且它是正确且能够通过编译的。很多人认为一门语言在变量定义时需要声明类型(比如这里的int)就认为它是”强类型“的,这实际上属于完全的误解——甚至 C 语言还是静态弱类型的。判断一个变量是什么类型的,实际上根本不需要依赖于程序员显式声明类型:

a = 1
b = true
c = []

在上面这段代码中,程序员并没有为这三个变量声明类型,但编译期仍旧能够很容易地得知 a 是数字、b 是布尔量、而 c 是一个列表/数组。这种无需显式声明类型也能推导出变量类型的能力被称为”类型推断“。因此,在很多现代的静态类型编程语言中,都是可以在许多情况中使用编程语言的类型推断能力,而无需操心为变量给出一个具体的声明的:

auto h{new Holder{42}}; // Holder<int> *
auto num{42} // int
auto nums{new int[10]}; // int *
auto nums2{std::array<int, 5>{1, 2, 3, 4, 5}}; // std::array<int, 5>
std::array<int, 5> nums3{1, 2, 3, 4, 5}; // std::array<int, 5>

静态类型语言一个很大的优势在于,能够通过类型系统在编译阶段检查出程序中一些潜在的 BUG,例如考虑下面这段 Python 代码:

def get_text_length(s):
    return len(s)

get_text_length(42)

这段代码只有在实际运行到了get_text_length(42)这一句时才会报错。而在一些较大的项目中,有些代码可能本就很难被运行到,若类似这样的代码潜藏在程序中,就会产生许多难以发现的 BUG。而在一些静态类型语言,比如 TypeScript 中,这样的问题将在编译时被检查出来:

function getTextLength(s: string) {
  return len(s);
}

getTextLength(42);

在编译时,getTextLength(42)会直接报错,因为 TypeScript 知道getTextLength函数只能用于字符串。类似这样的被称作”静态类型检查“的工作提升了代码的可靠性。

再考虑下面这段 Python 代码,思考静态类型带来的另一个优势:

def process_string(s):
    s.split(...)
    ...

process_string(...)

在你编写process_string函数时,你会发现当你输入了s.之后,编辑器并没有像你期望地给出提示,告诉你变量s上可以调用哪些方法,比如splitjoinisnumeric等——因为 Python 不知道变量s是什么类型的,不知道它是字符串、数字、布尔量或是其他什么,因此自然不会给你这些提示。

而在 TypeScript 中,你标注了s的类型之后,编辑器就能根据类型给出相应的提示:

function processString(s: string) {
  s.split(...)
  ...
}

processString(...)

在这里,当你输入了s.之后,编辑器知道变量s是一个字符串,因此就会给出相应的提示。

这就是静态类型的另一大优势:可以在编写代码时给出更多的提示。并且,结合之前提到的”类型推断“,实际上在很多现代的静态类型语言中,你都不怎么需要手动标注变量的类型——最常见的只是你需要标注函数中参数的类型而已。因此,对于很多程序员来说,静态类型语言反而意味着只要付出极少的代价(添上一点点类型标注)就可以获得极高的编程效率提升(得益于编辑器提供的智能提示和静态类型检查)。

而在过去,在编辑器和编译器还没有今天这么智能的时代,静态类型语言既没有类型推断,而编辑器也没有智能提示,人们普遍认为静态类型语言的开发效率要比动态类型语言更低。然而在今天,事情已经完全不一样了。

不过,动态类型语言也并非完全无法享受类型推断与智能提示带来的好处。例如 Python 自 3.5 后提供了被称作”类型提示(type hints)“的语法,你也可以通过在变量后面加个冒号,标上可选的类型,提示编辑器该变量的类型,以获得智能提示,就像 TypeScript 一样。很多动态类型编程语言都采用了这种方式,这被称为”渐进式类型“,即并不强制标上类型,而是在你认为需要的地方标上类型。只不过,相比大多数静态类型语言,大多数动态类型语言中的”渐进式类型“不够完备,缺少足够强大的类型推断能力。

Last updated