知乎高赞:为什么编程语言会发展出“类型”?
大家好,我是猫哥。知乎上有一些质量很高的问题,比如这一个:
对于此问题,某百万大 V 给出了很高的评价:
我曾写过一篇长文介绍 Python 动态类型&强类型的话题(点击阅读),两周前,还从 B 站崩溃的事故分析中,讨论了 Lua 弱类型带来的隐藏 bug 问题(点击阅读),更早的时候也分析过为什么 Python 没有 void 类型 。但是,关于为什么编程语言会发展出“类型”、为什么变量需要有“类型”呢?
该问题下有很多大佬的精彩回答,这里给大家分享一篇,希望对你有所启发~~
因为……我们有一个梦想:如果我们随便提一下计算机就知道该怎么做、如果有什么程序可以自动识别程序里的逻辑错误,那该有多好啊!!!
实践中,无论你学习数学、物理还是别的什么,数字都是有类型的。
比如说,桌子两条边的夹角是90度,这个90就是有类型的,为了方便,我们记为90°。
然后,同样的,桌子的宽度是90厘米,这个90就是一个长度类型的数据,记为90cm。
这两个90是不能直接加的。桌子边长90cm,加上桌角90°,得个180——什么鬼东西这是?
类似的,有人调侃小学数学题:红色的恒星表面温度是4000℃,蓝色的恒星表面温度是9000℃,问它们的温度加起来等于多少?
问这种题目的都是弱智,对吧。“不同恒星表面温度加起来”是没有意义的。
进一步的,物理学有个手段叫“量纲分析”,简单说就是让数据的单位也参与运算,比如加速度的单位就是(米/秒²)。
那么,当你总结出一个物理公式时,就可以先做个“量纲分析”:公式等号左右两边算出来的东西,其量纲一样吗?不一样就丢掉吧,肯定错了!
类似的,两个项做加减?它们的量纲一样吗?不一样?你把加速度和速度加起来算什么鬼。
能通过量纲分析的也未必就对,但通不过量纲分析的肯定是个错误。
借助这个工具,我们可以花费尽量小的代价、尽量快的识别出错误,避免浪费更多时间。
类似的,在程序中,我们也可以借助类型,让编译器帮我们查错。
比如,小张的生日是7月1日,小王月薪¥12000;程序员脑抽,写了个计算公式:
int sum = zhang.birthday+wang.salary;
编译器马上就可以指出这是个错误:
错误,+两侧数据类型不匹配:datetime型数据和decimal型数据之间不能执行+操作。
不仅如此,我们还可以借助类型,让编译器“心领神会”的帮我们选择(dispatch)正确操作。
比如,我们要给小张记个功,给小王涨100块钱薪水;如果没有类型系统,那么你可能得这样写:
//zhang.CV指向一块字符串区域,要往末尾续写就要计算原有CV的长度
zhang.CV = strcpy(zhang.CV + strlen(zhang.CV), "10月1日获得个人三等功。");
wang.salary += 100;
很麻烦,对吧。
但如果zhang.CV是一个string类型,那么就简单了:
zhang.CV += "10月1日获得个人三等功。";
wang.salary += 100;
编译器知道“字符串的+操作就是往后面拼凑另一个字符串”,和 decimal 的数值增加不是一回事;而我们就可以忘记底层差异,都写成+=就行了,编译器会自动选择正确实现。
程序写起来是不是就变得简洁快捷多了?程序员的记忆负担是不是也减轻了?
假设你参加单位组织的知识竞赛,理论上你可以知无不言言无不尽,可以在试卷上奋笔疾书、充分展示自己的才华……
但现实中呢,答题有时间限制,不可能让你一个人说一天;试卷留的空白有限,不允许你长篇大论——有些不太专业的人甚至会搞出“试卷上留白太少写不完答案”之类飞机。于是你只能留下一句名言“我知道答案,但是地方太小写不下”……
计算机也一样:我们的内存并不是无限的,不可能允许你在“性别”里面填写“12岁前男12岁后女18岁后女变男20岁后男变女”……
咳咳,开个玩笑。实践中,很多时候,一个字节也是生死攸关的。比如你要处理几千万条信息,如果每条信息压缩一个字节,它可能就能放到内存里完成;但多了一个字节……
Out Of Memory,BOOM!
在正常使用的数据结构里,人的年龄一般是个unsigned char,单字节无符号整数最大可以表示到255,足够用了;如果你搞错了:
wang.age = wang.salary
编译器就会告诉你,“薪水”使用的类型decimal太大,放不到年龄使用的无符号单字节整型里面(提示你“操作可能造成数据溢出”)。
不仅如此。
如果完全用二进制表示整数或者简单定点数,那么天文数字(比如十后面三四十个零)、高精度小数(精确到小数点100位,但前90位都是0),这些是不是也太浪费空间了?当然应该上基于科学计数法的单精度、双精度数啊。
再比如,二进制仅仅是数字,英文字符、中文方块字,这些怎么表示?
因此,我们不得不制定各种各样的编码方案;而不同的编码方案支持的运算法则、具体的运算过程,肯定是不一样的。
比如,对应到CPU整数指令上,就有“单字节加”“双字节加”“四字节加”“八字节加”乃至“有符号数加”和“无符号数加”和“加进位位的加”和“不加进位位的加”等等区别;甚至BCD码表示的整数在加法运算后还必须附加一个DAA指令完成调整(相应的,减法要用DAS指令调整)……
这些,都是半点混淆不得的。
这还仅仅是整数加减法运算。再加上单精度、双精度浮点数,再加上四则运算以及正切余切平方求根取对数等等等等……
明白为什么现在没人用汇编编程了吧?
这些东西,都可以借助类型系统,让编译器自动的安排合适指令、确保计算结果正确。
说白了,这还是前面提到的 dispatch(当然,同时还有类型匹配与否的检查)。
过去,编程语言的类型系统往往仅仅包含“基本类型”的完整支持,对用户自定义数据结构只有有限的支持;面向对象彻底的改变了这一点:从此,就连用户自定义数据结构,也可以通过“晚绑定”在运行时dispatch了。
与之同时,面向对象编程也增加了“接口检查”之类动作——你可以调用 Person.Run(),但调用Stone.Run()编译器就会报错。因为“石头不会跑”。
这东西继续发展,还有强类型、弱类型,动态类型、静态类型,以及“我不关心你的类型,只关心你能不能执行某个操作”的鸭类型等等东西。
但归根结底,类型系统做的就是两件事:
1、根据类型安排合适的操作
2、借助类型系统发现部分逻辑错误
做为初学者、业余开发者,类型系统对你可能是个束缚,因为你实在搞不明白这东西是干嘛用的、为什么有那么多类型、这些类型都有哪些差异。那么“无”类型的脚本语言对你来说就非常方便。
但实际上,哪怕号称“只有字符串一种类型”的TCL语言也是支持各种数据类型的。只是在你看不见的地方,这种语言自动替你做了类型转换而已。
这种隐式转换反而容易形成很多“坑”。比如知乎上经常被吐槽的 JavaScript,甚至因为数据值的不同,其加法、判断等等等操作都会有不同结果——想要用好它,你就不得不死记硬背这些结果,或者写个表达式就上网查查真伪……呸,是上网查查“加法的正确用法”。这显然太过累人了。
对专业开发者,如前所述,类型系统是个极为重要极为犀利的工具。
用好它,一方面可以借助它的“自动分派”简化程序,另一方面又可以借助“类型检查”自动探测出很多很多的错误。
你的程序越复杂、规模越大,类型系统的重要性就越突出。
还不过瘾?试试它们