静态类型语言和动态类型语言有什么区别?

1217

当我们说一种语言是动态类型语言还是静态类型语言时,这意味着什么?


1
@EricLeschinski,我认为单元测试现在可以帮助解决这个问题,而像JavaScript这样的动态类型语言可以放心地编码,因此使其有资格用于企业软件开发,你觉得呢? - pixel 67
8
最好的情况是那些单元测试会随着时间的推移而恶化,并被试图增加工作保障的同事关闭;在最坏情况下,它们根本就没有被编写。这就像建议一位专业机修工使用胶带来修理客户的汽车一样。没错,新手,对你来说,在这个变速器维修中使用胶带是个好主意…… - Eric Leschinski
这篇关于编程的文章可能会对你有所帮助:https://android.jlelse.eu/magic-lies-here-statically-typed-vs-dynamically-typed-languages-d151c7f95e2b - Dr Nisha Arora
19个回答

1096

静态类型语言

如果变量的类型在编译时已知,则该语言为静态类型。对于某些语言,这意味着作为程序员的您必须指定每个变量的类型;其他语言(例如:Java、C、C++)提供某种形式的类型推断,即类型系统能够推断变量的类型(例如:OCaml、Haskell、Scala、Kotlin)。

主要优点在于编译器可以进行各种检查,因此很多琐碎的错误都可以在非常早期被捕获。

示例:C、C++、Java、Rust、Go、Scala

动态类型语言

如果类型与运行时值相关联,而不是命名的变量/字段等,则该语言为动态类型。这意味着作为程序员,您可以写得更快,因为您不必每次都指定类型(除非使用具有类型推断的静态类型语言)。

示例:Perl、Ruby、Python、PHP、JavaScript、Erlang

大多数脚本语言都具有此功能,因为没有编译器进行静态类型检查,但您可能会发现自己在寻找由解释器错误解释变量类型而导致的错误。幸运的是,脚本通常很小,因此错误没有太多藏身之处。

大多数动态类型语言允许您提供类型信息,但不要求。目前正在开发的Rascal语言采用混合方法,在函数内允许动态类型,但强制执行函数签名的静态类型。

6
你能否列举出一些动态类型语言并实现 HM 类型推断? - Pete Kirkham
116
如果变量的类型在运行时被解释,那么语言就是动态类型语言:不对应于命名变量/字段等,而是与运行时的值相关联。 - Paul Biggar
19
错误的静态类型意味着“引用值明显(这与编译时不同)受到限制,以便于其能够表示的值类型相对应,并且语言实现(无论是编译器还是解释器)尽可能地强制执行和使用这些约束。” 这段话摘自http://c2.com/cgi/wiki?StaticTyping,而我所理解的是正确的。请注意,翻译并未改变原文意思,也没有添加解释或其他内容。 - Matt
6
Java、C、C++、Pascal和许多其他广泛使用的“工业”语言的类型系统最明显的特点不是它们是静态类型,而是它们是显式类型。换句话说,它们需要大量的类型声明。(在不太显式地声明这些变量类型的编程语言中,这些声明通常被称为“类型标注”。)这与静态类型无关。 - Vipresh
8
第一批静态类型语言是由于必要性而显式声明类型的。然而,类型推断算法已经存在多年,它可以查看没有任何类型声明的源代码,并决定其变量的类型。使用此技术的ML语言已经存在,改进了此方法的Haskell已有约15年历史。甚至连C#现在也采用了这个想法,这将引起很多人的关注,并无疑会引起“弱类型”方面的争议。继续... - Vipresh
显示剩余12条评论

516

类型检查是验证和强制执行类型限制的过程。

  • 静态类型编程语言在 编译时 进行类型检查。
    例如:Java,C,C++。

  • 动态类型编程语言在 运行时 进行类型检查。
    例如:Perl,Ruby,Python,PHP,JavaScript。


27
我认为这是最佳答案,特别是被采纳的回答在很大程度上事实上不正确。 - J D
2
@JonHarrop 具体来说有哪些方面? - 1252748
27
这意味着作为程序员,如果你使用类型推断,你不必每次都指定类型,从而可以更快地编写代码。例如 SML、OCaml、F# 和 Haskell 等语言都支持静态类型和类型推断。 - J D
4
在静态类型编程语言中,类型检查是在运行时之前进行的,但并不完全是在编译时进行的。 - Luiz Felipe
让我更加混淆你:从Java 10开始,引入了var关键字,为Java开发者提供了一种 动态类型 的体验,但Java仍然是静态类型 的。 - jumping_monkey
3
@jumping_monkey 我认为你把动态类型类型推断混淆了。使用var时,你告诉编译器,“请为我推断类型”。程序会被编译成与你写下的特定类型完全相同。当变量使用不正确时,编译器将发出类型错误。动态类型意味着类型仅在运行时(实际上与变量无关)才知道,正如NomeN所指出的那样。当然,这在Java中完全不是这种情况。 - Laar

384

这里有一个例子,对比展示了Python(动态类型语言)和Go(静态类型语言)处理类型错误的方式:

def silly(a):
    if a > 0:
        print 'Hi'
    else:
        print 5 + '3'

Python在运行时进行类型检查,因此:

silly(2)

代码完全正常,输出结果为Hi。只有当触发出问题的那行代码时才会引发错误:

silly(-1)
产生
TypeError: unsupported operand type(s) for +: 'int' and 'str'

因为相关的行实际上被执行了。

另一方面,Go语言在编译时进行类型检查:

package main

import ("fmt"
)

func silly(a int) {
    if (a > 0) {
        fmt.Println("Hi")
    } else {
        fmt.Println("3" + 5)
    }
}

func main() {
    silly(2)
}
上述代码无法编译,会出现以下错误:
invalid operation: "3" + 5 (mismatched types string and int)

12
感谢提供整洁的例子。因此我推断所有脚本语言都是动态类型的,因为它们没有编译过程? - CᴴᴀZ
10
是的,所有脚本语言都是动态类型语言,因为它们没有编译器来进行静态类型检查。这一点已经在这篇文章中被阐述了:http://www.sitepoint.com/typing-versus-dynamic-typing/。 - Shashi
9
Scala可以用作脚本语言,并且它是静态类型的!#讨论@Shashi - Sagiruddin Mondal
6
@Shashi 编译并不意味着是静态类型的。例如,Haskell可以使用runhaskell进行解释。 - Joshua Grosso Reinstate CMs
6
一段“代码片段”胜过千言万语。之前的回答中所有啰嗦并没有像这段代码一样点亮我的脑海。现在那些啰嗦完全讲得通了 :) 非常感谢。 - HopeKing
显示剩余11条评论

212

简单来说:在静态类型语言中,变量的类型是静态的,这意味着一旦你将一个变量设置为某种类型,就不能更改它。因为类型与变量相关联,而不是与它所引用的值相关联。

例如,在Java中:

String str = "Hello";  // variable str statically typed as string
str = 5;               // would throw an error since str is
                       // supposed to be a string only

然而,在动态类型语言中,变量的类型是动态的,这意味着在将变量设置为某种类型后,您可以更改它。这是因为类型是与其假定的值而不是变量本身相关联的。

例如,在Python中:

some_str = "Hello"  # variable some_str is linked to a string value
some_str = 5        # now it is linked to an integer value; perfectly OK

因此,在动态类型语言中,最好将变量视为指向类型值的通用指针

总之,类型应该描述(或者说应该描述)语言中的变量,而不是语言本身。在我看来,它可以更好地用作具有静态类型变量的语言具有动态类型变量的语言之间的区分。

静态类型语言通常是编译型语言,因此编译器会检查类型(很合理对吧?因为类型不能在运行时更改)。

动态类型语言通常是解释型的,因此类型检查(如果有的话)发生在使用时的运行时。当然,这会带来一些性能成本,并且是动态语言(例如Python、Ruby、PHP)不如静态语言(Java、C#等)扩展良好的原因之一。从另一个角度来看,静态类型语言有更多的启动成本:通常需要编写更多、更难的代码。但这会带来回报。

好消息是,两边都在互相借鉴特性。类型化语言正在整合更多的动态特性,例如C#中的泛型和动态库,而动态语言正在加入更多的类型检查,例如Python中的类型注释,或者PHP的HACK变体,这些通常不是语言的核心部分,可以按需使用。

在技术选择方面,两边都没有固有的优势。这只是一个偏好问题,你是想要更多的控制权还是更多的灵活性。选择适合工作的正确工具,并确保在考虑切换之前检查在相反方面可用的内容。


17
这很有道理。我认为它比其他答案更好地解释了名字背后的原因。 - JamEngulfer
1
Lucas,相反,该文档一直强调Python既是强类型又是动态类型。你在哪里看到的?你能引用一下吗? - mehmet
2
我认为这个答案以最简单的方式最好地传达了概念。许多其他答案尝试抽象地描述概念,但在某些细节上失败了。我宁愿看到这个答案排在列表的顶部。 - Hawkeye
7
大多数其他答案让我产生了更多的问题,而这个回答却解决了它们所有。在我看来,这个答案真的应该排在最前面。 - Hami Torun
2
我认为“typed”这个词会让人感到不清晰。你的回答帮我澄清了这一点 :)过去每次听到这个词时,我都在想键盘输入,就像与变量声明或未声明的方式有关;数据类型从未在我的脑海中出现过。因此,“Typed”指的是变量的数据类型能否改变状态,无论变量的数据类型是静态还是动态。例如:动态(Str -> int -> Boolean)。 - ThomasRones
显示剩余8条评论

47

http://en.wikipedia.org/wiki/Type_system

静态类型

编程语言在编译时执行类型检查而非运行时被称为静态类型。在静态类型中,类型与变量相关联而非值。静态类型语言包括Ada、C、C++、C#、JADE、Java、Fortran、Haskell、ML、Pascal、Perl(区分标量、数组、哈希和子程序)和Scala。静态类型是程序验证的一种有限形式(见类型安全性),因此它允许在开发周期的早期捕获许多类型错误。静态类型检查器只评估可以在编译时确定的类型信息,但能够验证所检查的条件对程序的所有可能执行都成立,这消除了每次执行程序时重复进行类型检查的需要。通过省略运行时类型检查并启用其他优化,程序执行也可以更加高效(即更快或占用更少的内存)。

由于它们在编译过程中评估类型信息,因此缺乏仅在运行时可用的类型信息,静态类型检查器是保守的。它们将拒绝一些可能在运行时表现良好但不能静态确定可以很好地类型化的程序。例如,即使一个表达式在运行时始终评估为true,包含该代码的程序也会被拒绝。

if <complex test> then 42 else <type error>
在运行时,而不是在编译时,因此 只有在程序运行时才会出现类型错误, 当值的类型与操作所要求的类型不匹配时。 这样的错误可能很难发现,因为它们 只能在特定输入或情况下出现。动态类型 检查器不能在编译时捕获这些错误, 但通常会在运行时抛出异常。

与静态类型相比,动态类型可以更加灵活, 因为它允许程序基于运行时数据生成类型和功能, 但这也意味着它提供的保证较少。许多动态类型 语言具有强大的元编程能力,可以修改自身的 行为,这使得它们对于某些问题(例如模板编程) 非常有用。

类型推断

类型推断是一种编程语言特性, 可以在编译时自动推断变量的类型, 而无需人工标注类型信息。类型 推断通常与静态类型检查器一起使用, 因为它可以减轻程序员的负担并提高 代码可读性。常见的支持类型推断的语言包括 C++11、Go、Haskell、Java 8、Kotlin、Rust等。

在程序错误发生的地方之后很久,也就是错误类型的数据传入了不应该传入的位置。这使得错误难以定位。

与静态类型的编程语言相比,动态类型的语言系统对源代码进行较少的“编译时”检查(但会检查程序是否符合语法规范)。运行时检查可能更加复杂,因为它们既可以使用动态信息,也可以使用编译期间存在的任何信息。另一方面,运行时检查只断言在程序特定执行中条件成立,这些检查将在每个程序执行中重复进行。

在动态类型语言中进行开发通常需要借助单元测试等编程实践。测试是专业软件开发的关键实践,在动态类型语言中尤其重要。实际上,为确保程序正确运行所进行的测试可以检测到比静态类型检查更广泛的错误,但反过来,它不能像测试和静态类型检查一样全面地搜索能够检测到的错误。测试可以并入软件构建周期中,在这种情况下,它可以被视为“编译时”检查,因为程序用户不必手动运行此类测试。

参考文献

  1. Pierce, Benjamin (2002). Types and Programming Languages. MIT Press. ISBN 0-262-16209-1.

5
因为这是指向维基百科而不是一些临时性网站的链接,所以这似乎有些多余,但我会记住下次不这样做。 - Jacob
2
不知道为什么,我仍然想不到在动态类型语言中存在这样一种情况:类型在编译时不清楚,但必须在运行时确定。你能给我提供一些例子吗? - Novellizator
5
@Novellizator 老评论了,但是可以想象一种情况,即从远程服务器中获取某些数据,然后使用这些数据来选择对象的属性。例如:myObject[remoteDataName]。那么就无法知道它将选择哪个属性,甚至无法确定它是否是一个有效的属性。 - Mike Cluck

19

编译语言 vs. 解释语言

当源代码被翻译时……

  • 源代码:原始代码(通常是由人类输入到计算机中的)
  • 翻译:将源代码转换为计算机可读的内容(即机器码)
  • 运行时:程序执行命令的时间段(如果编译,则在编译后)
  • 编译语言:在运行时之前被翻译的代码
  • 解释语言:在执行期间即时翻译的代码

类型检查

当类型被检查时……

5 + '3'强类型 语言,如 Go 和 Python 中的类型错误示例,因为它们不允许 "类型强制转换" -> 在某些情况下,值可以改变类型(例如合并两种类型)。弱类型 语言,例如 JavaScript,不会抛出类型错误(结果为 '53')。

  • 静态:在运行时之前检查类型
  • 动态:在执行期间即时检查类型

"静态和编译" 与 "动态和解释" 的定义非常相似……但请记住,这是 "类型被检查时" 与 "源代码被翻译时"的区别。

无论语言是否编译,您将获得相同的类型错误!您需要在概念上分离这些术语。


Python 示例

动态,解释

def silly(a):
    if a > 0:
        print 'Hi'
    else:
        print 5 + '3'

silly(2)

因为Python既是解释性的又是动态类型的,它只在执行代码时翻译和检查类型。所以else块永远不会执行,因此5 + '3'甚至都没有被考虑过! 如果它是静态类型的呢? 在代码运行之前将会抛出类型错误。尽管它是解释性的,但仍会在运行时之前执行类型检查。 如果它被编译了呢? else块将在运行之前被翻译/检查,但由于它是动态类型的,所以不会抛出错误!动态类型语言直到执行时才检查类型,而那一行永远不会执行。

Go示例

静态、编译
package main

import ("fmt"
)

func silly(a int) {
  if (a > 0) {
      fmt.Println("Hi")
  } else {
      fmt.Println("3" + 5)
  }
}

func main() {
  silly(2)
}

在运行之前(静态),类型会进行检查,因此类型错误会立即被捕获!如果是解释型的话,运行时仍然会对类型进行检查,结果相同。如果是动态的,则不会抛出任何错误,即使代码在编译期间被查看也是如此。


性能

如果一个编译语言是静态类型的(与动态类型相比),那么它在运行时将具有更好的性能;类型知识允许进行机器码优化。

由于在执行时无需动态检查类型(它在运行之前检查),所以静态类型的语言本质上具有更好的运行时性能。

同样,编译语言在运行时更快,因为代码已经被翻译,而不需要实时“解释”/翻译它。

请注意,编译和静态类型的语言都需要在运行之前进行翻译和类型检查,因此会有一定的延迟。


更多差异

静态类型可以提早捕获错误,而不是在执行过程中发现错误(特别适用于长程序)。它更加“严格”,不允许程序中出现任何类型错误,并且通常防止变量改变类型,从而进一步防止意外错误。

num = 2
num = '3' // ERROR

动态类型更加灵活,一些人很欣赏这一点。它通常允许变量改变类型,这可能会导致意外错误。


因为Python既是解释型的,又是动态类型的,所以它只会对它正在执行的代码进行翻译和类型检查 - 这并不完全正确。 Python(至少是参考实现)在导入时编译所有代码(您也可以在没有导入模块的情况下编译模块)。 编译器引入了各种优化(至少在Python的动态性允许的范围内)。 - Eli Korvigo

17
术语“动态类型”是不幸的误导。所有语言都是静态类型的,类型是表达式的属性(而不是一些人认为的值的属性)。然而,有些语言只有一种类型。这些被称为单类型语言。这样的语言的一个例子是无类型λ演算。
在无类型λ演算中,所有术语都是λ术语,唯一可以对一个术语执行的操作是将其应用于另一个术语。因此,所有操作总是产生无限递归或λ术语,但从不发出错误信号。
然而,如果我们使用原始数字和算术运算来扩展无类型λ演算,那么我们可以执行荒谬的操作,例如将两个λ术语相加:(λx.x) + (λy.y)。可以说,唯一明智的做法是在此时发出错误信号,但要能够做到这一点,每个值都必须标记为指示符,指示该项是λ术语还是数字。然后,加法运算符将检查确实两个参数都被标记为数字,如果它们没有,则发出错误信号。请注意,这些标记不是类型,因为类型是程序的属性,而不是由这些程序产生的值的属性。
一个这样做的单类型语言称为动态类型。
JavaScript、Python和Ruby等语言都是单类型的。再次强调,JavaScript中的typeof运算符和Python中的type函数的命名具有误导性;它们返回与操作数相关联的标签,而不是它们的类型。同样地,C++中的dynamic_cast和Java中的instanceof并不进行类型检查。

4
想象一下回答一个问题,却让我们产生了十个更多的问题... - Robert O'Toole

12

静态类型语言:每个变量和表达式在编译时已知。

(int a; 在运行时a只能取整数类型的值)

例子:C、C++、Java

动态类型语言:变量可以在运行时接收不同的值,并且它们的类型在运行时定义。

(var a; 在运行时a可以取任何类型的值)

例子:Ruby、Python。


11
在编程中,数据类型是一种分类,它告诉我们1)变量将保存什么类型的值,以及2)可以在这些值上执行哪些数学、关系和逻辑操作而不会出错。
当人们在以下情况下提到“类型”时,他们指的是“数据类型”。
在每种编程语言中,为了最小化出错的机会,类型检查要么在程序执行之前进行,要么在程序执行期间进行。根据类型检查的时间,有两种类型的编程语言:静态类型动态类型语言(或混合使用,用户说每个变量使用哪种(静态/动态))。
此外,根据是否发生隐式类型转换,有两种类型的编程语言:强类型弱类型语言。

typing- static vs dynamic, strong vs weak

静态类型:

  • 类型检查在编译时完成

  • 在源代码中,在变量声明时(即为新变量分配值的时间),必须明确指定该变量的数据类型(即不能是隐式/推断/猜测的),因为如果在源代码中指定了数据类型,则在编译时该源代码将被转换为机器代码并允许进行类型检查

  • 这里,数据类型与变量相关联(变量名而非值),例如 int count。此关联是静态的(固定的)

  • 如果我们尝试通过将不同数据类型的值(int count = "Hello")赋给已声明变量(int count)来更改已声明变量的数据类型,则会出现错误

  • 如果我们尝试通过使用不同数据类型(boolean count)重新声明已声明变量(int count)来更改数据类型,则也会出现错误

int count;         /* count is int type, association between data type
                      and variable is static or fixed */

count = 10;        // no error 
count = 'Hello';   // error 
boolean count;     // error 

由于类型检查和类型错误检测在编译时完成,因此在运行时不需要进一步进行类型检查。因此,程序变得更加优化,导致执行更快。 如果我们想要更具类型严格的代码,则选择这种类型的语言是更好的选择。 例子:Java,C,C++,Go,Swift等。

动态类型:

  • 类型检查在运行时完成

  • 在源代码中,在变量声明时,不需要显式指定该变量的数据类型。因为类型检查是在运行时完成的,语言系统从该变量分配值的数据类型确定变量类型

  • 这里,数据类型与分配给变量的值相关联。例如,var foo = 10:10 是一个数字,所以现在 foo 是数字数据类型。但这种关联是动态的(灵活的)

  • 我们可以通过将不同数据类型的值(foo = "Hi")分配给已经声明的变量(var foo = 10),而不会产生错误,轻松更改已经声明变量的数据类型

  • 我们可以通过使用其他数据类型的值重新声明它(var foo = true),而不会产生错误,轻松更改已经声明变量的数据类型(var foo = 10

var foo;            // without assigned value, variable holds undefined data type 

var foo = 10;       // foo is Number type now, association between data 
                    // type and value is dynamic / flexible 
foo = 'Hi';         // foo is String type now, no error 
var foo = true;     // foo is Boolean type now, no error 
  • 由于类型检查和类型错误检测是在运行时完成的,动态类型程序的优化程度较低,导致执行速度较慢。尽管这些类型的语言的执行速度可以更快,如果它们实现了JIT(即时编译器)

  • 如果我们想要轻松编写和执行代码,那么这种类型的语言是更好的选择,但不幸的是在运行时仍然可能出现错误

  • 例如:Python、JavaScript、PHP、Ruby等。


6

静态类型语言在编译时进行类型检查,类型不可更改。(不要在类型转换注释中玩花样,会创建一个新的变量/引用)。

动态类型语言在运行时进行类型检查,变量的类型可以在运行时更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接