论编程界的日经问题:到底如何区分静态类型和动态类型、强类型和弱类型?

论编程界的日经问题:到底如何区分静态类型和动态类型、强类型和弱类型?

我发现在我加的一些编程交流群里,几乎每半个月就会产生这样的一些争论:"Python 到底是强类型语言还是弱类型语言","为什么 JavaScript 是弱类型语言","动态类型语言和静态类型语言的区别是什么"...... 这些争吵喋喋不休,大多很难有一个确定的共识。

其实大家很难争吵出共识是很正常的,因为对于静态类型和动态类型,强类型和弱类型这些概念来说,他们本身就没有什么确定的概念,大家基于一个模糊的概念各说各的,自然得不出一个确切的答案。

但是如果我们按照一些已有的共识来重新规范一下两对类型的概念,那么其实还是很容易得出答案的。

值得一提的是,无论是静态类型和动态类型,还是强类型和弱类型,这些概念都是基于语言的语法这一层次来定义的,而不是语言的内部设计,否则我们大可以说:"所有语言最后都是由 0 和 1 组成的",那么就没有办法再谈什么"类型"了。

强类型和弱类型

有关强类型和弱类型的定义大都比较模糊,这里我采用 Wikipedia 上的一个结论:

强类型的语言遇到函数参数类型和实际调用类型不符合的情况经常会直接出错或者编译失败;而弱类型的语言常常会实行隐式转换,或者产生难以意料的结果。

先说结论,以下语言属于强类型:C#, Java, Scala, Kotlin, Groovy, rust, go, Python, TypeScript,而这些语言则属于弱类型:C, C++, JavaScript, PHP

我相信一部分人看到这个分类的时候一定已经开始有一些疑问了,别急,让我们慢慢道来......

Python 为什么是强类型

很多人觉得 Python 不是一个强类型的语言,因为其在变量声明时不需要指定类型,也很少见到 Python 提及"类型"这一概念。但其实,Python 是一门强类型的动态类型语言 ,虽然在变量声明时我们不需要显式指定类型,但是"类型"这一概念是实际存在的,举个例子,以下 Python 代码会获得一个 TypeError

arduino 复制代码
>>> 1+""
​
Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    1+""
TypeError: unsupported operand type(s) for +: 'int' and 'str'

这是因为我们将 int 类型和 str 类型相加导致的,Python 不知道应该如何将这两种类型相加。但是反观经典弱类型语言 JavaScript 会如何处理:

arduino 复制代码
> 1+""
< '1'

很显然,JavaScript 愉快的为这两种不同类型的变量做了隐式的类型转换,而此类类型转换在 JavaScript 中屡见不鲜,甚至沦为笑谈,而这一切都是弱类型的锅。

C, C++ 为什么是弱类型

有些人看到 C 和 C++ 是弱类型的时候可能会大吃一惊,怎么可能,C 和 C++ 明明拥有严格的变量类型标注才对!

但是想想 void*,想想数组传参时的指针弱化,他们都证明了 C 和 C++ 会随时进行隐式类型转换,而这种隐式类型转换在 C 和 C++ 中仍然是无处不在,这也是它们被称为弱类型语言最好的佐证。

警惕!语法糖不是弱类型

经过上面的介绍,你可能会联想到 Java 在字符串连接时可以由不同的类型,例如:

ini 复制代码
String a = 1 + "" // "1"

或者在 Python 中,也可以在流程控制表达式中使用非 bool 类型:

bash 复制代码
if 1:
    print("hit")
else:
    print("not hit")

但他们实际上都是语法糖而已,Java 的字符串连接是自动装箱和 StringBuilder 的语法糖,而 Python 则是为所有类型隐式调用了 __bool__ 属性得到 bool 类型而已。这都不能说明这个语言的类型系统是弱类型。

静态类型和动态类型

我们一般认为以下语言是静态类型语言:C, C++, C#, Java, Scala, Kotlin, rust, go,而这些语言则属于动态类型:Python, JavaScript, TypeScript, PHP, Groovy

其实动态类型语言和静态类型语言的区别主要是:变量类型是在编译期确定还是在运行时确定。如何理解?在 Python 中尝试以下代码:

ini 复制代码
a = 1
a = ""

显而易见的,这段代码可以正常被运行,但是注意到了吗,a 变量的类型从 int 变为了 str(这同时也佐证了 Python 是一门强类型的语言,虽然其不需要显式声明变量类型,但是强类型定义的系统是内部存在的),那么这样的代码在 Java 中能否正确运行呢?尝试在 jshell 中执行试试:

ini 复制代码
|  Welcome to JShell -- Version 17.0.2
|  For an introduction type: /help intro
​
jshell> var a = 1
a ==> 1
​
jshell> a = ""
|  Error:
|  incompatible types: java.lang.String cannot be converted to int
|  a = ""
|      ^^
​
jshell>

在这里我特意用了 var 关键字来声明一个变量,而不是显式声明变量类型,是想表明一个观点:动态类型和变量类型推断是完全不同的两个东西,虽然 Java 提供了 var 关键字让我们可以无须显式指定一个变量的类型,但是该变量类型依然在编译期就会被确定下来;上例 a 变量的类型被推断为 int,因此就不能再被赋值为 java.lang.String 对象,所以产生了编译错误。

当然,这里我们还需要讨论两个边界情况:

C# 的 dynamic 关键字

C# 存在一个 dynamic 关键字,使用 dynamic 关键字标注的变量的类型推断和函数调用检查都会被从编译期推迟到运行时,以下代码在 C# 中会引发报错:

go 复制代码
C# > var a = 1;
​
C# > a = "";
​
❌ Microsoft.DotNet.Interactive.CodeSubmissionCompilationErrorException: (1,5): error CS0029: Cannot implicitly convert type 'string' to 'int'

但是以下代码可以正常运行:

shell 复制代码
C# > dynamic b = 1;
​
C# > b = ""

当然,即便如此,我们仍然认为 C# 是一个静态类型语言。

rust 的 variable shadowing

rust 中,你可以在同一作用域中重复声明多个名称相同的变量,后者则会代替前者:

ini 复制代码
let spaces = "   "; // &str
​
let spaces = spaces.len(); // usize

仔细看,这可不是什么动态类型!两个变量的名字虽然相同,但是并没有进行重新赋值,而是后者作为一个新的变量代替了前者。如果转用赋值,则会得到报错:

ini 复制代码
let spaces = "   "; // &str
​
spaces = spaces.len(); // usize
​
error[E0308]: mismatched types
 --> src\main.rs:3:14
  |
2 |     let spaces = "   "; // &str
  |                  ----- expected due to this value
3 |     spaces = spaces.len(); // usize
  |              ^^^^^^^^^^^^ expected `&str`, found `usize`

Python 的 type hint

Python 在其 3.5 版本引入了一个名为 typing 的功能,可以为 Python 函数提供函数参数和返回值类型声明:

python 复制代码
def greeting(name: str) -> str:
    return 'Hello ' + nam

你可能会认为在这种情况下 Python 成为了一个静态类型的语言,但是实际上这种 type hint 只是一个暗示(正如 hint 的意思),可以被其他第三方工具采用,但并不会被 Python 运行时强制使用(Note: The Python runtime does not enforce function and variable type annotations. They can be used by third party tools such as type checkers, IDEs, linters, etc. ),这也就是说以下代码完全可以正常运行:

python 复制代码
>>> def print_me(me: int):
... print(me)
... 
>>> print_me("Hello, World")
    Hello, World

最后

之所以想写这篇文章是因为今天某个群又因为这个问题吵起来了,但好在最后大家还是达成了一个很好的共识的。讨论之末,有人问了一个很有意思的问题:"我一直想知道了解语言的 typing system 分类对工程应用有什么帮助",这确实引发了我的一些思考,即使我们争论的喋喋不休,又或者终于达成了某种共识,那么这种结果对我们的工程开发有什么实际的意义吗?

经过简单的思考后,我给出了一个同样简单的答案:

屁用不顶,说是八股也不是不行。

所以这种讨论还是少点好吧(

引用

相关推荐
Nan_Shu_6142 分钟前
学习: Threejs (2)
前端·javascript·学习
G_G#10 分钟前
纯前端js插件实现同一浏览器控制只允许打开一个标签,处理session变更问题
前端·javascript·浏览器标签页通信·只允许一个标签页
@大迁世界26 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路34 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
勇哥java实战分享36 分钟前
短信平台 Pro 版本 ,比开源版本更强大
后端
是一个Bug38 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213840 分钟前
React面向组件编程
开发语言·前端·javascript
学历真的很重要40 分钟前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
计算机毕设VX:Fegn089543 分钟前
计算机毕业设计|基于springboot + vue二手家电管理系统(源码+数据库+文档)
vue.js·spring boot·后端·课程设计
上进小菜猪1 小时前
基于 YOLOv8 的智能杂草检测识别实战 [目标检测完整源码]
后端