用新浪微博登录

只需一步,快速搞定

 找回密码
 立即注册

用新浪微博登录

只需一步,快速搞定

查看: 2426|回复: 3
打印 上一主题 下一主题

新手编程导论(八)

[复制链接]

该用户从未签到

667

主题

2111

帖子

5570

积分

LV 11.会员

MS爱好者!!!!

积分
5570

社区居民偶尔光临工蜂最爱沙发在线达人社区平民做个有钱人略有小成常驻会员忠实会员

跳转到指定楼层
楼主
发表于 2011-10-22 18:18:53 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式 |          
第7章 抽象之高级语法机制
7.1 真正的OO解
什么是系统?系统是关于一个事物的逻辑上的有机组成部分,因此系统分析师的工作就是用面向对象去表达系统(划分对象),这就是OO用来进行设计的意思。
对象之间是分配了责任的,如何把这些责任更好地分配呢?(即对象内逻辑和对象间的交互逻辑,UML中的对象消息)
一个分配得好的对象组合(兼顾了当前使用和未来扩展能力)往往不是现实生活一个具体的模型,而且有些时候你一点现实生活中的对应物影子都找不到,而往往是一个思想模型
设计模式的学习是无比重要的,只有掌握了设计模式,才能不会让思想局限于现有的代码,才会在一个更广泛的领域里去拆解对象并进行设计(原语领域去看待事物,因此程序设计实际上是一个反映你心智和哲学水平的活动),往往有些时候,,,真正的再编程能力不再是语言细节和问题细节,,而是在掌握了语言细节和系统细节之上的高层构架的学习(设计上的学习)..
往往有时候,,,开发一个程序是一个真正工程规模级的活动(不是指那种利用诸如.Net,JAVA之类的真正OO语言声明几个类,创建几个对象来进行敏捷,快速的开发过程,,而是指那种预计到未来扩展的需要而预留了很多发展余地的大型开发过程)
敏捷方法极限编程XP和RUP(ROSE公司提供的大型软件开发“方法学”)是二种软件开发的方法学。
而设计上的学习往往要求你掌握关于此问题的所有细节(在目前的科学技术水平下对该领域的现解),和与此问题有关的很多周边问题,所以要从原语领域去看待此问题和进行基于对象拆解此事物并构造这些对象之间的逻辑的系统活动,
比如为什么会有OO的出现呢?因为OO是对现实世界“Object”的抽象(不可否认我们周围的世界的确是一个一个的对象,注意这是用OO眼光看待问题域),我们可以在抽象的基础上构建抽象,进而发展出大型的系统(由土和石到房子,由房子到,不可否认我们一直在做这些的事和思想上的活动),而表现在OO编程工具上,我们用Class来表示一个Objects(注意这是应用领域,虽然这种CLASS对于表达现实的确显得有点单位过小-----运行时不可控的抽象,但我们可以通过不断地包装抽象和组合抽象,或者通过策略---设计期可控的抽象来达到更大的抽象),,CLASS的出现是历史必然的,以前编码时是分开代码和数据的,一个CLASS就是对于代码和数据的整合抽象(计算机就是用代码去处理数据嘛,这是计算机最低层,面向机器的抽象,在这层抽象上我们可以构建更大的抽象,以达到接近人的那一端的抽象,一切解决问题的方法都是抽象的演变,一切皆抽象!!)
比尔愿意招一个物理学家而不是计算机学者,是因为他考虑到,物理学者往往思考的领域比较广,而且他们拥有很好的数学基础,这些人往往比专业计算机科班出身的人(指机械程序员,只局限于一个类库比如MFC进行开发达七年,虽然在计算机领域掌握一种技术可以吃香好几年,然而一个有发展前途的程序员必定是一个学习新技术的高手)更能迅速地掌握一些思想级的东西。

7.2真正的对象
在当初DOS下用汇编开发程序的时候,一般都是先考虑这个程序会用到什么数据,要用什么样的数据结构在程序内部去表示它,然后才考虑开始写代码,,这个时候数据与代码是紧密相连的,再后来出现了模块化但是面向过程编程,这个时候,利用临时自动变量可以降低子程序间的藉合度,而当出现面向对象后,我们完全可以先定义一个预对象(什么是预呢,就是说这个类被写出来的那一瞬那,它并不像面向过程子程序一样存在某个一个rotuine执行路径中,而是作为预定义的一个程序组件,除非你定义并引用了一个该类的实例,这个以前定义的类中的代码才会进入某个执行路径),即类,一个类是对象是预定对象变量与对象实例(注意这二个概念,变量可以是一个指针,是声明性的并不分配内存,而对象实例是一块业已经过new分配的内存块,实例也可以是一个句柄,是指向对象实例的指针,因此它是指针的指针,形如VOID**或SOMEOBJ**,指针与句柄的区别即来源于此)的对象模板,而类模板是预定义的类模板,类这个东东来源于结构体,其实一个结构体也有它们的构造函数,在这种意义下,类与结构体很接近。
面向对象的方法成员广泛上的意义就是“对数据的操作”,也即计算机指令,,狭义上的意思就是“表示对象的主动或被动性作用的方法或函数”,,,面向对象的数据成员就是“数据或变量”,也即计算机要操作的数据,,这对应于面向过程中的函数跟变量,,,而面向对象将它们推入一个更泛化的境界(一切计算机数据都可成为数据,特别是OO对象)。
面向对象并不是一种凭空冒出来没有根据的概念,相反,,没有它的存在倒是很令人不自然的,面向对象和基于面向对象的设计模式使软件设计进入了一种高速发展的状况,甚至个人独立开发体系复杂的软件都成可能,这就是面向对象的作用之一所用所在,,,因为本质上,面向对象是符合现实生活的,,,那就是:面向对象特别类似于建筑学的过程,需要不同的原料并组合它们才能构成最终的软件或建筑,我们只要发明了砖和水泥这样的原料,就有可能发明楼房,(为什么说仅公是有可能呢?即使你发明了需要造一间楼房的所有原料你也不一定能造出楼房,这是为什么呢?砖和水泥应该如何组合,涂料跟墙体怎么样配合才能最终成功着色?这就要求对象间必定要有一定的逻辑,如果说造一间楼房是需求的话体现在建模工具中就是case view,那么你造出的所有原料就是compent view中的所有对象,,这些原料如何组合的逻辑就是logic view要告诉你的内容,这些逻辑实际上一部分被包含在当初分别创造这些原料的各个过程中,你必须为原料或对象预留“接口”,要考虑这些对象什么时候会被用到,以那种被动被调用的方式或那种主动调用别的对象的方式会用到,即一个类的方法并不一定就是类对象主动性的作用,一切关于此对象可能的操作都可以用一个成员函数来表达。属性也是一样,另外一部分被包含在当用全局的眼光考虑业已创建好的这样对象的时候,如何运用设计模式来组合它们得出一种适合的逻辑,而这些都是lgoic view的内容)
这样仅仅是一个软件内部的考虑,如果不在源程序的级别,你要提供你的软件为别人所用,或者是一个类或者是函数,你不可能向别人提供源程序,你就要在二进制级别上向别人提供面向对象的特性(即可复用特性),,这通常是建模工具deployment view中的内容,面向对象的三重机制,继承,重载。

7.3真正的继承
面向对象的缺点不在OO本身,而在于人们对OO的滥用,这主要体现在人们对继承的概念的偏解上,设计模式指出,对于复用,我们应该尽量用聚合而不是多层继承(单层继承和对等继承还是允许和鼓励的),JAVA不支持多重继承,一般也提倡不超过五层以上的继承(然而在策略的提出中,多重继承却起了不可替换的作用)
不可复用问题的出现绝对不是面向对象才有的问题,,面向过程更甚,实际上对象机制的确在一定程序上也促进了模块间的藕合,然而设计良好的对象构架可以很好地为以后的更改预留空间,并且似乎也并没有更好的比面向对象更好的机制出现,比如完全面向复用语言,,极端化地求完全可复用性又是不对的,因为复用这个概念本身完全是一个相对的概念,,,考虑一下MFC,它内部的各个组件都不能拆开来用,只有在作为MFC这个整体的前提下各个组件才有效,在这个意义上它是不可复用的,,,然而,就其内部来说,当你在MFC下使用MFC编程,你真的可以做到重用其中的一个组件可以避开不使到另外一个组件(这就是说,可不可复用从来都有一个最小可复用单元的概念存在),stdafx.h就允许条件编译和预编译(stdafx.h是VC给你的标准预编译头,它可以通过project wizard为你清理出一套专用于win32 app,win32 mfc app,win32 console..etc的文件头,当然你也可通过project.settings,C++.precomplierheader来定义自己的预编译头),条件编译常被用在一些大型库和软件系统的编译上,比如一个由很多可plugable的Dependencie组件组成的库,常常提供有具体的宏条件编译来让你自定义哪些组件要被编译进来,哪些组件不被编译进来,而每一套编译出来的库都是有效的,,还比如一些用到include了dx头文件的库,有时为了让没有安装dx sdk的用户能正常编译,就提供有宏让自用户选择不inc进这些东西。

7.4真正的OO
抽象使高层置为顶端,面向对象就是一种抽象,这种抽象使我们看不到我们不想用到的事物的一些方面,而把那些我们能用到的事物的方面用来作为描述此对象的全部,,(即抽象了的对象根本不能完整地反映对象本身而且压根就不能),接口是关于如何应用对象提供的服务的全部抽象,如果说面向对象和接口是代码级复用的机制,那么构件是二进制级真正的复用机制,接口把一个系统的可用部分按不同的形式透露给复用者,
抽象使高层置为顶端,而它的可定制部分都集中在顶端作为应用,这有点像网络的七层模型,只有定义了一个抽象而且是合理的抽象,才能为未来的应用预留扩展空间
一个OO程序本质是一些类数据(jar文件包里全是class文件),类是真正意义上的数据(类型),然而在JAVA中,JAVA提供了数值类型对象的对象封装型和非对象封装型,(因为对所有的东西进行对象封装有时不符合现实而且对象机制一定程序上比面向过程慢--这就是C向C++转型中有人担心C++其实由于它的封装技术而导致的速度问题,你可以把整型看作一个对象但是传统观念里已经把它作为数值看待,一般程序设计语言都将数据类型的数据作为first class来看待,就是那种Java库src包内的那些对象,这些对象是高级对象,因为程序设计语言一般为他们装备了足够多的功能和运算符,可以由它们派生很多其它的数据类型)
面向对象数据库db4o的出现,使我们开发的重心不再集中在如何存储和声明数据本身,转而将注意力集中在这些业已创建的对象的逻辑上,如果说UML建模工具可以用来规划这个过程,那么DB4o就是实际产生和持久存储了这些对象,
现实生活永远是多元的,可定制的东西可拔插可扩展的永远是最好的,巨型的应用只能集中在某一种平台上,因此很多软件比如sql都发布了它们的embed版
当今潮流下,很多东西都越来越变得合理和可扩展,局限于某一个平台之上的应用正渐渐消退,一个应用程序的exe主体不再是一个包含了大量对象和对象逻辑的二进制体,而往往是一个执行框架,它的主体在配置文件和面向对象数据库内,配置文件指明此可执行体的平台等属性,数据库内存有可执体内用到的全部数据
现在再也不是早是不是要用OO还是不要用OO的问题了(这个问题已经早过时了),因为JAVA和DOTNET出现了,它们的出现表明,,不但要用OO,而且还要用虚拟平台上的一套OO,(也即所有的应用应该从平地再加一层逻辑,加到虚拟平台上去,以前我们是从汇编到C,这是对CPU编程转到对OS编程的改变,从DOS到WINDOWS的编程平台转变,,对网络编程和对本地编程平台的转变等等。因为我们OO编程时总是用一套OO库来进行我们的开发嘛,以前我们是直接用本地平台上的OO库,现在好了,我们被迫用虚拟的OO库,因为它建立在平台之上,同平台 独立,因为这层抽象最大的作用就是1.使我们利用JAVA或DOTNET开发出的程序是真正的OO程序 - 因为JAVA和DOTNET提供的虚拟编程OO库都是严格组织和科学划分的,专门为OO而设计,而且更重要:2.它们独立平台,这样编程的时候我们的眼光就会不再局限平台而专注跟平台无关的通用应用领域,问题域,如J2EE)
应用往往是分布的(现在的网络服务器主要是应用服务器而非资源共享服务器),面向对象领域的很多东西都越来越趋于WEB这个分类粒度,Web使我们的应用分布于网络,使WEB往往成为一个分布式的并发OS,(网络最初是纯粹的通信网,后来才更名为计算机网络,是因为出现了NetWork OS,今天,面向对象和RMI,RPC,Corba,DCOM,使对象可以在网络上不同的计算机间交互,)
越来越趋于WEB这个粒度(粒度是我们作原语设计时用到的一些辅助用词)这个事实表明,分布式计算和面向构造软件设计的企业部署需要被学习.
Dcom是微软用来抗争Corba的东东,,微软是一个很有自己理念的公司,Java等的崛起,使虚拟平台呈现多元发展
Java和.NET是真正的OO语言,独立平台,然而它不是本地平台,一方面,不由本地OS直接分配内存,另一方面,它们是动态成型的语言,而不是编译期静态语言,因此速度上会比Native普通程序慢好多(虽然也有JIT技术的支持),但是据称,,JAVA速度越来越接近于C++(不知道是本地C++还是C#,这里说的JAVA是指JAVA库和JVM的综合)
回复

使用道具 举报

该用户从未签到

667

主题

2111

帖子

5570

积分

LV 11.会员

MS爱好者!!!!

积分
5570

社区居民偶尔光临工蜂最爱沙发在线达人社区平民做个有钱人略有小成常驻会员忠实会员

沙发
 楼主| 发表于 2011-10-22 18:19:46 | 只看该作者
7.5真正的OOP
在一个常见的C++编译器中,总是会带一个精心安排的简化程序设计的公用库,比如MFC,VCL等等,MFC显得有点过时了而且它的实现比较繁复,C++标准把STL加进来了编译标准,一般C++编译器都支持STL,
库跟语言的关系是什么呢,,语言可以通过库来扩展,,《C++沉思录》中说“库就是语言”,“语言就是库”,,比如数组是语言内含类型,但是基于面向对象的需要,,就提供了Vector类来wrapper数组,,这就是库对语言的扩展作用。。(相比来说,如果语言是一种使用规范,那么一个库是为了某个特定领域—比如OO,而精心设计的,一般要求精细的设计)
OOP不仅仅再是指单纯的继承啊,多态啊(这样面向对象的机制本身),还指一些公用的oo程序设计手段idioms,,比如容器(用来盛放对象),迭代器(用来遍历对象),适配器(统一对象的接口)也即OOP包括面向对象和Generic programe技术
模板的初级应用是作为容器(异型或同型)来使用的,实际上它还有作为Generic programe的很多高级应用,如编译期策略
STL被称为总数据结构(因为它的元素是数值和对象都可以-异型或同型容器,而且它包含了对于一种数据结构的存储,,访问的方方面面的抽象)
这就是对数据结构的整合(stl)还有策略(可以称得上是对设计模式的整合)
stl甚至都抽象了多种数据结构的使用方式,stl被作为数据类型的模板(但是它显然还有更更高级的应用,,作为container的模板只是模板的初级应用而已)
这些原语程序设计手段也作为一个库(即stl也跟mfc一样是OOP的一个重要方面,,,当然还有构件库比如ATL等等)被提供在一门语言的编译器中,但是.net就完全整合了这二种库,,而且它是跟本地脱钩的

7.6 真正的构造函数
构造函数不会常常被声明为virtual,,,因为子类不一定非要实现它自己的构造函数(而往往在它的基类中就有过实现)
一般来说,我们为某块内存中的对象设立它的指针,只为能使用它,,,因此,指针就是关于此对象的使用抽象,中间层,,我们不必知道这个对象的具体情况就可以操作它(即使不知道它是一个什么变量的情况下),,这个指针便提供了操作它的全部接口,对这个对象的访问与修改操作都是通过这个指针而来的,,,明白这个道理有什么呢
比如有如下一个程序
int var1;
int * var2 = new int;
var2=&var1;
为了使用变量var2,,我们当然可以直接通过var1变量(也即var1名称本身)来访问到它,但是有什么我们还需要定义使用它的中间层,这就是指向var1的指针(换言之,我们并不想通过var1变量名称本身来访问变量)
一个对象的指针是关于这个对象到它的使用级的第一层间接,指针的指针就是二层间接,,
也即,为一个变量定义一个指针等价于计划通过这个指针去访问对象,(这个指针也可实现访问控制)
如上所示,这第一层间接和第二层间接都是通过type*的形式出来的,这样就保证了与原对象的充分脱钩
也即 int * var完全可以和原对象脱节(var2这是第一层间接的其中一个指针,因为可以为同一对象声明多指针)
这样到底有什么好处呢?
有时我们想访问var1的同时不想改变它,这固然可以变int var1为const int var1,然而这样改变了var1的属性(也即我们需要在不改变var1的属性下实现“访问var1却能保证这个访问过程并不会改变var1”)
于是,我们可以用const修饰修饰指针指向的对象,
int var1;
const int * var2 = new int; (这样*var2就是const int了,因此var2指向的是一个不能改变的int值,虽然var2的值依然是var1的地址,,但我们的确实现在使用层就保证使用过程也不会改变原var1的值)
var2=&var1;
对于var2来说(虽然我们不知道还有多少指针会跟var2一样指向对象),,它作为一个指针出现以访问和操作它指向的对象,,,在var2的层面,我们只能通过这个指针去修改对象,,,可是,var2被定义为const int*,,,因此并不能使用它来修改指向的对象
即,除了类型之外,指针的声明可以与它指向并要操作的对象无关(比如关于操作的const,,,它就可以仅仅并施行于var2上,而并不要求它的指向对象为const,,因为var2只是关于原对象的使用逻辑而已)
总结:指针是关于一个对象的间接访问层及访问控制逻辑集合点

7.7 真正的私有,保护和公有
权限修饰出现在二个地方,1,在一个类内部对成员的修饰,,2,用基类派生子类时,对基类作何修饰,,这是修饰派生方式的修饰,,这二个因素共同决定了外界以如何权限组合,,读写一个类的成员(这才是我们最终要访问的目标,但是有二重门要过),当这个相对基类是。

7.8 真正的重载与复写
Overroad与Override,重载是根据已有事物定义新事物(这个新事物是与已有事物貌合神离的,通俗来说就是在一个具体类中,可以定义多个函数名相同但参数不同的成员函数,前面可以用Virtual或没有这个关键字),覆写是将旧事物升级为新事物,因此重载时的旧事物与新事物都有效的而覆写时的旧事物作费(通俗来说,就是在一对具有基类与派生类的二个类中,派生类中可定义一个与基类成员函数名和参数均相同的成员函数,此时基类函数前必有Virtual关键字),诚然覆写可让基类同名函数作费,然而在C++中还存在其它函数作费规则,这里“作费”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
7.9 C++的元编程
编译期解释是被发现的,而非被发明的,在一次国际会议上,有人发现编译输出的错误中居然也有运行期相似的结果,,这是模板实例化的结果,,于是C++的这种语言机制就被发现了
编译期解释本质是什么
我们知道C++主要是用模板技术来实现元编程的,传统上C++主要在动态运行期发挥作用,然而如果我们对编译期编程(静态编译期),这将发展出一种元编程的技术.(利用模板实例化机制于编译期执行一些计算。这种通过模板实例化而执行的编译期计算技术即被称为模板元编程。)
模板实例化是生成采用特定模板参数组合的具体类或函数(实例)。例如,编译器生成一个采用 Array 的类,另外生成一个采用 Array 的类。通过用模板参数替换模板类定义中的模板参数,可以定义这些新的类。当然它跟模板特例化不一样,具体的模板技术细节请去参考其它书
先来说一下编译语言
我们知道这个过程,词法分析和语法分析构成了编译器的前端(得出一个抽象语法树),然后是语义分析,然后是中间码或目标码生成(如果有一个虚拟机,比如JVM和JAVA语言,那么这个目标码就是JVM中解释器要执行的目标,如果是裸机器本身,那么就是一种接近二进制但不是最终二进制的表示形式,当然一般在生成最终目标码之前要先生成中间码),然后是对生成的目标码进行优化,优化之后进行汇编形成真正的二进制
再说一下C++中的模板技术,一般用typedef来实现模板中的假变量定义-因为模板中不能直接定义全局变量(以下提供的例子是一个数据结构模板)
template <CLASS T, U>

struct Typelist

{

typedef T Head;

typedef U Tail;

};

显然,Typelist没有任何状态,也未定义任何操作,其作用只在于携带类型信息,它并未打算被实例化,因此,对于Typelist的任何处理都必然发生于编译期而非运行期

7.10 模板的意义在于编译前端的设计
看过stroupre的C++简史的人都知道,模板最初是用来取代宏(预编译)的,这说明它的地位是靠近于编译期写代码时的设计过程的,(虽然它也一直参加编译以及后来的代码生成,这就是它跟宏不一样的地方)。而且模板也是类型严格的所以也是语法的一部分,既然是语法,就是语言的一部分(而宏函数虽然被冠于函数之名,但其实他不是语句也不是函数,根本不参于编译)。。
模板,是在编译期靠近编译前端那一部分对类型进行控制和抽象的语言机制(即部分地在语法层次上直接写程序,针对编译时的实例化写程序不需等到运行期才检验结果,而且更不是专门针对运行期语意和运行期语言机制设施写程序。比如C++运行期OO),是C++建立在它的类型机制(特别是class通用类型)上的语法机制(所以不光有函数模板,结构模板,还有类模板),在这个语法机制上。C++可以做很多强大的事情(特别是设计上的,集中体现为泛型编程与元编程),,运用这种编译期的能力我们可以将C++视为另外一种语言。即编译期以模板为脚本机制的语言(而模板是主要针对处理类型的)),.
但其实编译非运行,我们知道运行期才能申请到系统资源,才能进行计算机的所谓运算,
其实对编译期和运行期的误解一切的罪原是解释语言跟编译语言的区别(如果没有编译后端,那么运行期的目标就是中s间代码),我们知道,在编译前端完全之后,代码就被生成了,对于解释器来说,编译后端是不必需的,此时它就是逻辑上可运行的一个整体。。即设计逻辑=编译结果=最终运行逻辑。。只有等编译后端是为具体平台生成代码时,,这个时间才出现第二个运行期,,要打乱设计逻辑,将语法级的编译时的设计逻辑转化为变相的平台逻辑,运行时就可以申请到系统资源了。。(因为类似C++编译器它的编译部分和运行部分是他大爷的分开的),,这才是运行时的标准定义。。
而模板绝非为后一个运行期而存在,它在中间代码层次上就可以工作了,因此模板中不需要分配变量这些运行资源(只能typedef,提供编译符号给它就可以),它只是关于类型的纯语法逻辑。因此类型也可以仅仅是一个语法占位符(很难想象类型占位符也能参与运算吧)。就跟宏一样。。在写模板时并不产生代码,只有模板逻辑被实际调用时才被生成代码。。
这对用语言来进行设计尤为有好处。因为他可以站在范类型的角度上进行脱离了运行考虑的编程(泛型,比如可以写出type function这样的语言结构,就跟纯虚函数一样,只是一个接口抽象。),
这说明,设计越来越靠近编译期(甚至是设计期,比如对类型进行template化的策略行为),而非运行期,等到运行期才去验证设计,,,这种用设计(而且是符合语法的设计)来制导编译和代码生成的过程就像语言中内置了一个UML一样。。
而且模板是编译期的,可以控制编译器对逻辑的生成动作(元编程,比如它可以刻意不让某些类型的模板进入编译,你写有关模板的代码时,并不算待编译的一部分,只有那些实际发生作用的类型的模板代码才被编译才被生成代码,因此是一个很好的设计时机..
那么编译期运算(就是实例化,所以它会发生代码膨胀问题)是怎么回事呢?traits和policy是什么意思呢?
首先我们要弄懂实例化
回复 支持 反对

使用道具 举报

该用户从未签到

667

主题

2111

帖子

5570

积分

LV 11.会员

MS爱好者!!!!

积分
5570

社区居民偶尔光临工蜂最爱沙发在线达人社区平民做个有钱人略有小成常驻会员忠实会员

板凳
 楼主| 发表于 2011-10-22 18:22:34 | 只看该作者
7.11 C++的元编程和泛型编程
相比C#和JAVA,RUBY这样的语言来说,实际上即使是C++也没有直接在语言级上提供太多的抽象机制,而其它的语言,比如JAVA在语言级就有代理,RUBY在语言级就有混入。相比之下C++中只有OO和模板,(故称C++是第三代语言,而前者是第四代语言),而且C++的OO是二进制导向的,一直为后人稍稍诟病,但C++的模板技术却大受吹捧,它的抽象能力不小,首先它正用能得到generic programming,偏用能得到meta programming,而且丝毫不比第四代语言的那些抽象弱。这迎来了C++的再一次发展高峰。
模板中不需要变量这些运行期的设施,只需要type和常量,, 它又是语法导向的(所以其复用的手段往往是提供源程序,而且其缺点是不能提供强大的运行期能力,比如Croba),而不是二进制导向的,, 因为运行期才有变量的概念,我们知道C++是个强类型的语言,因此无法做到运行期的duck typing,但模板使它具有编译期的duck typing功能,OO的那种产生类型的机制是不纯粹的,可侵入的(我们称这种语言为面向对象),而duck typing才是好的产生类型的机制(我们称Ruby,python动态语言实现的OO为基于对象),而C++对于类型控制的能力在于它的编译期(因为它是强类型的),,而不是运行期的RTTI,实际上C++的RTTI是很弱的,它用来支持OO是需要花费运行期资源的,而作为一门静态类型语言的C++在编译期就有了关于类型的一切资讯(比如它实现多态就把这称为静态绑定),而如果将这种决策推迟到运行期(来实现运行期的OO多态),那么就必须借助于语言的RTTI机制。
meta programming是对模板的一种偏用,即trick而不是C++的一种技术,它是被发现而不是被发明的只有范型,人们发现编译期也能实现一些运算,比如模板中也可使用if else和递归这样的语言机制,但实际上模板不是语言,无法直接识别if else和递归,实际上它只是傻傻地实例化(即使是这样也能图灵完备),但是从另一种眼光来看,因为模板机制能间接支持 if else和递归,,所以它终究是一种图灵完备的系统,即模板实际上可以看成是C++的一种子语言,另一种意义上它也是C++的embeded language)。(比如它用这些tricks艰难地实现了一套编译期的stl,详见c++ meta programming那本书,而我们在用的源于HP的stl是运行期的)
最新的C++0x标准或许可以改变这种局面。C++0x标准为C++提出了一系列改革,的目的就是为能使元编程成为模板的正用。

7.12 元编程和函数式编程
lisp语言常常被作为教学语言,因为它源于严格的图灵理论,lamaba高阶函数理论,它的变体shemhe语言采用list,即广义表作为数据结构,这种数据结构可演化成任何一种数据结构,采用函数作为语言内first class来作为主要的开发范式,,,因为这种开发方式是图灵完备的,实际上它的计算能力是等价于任何一门语言的。理论上可产生一切逻辑。
C++中的函数并非first class,但它的元编程实现了一种metafunction,,这使得function(实际上是变形了的类模板)可以使C++在编译期实现“函数式编程”,(在这种意义上C++的模板就成了图灵完备的子语言了,实际上C++所谓的元编程主要是在模拟函数式编程)当然这种动作是深受限制的故而需要作抽象迂回的,(比如它的metafunction不像stl的仿函数是利用运行期支持的设施实现的,BOOST MPL库中的每个function独成一个文件,都需要include才能用,这主要是因为模板实现的函数并非类型,编译期并不认识函数调用及函数原型不能以函数(参数1,参数2)的形式进行调用,事实上它只认识模板以及相关机制,实际上你还可以看到编译期C++的好多奇特的语法都是源于编译期不完善的设施在看相关书籍的时候一定要处处提醒自己把握这个要点(除非改良C++标准以使编译器厂商支持编译期待性比如最新的C++09的提出),
我们下面再举一些编译期的语言设施及导致的迂回方式(某某说过,抽象是解决问题的一切方法)。
1.编译期只识别递归(比如在递归中一个特化可以指定递归结束条件,要知道递归首先是一种思想,,它成立的本质有二,1,自身用自身来定义2,结束条件,,,如果说template<类型>是递归的第一层意义,那么template<0>这样的特化体就是递归的第二层意义使得递归得于成立),
2,enum(变量就不能用,只能用类型和常数这样的编译期就确定下来的值比如typedef),struct(用struct而不用class是因为struct默认它的成员是public的)这样的语言设施,而且只有int,bool作为模板参数可用。
2,模板不能以模板为参数,,除非“类模板模板参数”,这样就不能直接产生foo(class foo1,class foo2),这样的结构,,,要绕一些弯子,比如tag dispatch function.
3,循环展开通常采用了复杂的迂回技术,
4,让模板的成员带value,模拟函数式编程的返回值。
5,因为元编程实际上是对产生关于类型逻辑的一种编译期策略(要知道多态化是设计的一个重要方面,编译期模板使C++变成动态语言或无类型语言,而这使C++成为跟Ruby,python一样的高抽象设计语言),它将要做的事情要完成的逻辑提早到编译期完成而不用到运行期的资源(因为C++模板赋于它这个能力可以这样做),所以通常可以用它来进行设计,模拟设计模式等等。因为模板是语法导向的,所以它的源程序是需要按源程序文件的发行的,进行语法级的复用(模板程序只需要编译前端就逻辑成立),而面向运行期的程序是二进制导向和复用的(事实上除了C++,没有那门语言有这样的能力吧),,这就是所谓的二进制复用,因为变量,class的内存映射,,全部是运行期的编译后端的事情。。
6,用list数据结构实现typelist.
写在后面的话:其实C++的元编程属于十分高级的抽象能力,我们不应该把精力放在这样的语言机制上面,基础薄弱的读者和程序员只需要把精力放在学好C++的运行期OO和数据结构就行了。因为大部分公司开发中甚至都不会用到这些技术。

7.13 C++的模板编译技术本质
编译器的工作在于将高级码转化为平台(这里的平台之意主要指CPU)目标码,但是现代的编译器和IDE还加了一些高级单元,如汇编器,装载器,重定位器,链接器,调试器,这些的功能来直接生成可运行文件.
一个编译器,如WINDOWS上的GNU,VC,BC等等,,对应于一个特定平台,编译器实现,链接器实现都是平台相关的(这里的平台主要指OS)往往有一个RUNTIME 库,,这个库是什么呢,,一个OS一般由C编成,这些C的库一般向程序(向编译器编译出来的,往往是用户空间的程序)提供CPU进程,,线程,SOCET等资源,运行库就是提供这些访问功能的地方,这就是编译器跟平台发生联系的地方,一般一个编译器都有它对应于某个平台的运行时库..
那么什么是编译期呢,什么是运行期呢?什么是编译期静态,什么是运行期动态?什么是静态语法错误,什么是动态逻辑错误呢?RTTI与RUNTIME有什么联系,为什么有new这样的动态分配内存.
编译期静态和运行期动态主要是指类型来说的,
那么泛型编程跟模板又有什么关系,一门语言的语言机制必定可在它的编译器实现找到答案.那么模板技术在编译器中是如何实现以支持泛型编程的呢
我们知道,一等公民在一门程序语言中是重要的,C++不支持数组的直接拷贝和赋值,不支持对象的直接赋值,不支持范型类型,这就是因为对象和数组是C++用其它方法抽象得到的语言机制,比如按位拷贝这种机器很擅长做的事,其实范型编程是可以用很多方法达成的,C++唯独用了编译器的"参数化类型"作法来实现模板再由模板实现泛型编程,而RUBY等语言有它自己的范型编程做法,,基于对象编程跟面向对象不同,不要以为你会写CLASS就是面向对象,如果没有用到OO的继承和多态,你同样是在写基于对象的东西.。

7.14 OO的缺点
比起函数式语言来说,因为在一门OO语言内它的一等公民不是函数体而是OO对象,而且OO直接跟我们的设计思想中出现的元素挂钩,因此OO成为代码复用和软工时代最好的语言,(软工这个字眼就指出它是计算机离散倾向人的结合.OO的提出就是为了解决编程离散形式与人思想中出现的因素的对接,解决复用和开发中人人的交流等问题,所以离开OO是不可想象的),但是构建在OO之上的设计模式却导致了越来越深的复杂性,这是因为人们没有想到OO以后的思想世界和现实世界应用,其实比OO这种形式要复杂得多,比计算机内部的离散形式还要复杂得多,换言之,OO并非一切,相反,在一定程序上提供方便性的同时,在另外一些维度和应用上导致了另外的复杂度,因为思想永远是复杂的嘛(杀鸡用了杀牛的刀)
而函数语言或动态语言却可以绕过设计模式实现同等的功能,因为函数作为first class是一种最接近计算机处理的形式(而OO同时接近计算机和人的思想,或者更确切地说是接近人的思想,看OO以后的GP,DP就知道了),,反而可以用这种唯一的形式绕过很多DP要花很多劲而函数式语言轻易就能搞定的一些东西.
但是不要认为动态语言就能提代OO静态语言(这种说法真可笑,复杂性有它的好处,复杂性同时也是相对的而已,RUBY在一些领域导致的复杂性比起JAVA来说会更多,只是不同情境的问题而已),哲学指出,任何问题都要实事求是,我们需要在问题需要什么类型的语言才去考虑选择什么语言,,考虑一门语言优劣时,并非纯粹技术问题,有时是多维的问题,,比如语言的优劣还得用它适用不适用软工这个指标来考虑呢
还比如JAVA的动态机制(反射加代理),这种历史复杂性是为了实现动态语言的"由行为决定类型",,,这就是AOP,,JAVA居然站在它本身是个静态语言的基础上去做动态语言做的事,造成了很多历史复杂性,讨厌
而且,RUBY这些宽形式,自由的语言,正是因为太自由了,就像C语言一样,太自由,反而不能成为软工的首先,因为软工要解决复用和简单意味着统一的形式的需要,即编程语言的离散形式和人的关系(所幸RUBY本身也提供了OO)..
所以,JAVA在WEB开发中,OO以后的那些WEB框架,是现实所导致的,并非JAVA本身导致的,,,改变J2EE框架就行了,并不要把JAVA改造或加个JRUBY就行的,,,相反,我们应更多地解决现实世界的思想模型,而非增大或扩展JAVA或JVM平台自身,感觉在JVM上集成JRUBY实在是个蠢的作法,凭空增大了抽象和运行时开销,就像把车拆了放到车库里明天又拼装起来再开一样..
其实以我来看,所有语言都是一种形式,要解决的问题才要弄明白,如果这种形式不能表达更深的问题,那么问题就出现在形式上,,,,OO最大的特点不是继承,不是多态,而是"OO接上了计算机离散和人的思想中的因素体,促成了软工时代的出现"
所以,不要去为了技术而探索技术,,,现实世界才是我们要最终解决的问题,而不是那些表达现实的形式语言..语言终究是形式.技术问题的复杂有时是历史原因导致的,,,,细节和理论,形式和应用,,是二个可以并行发展的维度...其中现实应用更重要...RUBY声称它能提供编程对于的简洁性,这就是一种很好的作法。

7.15 模板的继承

7.16 模板的偏特化

7.17 真正的策略
策略为什么跟设计模式有关?
设计跟编码之间无法精确定界,因为设计可以仅仅是提出一个用于实际编码所用的架构,也可以是考虑了所有编码细节的详尽设计,设计的最终目标可以是一张UML图,也可以仅仅是一张草图或一堆小卡片(Wildcard说法即来源于此,当然,并不一定要求设计要成档,但是将它具现化表达出来还是很好的行为),然而归跟结底,我们设计最终是想产生一堆有机的类文件(UML图也是,卡片也是),也即我们在进行设计时,我们的目标(Dest)是产生“编程领域对于现实事物或问题抽象的OO解(OO解只是解空间的其中一种而已,单纯使用OO的三重机制这只是初级和简单的范型,但已经是非常强大的范型,它产生的类文件已经可以实现出一个很大的架构,然而,结合了模板和策略的范型就更加是功能强大的范型了,可以更为灵活地产生出一个架构)”,这往往就是一系列的Class文件,而设计的源(Src)则是“经过了编程抽象的现实事物或问题”,设计模式就是用来描述这个过程的,然而它又跟算法不同,算法体现的是大领域内对于某个事物的解决方法(这就是为什么算法也可以用计算机来表达的原因所在)
我们知道设计是高于编码的,这就是说在我们进行设计时,进行的是一种纯思想的活动(非常重要的是这里的设计二字远非设计模式中的设计那么狭隘),并不预先想到要面向编码而设计,然而我们得出的某种设计方案(比如一种架构逻辑,,设计模式)并不是不可以在技术上用一种方式来实现,因为狭隘的设计模式之说的设计不过是根据设计方案产生一堆类文件而已,设计模式高于成码跟设计模式可以用一种技术来实现并不矛盾,关键是找不找得到一种技术实现手段的问题,你可以联系ISO网络参考模型来考虑,一种思想或协议的确可以用来实现。。就凭这点,我就十分佩服策略的提出者
策略是属于设计的而不是编码的(虽然策略也是写代码),,,这是本质,明白这点非常重要
策略就是这样一种技术,模板技术天然就用于参数化类(这是模板的本质抽象)并产生类文件,这在设计期是一个天然的技术实现,比如一个类文件可以产生一个职责或对应设计中的一个对象,而模板可以跟据对象本身的特征(比如对象的类型)去决定产生的表示这个对象的类逻辑,,而这正是策略要解决的问题
策略是多范型设计的较高境界,虽然OO也可很好地表达设计和思想,其它范型也可以,然而策略更接近设计,比如DP的Sington,Boost 的Enable If,,从这些字眼上看就直接跟思想和设计已经十分接近了。(不要忘记DP中一种是模板方法设计模式,,这更一步反映了模板与设计的深层渊源)
策略与OO是什么关系呢,(OO和OOP出现在前,,然后是DP,GP,Policy这些范型),因为模板就是一系列可运行的参数化类的逻辑,那么对这个过程也可以用OO(OO与策略并不是包含或被包含之类的关系)来实现,,因为模板也支持OO,比如STL的源码中,对容器,通用算法这些的策略大都没用到OO而是一堆面向过程的过程模板(函数模板就是子过程模板,类模板就是用到了一点OO的模板),,(如果策略本身也是用OO范型(通常说的OO就是类文件级的OO)来实现的,那么这可以称之为二阶OO,第一阶是1,模板产生类的过程中用到了OO,2类本身也是一种OO,)
因为策略就是模板技术描述的设计思想,设计思想可以是Gof,Boost,Loki,Core J2EE Spced DPS这样的大思路,也可以是一切反映设计的小思想,,因此策略不仅仅是我们现在已经见过的上述一些实现,,,它可以是千千万万的不同思想的模板实现形式(因为思想有千千万万嘛)
什么是编程,什么是抽象能力,编程能力,什么是学编程(即使这样看似最最简单的问题也其实不会简单,网上的确有太多这样的争论,那么这里将会给你一个有形式描述的概念,关于我在一个维度的认识,因为我们在上面提出了诸如设计,抽象,原语之类的用词,因此接下来的描性会变得容易界定,这就跟设计模式内部的那些功能分工的说法一样,,只有给出了能形式化的中间逻辑用词,那么才能不会在争辩时连最细小的中间共识都不能达成,那么也就不可能得出一个最后的结论),比如我在第五部分会写到一个世界逻辑库,因为要在这个逻辑世界里产生很多Actor,,而Actor本身又有Type,,Ablility之分,那么我可以用模板表示,,(ectc),如果你能像我这样想,那么恭喜,你已经在非常有成效地学编程了,,,而你在看我写的代码,,那么再一次恭喜你,你已经在实践了
所以什么是编程,,编程就是用各种范型来实现设计,,,
学编程就是在大范型之下学大量细节的小范型(所以说JAVA并不是一种很好的拿来作为教学语言的语言。因为它在OO方面和OO后面的事做的很好,然而在OO前面的那些范型全部被忽略了)
抽象能力就是对外来架构的理解能力,和掌握大幅中间逻辑,,以构造新逻辑的能力(计算机与人脑的区别就在这里,人脑可以主观能动地发明抽象,,而计算机只是我们用来反映抽象的工具,它本身并没有主动意识和主动构造抽象的能力)。

7.18 为设计产生代码
什么是模板会产生代码呢,这个说法其实无比简单,只是当我们摆出诸如“为设计而设计代码”,“编译期而非运行期多型””C++的元编程技术”这样的别称时,它就变得远离我们的理解变得面目生疏了,
我们知道,在编译型语言中,比如c和C++中,写代码就是为运行做设计,程序设计期design time的就法就因此而来,写代码的本质就是在C的数据封装抽象基础上,C++的基于对象抽象上,C++的OO抽象上这些语言机制逻辑上+操作系统封装了的CPU逻辑上,写最终面向CPU的二进制代码,这就是我们说的,系统编程语言的本质在于基于系统逻辑(并且语言本身也是基于系统逻辑的,我们把计算机系统离散基础,语言实现离散基础,CPU汇编语言所涉及到的那些机器离散基础都称为系统逻辑),写面向系统逻辑的代码。(与它对应的脚本语言更多地是为了直接为应用服务因为它极力让开发者绕过系统逻辑)。程序运行的本质是什么呢,,就是向操作系统和CPU请求资源,CPU执行二进制,(任何我们写出的源程序,在经编译优化后形成目标平台二进制码,我们向PC编程以构造应用的过程只是系统逻辑到应用逻辑的的一个维度变换),这是指比如C++这的以本地平台为运行环境的系统编程语言,而非指自带VM的,向VM编程的脚本语言而言的。
设计期就是写代码期,是用户动作,发生在编译期前,编译期不是用户动作,运行期又不是用户动作。。
模板是一种参数化的泛型,泛型这二个字决定了模板没有一个类型(在写模板函数,或模板类时的设计期,能难想象没有类型的东西能够堂里皇堂的参加运算吧^-^,只有编译器技术到家就可以),,在写出这个函数模板或类模板后经编译时它并不产生代码(机器动作时),只有在接下来实例化时(注意,这个实例化是指在设计期用户动作期,用户为这个泛型占位符指定一个具体类型并写出关于它的代码,这个实例化跟我们在OO程序设计中,由一个class定义一个对象类似,接下来,在编译期机器动作时才会在编译期实例化产生实际代码到中间目标文件中比如obj中(非飞行模式),
什么是实例化呢?注意这个字眼
在OO程序设计中,由一个class定义一个对象并不实际动态分配内存,因为这还是指为运行期作规划的设计期并没有实际进入到运行期并分配了一个内存,如果用了new关键字,还是没有分配内存,编译期永远不分配内存,只是为程序将来在运行期如何获得内存作分配上的控制而已,,
只不过一个实例化用了不同的情景而已,实例化并不一定指为运行期实际实例化(也就是说实例化并不一定就是一种“为运行而作设计”的设计,,它还可以是一种“为设计期而作设计”的设计期动作,)它其实就是一种编译期的宏,只不过它有类型的判断,因此远远不是简单的文字替换性质的宏,,而是一种编译技术,模板由于有这个特性,第一,因为它是泛型,所以为C++带来泛型编程机制,第二,模板给C++带来元编译技术。
回复 支持 反对

使用道具 举报

该用户从未签到

667

主题

2111

帖子

5570

积分

LV 11.会员

MS爱好者!!!!

积分
5570

社区居民偶尔光临工蜂最爱沙发在线达人社区平民做个有钱人略有小成常驻会员忠实会员

地板
 楼主| 发表于 2011-10-22 18:23:56 | 只看该作者
7.19 真正的metaprogramming
不是指.net那种共用一个库的CLS
面向对象在复用工作方面做得很好(比如它提供了继承,多态,还提供了二进制复用比如COM,还提倡用类聚合代替继承而不是tear off,还出现了诸如设计模式这样的复用经验),但是这是相对现实生活的那一端做的工作,,然而它对于编程工具端(编译器)本身来说是不友好的(程序源码必须要进入runtime才能让我们看到这所有的OO里面发生的事,在编译阶段(一般也称为design - time)我们不能控制这些OO对于问题域的实现),我们应该在没有让程序进入某种具体编译器之前,,就让它得以被控制,而不仅仅是预测这些编译的文件进入runtime以后会形成怎么样的逻辑
也即,类的职责单位是类文件,这种机制有一些的缺陷性,问题域是巨大的,如果我们动手一项工程,我们不希望被无穷的细节所困扰(现实问题总要分解为一些类,最终要形成类文件,一般每个职责形成一个类),我们希望有一种介于编译器和现实问题之间的更大的整合层来考虑事物(而不是一个一个的类文件),,也即,我们不需要考虑现实问题到类的实现路径,我们希望在设计期就考虑现实问题到一个“比类还大的”,“更接近现实问题”的逻辑层上去,再由这个逻辑层到最终的类实现路径(比如单例模式,就是指代设计中只能出现一个实例的逻辑实体,这已经十分接近设计了)
如果这个层面被提出来,它甚至不占用到运行的时间,,即增加这项抽象,并不耗费运期间任何成本(因为它只发生在编译期)
因此它是语法导向的,而不是虚拟函数集导向的
这个整合层就是策略,,,模板技术允许我们在编译期就用“策略组合”加“模板技术”来生成源程序,这实际上也是编写库为用户所用时所要考虑到的问题
用户希望能从库中提取功能的子集,这势必产生这里提到一个trait的概念,简单描述一下先
这就是元语言,
问题域永远是多元的,因此需要元语言,数据格式是多样的,因此会有表示数据的通用方法 XML,一般用XML格式的metadata来封装其它格式(即:不可能把所有格式的数据包括二进制啊,PE啊,,TXT啊都做成XML,但我们可以把这些数据格式放进一个XML的字段内,XML就成了一种通用的数据打包形式并用它用于进行统一数据交换过程,然后在使用时从这些这段中取出各个格式的数据再解码成所需的格式,W3C组织正在策起一个关于XML二进制格式的方案)。

7.20 元编程技术
承接我上面一篇文章《为设计产生代码》
如果实例化不是为运行进行设计,,而是为了为设计期产生代码这样的目的而进行的设计期的设计(很显然,它本身也是一种设计,并不面向运行实例化,这是一种静态实例化,编译期的实例化,即它向前推进了一个意义层次),而且当模板机制的编译技术实现涉及到词法处理,语法分析这样的编译原理知识时,,,那么模板就是一种地道的编译器的编译器了,是一种元编程技术。
很多时候,理解以上的东西,不但要求你有实践经验,而且还需要你有精深的抽象组织能
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

     
    Archiver|手机版|小黑屋|( 沪ICP备12034951号 )

GMT+8, 2024-4-28 07:20 , Processed in 0.217654 second(s), 28 queries .

© 2001-2011 Powered by Discuz! X3.1

快速回复 返回顶部 返回列表