把编程语言分为强弱类型没有意义

把编程语言分为强弱类型本来就是很蠢的分类方式,得亏初学的时候没怎么见过,不然也得被绕进去。
隐式类型转换是编程语言中的重要部分,没有它编程就会变得异常复杂,所以哪怕像Rust这样学究式的语言,也提供了解引用自动多态的隐式类型转换方法。

真正要关注的是一个语言什么时候会发生隐式类型转换,以及这种隐式类型转换会造成多大的影响。
例如C、C++中存在数字的隐式类型转换,就需要注意长度,避免出现截断、环绕等现象发生。
而众所周知的弱类型语言JavaScript,则提供了数不清的隐式类型转换,成为了前端的天坑之一,我今天还看到讲parseIntMath.floor区别的视频,如果JS肯在parseInt参数不是字符串时报错,哪里还有这些视频的生存空间。

而同样是动态类型脚本语言的Python就要好得多,除了数字运算会出现整数到小数丢失精度以外,虽然存在隐式类型转换(如果你认为双目运算符两边类型不一样就算的话),但其语义都非常清晰,并且具有良好的文档支持(不要去瞎自定义魔术方法的情况下)。例如内置的序列(tuple, list, str, bytes, bytearray)都支持乘一个数字,其结果为重复n次,再比如numpy的广播。我认为这些都是隐式类型转换的优秀实践,符合直觉时允许这么做,而出现模糊的直觉时则尽快报错。

而当前较新的静态类型语言,都禁止了绝大部分的隐式类型转换。例如Golang中,不同精度的整数、浮点数禁止直接运算、类型别名也不会发生隐式类型转换、不允许重载运算符、传参时也不会自动提取出嵌入的匿名结构体,而需要手动提取(如果不了解Go,你可以认为其行为类似于父类指针无法指向子类对象)。Go中仅会发生具体类型到接口的隐式类型转换,可以说是编程语言中最严格的一档了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Runnable interface {
Run()
}
type Animal struct{}
func (a *Animal) Run() {}
type Dog struct {
Animal // 组合而非继承的匿名结构体,但提供了一部分继承的能力
}
func ReceiveRunnable(r Runnable) {}
func ReceiveAnimal(a *Animal) {}
func main() {
var dog Dog
ReceiveRunnable(&dog) // 可以,发生从指针到接口的隐式转换
ReceiveAnimal(&dog) // 不可以,无法提取匿名结构体
}

隐式类型转换造成最大问题是出现了无法预测隐式控制流,进而造成难以理解或不可预期的行为,例如C++会自动执行构造函数、JS的('b' + 'a' + + 'a' + 'a').toLowerCase()==='banana'。所以C反而没什么问题:除了数字运算之外,C的隐式类型转换其实没有带来任何隐式控制流–指针的类型转换(无论是强转还是隐式)都没有任何动作,它们仅仅发生在编译期。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2019-2025 Ytyan

请我喝杯咖啡吧~