误区
静态类型语言 != 强类型语言, 动态类型语言 != 弱类型语言, 这是两个不同的评判编程语言特征的体系.
静态类型语言和动态类型语言
评价维度为 变量的类型是否需要显示指明, 指明后是否可变化.
静态类型语言
以 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:
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 了:
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:
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 作为强类型的一面:
a = 1
b = '1'
# c = a + b # 整形无法与字符自动相加虽然变量可以随时绑定任意类型的数据, 但不同类型之间做不相容的运算并不兼容.
然后还是我们的 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 死之前不可用. 关于这点可以参考之后将会引入的生命周期机制.
弱类型语言
请输入文本: 
孰优孰劣?
很明显, 这取决于你愿意让语言替你做多少事. 在 AI 编程盛行的当下, 这一问题有了更倾斜的答案.