对语言演进的理解

来源:互联网 发布:微信秀是什么软件 编辑:程序博客网 时间:2024/06/07 00:25

语言的本质:就是为了让人能够方便的写出解决目标问题的软件

 
1、机器语言
机器只是01状态,内存、寄存器、高速缓存的每个存储单位就是 16/32/64 为的 0和1组合
机器知道如何将这些 byte/word 解释,并送入 cpu 执行
 
2、汇编语言
但是要让人来写这些01,没有办法,只要使用助记符,无论是命令还是数据都采用助记符
  这样一来,有了汇编翻译器(当然初始版本必须使用机器语言写出),就可以将汇编程序编译成机器语言了
  而且,不同型号的机器针对同一功能的二进制区别在汇编语言中予以减少和消除(当然是通过汇编编译器)
 
3、C语言
  但是,计算时无非就是申请全局数据区,栈区,堆区,然后执行程序(包括子程序子函数)引用这些数据
  因此,无论最终底层的汇编机器语言如何实现,都是要做这些事情。
  那么干脆对这一层抽象的计算环境做一个语言,这个就是 c 语言。
  他可以定义cpu可以直接处理的各种数据类型和运算符,可以进行程序流程的控制,可以通过栈来调用子程序。
  ok, c 语言给我们提供了通用的机器,他基本上可以覆盖已有的计算硬件(虽然是在机器世界解决问题而不是用户的世界)
  于是,我们看到,所有的计算机上包括手机,全部都有 c 编译器
  从此,我们摆脱了低级繁琐的非人性的机器语言和汇编语言
  堆栈的存在使得用c写程序必须要定义好参数和局部变量,因为他们要在堆栈上保存
 
4、基本的 C++
   但是,我们发现,我们需要发展出更复杂的数据类型,而不是直接由机器支持的数据类型
  我们需要引用这些数据类型实例的指针,c 有了 struct
  但是我们发现,一些函数是针对同一对象实例或类型的操作,因此应该把它们放到同一个程序包中
  我们又发现,这些函数集需要保持自己的状态,包括类状态和每个对象实例的状态
  可以把他们写到自己的源码文件中,向下面一样
   prop = array of struct
   create() -- 在堆中新生成一个 struct 实例,并把地址返回供调用者持有
   class_func1()
   instance_func2(pointer2struct)
   当我们要调用时,如果使用非实例的话,可以直接使用所谓的类方法。
   如果要使用实例相关,那么先通过 create 创建一个堆struct数据,并hold该指针
   于是,基本的面向对象就被支持了。他包括数据和方法的打包,类和实例的支持。
   进一步,如果 struct 区分为 struct_pub,struct_private ,虽然他们内内存的分配是一并进行的,
   但是使用该程序包的客户只被公开 struct_pub,这样,他们在c语言代码中,就不能引用到 private 的部分
   同理,程序包的函数也可以有一些不公开,这些函数就成为了私有方法。
   因此,私有公有的属性和方法也都具备了(虽然只是在编译时予以保护而不是运行时)
   再有,实例方法的第一个参数必须是使用 create 后返回的指针,而类方法不要求。
   经过了精心设计后,发现 c 除了继承相关机制外,已经很像变相对象的了。
   但是,我们发现出现的命名空间的问题,属性和函数在语法上没有一个针对类和对象的前缀命名空间,
   所有的名字已经公开都是全局的。
   因此,我们将c发展到了c++,可以使用 class::method object.method 了
   这样就非常的清晰了
 
5、支持继承后的 C++
   后来我们发现继承类型判断非常重要
   那么我们发明新的语法来表示继承,并自动将原先的 struct 添加几个新增的成员
   同时,我们可以添加新的方法,也就是说在方法 struct 上添加几个新成员
   但是当我们重载父类方法是,我们要将自己子类的方法 struct 上修改一下指向新函数
   而使用 supper.method 可以由编译器自动翻译成调用父类该方法的指针
   ok 静态继承我们已经实现了
   当访问类和实例成员时,因为所有变量都事先声明从而指定了类型,因此编译器知道应该如何安排实例struct的内存分配结构
   于是 c++ 支持继承了
   当需要多重继承时,多个父类的 struct 混合在加上新的成员构成新的数据 struct,方法 struct 类似
   如果父类有重名数据和方法,调用时需要指定是哪个父类的即可
   引用子类时,偏移量可以由编译器算出
   如是,c++ 支持多重继承了
 
6、运行时类型判断
   我们发现声明一个对象为父类类型后,将一个自己的实例赋予,并按照父类(声明的)的类进行操作也是可以的
   如是我们完全可以忽略自己对于父类的不同,将它当成一个父类来看
   当然,如果是多重继承的话编译器无法确定偏移量,是不行的。
   再有,就是编译器不再编译时确定内存偏移量,而是保存类型ID和偏移量表到运行时的内存中
   当声明为父类的变量引用了一个子类的实例(他会带着类型ID号)后,访问其成员变量时会有运行时根据类ID查找子类成员偏移量表从而按照子类进行解释执行
   于是,有了类型 runtime 表,c++ 支持了动态类型确认
   最后,我们发现 c++ 在本质上,只是一个更容易 coding 的语言,它最终还是机器的映射
 
7、java 的出现
   使用 c/c++ 都是基于内存偏移量的,容易出现缓冲区溢出的严重问题,因此有必要在预防上禁用溢出的可能性,因此必须在语言层次上禁止使用内存指针
   这种指针只允许通过 java 的语法进行间接引用。
   所有的变量类型都应该按照实际类型进行处理,并且是默认的和唯一的。
   为了确保在所有已有的计算机的硬件上可以不经本地机器编译就直接运行,有必要对c/c++的机器进行更高一层更通用的抽象。
   在任何机器上编译后的 java 字节码都应该可以在任何 java 虚拟机上运行。
   于是,java 诞生了
   java 抛弃了裸机世界,提供了虚拟机,虽然性能不如直接编译完映射到物理机的c/c++,但是提供了更多的灵活性,更高的安全性。
 
8、javascript 的强大
   和 java 一样,js 使用堆而不是静态内存和栈。
   更甚的,连一个对象的成员也不是物理内存连排的和固定的,它完全采用 hash table
   js 的局部变量不是进栈,而是一个执行对象,也就是说一切皆为js hash table,无论是又名的或是无名的(执行对象)
   所有的运行时解析就是在函数的作用域链上解释标示符,然后通过 . [] 来访问成员
   因此,比起 java 每层对象访问都多了一部查找 hash table 找属性值的动作开销,还有存储每个对象属性名的内存开销
   但是,有了 v8 ,可以模拟类,从而缓解了上面的对于标示符解释的执行和存储开销
   因为 v8 还可以将 js func 代码进行编译(毕竟这些代码是静态的),从而省去了在运行时进行语法语句分析和解释的开销
   这样,js 在 v8 上的执行是使用一次性编译到机器码,这就像是 java 的 jit 一样
   因此可以看出,js 在 v8 上的性能其实和 java 已经没有什么本质区别。
   特别的是,js 支持闭包和函数为值(函数可以有多个引用从而有多个名字),因此无需提供全局设计的 namespace.
   因为根据 commonJS 规范,js 并不直接引用并引用的名字而是在调用者源码中使用别名,因此在任意一个 js module 中都不会发生命名冲突
   当然,java 也可以通过传入对象,并回调对象的方法来实现函数为值似的回调,
   比如定义一个 interface callback_xxx 任何 java 类实现了该方法都可以被回调
   怎么讲呢,其实使用 c++/java 这些个静态语言来说,还是比较有板有眼的,比较严谨。至少不会搞错类型。
   但是从另外一方面讲,如果程序员根本就不会搞错类型,他只是需要敏捷和灵活,那么 js 是更有价值的。
   js 的灵活参数和灵活类型使得像 jQuery 这样的极为精炼的 API 得以发明,同样的功能如果要使用c/c++/java去做会非常繁琐纷乱
   事实上,所有传统面向对象语言的 overloading/template/property 对于 javascript 来讲只是小菜一碟
   而类似继承多态对于基于 prototype 的 js 来说也轻松实现
 
9、js 的一些缺憾
   但是,强类型语言语言,并且使用全局namespace后,IDE 对于代码支持肯定会更加方便
   由于 js 是动态语言,因此如果想要获得变量的属性列表,只有动态的执行它,执行到这里后,自然就知道他又什么成员了
   还是比较痛苦的。
   毕竟,使用 c/c++ 还有 include,使用 java 还有 import 因此我们都可以获得代码提示,唯独动态语言不行
 
如果每个 commonJS module 都可以被IDE引用并分析其中的成员,那么做调用方 module 开发的时候就可以进行代码提示
每个函数的类型如果在函数前的注释中给出,理论上讲调用该函数时可以进行代码提示,但是调用的是哪个函数IDE恐怕很难知道吧
当然,js 语言内置的对象的代码提示还是可以去做的。
可以看出 js 的函数就是一个指针,js 的变量也就是一个指针,而且他们都是无类型的指针,比 c 还灵活,从而难以被 IDE 利用。


原创粉丝点击