微信公众号:OnlineThoughts
有人说计算机就是一层层的封装和抽象,我赞同一半,因为任何科技(机械,电子,物理,化学)都是一层层的封装和抽象,不过计算机行业将这种思想应用到了极致。
上层与底层
你理解一个事物的更底层原理对理解它会有极其深刻的影响。这不仅对计算机适用,比如你学了量子力学,就理解了化学中元素周期表是怎么回事(元素周期表就是化学的封装);你学了c,就理解了python是怎么实现的;你学了机械原理,也知道了汽车很多零部件功能是怎么实现的(机械原理就是汽车运行的封装)。这种互相联系,层层深入的学习思路是极其重要的。
最底层的计算机就是电信号,当然这个电信号在物理上解释起来也极其困难(从量子力学到固体物理到半导体,再到数字电路里半导体掺杂做成晶体管,然后做成加法器等基本“机器指令”)。
所以学习计算机语言的关键就是当它是个傻子,傻到什么都不知道:比如你写一个a=1,你就要问:它怎么知道=?它怎么知道1?他又怎么知道a?
答案很简单:它当然不知道,它什么都不知道,问题是,你怎么知道你打的那个符号就是=,你只不过按下了键盘上的一个键,然后这个键帽底下连了一根线,通了电,一路到了内存,CPU。然后你得说这是一个=,那就是咯,但是计算机什么都不知道,如果理解了这一点,学计算机只是时间问题了。
正如学习python我们要问:a = 1,它(解释器/编译器)怎么知道我写的这个是整数或小数呢?我们在c语言里(python解释器是用c写的)就会发现,它不知道,只不过我们写了函数模版,看起来就自动识别类型了。
c++的底层呢?当然就是c的底层,也就是汇编语言,g++ -S main.cpp 就可以把一个c++代码文件编译成汇编文件,看起来的for循环,根本没有任何智能,只是编译器为我们做了这些---把某个数从内存取出来放入某个寄存器,再加减,再压入栈---重复又繁琐的工作。而繁琐也是相对的,否则没有会放弃写c++代码去玩游戏(很多大型游戏底层都有c++的身影)。对于c++和汇编的“一一对应”关系(类似c和python的关系),《Computer System:A Programer‘s Perspective》这本书描述的极为清晰。
问题就来了:这么学习岂不成了无穷尽的了?本来学个python,结果还要学c;本来学个c,还要学汇编;本来学个汇编,还要学数字电路;本来学个数电,还要从量子力学学到半导体?!
我的理解是:学习本来就是无穷尽的,你只能搁置一些问题,但不能没有问题。
计算机代码的“不人性化”
理解了计算机的底层原理,就自然知道了代码不可能人性化的。一层层“不人性化”的代码封装,就成就了人性化的软件和操作系统,也就是拿过来直接用的手机。
比如我们写一段代码,句与句之间必须要有分隔,有的语言可能是“;”有的可能是<enter>键,即使我写这个,我也用“”来表示这是一个表示意义的符号,而不是我用来断句的符号,而用<>来表示enter是键盘的按键而不是enter这个英语单词的意思。
计算机不是人类,他们的工作原理要求他们对语句的理解不容有歧义,这也就造成了看起来的诸多不便。
目前以及可预见的未来,编程也就是实现计算机和人类的交互,就是把人类理解的世界现象和规律变成0和1的过程。而这一过程必然会有一定量的复杂性,是不能被简化的。
比如现在软件工程师常用IDE来编程,因为有关键字高亮,自动补全,检查基本错误等功能,看起来让工作变得越来越容易起来,但在全局来看,这部分复杂性只不过是转嫁到了制作IDE的另一部分软件工程师身上而已。所以,一定程度上,这个过程的复杂性或者不人性化的程度是注定的。这也是整个计算机系统如此复杂庞大的原因。
举例:
python 和 c++有很多区别,其中一个重要区别就是不管是变量,数组,还是函数,python都不需要声明再定义,且不用指定类型,看起来非常人性化,事实也是如此,但没这么简单:
首先c++在很多情况也可以“智能”判断,但是首先要知道,机器是不会给你“预先判断的”,所谓预先判断都是编译器的算法而已,而在使用角度肯定是越“人性化”越好,但是这样你为什么要学习代码呢?直接在屏幕上划一划不就好了,而实现这些“人性化”功能的工作总要有人做,而作为一个想了解计算机的人,这也是必经之路。
再一点就是声明也有声明的好处,比如你有些量想定义浮点值2.0但写成了2,这时候python不会告诉你的,而如果在c++里你声明过就没有问题。python中调用函数需要先定义,就是因为解释器也需要先读取函数声明,但python简化了这一过程,所以也就不能之后定义。
这更解释了python中一特例,就是在a函数中调用b函数时可以把b函数定义放在a后,这是因为python执行def时并不执行def里边的代码,只看def了什么样的函数,返回值是什么,就像是c中这个原型的声明。
当然c++也可以把函数定义放在调用之前来代替原型的声明,这也是合法的,但是main()函数的书写规范就是在最前边,这也让代码的可读性极强,逻辑清晰。这对构建大型程序来说很重要。
看得出来,如何在代码易读性,代码书写简洁程度,和代码执行效率之间权衡,是目前计算机语言之间的根本区别。所以在很多情况,python是不可或缺的,因为它实现一个类似的功能,其代码要简单很多,这也是python的“占有率”这么高的原因之一。
总结:我想要说的还是那个结论,就是理解一个事物,必须联系的看待,即理解底层,又了解上层,就非常清晰了。
面向对象?可没有这么“玄”
Python 是C 写的,这其中就有一个关键的问题:c语言并不是面向对象,也就是没有类和对象这些定义,那怎么解释python中的面向对象部分呢?
答案也很简单:c++的面向对象是怎么来的?其实这只是计算机语言体系众多抽象概念的一个罢了——函数是什么?编译器或者解释器怎么解释?list是什么?string是什么?那么类(类是面向对象特性最重要的概念)就是什么!也就是一部分代码的集合。
对于机器来说,没有函数和类这些概念效率会更高,而对于人类,一段机器码(或者c语言中变量声明,赋值,循环,)实现了一个功能,如果把它打包做一个标签就是再好不过的了,这就是函数,这也是类。这个包。就是申请的一段连续内存(当然虚拟内存技术先忽略)。
举个例子:一辆汽车,大家可能都想象得到是什么,但其实没几个人懂,你知道汽车怎么实现转向的吗?恐怕不知道,但甚至没有驾照的人都会转动方向盘,这个方向盘,就是函数,这个车就是类。这就是为什么大多数人不懂车的工作原理(不知道类的所有原代码),但却能开车(可以进行类的实例化),也能转弯(调用类底下的函数/方法),而且换一个车基本还能开(类的继承)。
list
刚开始接触c语言或者python等其他,就会想,list这么着重讲有什么用?其实几乎所有数据都是一个list,甚至包括string。所谓list,就是一组顺序不可以更改的二进制数,然后按照特殊的人为规定的规则编码成不同的格式。比如字符串,图片,音频,视频,实质上对于计算机来说就是list。所以这种数据格式看起简单,但却像二进制一样意义深远。当然对于图像,原数据可以看作一个二维数组,当然也可以看作一个矩阵,这也就是为什么关于图像的应用(游戏和图像识别)这么吃GPU的原因(GPU专门处理这种矩阵算法)。
标准更新与编译器
整个计算机行业还是在“高速”发展的,尤其是和传统行业相比,学习十年前甚至几十年前的东西,当然会对理解行业底层,了解行业发展有些至关重要的帮助,但是如果要应用到当今社会,了解“更新”也是必须的,c++也是如此,常用书籍如c++ primer只是更新到c++11,而在2020年学习这些,c++20的标准已经落地了,c++14和c++17也有不少小的改动,包括对一些指令的弃用,增加和完善了一些新的功能,而这些细节虽然对整体框架没多少影响,但随着发布版本积累,积少成多的更新会影响代码结构的改变。所谓更新,就是编译器想帮你做更多事情,比如类型的自动选择,比如数组的动态长度,当然同时让代码更精简,综上所述:学习框架不分新旧,但是应用还是要了解更新,用好更新就是再利用别人(编译器)不断更新的工具,这样才能更快更好的做出自己的工具。
编译器就是对标准的“践行”,比如apple用的“自己”开发的apple clang & llvm编译器,目前2020年更新到11.0.3版本,实现了对c++17特性的全部支持和c++20特性的部分支持。