Skip to content

误区

静态类型语言 != 强类型语言, 动态类型语言 != 弱类型语言, 这是两个不同的评判编程语言特征的体系.

静态类型语言和动态类型语言

评价维度为 变量的类型是否需要显示指明, 指明后是否可变化.

静态类型语言

以 Java 为例:

java
int someNumber = 114514;        // 整形
String someString = "abcd1234"; // 字符串类型
final double PI = 3.14159265358;// 双精度浮点型

public int add(int a, int b) {  // (int, int) -> int
    return a + b;
}

如上, 所有变量定义时必须显式说明类型, 否则编译器并不认识我们要做什么.

又例如我们正在学的 Rust:

rust
let some_number = 114514;           // 推断为 32 位整形     i32
let some_string = "abcd1234";       // 推断为字符串切片类型 &str
const PI:f64    = 3.14159265358;    // 指明为 64 位浮点型   f64

pub fn add(a: i32, b: i32) -> i32 { // (i32, i32) -> i32
    a + b
}

技术上来讲, Rust 比 Java 更静态, 但我们大多数时候无需手动指定类型, 强大的编译器将根据实际初始化的内容自动推断类型, 这一定程度上得益于, Rust 不将变量类型作为定义变量的关键字, 这使定义变量指明类型两个不同的行为相解耦.

动态类型语言

著名臭名昭著的就是 JavaScript 了:

js
let x = 114514;      // 此刻 x 持有 number
x = "呜呼";          // 此刻 x 持有 string
x = null;            // 此刻 x 持有 null
x = undefined;       // 此刻 x 持有 undefined

function add(a, b) { // (?1, ?2) -> ?3
    return a + b;
}

稍后我们还会遇到这位仙人.

另一个动态语言的例子是地表第一胶水语言——Python:

py
x = 114514                  # 整形
x = "114514"                # 字符串
x = [1, 1, 4, 5, 1, 4]      # 列表
x = lambda y: y*2           # 闭包

def foo(flag):              # ?1 -> ?2
    if flag:
        return 114514       # ?1 -> 整形
    else
        return "114514"     # ?1 -> 字符串

在这里你甚至可以让函数返回不同类型的东西.

在动态语言中, 创建变量往往无需指明类型, 使用变量时也无需关心类型是否匹配.

孰优孰劣?

答曰: 开发成本越低, 动态类型越优; 表达需求越强, 静态类型愈佳.

前者的例子就是 Python, 他太好用了, 所以大量非程序岗的工作者也可以用 Python 写自己想要的工具, 这其中包括但不限于数学工作者, LLM 研发岗, 建模师, etc.

后者的例子是 JavaScript, 运行时过于随意且不可控的隐式猜测使其前景大受掣肘, 由于其日渐重要的应用场景, JS 的超集, TypeScript 应运而生: TypeScript 是静态类型语言.

强类型语言与弱类型语言

评判标准是 是否允许隐式转换.

有时会有此般场景:

int a = 1;
string b = "2";
print(a+b);

根据语言的强弱有不同的处理方式.

强类型语言

继续看 Python 作为强类型的一面:

py
a = 1
b = '1'
# c = a + b # 整形无法与字符自动相加

虽然变量可以随时绑定任意类型的数据, 但不同类型之间做不相容的运算并不兼容.

然后还是我们的 Rust:

rust
let a:i32   = 1;                    // 显式定义为 i32 类型
let b:i64   = 2;                    // 显式定义为 i64 类型
// let c    = a + b;                // 隐式转换: 非法
let c       = a + (b as i32);       // 显式转换: 若存在此转换则合法
//----------------------------------//-变量遮蔽--------------
let a       = String::from("hello");// 自动推断为 String 类型
let b       = "World";              // 自动推断为 &str 类型
// let c    = b + a;                // 非法
let c       = a + b;                // 合法

变量遮蔽

重复定义相同名称不同类型的变量并不会出错, 但其中细节为: (伪代码)

let a:TypeA = someval;
    <=> new a in TypeA = someval, 新建一个 TypeA 类型的变量 a 为 someval
let a:TypeB = otherval;
    <=> drop a, 首先掩盖原有的 a
     => new a in TypeB = otherval,新建一个 TypeB 类型的变量 a 为 otherval

形象地说, 新的 a 遮蔽了原有的 a, 原有的 a 在现在的 a 死之前不可用. 关于这点可以参考之后将会引入的生命周期机制.

弱类型语言

请输入文本: alt text

孰优孰劣?

很明显, 这取决于你愿意让语言替你做多少事. 在 AI 编程盛行的当下, 这一问题有了更倾斜的答案.