新手编程导论


第一部分

前 言

By Chenyi
眼前这本书充分体现了作者的所思、所想、所感,他用自己独特的眼光审视着计算机技术的世界,也用自己独特的思维逻辑对技术进行解读,并用自己特有的,呵呵,偶尔带有“四个逗号=一个逗号”这样的语言风格,进行着自己的诠释。创新是一种美,独立思考也是:)

学习是一件因人而异的事情,因为每个人的生活经历、教育背景、年龄、认知模型等等,都是不尽相同的,也就是每个人所处的“维度”不同,而作者有一种“建立更高层抽象的能力”,用一种特有的方法尝试着给大家建立一个学习计算机的、相对高层的构架,这样,可以在一定程度上突破个人的“维度”,使大家从与周围事物建立联系开始,一步一步的走向计算机的世界。不识庐山真面目,只缘身在此山中。确实的,在学习技术的过程中,横看成岭侧成峰,远近高低各不同,但是作者却尽力想让这高低或是远近都不同的山峰,能在我们面前呈现出一种规律、共性来,这是难能可贵的,因为这个架构的过程对思维的要求是比较高的:)

哲语有云,动身的时候到了,有的人去生,有的人去死,只有上帝知道,于是这个问题被回归到“ To Be ? OrNot To Be ”的问题,是生,是死,只有上帝知道。

但是,人类对真理的探索和对知识的追求,却从来没有因为“生死”的维度而停止过,是的,一颗崇尚真理、探寻真理的海洋之心,将从来不会因为泰坦尼克号的沉沉而消沉,它将永远绽放在人们的心中,激励着我们向更广阔、更深髓的世界,一路前行、风雨无阻:)

在这个意义上,鼓励作者的写作和思路,也是对我们自身追寻真理的一种鼓励、一种回路。是为一点小感想:)与作者分享!!
By Minlearn
对思想的认识和界定是重要的!!因为我们需要一个知识体系才能不致于困惑!!(而身处编程界,纷繁复杂的术语和概念足以让一个初学者却步)

我抓住了哪些转瞬就在我脑中消失的思想,,因为它们远比一切成书的东西都让我感到它的珍贵!而更玄的是,他们竟然真的能够被文字描述出来!!这整本书就是小说式的教学。它力求呈现出一个精致化了的术语集。以使初学者能真正理解至关重要的那些概念。

正如Chenyi所说,每个人都是某个维度上的人,有他自己的年龄和认知,具体到某个历史时刻,我们的人生阅历已然被格定,而这决定了你接受新事物的能力和眼界,人生在世,已经不可能脱离某种信念(也异或某种阻力和障碍)而活,当我们开始学习编程,我们永远都是用外行的眼光去看待某样东西,而当你占在巨人的肩膀上成为一个专家之后,你就需要用全局的眼光去看待曾经陌生的知识,此时你不再是个学习者,而会批评产生你自己的认知,但那毕竟是要过的第二道槛,而初学者就是那些连第一道槛都难以过去的群体。

这其中最大的拦路虎就是对术语的理解,很多书并不切合初学者的实际,从他们的角度呈现一条清楚可见的理解路线,而只是一些大部头衍生下的反复抄袭品。

给你一个术语或道理,这个道理有什么用?没用,是的,因为要给你一个情景,你才能理解它,仅仅让你去学一个知识,而知识和众多其它知识之间相似而微有不同,如果不给出它被产生时的历史和它所处的架构(这本书不但给你思想,而且给你对应的细节),那么我们就会迅速迷惑,更遑论运用它,因为我们不是泛化主义者,形而上学者(但是的确存在超前主义学说,只是为了创立一种学说,后来才慢慢与实践相结合),我们需要一种与自身相联系点去理解它,我们只是生活的人,我们不是高高在上的学院派高手。

一个高手必定是与常人有不同的思想级深层的东西和他自己特有的体会,因为他也走过初学者才走过来的路,可是往往人们都忘了归纳那些至关重要的经验,那会是什么经验呢,那些是不会出现在任何描述具体技术细节的书里的思想级的东西,那么这本书尝试的正是记录那些秘诀,如果真的想当高手,请你不要错过这本书里任何一个字眼!!如果你是高手,这本书一定与你内心深处的某些想法偶合。

本书过后,再辅于其它教科书(比如你手头上的一本C++教材,本书后面列举了一些与本书能很好答配的推荐参考书)你应该会具备基本的编程能力和编程理解能力。本书前半部分是对思想和认知的导论,后半部分注定实践和技能能力的形成。

知识是事物之间的联系,那么实践就是强化或深入这些联系的方法,我常想,到底是什么重要,是认知还是技能,人们普遍认为实践应在任何情况下都高于认识,事实是:可能有技能但是没有认知,但却不可能有认知但没有技能,就拿学习英语来说吧,看英语报纸也是一种实践,因为它也能够加强你实际使用英语的能力,(我不是在模糊这二者之间的区别,我只是企图站在这二者之上求得一种更泛化的认识),实践不过更侧重动手能力而已民,而认知跟它并不矛盾,知识的获得与能否运用知识本身无必然因果,拥有足够的知识,再加上泛型的思维,,你就会快速得以实践,一切都是一种格物致知的过程,只有格物至知,先格物,认识到了一定程序后就会产生对事物本质的认识,也可先认识事物本质再在指导下去发展技能,但是认知可以直接传递给你(至此只是一个你所能想象得到的浅层和大概,而且除非实践,这个大概形象你也不知道它到底是正确的还是错误的,更深层的你想象不到的抽象以及关于这些认识的正确性要求实践),相比之下一本书不可能传递很多实践的东西。本书前一部分正是力求让初学者完成从认知到实践的有效过渡。

所以说实践和认知都是重要的,没有谁比谁更重要的说法,然而对于初学者来说浅层认知的重要性要高于实践,一开始就有一个好的思想和基础显然可以为未来的实践扫清障碍,这是因为学习是一个层次上升阶段,在拥有一定知识后,理解基于这些知识之上的更高层知识会很快,, 即掌握了基础再加上一定勤奋的博物广识,知识量是几何级上升的,因此一种很好的学习方法是,学习应该先吞,(在一定知识量的前提下尽可量地博物广识,即使看不懂也要浏览完,以获得浅层的认知继续下一步学习),这是学习中自然而痛苦的过程。(不是提倡光谈和光看理论,而是把理论整理成一个架构也是一项重要的工作,不是不能直接把这个认知传递给你,而是需要再找一个与你的结合点来让你认识它,因此它是一本同时讲解到认知与实践的书, 不是提倡导光谈理论,而是如果事先有理论的指导,那么学习中就会少走很多弯路,学习中最怕不能理解细节,更怕以为细节就是一切,所谓一叶屏目不见泰山,更有人把学习语言作为编程的终极目标,而如果事先有人给你指导,你就会少走很多弯路)

在学习方法上面,有一个问题是关于细节和思想的。

我们鼓励在实践基础上去学习,也提倡速成,但大多数人显然不会拥有正规的实践教育,我认为学习不应该提倡逐步深入,人的生命有限,染启超在渡日的般上一夜之间学会日语,这就是说他掌握了思想,细节的东西永远是后来的,只要思想是重要的,(了解足够多的细节才能泛思,,在学习编程中,除了一些对至关重要概念集的理解之外,,从来都不是大思想决定一切,而只是小细节,这就要求你作很多的实践)

掌握了思想和基础后,每天写小程序,编程能力就会日渐提高,而当你写过和分析过很多程序之后,你就会具备一眼看出的本事,过程的最后你发现自己蛹变蝶飞了

学习应首先理解框架(这是泛读),然后是细节(这就是对某些内容的精读),就好像在一个大型应用中,编译跟解释并不会走二个极端一样(低层用编译码,而高层用脚本),学习往往是混合了这二个过程的过程,,,矛盾和老子的不可绝对在这里起作用

所以说思想和基础永远是重要的(人月神话的作者固然精通很多细节,但是他写出来的却是一本思想书),,知识和思想自然是越多越好(泛读可以无限进行,花再多人年都无碍,人年是人月神话里面的概念),但是有一些知识不必深入(精读却需限于自己的开发领域),但一定要知其然

本书主体中的二部分就是认知和实践,思想和细节的结合,所以你要做的就是在认识的基础上作大量实践。这就是我在前言的后半部分推荐给你的看书方法。

如果说一些知识仅仅知其然就够了的话(仅仅是不致于迷惑和建立知识结构),那么有一些知识却是要精通的,因为不但要知其然而且要实际拿来应用

要成为某领域能实际胜任某份工作的程序员,就要做到精通四个“Idioms”(注意这是精通)
1. 你要用到的语言和IDE的“Idioms”(一门语言,一种开发库)---编程首先就是会用一门语言和它的库
2. 数据上的”Idioms”(数据结构-数据的内存模式,数据库-数据的外存模式)---编程第一个就是数据,想起DOS下的编程了吗,一是数据,二是代码
3. 设计上的”Idioms”(面向对象,设计模式)-----编程第二个就是代码或代码框架
4. 以上三条都是前提,那么这第四条就是最终的你要实际涉入的业务领域的”Idioms”---编程最终是为了为这个领域服务
以上四条是主干(最好按1-4的顺序精读),而其它的都是支节。比如工具的使用啊,XML啊,UML啊,XP方法啊,ANT部署发布知识啊等等

对于计算机专业来说,为什么也才那么几门课程(高数线代离散,编译原理,C与算法,Java),,因为这些学科是最重要的(真正掌握了这些基础,你会发现再多后面的技术用语及其背景都是支节),这就相当于前面提出的四个Idioms

对语言细节的学习和深刻理解永远都是学习编程的重头戏,但决不是一切,比如拿编程语言来说,只要越过语言的表达这一层,我们才能用语言表达和理解事物(语言跟你要说的话就像用C++语言去表达一个算法,方案领域跟应用领域的对应,就像穿鞋去上海, 穿上鞋只是开始,真正你要去的目标-上海还远着呢),就像口才,一个好口才的人说话时绝对不会想到措词,因为语言已经成为一种意象,只要把一样东西思想化,才能超越这个东西而去想到别的东西而长足发展,比如面向对象,这本书将帮你解释为什么面向对象是一种科学的机制,解释的过后你甚至会觉得这是一种本来就该存在的很亲切的机制,只要超越了面向对象我们编程时,再加上一定设计模式,才能真正不会一动手编程就考虑什么是面向对象之类。。(而这些,好像都是一个高手所能做的事了。)。

编程时碰到的信息量永远是巨大的,有生之年我们不可能掌握这些信息的来龙去脉,对于程序员来说,提供一个关于它的编程参考文档可以说是掌握了此信息,因为这个文档就是这个信息的大概,实际上我们编程大部分情况下都只是用第三方的代码库来编程,这个信息用于编程所需的全部东西,对于编程来说只要掌握这些东西就行),换句话说,一些知识如果不能理解就得先放(这本书并不适合于赶考使用), 在这个信息的社会,至于信息,是撞事学事!一个程序员并不全知全能,它只提取和了解事物对于编程方面的信息。对于事物的逻辑认识,只能在对它的编程中不断掌握它,抽象是惯穿这本书的重要的思想,维度也是,我们是从学习编程的眼光来组织这本书的。也是站在初学者的角度来解释诸多概念及其关系的。

问题随之而来,既然存在这个界限,又如何突破,否则这就是一个空谈

多走弯路,学习是认识事物间联系的过程,而记忆或实践是加强这个联系的过程,,能够认识到事物之间的联系,即便是自想的联系也可加深对事实的记忆(一个程序员有他自己的知识体系是重要的),这就是知识

一切东西,我们应该查本究源,深入其原子世界(任何一个术语都不会简单,有它自己产生的环境与其它知识的联系,但也正是因为这样,这也决定了它的有域性,任何知识只要放在它自己的领域内去理解才能更容易被理解),, 翻译过很多文章你就知道要措词,措词跟概念有关,二个稍微相差不大的措词都会让读者摸不头脑或让他们恍然大悟。

我们高中做到了英语的相似名词不同分析,本书也打算在一定程序上这样做(虽然计算机技术领域一个概念可以自成一书这种现实不允许我们这样做),咬文嚼字在这里起作用,,在某些情况下,它是一种好习惯!

然而千万不要走入另外一种极端, ,知识用词和技术用语没有一个标准,比如方法和函数指的是同一个东西

什么是线性(一次就是线性),什么是离散(离散了的量),这都是仁者见伍,智者见,但人们对此的理解都不会差到那里去,并且也不会影响后来的学习,这里有一个描述或形式的概念,相信大家都还记得初中学过的集合,是描述性概念,但集合其实还有一个形式概念,给定了形式就可以框死

而且,要知道,即使是《虚拟机的原理与设计》这本书的作者也会对别人书里的进程概念感到不解。

我注意到程员序考试中多了一项标准化,的确,知识的传达也需进入标准化时代了

最后,如果说任何行为都是功利的,那么我写这本书的目的只为博你一笑。

书中错误再所难免,望不吝赐教!!

l 别怀疑,这正是一本同时可作为入门和进阶级的书(更偏重入门)!然而真正的高手和真正的初学者都将从中有所得。
l 你还在为学不懂大量术语而烦恼吗?如果你真有这种体会,那么你可能先要看这本书再看你正在看的C++的书,因为你仅仅缺少一根主线, 而它是能让你少走很多弯路的拐棍
l 对架构的学习才是真正的学习,知识也有它的架构,然而在这本书内有架构也有细节(高手固然知道细节,然而高手也有精神空洞,因为你还需要懂得一些细节之外的架构级的东西)!
l NotWar3的从零到尾的实现,让你知道其实你一个人就可以做出类War3的游戏!!(本书作者也自称是一个菜鸟,但是这个程序的确是他自己写的)

导 读



任何语言都是有门槛的
C用最初级的封装了汇编的逻辑写应用,这也就是C代码看起来难的原因,如果你看过位操作(比如oopc实现的RTTI),看过用C表达的数据结构,就会发现,有时看出来样子普通的一堆语句,你根本不知道它想表达什么上下文逻辑(因为C代码本身跟现实问题走了二个极端,你看懂了代码却看不明白代码后面的东西,这是因为有设计在作梗,而C跟设计和现实问题走了二个极端),除非你真一开始就明白它想写什么(有文档的情况下),但Java这样的语言,能够整体上看起来是一个一个的类,显得意义明了,但实际上语言越接近应用问题反而代码看起来更复杂,,因为现实生活是很难的你根本做不到绝对靠近除非你为每一个问题写一个dsl(C表达的向系统问题接近和Java表达的向现实靠近各有各的难处,作为一门工业语言,要求它看起来便于程序员理解和复用,Java这方面是做得不错的而C肯定只是专家语言),一堆有机类有时反而难于让人看出它想表达什么,而且另外一方面,类里同也是函数,也是Java的语句逻辑而不是封装上的类逻辑,这些语句逻辑,同样用Java中的库来形成,Java的库是重新经过设过的,相比C语言,比如它规范了流的概念,Java的OO和规范的库是人们说Java比其它语言易的二个方面。。

但是虽然熟悉编程的人可以拿来用,但是对于没有编程经验的人来说,它照样跟C一样难。

因此JAVA的所谓易,是相对用过C的这样的熟悉编程者来说的。至少使用JAVA,我们照样得学好跟C一样的数据结构知识。。

看不懂一套源程序,主要是

1)你不知道普通的语句是体现什么样的数据结构

2)你不知道普通的语句是体现什么样的算法

3).........抽象惯用法

4)。。。。。如何向现实问题靠近抽象并设计的

5)现实问题的复杂性,跟语言逻辑的简单性,非dsl之间的矛盾(语言从来被设计成通用的)..

因此要学好一门语言解决问题,不但要学精语言的语法和库,而且学习的重点还就在于:

数据结构和算法,现实问题,,抽象和设计。。。

什么是语言级和语言外要学习的(数据结构与代码结构)
设计最终要被体会到源程序,如果把设计作为整个软工过程,那么源程序是这个工程最好的结果最终证明,(参见《源程序就是设计》一文,你可以google得到它)
我认为这样的说法是对的,因为它综合了我在下面要谈到的1,2二个过程。(但因为它没有显式提到下面我列出的二个过程,所以我认为该文在一定程序上是一篇含糊其词的文章)

1是脱离了语言的那些映射,即人们通常说到的设计一词的意义(请参照我的《什么是设计》一文,这里的设计实际上是指大设计,指领域抽象,语言选择,这些东西,包括数据结构),2是结合了语言的实现映射。即人们通常说到的实现,不过人们通常把1作为架构师的工作,而把2作为程序员的工作而已。如果架构师的工作深入到类的类部,深入到详细设计,那么他实际上担当了一部分程序员的工作。但在人类的工作中,2是受1控制的,

这里面其实有一个代码抽象模式和数据抽象模式的区别。

类实际上是一种数据抽象,而不是一种数据结构,,因为它将代码抽象为一个一个的“数据模式”,即将C++这样的通用语言增加DSL词汇,让它成为DSL,可以表达class cat,class pig,猪猫,诸如这样的领域词汇,所以类是一种数据(把词汇抽象为语言的一级first class数据,即UDT,ADT这里面D的意义)抽象模式,也就是代码模式,而数据结构学是一种实现模式,而不是代码模式。

数据结构学与代码结构学的区别,是解决问题的问题和解决语言映射问题的区别,两者在不同抽象层次,这就是为什么数据结构可以用任何语言可以用基本流程实现也可以C++的类来实现,因为数据结构学跟它如何用一种代码结构来抽象是没有直接关联的,前者是如何解决问题的抽象(是一种脱离了语言的映射,即我在1中谈到的设计),后者是代码问题(如何面向用了类的可复用目的来进行对具体语言的映射,即我在2中谈到的实现)。

在编程的实现领域,从来最终都是过程编程即Core C的那些内核逻辑〔类体里面也是〕,所以,用了类,只是将面向过程抽象到了一个更好被复用的问题,并没有将如何实现这层抽象到程序员不需要理会,所以对于不懂C的人来说,即使它能很好理解OO,也做不了什么程序〔类只是将实现套了一壳,从而将面向对象层次的复用维持在这壳外,壳的里面却不行,照样是过程编程〕,OO的强大只是指它类外的那些代码抽象模式。OO提供的最本质的意义和作用有二,1,将C++这样的通用语言“在类层次”变成为DSL,2,正是因为1的作用在先,所以普通个人也可以开发复杂的软件系统。

也是C程序员的能力更多在前者(数据结构),而数据类型属于后者,设计模式,面向对象都是后者,是C++程序员或架构师的事。

对于2,即实现。跟具体语言有关。下面举C和C++为例。

遗憾的是,C++只是对C的增强而非替代,如果他离开了C,就只能有类的代码模式的复用能力,却无法保留C的强大而直接的实现能力(如果写数据结构这样的东西都要强制用到类,那么除非有必要为复用作目的,否则没有用C++的必要。),所以一个C++程序员(假设他用了OO来实现数据结构)既是一个实现者,也是一个设计者。因为它既抽象了如何能被解决的问题,又抽象了如何能被复用的代码。



程序员对于算法,数据结构,数学实现问题这些细节的实现问题把握最重要,而不仅仅是语言能力设计,代码抽象能力,如果有架构师的工作在先,C的程序员仅仅需要提供函数过程模块,(极度求接口化和求极度模块化,设计是需求驱动的,接口是要提供的功能驱动的,都是不必要的那是架构师的工作)
什么是语言级要学习的
什么是编程要学的。首先给出一张图。图中描述了编程知识结构。




就学习一门语言比如C来说。
综观C语言的语句形式,就只有编译原理后期翻译的那些语句形式了,,仅仅从语句形式上来看是这样的。。

计算机能做的事,就是CPU的取指令,译指令,执行指令,从内存中取数,CPU用逻辑器和运算器运算,,中断处理例程,IO设备的功能,
这些是计算机能做的全部事情,由CPU统一控制,,因为这是所有硬件资源能做的事情。。计算机后来所有的软件逻辑包括OS的所有的功能都由它决定。。
由OS生成调用这些如上硬件资源的策略。。

template是C++的一种高级语言机制,它建立在C++的类型机制上提供范型,但是它的语法及其古怪,跟C++那些其它的普通语言机制十分不相同,(由于语言是由语法定义的,因此我们称,类型,表达式,控制语句形式,赋值语句,这样的东西为语言的要素,打开任何一门关于程序语言教学的文章,我们都可以发现这一点。。字符串,IO,异常,标准库,,数组,与Windows的接口,这样的东西是语言的高级话题,和高级功能,不是语法级规定的,,比如可能是库提供的)
编程学习方法
只要学习C语言,计算机系统知识,语言实现原理,网络原理这些系统知识,才能从一个比较大的侧面去看待现实问题对于计算机的实现问题(也即编程问题),,只有这样,只有懂C,懂编译原理,懂计算机系统,懂网络,才能从一种大局的高度去看待并计划你的系统你的应用。。
  
  要学习编程,我觉得C语言是起码要学的,即使是以后学LUA,RUBY这样的高阶语言,最好的途径还是先学C.
  
学习编程必定最后要涉及到系统逻辑,那么是首先学习上述的计算机专业课程的知识,还是先学语言呢,,,,我觉得是先学语言,,因为理论的知识在没有实践的前担下很难掌握,,而一开始就学习语言(我这里只指C语言)是最好的办法,,,因为在学习的过程中,你需要一门语言来实践,,,验证各个小问题的语言实现,来掌握更多的知识

计算机学生专业课程本质讲解
RUBY跟C语言还是结合得蛮紧的,,RUBY在数据结构方面还是有C语言影子的,,人们说RUBY偏向于人,但是几乎所有的语言都脱离不了数据结构,,都脱离不了底层,,RUBY的OO只是指类的那一方面,,RUBY的IO,数据结构,跟其它语言是一样的,,,复杂,跟底层相关,,数据结构和底层有什么关系呢?内存地址是线性的啊,这就是数组,,我们的虚拟机是栈式机啊,,所以要堆栈,,我们的运行时把类对象分配到堆中,,,所以要堆,,这就是编程语言处理计算机,解释器本身的离散形式而导致的复杂性,,所有的语言包括RUBY都没有很好地用OO封装数据结构,STL是个例外,,但是无论理解OO的STL还是RUBY的数据结构,都是差不多要理解到底层,即数据结构的原理,每种数结的具体情况,,学习RUBY并不能让你省这个懒,,问题就出在这,,,要学好RUBY,C的数据结构方面还是要下点功夫的,,任何一门语言,语言库包括STL都没有自称它把数据结构简化到不需要明白任何具体数据结构的形式就可以拿它来进行编程的程度 ? 其实计算机专业学生的那些课,,数结,C,操统,都是无比珍贵的东西 ,,学校设立这些课而不是C++,不是RUBY,,是因为那些才是可以解释后来一切的底层 ,,而并非一种舍本未未的作法..
语言就是处理三个东西之间的关系,平台,语言,要解决的问题(解决什么问题用什么语言最好),其中,汇编基础:解释了硬件平台,即CPU中内置了控制内存的模块,因此要涉及到寄存器,内存地址等,操作系统课程,如果说汇编基础解释了解硬件平台,那么这就是解决的软件平台的问题,而离散数学,,就是一切离散形式,计算机和程序语言环境,和语言本身都是本质上一些离散形式,比如图灵机就是程序模型,是个离散东东,在编译原理中体现就更明显了,比如函数语言实际上就是一种高次方程的离散,编译原理的本质是什么呢,,,如果说硬件和操作系统都是解释了平台,那么编译原理就解释了程序本身的本质,那么C语言课程呢,,这解释了硬件编程的一些方面(C+大量汇编的形式广泛用于硬件编程,驱动开发),,而且,C语言这门课程最最重要的意义还在于,它解释了一切后来的高级语言比如RUBY,比如JAVA,要学习到的跟底层相关的离散形式,,这是尤为珍贵的,比如进程,比如并发,比如异常,,比如数据结构,而数据结构中的"数据"二字永远是一个程序的中心因素,,从普通数值,字符串,数值到结构体到OO数据,体现了人们封装程序开发数据对象的复杂性的要求..而且这种发展是为了产生一种广泛深度的开发方法的,这导致了软工,JAVA就是这样一种好的语言.
应该如何从C的观点来看待你的编程语言比如RUBY跟操作系统之间的关系呢?一方面,C本身就是一门硬件编程语言和系统开发语言*(OS一般就用C来开发,很少用第三代语言比如C++,JAVA,RUBY),而且,C语言就是一切语言的基础,JAVA,RUBY都是用它写的 ,这导致了一些现实,,下面一一说来,C语言跟OS编写有关,是OS的直接宿主语言,,其它的比如RUBY,比如C++,比如JAVA,,是C的上层语言,学了C语言,,RUBY,JAVA,OO,都很简单 而且C语言是系统编程语言,是底硬件编程语言(embed c),,,是使用最多的语方,opengl能用JAVA开发吗,,不能,那会慢得要死,而且根本也不行,,,驱动程序能用JAVA吗,,那简单是笑话,,只有WEB开发,,这些不需要很实时的I/O,这些架在逻辑上的逻辑(TCP,HTTP,WWW,SOAP一层一层而来)不需要很强的反应,,才可以用JAVA,而且JAVA编程统一,OO复用强,,是软工理想语言, 一句话,C扩展RUBY简单,而RUBY调用C却需要BIND,,一句话,编程三个平台,1硬件,裸机,,用C好,,2配有OS的桌面,用C好,,用OO的C++也可,,,3,WEB,用JAVA好,用OO好
C语言一般用来作底层编程,比如那些靠近硬件层的,选用普通的C语言(C语言标准)可以在WINDOWS平台上开发控制底层的编程,而且还存在嵌入式C语言用来开发各种硬件编程,驱动,BSP(主板支持驱序),比如一些智能手机的操作系统开发等,其实C语言开发也就是一种范式,一种编程习惯,它是过程式命令编程范式的代表,世界上用得最大的编程语言不是JAVA,不是RUBY,不是VB,而是C,因为C用来作系统编程时,它可以提供较快的运行速度,接近底层的更好控制,实现C语言的编译器是一种将C语言代码直接编译成本地码的工具,不存在任何逻辑中间层次或解释程序(比如虚拟机)因此运行速度很快而且,C语言提供的指针等,直接与硬件内存地址啊,这些东西挂钩,而且系统本身大都用C语言开发,比如WINDOWS,这就在你要开发的应用程序和宿主环境中提供了统一性,你可以更好控制和调用系统DLL,不必作语言之间的BIND,而且C语言不必涉及到OO(因为OO主要是面对人的)而C语言更多地是机器因素,它要求人用机器的思维方式来编程,这句话本身也就说明C语言是靠近机器的,因此它适合用来作系统编程,而RUBY等OO语言用来作面向程序员的高级脚本编程,所谓脚本语言,就是相对系统编程语言来说的,系统编程语言提供了强大的底层编程能力,而RUBY等脚本语言提供了调用这些系统功能的高级应用层的开发..
无论如何,一个程序员是要天天学习新知识的,如果不能快速接受(也即你的基础知识跟不上来),那么基本上你会很累 大学只是学习这个基础知识的阶段,你最好把编译原理,离散数学,汇编程序设计,操作系统,C语言,这些基础弄得滚瓜烂熟,,并积累一些具体的开发经验,,等出了社会之后,你会发现社会上的编程知识跟你在大学学习的东西差了去了,这个时候你的这些基础知识就发挥了很重要的作用,你需要这些基础来理解软件开发中的大学问,新思想,比如设计模式,AOP,OOP,DP,DSL,STL,UML,LAMP,REST,COM,J2EE, 总之钻进去了,也就是一种乐趣..好自为止,
用C++开发要学到什么程度
鉴于这样的话题太有争执性,本文只给出一个参考意见。

都是无穷无尽复杂的细节让初学者停滞了他们学习一门语言的脚步,在你开始满怀喜欢地按书上的例子用C++开始写HELLO WORLD时却被IDE少引用了一个文件之类的问题折腾得一头恼怒浪费不少时间,而书上没有任何一点关于这些的描述,或者网上也只能找到零碎不成文的信息,即使一门语言内部被宣传得天花乱坠比如拥有OO有多少语言机制多么利于复用这种论调泛滥的的今天,在真正动手使用一门语言的时候,作为新手的他们还是会能遇到轻易就把他们击败的细节问题
这些细节问题往往不是大思路大方向(比如什么是OO,这个逻辑用什么来设计好),反而是那些日常时常见到的工具上或语言上的陷阱(象其名家一般研究基础的东西,因为任何东西都是基础的东西通过泛化得来的,而很多人就是基础都搞不明白,,虽然存在大量关于某主题的高级论题,但是基础的东西永远是要求精和熟的),往往这些细节问题可以被理解,但就是难于掌握(这就是那些被称为语言陷阱的东西,然而这是一个大问题,对于软工来说它是),,应该被尽量在语言被设计时就避免而不是人脑避免,比如IDE环境,比如语言细节,比如目标领域的问题,当一门语言复杂到你真正需要掌握这些才能编程的时候,编程就演变为不仅仅是简单的复用了,虽然我们以为OO语言就是简单的复用语言但它们明显做得不够.(没有一门语言,即使RUBY,也没有把语言细节弄得足够简单,使得你可以不管任何语法产生式的机制去直接构建你的应用,,在不损耗逻辑的情况下,而且RUBY的一些语法机制也不是不简单.需要你有深厚的系统知识去理解他们,比如协程,元编程等技术,在接确过一点C++人的眼里,丝毫不比C++简单)
用C++进行开发要学到什么程度,需要什么知识最小集呢,当然要根本目标问题的不同决定不同的复杂度,但语言和工具级的复杂度都是一样的
那么真正要掌握C++进行开发,你需要掌握那些语言级和库级的知识呢
1,至少熟练一个IDE,make,install,,调试,等编译技术,能在编译期出现错误的时候能搞明白基本的I关于DE环境配置的错误
?2,在语言级和库级要明白的就多了出去,比如对STL理念和当中每个LIB的理解,对指针进行理解才能明白诸如函数指针,指针当返回值以返回一个数组等机制,,,当然还有很多很多C++的惯用法,等等,这是主体部分,注意我这里说的是会用,人们可以不懂STL原理和任何实现却照样可以拿来熟练使用的人大有人在.
3,要明白你使用的第三方库的一些知识,要了解它们OO的架构,一个字,要达到一种能用C++使用它们的接口的能力就够了,这就是OO语言宣传它们的资本..又一次,你只会使用就够了,不必懂得库的OO架构,,你需要了解它们透出来供你使用的粗略架构模型和接口就行了.
4,在复用方面,要明白设计的一些知识,知道OO,GP这样基本思想,知道你的应用大家都用OO作了什么设计,你所使用的库用了什么样的封装上的设计.OO并不仅仅是封装,封装是为了复用,,因此OO最终就是为了复用,封装只是中间过程..就像接口并非用来封装一样,而是用来抽象,,一切都是抽象..
5,在开发自己的库和逻辑方面,要明白应用域的一些知识,这样在设计时就能知道要构建什么样的架构.用什么模式来设计等等,用什么语言细节来实现,等等
6,要尽量熟悉以上,多练手,才能快速打出代码,,,,要记住,这个过程很自然,,就像你学好了英语的语法,再多看了一些英语的文章,那么你就可以写出英语文章来了.一切都是习惯用法,和语法游戏,,,除此之外,编程中其它的一切就是设计问题,而不是编码问题了,,,设计的问题是其它领域的问题,比如算法设计,,而不是编码问题(有些书用鸡免同笼这样的问题来放在C语言的书,这对学习C语言本身的语言机制如流程控制有意义,数学问题的复杂性只对研究算法有用,对解释C语言本身无任何作用,而算法是设计通用的,不跟语言相关)
一切在于多看,多写!一开始不要写逻辑和架构过大的逻辑,在懂得设计时就可以接确了.
设计本不存在?当你快速写代码时你根本不会觉得设计的存在,,这是因为编码就是一种习惯,而设计就是一种关于要写出什么样的逻辑的设想,用什么编码方法来体现,,,设计就是对编码进行控制和计划,,这里就是编码跟设计的关系,,难的不是编码,因为所有人都可以学会语法,学会写作习惯,,,但是设计样的文章却千差万别.;.
?设计不仅是算法设计,而且是复用设计
本书目录安排
宜交叉来读本书!!本书的架构是自成体系的,除掉第一部分(书前附录)和最后一部分(书后附录)外,中间三部分是主体,这三部分自成体系,独立且又相互联系,其中的每一节也自成体系,既有知识架构,也有学习方法,技术细节的描述,第二到第三部分很多知识点的讲解都为了第四部分服务,比较侧重于讲解通用的编程知识 (同时兼顾了游戏编程),因为我们本书的主要任务之一是产生一个游戏(本书第四部分《一个例子》),下面一一讲解:

第二部分 导论,这一部分主要是关于编程的导论,
(要懂得一点思想具备一点常识)《设计,编码,,与软工》(编程与思想)这一章解释了三种思想,原语,抽象,组合,,和软件开发的二个重要过程,,软件工程的相关概念,是编程入门的关键
(要懂得一点领域内的数学)《数学与算法》(编程与数学)计算机整个就是架构在数学上的,跟计算机平台实现,算法设计,,架构密切相关,,真正要深入编程,,,对数学的学习是必须的,,千万不要相信别人编程不需学数学!!那是肤浅的编程!!
(要懂得所处的环境)《硬件,平台,网络》(编程与平台)三种大的编程环境,,对他们的理解是必须的,才能走出窗口,,在一个更广阔的思维空间里进行考虑问题和编程。

第三部分 C,C++代码阅读与控制

(要精通所用的语言的语法语义)《语言和语言库》这一节讲解了C++,JAVA和,,JFC言和类库相关的知识,要实际编程,,掌握一门语言和库是必须的
(要精通处理什么样的数据) 《数据结构与数据库》(编程与数据)计算机就是用代码来处理数据,
(要精通写什么样的代码或框架) 《算法与架构》(编程与设计)写什么样的代码,,,不再是写什么样的实现,什么样的算法的问题了,,,而且还是要体现什么设计,,构造什么架构的问题,,如果说面向过程的写代码就是注重设计算法,,,那么在OO这个注重可复用(而且现在这个年代,我们大部分情况下是一个开发者而不再仅仅是一个实现者,我们的确经常是使用外来库来构建应用)的软工时代(时代真的不同了!!),,而且要懂得设计架构,在本节的最后讲到《J2EE大领域学》J2EE是现在最流行的,,OO以后的范型和问题集,因此对它的学习就代表对大多数计算机技术的学习,因为第一部分到这里为止的基础,,所以我们这里可以大致描J2EE的整个模型
(要懂得待处理问题域的细节,为第四部分作准备)《游戏编程与虚拟现实》从这一部分开始讲解多媒体编程和游戏引擎开发的细节,,为下面一部分做好充足准备。。

第1到第3讲的都是范型(1讲的是语言级的,2,3讲的都是非语言级的范型),第4讲的是问题

第四部分 一个综合的例子

这部分就是讲解如何用范型来解决实际问题,最终产生一个可玩的游戏。。注重了架构的学习

《设计----GVWL1.0开发过程》
《编码-----一个NotWar3核心Demo的实现》
.作业与重构

注意各个小主题前面的标号,,有这么几种(组合),,初级和基础主题,,进阶主题,,例子主题,,理论主题,,等等,这些主题都是最新的,一个程序员深入编程或迟或晚都要遇到的(摒弃了一些过时的),,当然进阶就是高级主题,,,基础的主题还是要花大力器的

哲学和基本思想这一节,我会写跟软工密切相关的思想和学习IT知识,以及任何一切知识通用的思想,,比如抽象啊,范式啊这对解释以后OO,都是必须的基础

领域数学并不打算写一切近代数学知识,我只写与计算机领域交集域的那些数学知识,,它对以后一节“机器,平台,网络”是必须的,因为我要在机器平台网络 这一节讲解程序设计语言原理,也就是专业学习必学的编译原理 而数学知识其实还可以被用到以后无论什么时段的编程工作当中,,对机器平台网络这一章的写法中,我并不会突出太多技术细节,我意在只让初学者明白这一切都是怎么回事,学习编译原理为什么会对学习程序设计语言有帮助(是因为它解释了很多语言被设计出来的原理)

机器,平台与网络揭露了软工所处于的三个环境,WEB编程越来越流行了,这就是网络上面的一层抽象(internet是internet,web是web),编译原理其实有很多方面跟汇编有关,这就是跟机器编程有关,,平台 就是OS,,OS就是一个编程层,就像脚本机一样,,,,我们native编程是对本地编程,这里的本地就是指OS,,而虚拟机是可以不需要操作系统就 能运行程序代码的层,,这是另外一个软件运行环境 总结起来,,,这机器,平台与网络讲解了很多“软件运行环境”,特别是我在编译原理讲解中涉及到的机器部分,是最底层的软件运行环境,对设计虚拟机也是必须要用到的知识,,对解释OS的实现又是必须用到的知识,这三个环境是天然不可分割的,,站在软件的角度来看

应用,设计与软工,如果说机器,平台,网络是讲解软件环境,那么这一章这就是讲解软件与人的关系了,一言以概之,这就是软工,软工是为了让软件产品适合“应用”而出现的人类的活动的统称,(界面与逻辑分开啊,数据分开啊什么的),在这个大活动中,涉及到多人合作,,需要考虑软件作为产品的可扩展性的时候,,就需要提出设计和架构这二个字眼和关于它们的一切系统非系统的知识,,而这是很多书本都忽略写出来的 作为产品的,,注意这几个字,,本来软件是不需要写构架的,,但是如果想让软件成为一个好产品,就需要构架,,这就是说“构架不是功能的要求,但却是工程的要求”

  以上我说的都是“要懂得的部分”,,,一个程序员只有懂得这些,才算是理解了软工的常识,,,否则一味钻细节,什么都不知道,处于一种很可怜的未开化状态,,更要命的是,不知道何为设计,也就不可能主动领会别人代码或库中的设计之巧,,仅满足于了解算法实现的人是不行的,这样的人,由于一开始不考虑软件作为产品的特性,,最后写出来的东西生命力就不怎么样, 事物之间的联系很重要,你在学习的时候,意识到了计算机和其它东西的联络,然后你构建了这种关系之间的系统,就自成一个学习体系了,这是有价值的地方而且你非了解不可!!因为除非这样,你才能有效地学习,,否则迷茫于庞大的细节,,巨细不分,也就不能界定概念之间是什么关系,,,这是十分造孽,,比 如平台网络机器这一章,“为什么要出现编程语言”,,“我们看到的编程语言到底是什么东西”“它们下面是基于什么离散数学构建的抽象”我把一切功能的达 成的中间层,,无论是软件,还是硬件,都看成抽象,我们今天能看到编程语言为什么要求我们用语法,,而不是直接在语法树上写程序为什么Lisp语言就能 直接在抽象语法树上写程序,而不需要一个语法,,,为什么Yacc是一个没有语法的词法编辑器解释了为什么“我们要用流程控制,逻辑转移”这样的方式写程序,而很多人并不知道,他们学习编译原理,就是为了学到这个知识,这是死读书的人多么大的损失啊,,而我打算择其要点,,组成一条线,,解释语言的由来,,浅而不缺地解释很多很地道的东西,有机地讲解,,择其重点和必要的,去除过时,历史复杂性
  
  我们要编程(如果说第一部分是要懂得的原理,那么这里就谈实践细节,当然要求精通),当然首先用一门语言,我打算举C++和JAVA作为例子,因此语言 和语言库这一部分重点是说到了这二大编程语言,我现在的想法是再增加一个Ruby,,,因为Ruby是新兴的动态脚本语言,是历史所趋,应用所朝,必须要讲解出来否则就是不合理,不完整 当然我也不会讲个大而全,我会从某个线上某个维度上作一个系统讲解 要明白,学语言只是学语法,我们编程是用语言所带的库的,C++有STL,BOOST,JAVA有JDK《J2EE,,所以库是不能不学的,,因此学一 门语言=这本语言的语法+实现了这个语言的某个开发库,,而且程序=算法加数结也是不符合现状的,这也是我接下来要谈到的
  
  而且,其实C++和J2EE作为编译语言,是静态系统语言,因此有设计模式的出台,,,而动态语言就不需要设计模式,因为它是运行期语言,类作为数据结构的定义在运行期可以被动态改变,,根本就不需要编译期cast 这和《C++新思维中》“编译期策略”是一个道理,C++只能提供“编译期动态”以实作出设计模式,而动态语言由于它的性质根本不需要设计模式 有人说设计模式是C++程序的“补丁”,,这话说错也可以,说没错也可以,,这是因为狭隘设计模式之说就是为C++,JAVA这样的静态语言作补丁,, 而广泛的设计,包括设计模式所提出的那些东西,,你知道范式是什么东西就明白我为什么这么说了,广泛的设计是一种意识内你要明白它是广义设计,,但是实际设计时,你要因地制宜的这因地制宜,经济学就指明,,你有自己的资源,,要设计出一个适合你自己的框架,在你的能力和时间范围之内,不要做大而全的超级设计,,虽然大而全正是设计的本质意义所在,,,总而言之,设计的意义是泛义的,但是人的掌握范围之内就得因地制宜,就是多范型设计
根据我们在前面“平台网络OS”那一章提到的,语言的本质就是数据加处理数据的方法写程序的过程就是操作这二个东西,,手工作坊的写软件时代,,就是编程=数据加算法,现在软工时代,编程=数据加算法加架构,其实算法是比架构更广的概念,它侧重于指"功能",而架构侧重指"扩展功能",但这二者都是编码能实现的"真正功能",,说明白点机器语言也是数据加语句,,,这也是一切高级语言在机器端的实现形式

  一种语言要最终被体现成数据加语句的形式,那么创造这种形式的语言本身也要提供这二重机制,,这二重机制就是数据加操作数据的方式,数据由原来的数学,字母,这些计算机硬件能直接处理的东西,变成了OO的对象,而处理数据的方法,则变成了架构加算法

比如类形式,,用一种类的形式综合体现了数据和操作数据的方法,,这就是OO



对你死亡级的提醒,请不断写代码唯手熟而的方式是阅读代码,成千上W,并实践!!!第二部分 基础:导论

第1章 系统


1.1 何谓PC
计算机俗称电脑。我们平常谈到的计算机就是指PC。

当然,计算机不光指PC,还可以指服务器,大型机,我们无意去为分别这里面的区别花费时间,这纯粹是一种历史叫法。在不加特别说明的情况下,本书作为编程教学书以下部分我谈到计算机就是指PC。我们是对PC编程。

PC的概念更多地是一个 历史概念而非范畴概念,历史上第一代(注意并非第一台)PC是指1981年8月ibm推出的第一代个人计算机,称为IBM PC,它配备了intel的8088 CPU和Microsoft的MS-DOS,从这里开始,intel确立了它PC行业的CPU霸主地位,微软也慢慢成为了软件领域的巨无霸。Wintel组合出现。

当然,IBM是卖电脑的,Intel是做CPU的,而微软是搞软件的,这三者业务领域不一样。但电脑就是从这个时候开始进入大众生活的,在这之前是一些巨型机,科研设备机器,以及个人爱好者研制的雏形所谓“计算机”。正是这样的发展,带来了计算机向PC发展的态势(硬件上的,软件上的,总体架构上的)。

关于这其中的种种历史,存在有一些趣闻,比如:

l 【硬件上的】:
第一台PC其实是由一个叫毛利的人发明的而不是教科书中普遍谈到的那个巨大的家伙。
l 【软件上的】:
苹果图形界面拉开了计算机图形时代的到来。
50,60年代某段时间一种叫CP/M的操作系统差点取代MSDOS而使它所属的公司成为IBM的合作伙伴。
MSDOS实际上只是一层壳。它是改PCDOS来的。
l 【架构上的】:
CPU是有架构的,AMD和Intel主频之争从来都没停过。
1.2 图灵机与冯氏架构
计算机的产生源于对自动控制(自动机理论)和人工智能(机器人)的研究。

图灵开始研究人工智能的时候那时计算机尚未产生,他的图灵机是对计算机领域的贡献但其实更多的是对人工智能的探测。

图灵机是一种通用自动机器的模型,这种机器有极其简单的形式(因为要能易于实现),然而只要产生足够丰富的变形它可表达并产生一切逻辑(计算函数,表达其它离散算法逻辑等),图灵机在理论上是理想的,它有一个二端无限沿伸的纸带作为存储装置,输入,输出和状态转移函数是一个机器的三要素,这三要素组合并变形可成为一切机器的原型,可解决一切图灵机能解决的问题。因为它揭示了计算机在抽象层次运行的本质即形式计算。所以图灵因为他的这个贡献被称为计算机之父。各种形式的自动机理论随后在那个时代发展开来。

图灵机是被严格证明的。虽然它是一种抽象机难于直接说明,但正是因为这种抽象可用一套形式化的东西来表示并证明(图灵机的运作原理被称为形式计算,是离散数学自动机的一部分)..通过测试的机器就叫图灵完备,,所有通过图灵完备的机器,这说明它们可以产生等价的算法。。可以解决同样的问题。

图灵机的意义是伟大的,因为它是抽象的,所以它能被实现在很多不同层次的地方,比如大到语言识别器,虚拟机模型,小到自动售货机等等。。

如果说图灵机阐述的是一种泛义上的自动机,那么冯氏模型就是专门针对计算机的自动机理论了,以前的机器(在可编程电脑出现之前),指令是硬化的,要为某机器编程相当于重置整个机器。

冯氏机的精神在于"指令存储,顺序执行",在冯氏模型下,执行指令的机制和存储指令的机制是分开的,要换一套程序不需要更换指令系统,因为程序是被内存存储的,取指令器是从RAM中随机取出(某条活动指令总是会在某个内存位置待命,随机的意思就是直接指定内存地址并从里面取码进行执行),并不用预先在内存位置固化程序,然后通过控制和地址总线交互存取到内存,这样CPU只负责执行,对于不同的程序,它可以只用一套指令系统。这样的架构就形成了一种执行指令加调用数据(指令数据在内存中的位置,或编码了的数值和字符)的方式,然而同图灵机一样,这种简单的抽象形式同样可形成高级的具体逻辑。

这样一来,我们编程也成了书写指令和指定供指令操作用的地址,指令内含内存地址为操作码,而如何在内存中组织数据又发展出了数据结构,用底层的观点加数据结构的观点来解释现实事物的解法就形成了算法。(当然,计算机的观点是如何寻址和执行指令,实际上在没有高级编程语言理论之前,远远还没有算法的概念,因此不能说指令是语句,也不能说地址是个变量,地址里面的东西更不能称为数据类型,CPU能直接执行和认识的只是由0,1表达的指令和编码的地址以及各种基本数值和字符,只是后来以人的眼光来看可以无穷地把0,1编码成其它各种逻辑而已,不过在汇编语言级别,这些都不是机器能直接理解的)冯氏机以及它提出的“指令控制存储并串行顺序执行”的理念,这些都深克影响了我们这个时代的编程工作。

l 我们注意到冯氏机主要处理数据和代码,而指令是控制数据的,这使得我们后来的编程工作的抽象焦点在于数据和语句。这是冯氏模型对编程工作最大的影响。

l 我们注意到指令是被串行执行的,这说明冯氏模型一开始就被设计成为非并行计算机,因此在后来编程中,要实现并发逻辑需要从软件上和设计上去突破。

l 其它。。
1.3计算机能干什么
我们要首先懂得计算机能干什么,它的功能发源主要在哪里?这样才能懂得编程的出发点和目的。这就不得不首先谈谈计算机系统的组成。

首先,CPU是PC中的总控制中心,在前一节中我们谈到CPU执行的过程就是一个不断取指令并译码的顺序过程(所以本质上它其实并不智能,PC只是一台高速自动机而已),其实CPU不但处理它自己的事情而且是作为整个PC的总控制中心存在的,CPU不但接管了内存管理,还接管了其它硬件,比如它把显存归入它的管理统一跟主内存编码,而且把硬件消息归为它的中断并把其中断处理程序统一跟内存编码。CPU在执行中(有一个时钟发生器指导CPU不断地运行)不断变换其状态,输入输出则是这些二进制(因此整个PC做的工作都是某种意义上的IO)。总而言之,冯氏架构中,CPU统冶了整个机器,内存二把手,其它硬件则是被它统治的。在CPU眼里,一切都是内存地址和指令(这就对编码工作提供了无比便利的条件,使得冯氏下的编码工作变得无比统一)。

CPU中最主要的二个部件是控制器和运算器,计算机之所以被称为计算机,是因为它提供了形式计算,还是CPU里面的逻辑和算术运算呢,当然是前者。实际上运算器和逻辑器的地位就像协处理器一样,是CPU不必要的组件,是CPU扩展它的功能加的电路板。这些硬件功能固然是计算机功能的重要组成部分并影响对其的编程工作,然而并非全部。

计算机逻辑主要发源于二个地方,这决定了他的功能和对于编程的意义所在。

l 首先,计算机各个部件都是有功能的,比如浮点器可以表达浮点数并处理,计算机籍此可以直接表达硬件级的浮点抽象以及用浮点表达更大的编码了的抽象(实际上也存在软件的浮点处理器),这就为编程引入了浮点逻辑。另外一个道理,你不能编程指使PC为你做“给我泡一杯牛奶”之类的事情,因为它不是硬件上能提供的功能和逻辑所在(即使是抽象也得不到),只有当PC真正接入了一台牛奶机之后,你才能编程实现诸如泡牛奶的硬件功能。

l 计算机的功能不但在于表达硬件逻辑,而且更大的地方在于表达广泛意义上的应用逻辑。我们可以对PC编程创造一个游戏,虽然图形功能是来源于显卡的,但是游戏世界的逻辑明显不是硬件的。Web应用就是这个道理。

在机器级别编程,我们无法表达高级的应用逻辑,因为0,1能直接编码的逻辑是非常有限的,只有当PC发展到提供了操作系统,进程等高级逻辑之后,我们才能在一个新的工作起点上构造更强大的应用逻辑。

所幸的是,当编程发展到高级语言阶段之后,这些机器的细节都被完全抽象化了。
1.4 内存地址
既然冯氏架构就是将执行指令的CPU和存放程序的内存分开的一种架构机制,CPU中集成了对内存的管理,在CPU的眼里一切都是内存地址,而且这导致了冯氏模型下编程的统一性。那么CPU是如何与内存发生关系的呢? 学习这个有助于我们理解操作系统诸如这样的软件的系统逻辑是如何在硬件基础上包装出来的。

CPU与内存如何发生联系被称为CPU的存储管理机制,CPU管理内存的硬件是它的地址总线和数据总线(我们知道CPU虽然自己处理数据,但他同时还主要做着一种IO的动作,CPU管理IO的硬件被集成在主板上一块被称为芯片组的地方),其中地址总线是负责寻址机制的通道,而数据总线表示CPU能一次性处理的数据通道,地址线数与数据线性往往长度不一,这就导致了CPU的寻址跟处理产生了一对矛盾,地址线决定了它能看到(CPU能使用到的)和管理到的内存总量(物理地址),而数据线决定了它能一次性处理的数据长度和能表示的地址形式(逻辑地址),,,这就是表示(逻辑地址形式)和实际内存(物理地址)上的矛盾。

撇开其它因素,我们就事论事来讨论这个矛盾,20位地址线可以使用2^20个最小内存单元即1MB的内存空间(这就是说使得这种CPU的一个任务理论上可使用至多1MB的线性空间,因为它只能寻址到这么大的地儿)但16位CPU中的寄存器只能表示前16位(CPU是用寄存器来表示地址的,这就是说虽然CPU能看到整整1MB的空间,但他一口吃不下其中的最小一个单元,因为它首先都不能直接表达这个单元)。因此CPU要表达和要用尽这1MB的空间,不能以直接线性对应的方式来表达。除非数据线多于或等于地址线。

间接的方法就是设置另一层抽象。可令16位先表达一个大小为64KB的段,1MB的内存刚好等于1MB / 64KB倍数个这样大小的段。在这种情况下,内存就不再是绝对线性的了(当然,实际上所有内存都是线性的,这里说的是分段之后的逻辑情况下,而且你不用担心CPU认不认识这样的划段法因为它的确认识,下面会谈到一个实模式的东西),而是被划分成了一个一个的段(在段内才是线性的),16位用来表示段内的每个最小内存单元,空出的4位不再用来表达内存本身,可以用来表达段本身。

以上讨论的情况是8086CPU在实模式运行的存储管理逻辑,即从逻辑地址(CPU要形成任务用到的地址形式)到真实物理地址的编码(实际机器有的地址),这中间要经过一个变换,CPU负责这个转换。无论在多少长度的地址线和多少长度的数据线产生矛盾的情况下,都会由CPU负责这个转换,不过32位数据线的CPU转换方式要特别复杂而已(特殊的分段方式再加一个分页的方式,段寄存器不像实模式那样用来实际存储物理地址的线性表示,它只用来实现硬件级计算最终物理地址的一个中间存储)。

在32位CPU出现之后,寄存器能表示的逻辑地址早就是4G了,而地址总线超过了32位(除非地址总线少于逻辑能表示的数量才需要实模式那样的分段,然而32位CPU下并没有产生这样的矛盾因此可以以线性地址的直接表示方式来表示逻辑任务线性空间,然而32位CPU照样也实现了一种转换机制,这是因为它需要实现更强大的保护模式而不仅仅是完成寻址。

综上所述,逻辑表示是寄存器能表示的地址形式,,真实地址是系统装配的内存量,而线性表示是CPU用来形成任务的任务地址。统称为任务空间。不跟硬件地址相关,也不跟逻辑表示形式相关,这完全是一种独立机制的编码,32位CPU下,一个任务的线性空间表示总是4G(注意总是这个词),只是一个转换机制会负责这逻辑地址到线性地址的转换,,然后又有一个转换机制负责线性地址到真实物理地址的转换,程序员完全不必为线性地址过少和这中间的转换而烦恼那是CPU必须管的事,,否则32位的汇编工作几乎要手动驱动CPU。

明白了以上这三个概念,让我们再来看下面的章节。
1.5 分段和分页以及保护模式
32位的CPU利用它的转换机制可以保证4G空间一直存在内存中(这就是说,实际上并没有一个实际的4G空间在内存中,只是CPU可以看到一个4G大的线性段,能以它为基础形成任务直接供程序使用这才是CPU关注的)..这样的话,对于编程来说,只要是在保护模式下,我们都可以拥有一个4G的编程可用空间,不必调进调出的(它本来就一直在内存中为CPU所看见)。。

上述32位CPU的转换机制是分段跟分页的综合作用。分段机制不管这个4G空间的实际调进调出,因为它不负责实际分配内存,它只负责逻辑地址到线性地址的转换过程..实际分配内存的工作由分页机制来完成,它负责线性地址最终到实际的物理地址的转换,它把这分段后形成的4G虚拟的空间用来调度真实的内存页,内存页就是真实的物理地址的载体,分页机制只需保证生成的页面大小总和局限在这4G空间中即可。。页面是随用随载,调进调出的,以较小的单位而不是段来实际使用内存,,这就加大了内存的使用率(虽然分页并非百分百做得到如此)。

要深切理解CPU是如何完成分段分页形成任务可用空间的过程是一个精微的学说,段选择符,门,LDT,GDT,pae (CPU中有一个pae位可以保证所有CPU都只在概念上只看到这一大小)这些概念之间是如何相互间发生关系并维护这样一个4G空间。以及这个机制产生的结果和意义所在,需要翻阅相关的书籍。下面试阐述一二:

l CPU首先检查这个选择符的ti字段,以决定是选择到ldt还是gdt中选择描述符,然后检查相应的索引以在ldt或gdt(这些都是内存中的数据结构表,表驱动方式)找到最终的描述符。。这些符结构都在4g内存的高端部分,注意是在内存中..找到描述符之后,再以判断选择一样的方式在描述符中判断各个字段,主要是生成段基和段长,段的三个权cpl,dpl,rpl..

l 比如在如下的一个指令中 mov ds,ax, ax存储的并不是指向ds的指针,,也就是说它并不实际存储ds的地址。而是一个选择符(注意,第一它不是段描述符,第二它段选择符是一个32位的数据结构,它的各个字段表明了不同的意义,组成整个段选择符的意义),,,段寄存器大小有限,CPU中的寄存器只用来存储指针值,或者描述用的数据结构,比如在这里是一个段选择符。。

l CPU就是靠以上方式来实现对于段机制的保护的,每一条指令都有内存读写逻辑,每一个内存读写逻辑都有这样的寻址,最终要涉及到进入段中。涉及到保护模式的一系列机制。。

l 门是CPU中的一种迂回机制,有调用门,任务门,中断门,异常门,这四种门的用途不一样,但都是为了实现CPU对内存访问进行保护的一种迂回机制(比如数据访问权限啊,任务安全切换啊,中断正常返回啊),调用门实现了数据访问的权限机制,跟四种普通段,段描述符有关,任务门跟tss,tss段选择符有关,中断门与异常门跟中断或异常处理例程有关。。
首先来谈调用门。对于一种跨段的段间调用call或跳转jump,统称调用和跳转和转移,有直接转移和间接转移,,但是直接访问方式不能实现段间调用的同时转变程序的特权级,调用门的设置就是为了实现一个间接调用加同时改变程序特权的跳转方式。。
任务切换有直接切换和间接切换,任务门跟一个tss段有关,跟调用门谈到的对于普通段迂回机制一样,任务门也是实现间接任务切换的手段。

而对于中断门与故障门来说,门描述符内存储的是中断与异常处理例程的地址。。
总而言之,CPU提供了对保护模式的硬件支持。而所谓保护,是对段,内存,这些保护模式的概念提供迂回机制,,于是发展出分段,分页,调用门,任务门,中断门,异常门这些的CPU机制,更多请自行研究。。


1.7 操作系统
当CPU能够被用来形成任务控响应中断时,操作系统这样的软件级的逻辑就可以在上面慢慢构建起来了。实际上机器往往并不需要一个操作系统也可以运作,比如电传打字机和电子打孔机,在裸机层次也可以实现其功能。

但配备了强大CPU的PC绝不是一般的电器,冯氏模式一开始就指出,它是能够换程序和可编程的,对PC的使用往往源于它抽象出来的强大的软件功能,而不是仅仅是用CPU来加热鸡蛋(如果实际上CPU真的能煮熟一个鸡蛋而且业界流行这样使用CPU的话)。

操作系统就是这样的软件的第一层,它沟通了裸机与使用机器的用户,对于裸机来说,操作系统可以由它直接抽象出线程,进程,网络,图形等系统功能,运行于操作系统下面的各种应用软件可以使用它们进一步抽象出各种更为高级的应用逻辑(比如Web,多媒体),对于最终用户来说,操作系统提供了界面使得各种高级应用成为可能,而且对于程序员用户来说,使编程工作脱离直接面向机器编程,,因为操作系统可为各种应用软件提供一个执行路径,程序员可以面对操作系统编程,这是一个巨大的成就。即操作系统不但是计算机功能新的提供者,而且是开发环境。

这样计算机系统实际上是硬件系统支持下的软件系统了。人类要直接面对的就是软件系统(这就是抽象的能力,它一方面隔断了人类并不擅长的硬件细节,另一方面提供给人们一个更为强大的软件环境)。不论对于最终用户或开发用户来说都是如此(当然对于硬件工程师来说不是这样)。

一句话,操作系统的出现是源于人的需求。

操作系统提供那些对于应用和开发来说最最基础的功能和逻辑,因为它直接关联机器和CPU机制而直接面向初级软件需求(比如形成过程以运行程序使操作系统具有执行程序的功能),在操作系统内核中,内存管理,进程,文件,网络,用户界面,是最先应被抽象出来和最先被解决的问题。我们知道软件即抽象,所有的计算机系统能呈现和解决的逻辑,第一步是解决系统支持的问题(这导致了对系统编程的意思所在。),第二步才是应用逻辑的表达和解法(应用编程和领域编程)。
当然,操作系统是一个大的概念,它小到只需要包含一个内核,大到可以包括内核层,开发支持层,调用层,硬件抽象层,应用接口层这样的操作系统架构(参见Google手机平台,这本质是因为软件是抽象,所谓抽象就是在不同的抽象层次完成不同的工作,操作系统作为软件也不例外)。因此讨论操作系统我们只需讨论操作系统内核便可一窥其端倪,我们只讨论巨内核的Linux Core(相比之下有的系统内核是微内核,这种内核基本上只提供对进程,内存管理,图形,用户界面,这样抽象层次的封装和架构逻辑而不实际实现它们)。

Linux内核实际上主要是一个定制硬件功能为软件的逻辑,怎么说呢,它一方面沟通了硬件驱动和软件,使硬件可以运作起来,为软件逻辑服务,,,这是第一,第二,它对于硬件功能的定制功能,比如进程,,,就是把CPU硬件资源转化为软件可以使用的进程资源,把网卡资源转化为socket跟进程一样是种OS资源,,因此,内核实际上只是初初级的硬件功能抽象,

界面就是表现抽象,它的窗口机制,都是独立内核的(KERNEL硬件抽象层),,如果称内核为操作系统(实际上只是OS的核心一部分)的话,那么,X协议的实作品,桌面环境,窗口管理器,该桌面上的APP,,,都是作为应用程序来运行在内核之上的,而不是像WINDOWS一样直接在内核就集成了GUI,苹果OS在内核集成了X,因此图形效果很好(LINXU桌面没有WINDOWS快), 这就是移殖问题产生的地方之一。,因为UNBUT逻辑并不是其它逻辑的泛化源而是与其它APP一样地位的基于内核的普通APP。

1.6 并发与协程
在操作系统和应用程序的逻辑中都会涉及到并发逻辑(特别是数据库系统中),并发逻辑可以多线程也可多进程,甚至多线程多进程的方式进行,当然,单核环境下的并发永远是一种对并发的模拟机制。

解决多线程的问题是多解的,,源于解决方法的高层模式各有不同,,(也即,从开发的眼光来看,它首先是一个设计问题),,有不同的相关概念,,比如锁,,,就不需要用到信号量。

当然,有时你用的语言提供了并发的支持,,而且有时候,操作系统的并发逻辑会影响到你选用一种更有效的相容于操作系统的并发模型

并发问题源于图灵模型与需要开发处理的现实问题之间的矛盾,图灵模式是控制主导数据(机器的底层问题都是IO问题),这使得

1.6 CPU与异常
一般谈到源程序的跨平台特性时,总是说在p4加winxp下编译通过(同时提到了CPU和OS),,CPU与OS的关系该如何理解呢,如果说CPU控制了计算机硬件上的一切,OS就相当于CPU的外层调用策略(shell就相当os的外层调用接口)。它把CPU逻辑和CPU控制下的外设逻辑,存储逻辑封装为软件可用的东西。os的许多东西,是CPU硬件加速下的结果。

比如说Windows和linux的内存分页机制和保护模式,,如果CPU没有提供三种模式(实模式,x86虚拟模式,保护模式)中的保护模式,那么在OS级虽然可能可以实现一个所谓的保护模式(但却没有直接硬件的支持)

在保护模式下,CPU中的段寄存器不用来实际存储物理地址的线性表示(而在实模式下是这样),它用来实现硬件级计算最终物理地址的一个中间存储。。

中断是硬件级的消息,中断和异常是CPU直接相关的东西(CPU有二跟中断引脚),,,很多编译器提供了异常处理机制,Windows更是实现了一个SEH,,但是,这些都是抽象了CPU的这方面的逻辑。。

中断中的硬件中断是真正意义上的中断,把它称为中断是为了跟异常区别开来,二者都指CPU对影响它原来指令流程的意外干预过程。(CPU对这二者都提供了处理例程)。但是中断是硬件级的,是来自外部的,异常是来自CPU内部的指令执行过程中的一个出错,,是来自指令执行过程中的(所以,所谓的软中断指令其实也是异常)。

而发生中断或异常时,二者都是靠CPU的跳转指令来完成跳转到相应的处理例程的,,这是CPU直接执行指令的结果(Ruby甚至鼓励用中断和异常来代替正常跳转,这是直接用CPU的指令的结果,编译器控制不好会造成很多隐患),机器级的跳转指令是程序语言实现它的控制流的一个重要方面

在保护模式下,跳转应在各任务间正常切换,否则会引起著名的操作系统保护错误,处理例程调用完之后通过一定手段返回正常任务),那么CPU就发展出一些诸如调用门,任务门之类的东西。。用来规范这些跳转。。

再比如递归,由于它每次调用实际上都产生一个新的函数,对于栈式CPU的计算机来说,由于它主要利用内存而不是寄存器来存储这些新函数的临时变量和压参操作,,因此这个递归过程不应有过多的调用深度,否则压参入栈方面的开销会过大(这是指空间方面的优势,栈这个段区可用来存储很多参数但是速度不快,而reg式计算机速度快但是没有过多的reg供递归存参用。因此空间方面是个劣势。)。

1.7 所谓堆栈
运行时是机器挂钩的概念, 非语言挂钩的概念,(但是实作品可以用语言来描述)一门完善的语言的提出,,包括提出一个针对该机器环境关于此语言版本的的代码运行时环境.(因为语言实现需要被运行,那么就需要发展出一个c runtime dll的东西了)称为native runtime,因为是对机器本身,而非类jvm软件机器,的本地机器运行时逻辑的C语言封装。(我们将一种机器是属于reg方式来执行逻辑还是用内存stack方式来执行针对它的平台逻辑,,以及相关的一系列知识称为运行时)

我们知道,REG和内存都是语句运行时(不妨改成运行空更好)的存储空间集散地,但是不同的机器,有的不具备REG,有的侧重利用这二者的机会又不尽相同

如果机器是堆栈机(采用内存stack方式的运行逻辑),那么它就是用内存来作为主要运行场所的机器,JVM速度很慢,但是很耗内存,从这点就可以看出来了(因为它的机器中没有软件摸拟的reg,,为了跨平台,jvm根本不打算将x86的reg机制引入,而我们的x86平台是硬件的reg,所以称为本地平台,硬件的reg总比软件的快,软件的3d render machine总没有直接硬件访问的快你可以联系这点来理解)

机器如果是寄存器机,那么主要用寄存器来执行语句逻辑,指令逻辑,完成数据的传送(语句即指令加数据,为什么任何一个逻辑正确的可执行文件为什么CPU能执行它并产生结果呢,这是因为一切pe逻辑都可反汇编译成指令加数据的语句,它能被执行,因为它同时包含了代码加数据,CPU不能执行纯data或纯text),,因为寄存器是CPU的而堆栈是内存的,所以reg机明显比stack机快,但是寄存器器数量和容量有限(比如递归不好做),所以针对这类机器设计的单个指令代码长度一般不长,,在一些方面是比不上stack机的

当然,intel平台中,,CPU和内存是共同发挥作用来实现寻址的,而且也共同(主要指CPU中的REG,一方面,SP这类REG发挥指针的作用指向内存,一方面,一些REG本身可用来直接存数据)作用构成运行时(时空=CPU时间加内存场所),,那么intel平台是stack机还是reg机呢,当然是综合机

首先,对于x86平台,是如何执行函数逻辑(比如反汇编的一段C语言的函数的附近得到的一段汇编语句)的呢,,通过探索这个过程分析那些汇编逻辑(其实就是编译器产生的平台逻辑,机器码嘛),我们可以发现这个平台的运行时底层信息

首先,PE载体逻辑内有一段堆栈段,被映射到内存(载入器和重定位器会完成这个工作)后形成一个堆栈区,寄存器此时高速活动,它的一个活动记录中,必定保存有栈基栈顶的一个指针(此时它们重合)

堆栈的基础概念要掌握,它是一段从高地向低地生长的空间,而且是活动空间,因为栈顶(指示当前出栈入栈位置的指示,它存在sp reg内,永远只拥有一个是‘’当前“的值,减它就是在低地址更低所以是为堆栈腾生长空间,加它是让低地址往高地不低一点,堆栈空间会变小)会因为频繁的出入栈动作而变动。这种变动性使得我们掌握堆栈的方式(求某一个成员在此活动堆栈空间的位置)只能用简单的'bp"+-该成员对应bp的偏移,的方式来指定了,而栈顶的偏移永远是sp-bp


在进行一段函数调用时,必定是存在调用方和被调用方,一定存在参数间的互传,怎么传参,调用结束后指令权如何在调用方与被调用方之间协调,平台是如何执行这些运行时逻辑的呢?

对!x86用到了堆栈,对于C语言函数,参数右向左入栈(显示成汇编逻辑时是push),第一个参数反而在堆栈(此时是堆栈对应这个函数附近活动区域的一个帧,因此称为堆栈帧,即堆栈区的某个真正的活动堆栈)的最上面(偏向低地址方向生长)这样,相对其它参数来说,它就显得先进后出嘛,后进所以先出嘛,当然,它到底出不出,此时出不出,这跟当前sp没有一定联系,,因为sp当前位置不只是由压参影响的(甚至没有必然系,因为sp这东西可以人为用汇编码指定),还受其它自动变量的影响呢
C语言的这种规则跟pascal的规则完全不同,,这导致的C语言函数可以有变参数数量的好处
1.8 真正的保护模式
8086/8088有20根地址线,寄存器为16位
寄存器的位数,16位表示决定了计算机的字长,即一次性能处理的数据总长度(16个位,始终记住,位是计算用来表示存储单元和数据长度的最小单位,无论是外存或内存都一样),因此在程序中只能定义至多16位长的变量,因为只能定义16长的数值(变量),故(寄存器大小)它也决定了计算机能表示的数值的大小,即2的16决方(1048576),这种数值至多能用来表示的存储单位是1048576个,,或者说1048576个位,而这正是一个16位的数值变量所能达到的最大值,因此这种寄存器为十六位的CPU只能表示至多1024kb个内存位,虽然可能计算机本身不止这么多内存,虽然有时地址线不止寻址这么点的空间,但16位的寄存器只能看见并寻址这么多的内存(因为地址线是CPU引脚线,是CPU的概念),寄存器的位数理论上决定了CPU能"表示"的最大的内存范围或外存范围(当然,连CPU表示都无法表示的内存范围那就没有意义了),而地址线决定了计算机实际能存取访问寻址到的内存范围,即1M,不包括CPU连看都无法看见的那部分,(当然如果你的计算机都没有这么多的内存也是枉说)一个是16位,一个是20位,CPU是怎么样产生数值用来表示地址线所能寻址到的1M地址的各个单元的地址的呢???,(这里以字节来说)1M可以分为64k个64b,这样,寄存器用来存放地址,80286有24地址线,寄存器为32位
因为寄存器是存在于CPU中的,因此说是CPU的寻址,又为什么说CPU对内存的寻址呢,为什么我们在这里口口声声地说CPU对内存的寻址呢,这有什么意义呢?这当然是为了计算机能正确执行指令而准备的,这是计算机工作的本质啊(而为了执行指令,CPU能对内存进行寻址是首先必须要办到的,因为程序最终从外存到内存中才能被CPU执行,CPU和内存都是靠电工作的,CPU提供执行指令的本领,而内存提供存储指令的本领,这是冯仪曼说的,成功完全指令必须是CPU和内存一起工作完全的,而外存是靠磁来工作的,CPU只能执行内存RAM内的指令,外存用来永久存放文件,称为持久化),程序要从外存被加载到内存中才能形成指令

(指令在程序的CODE段中,EXE文件被加载到其进程空间时------这个过程术语叫映射到其进程空间,
它的代码段就在物理内存中了,因为只有代码段才包含指令,这部分要首先映射到物理内存中来,程序的指令用到的数据------这通常表现为程序中的变量常量存在data中,数据段部分被开辟成堆或栈的方式被映射到EXE的进程空间中来(分段机制),形成EXE的编译器不同开辟模式不同,像Delphi的编译器实现的exe进程空间只有堆没有栈这种内存模式,堆和栈是系统级运用内存管理器进行分配内存的动作后(是系统级的作用)形成的特殊内存块,这些内存块被映射到EXE的进程空间,这有点像EXE的DLL模块的映射模式,dll文件被映射到其宿主EXE的进程空间时,不像EXE一样一开始就把代码段实际加载到物理内存中去了而是在EXE实际调用到它时才到实际的物理内存中去(分页机制,只要你的计算机定义了虚拟内存,那么在执行大程序时,,这个分页机制就会频繁用到),跟EXE的DATA段一样属于一开始就映射到EXE进程空间而不实际形成内存的部分,EXE,DLL被加载到内存后,它所占据的内存块就叫模块是静态的,而进程是一个执行体的实例是活动的,线程是一个进程的某个执行单元,所以我们说程序被映射到到其进程空间而不直接说映射到物理内存中,只是需要执行的代码段(注意此段非实模式彼段,后面会谈到)才被进入到物理内存,但不需执行的那部分不需立即加载到内存(就像DATA和DLL)不得不说的是,进程空间并非物理内存,前面一再强调"程序被映射到到其进程空间而不直接说映射到物理内存中",而且更准确地来说,它们二者是完全没有关系的,4GB虚拟地址空间整个儿压根儿就是虚拟的(这个道理就像你玩一个客户端为2G的游戏时,你启动客户端的时候已经把整个客户端的2G资源都加载到4GB空间去了,但是只要这2GB中需要当前调用的那部分资源才进入内存,分段机制开避四GB任务空间,分页机制把需要用到的数据动态加载到进程空间,任务空间就是进程空间,然后通过这些资源在程序中的自动变量表示离开内存),它只是能表示4GB空间的虚拟地址而已,并不是实际的物理内存,仅仅根据32位寄存器能表示那么多的内存来设置的那样一个机制,这种机制成全了将进程空间隔离的好处(所以四GB的说法是进程的一个概念通常说4GB是进程的空间),而不像整个六四KB都可以,Windows的虚拟内存管理器(保护模式下)会负责适当的时候把需要的,映射到进程空间的内存搬到物理内存中去(分页机制),现在来解释"注意此段非实模式彼段,后面会谈到"这句话,在FLAT下,已经没有段segment了,,在Flat模式下就无所谡code段跟data段的了,因为原本实模式CS,DS意义下所指向的段全都在一个4GB大的虚拟地址空间中,实模式下段的意义不存了,是段选择子,FLAT内存模式下,CS,DS根本就不实际用来指向段,即不实际用来指向一段内存(而是存储一个称为段选择符的数据结构),FLAT下说的代码段是指EXE头定义的段,是RAW FILE(RAW指没有分配实际内容的内存)里定义的段而非实模式下CPU段寄存器指向之"段",模拟原本的段取而代之正是EXE头的节的意义,程序员无须知道这些段的实际的物理内存地址而只须知道虚拟地址,(我们知道在32位寄存器,在RAW FILE里才有节section,有了PE文件的头中的这样段定义,当EXE被加载到内存中来,就相当一个跟内存数据段一样的数据结构,虽然平址模式下无所谓代码段数据段,但PE文件的格式间接实现了它,就像XML文件它本身就像一个数据结构一样所以它被很多程序语言的IDE用来持久化,被很多程序用来保存信息如3D网格信息也会用xml文件格式)



1.9 异常与流转
Linux内核实际上只是一个定制硬件功能为软件的逻辑,怎么说呢,它一方面沟通了硬件驱动和软件,使硬件可以运作起来,为软件逻辑服务,,,这是第一,第二,它对于硬件功能的定制功能,比如进程,,,就是把CPU硬件资源转化为软件可以使用的进程资源,socket跟线程一样是种OS资源,这之后才出现OS,,因此,内核实际上只是初初级的抽象,而SDL,就是更高一级的抽象..
它的窗口机制,都是独立内核的(KERNEL硬件抽象层),,如果称内核为操作系统(实际上只是OS的核心一部分)的话,那么,X协议的实作品,桌面环境,窗口管理器,该桌面上的APP,,,都是作为应用程序来运行在内核之上的,而不是像WINDOWS一样直接在内核就集成了GUI,苹果OS在内核集成了X,因此图形效果很好(LINXU桌面没有WINDOWS快),,,,我们平常所谓的UBUNTU也只是APP集(在内核上面一点点的逻辑),,,在服务器版中能精简得到,这就是为什么移殖了UBUNTU却不等于移殖了它上面能进行的一切APP一样,因为UNBUT逻辑并不是其它逻辑的泛化源而是与其它APP一样地位的基于内核的普通APP
运行时是不是移殖的产物?比如apache运行时,是一个抽取最核心的运行库,比如C库的运行时,等
异常是什么呢?异常不是错误,,是比错误轻一级的概念,当异常(即使被捕获到了)不能(在程序中预定义的处理块)被正确处理,就可以抛出一个错误(当然,错误和异常在程序中可表现为很多种,编译型语言如C++就有静态编译期语法和动态运行期逻辑错误之分)而调试,包括对这二部分进行调试,静态期的调试要容易得多,而运行动态期的类型在写代码时(编译前和编译中)显得易读,但是调试时十分困难(因为要从运行中调试)..这就是动,静的区别所在
(这个过程通常是,我们按照正确的程序流程来处理错误,发生错误,在程序预测到它,并不尝试恢复,,比如跳栈,如果能处理则处理之,不能则崩溃)
在传统编程中(一种语言未引进异常机制前),我们总是用IF,,THEN这样的判断跳转语句来提供处理异常发生时的处理逻辑,,,当一门语言内置异常机制时,此时,我们就可以用新的处理逻辑,此时,通常程序语言会作跳栈和保护现场的动作.因为大凡大型软件,需要一种很强大的异常机制,它必须全天侯运行,不能出错就恢复不了,这种动用了程序语言原理(一切程序即函数调用,如果你反汇编过就知道了)来处理异常的方法就是真正的异常..(异常与跳转关系如此亲密,RUBY甚至鼓励用异常实现流转)



1.10 最小,最完美的系统
在操作系统方面,LINUX KERNEL是最小逻辑,GNU LIBC在这个时候就被集成了,很好,内核的架构是重要的,LINUX先是硬件逻辑,再是语言作为中间层,用户程序SHELL等,作为用户空间,这种架构很模块很科学
我觉得用语言本身来作为架构的一部分,这样来实现移殖,可以解决一切移殖问题,只有该语言的VM被移殖了,那么该语言之下的逻辑就全部被移殖了
对一个平台的开发是促使这个平台能得于流行的基础,,,我觉得编程语言要尽量靠近自然语言,无论这种编程语言出于什么理念(函数式,OO式),还是与什么离散基础相关(机器结构堆栈式,,这是不是崭断了人们对底层进行扩展和编程呢),跳转啊,数据结构啊 ,这些东西应该跟开发者没有一点关系,用户应把他们全部精力放到要解决的问题上而不是系统和语言(比如这门语言的语法都不要掌握)这二个东西上,,这个道理就像EXE跟RPM的关系一样,只有无聊玩得太深的开发者才对RPM感兴趣
(画一张架构图,类andro架构,架构是重要的,当架构不正确,一旦当它发展畴形了,那么要改动底层是很困难的,就像LINUX桌面被设计成一个外挂于内核的APP一样,这样的架构并不适合开发游戏,内核与桌面的分离一方面让内核可以脱离GUI移殖另一方面却导致了一些不好的后果)
加入微内核的多进程多线程支持,不需把线程作为开发语言的用户级线程,系统提供相关进程的API
也可提供用户态的多线程支持
从裸机开始,到驱动程序到OS,最最应该注意可执行文件的载体逻辑(EXE载入逻辑和逻辑承载逻辑),应把它发展为类Windows下EXE的东西.不要像LINUX下的RPM那样的东西,让用户下载应用程序的体验足够好
对于桌面系统,我还觉得WINDOWS的菜单,,窗口机制什么的都有欠缺,,如何改造呢?我觉得,应尽量避免展开菜单(即使最常用的打开命令都要展开一级才能执行到这很差劲),我们应把最优先能用到能直接点击到的地方,比如WAR3的操作盘,,当你选定一个对象时,显示关于这个对象的所有详细信息,当你圈选一堆对象时,在显示栏内实时显示你选取到的对象和概况,这很不错
这样的操作系统最好是EMBEDED的,可同时移殖到ARM(手机多使用)和PCX86上,,体积上要小
驱动程序过后,多媒体开发方面,一切用SDL,SDL是用C写的,不如用它,不如用SDL实现一个GTK的东西,因为SDL是直接访问媒体硬件的(DX作为底层也用了OO的COM,这样不好吧)
在桌面环境中,可以改造一下X,使得它不仅仅能在服务/客户环境中画简单图形,而且能画3D游戏效果,,这样网游就直接跟桌面集成了,哈哈
桌面和SHELL,SHELL就用RUBY SHELL,(尽量让最白痴的用户愿意用命令行)
在编程语言方面,C是最小逻辑(GNULIBC是不是太单一了,它应该能提过一些模块扩展出OO功能,动态装载,在OS KERNEL内不需要集成,在后来向RUBYSHELL时就需要集成这个OO模块),我想知道有没有一种类RUBY的C语言,它应该接近底层,为本地机器作优化而不需要一个虚拟机,没有太慢的GC机制,而且采用OO,有RUBY库的那些功能和编程理念(RUBYC?呵呵,就相当于把C++改造成RUBY语法了,彻底去掉编译期设计模式导致的复杂性)
这样的RUBYC是不是就是JVM的JIT模式呢,同时拥有对本地机器和JVM进行编译的功能
C开发理念跟RUBY开发简直就是二个节然不同的理念(虽然RUBY也并没有把这种理念做到极限):一个用机器来考虑问题一个用人脑靠近应用本身考虑
像一般的智能手机平台上都是用C作为底层开发,而用JAVA作为架构的上层,作为高级开发,比如GOOGLE的手机平台,但是就是不常有C++,开发
传统上C开发一个应用(比如一个大型应用),那么就要考虑用什么库,用什么算法,用什么语言已提供的,,RUBY在这方面虽然也要最终考虑这些问题(这些问题已经被SOA式的逻辑实现了),但是一着手时并不是考虑这些,而是考虑应用问题是什么思想模型,a比如是一个MVC,那么就用ROR,,这样理念更自然,,因为它的机器支持被提前实现了
编译器和链接器是平台相关和有关的,,,为某一个平台特定服务(因此也可针对某平台进行优化,速度大为提升),,而JVM直接提供了执行宿主,JVM中的解释器就是这个作用.
提供一个极好的关于这个语言的解释器和IDE,运行环境为OS而不是虚拟机,因此不需要JVM的HOTSPOT技术(但是要保证这门语言在不同的软硬件平台上的移殖能力).
在网络方面,P2P是最小逻辑(P2P实际上是最科学最好的,一切网络程序都应是P2P服务模型,当然也兼容CS,即P2P在CS之前),JXTA也是用C写的,WEB根本不需要用一个IE导出去,网络应该直接跟桌机集成,不需要定义一个WEB逻辑,这是很差劲的,这样桌面开发和WEB开发都在一起了,RUBYC成为桌面语言和WEB语言.同时成为系统编程语言和应用编程语言.
要开发一个类GOOGLE EARTH那样的网游3D桌面环境(虚拟世界),定义一个逻辑上的虚拟世界,
XML成为一切数据的格式,不需要OODB,文件系统跟压缩包,跟磁盘完美结合,数据库可持久(XML逻辑应发展出一门DSL语言吗?不需要,可以将其逻辑内置到OS KERNEL?或者KENRL之外作为用户态的东西?)
在人类的手控方面,我觉得手机式的10个数字键才是最小逻辑,应该成为一切输入机器的最终原型.
Flash的SSD固态硬盘卡很好,可别在钱包内,,主板上集成的卡不行,坏了就拿不下来了(这是属于WEB2.0的个人数据所在,每个人都有身份信息在内)
再发展一个世界逻辑,模拟我们这个地球的世界逻辑,定义


1.11 操作系统与语言的关系
语言有它跟OS直接相关的地方,虽然形式语言的理论不跟任何OS相关,但是具体一个语言实现(编译系统或IDE)都服务于一个OS。
  最初的第一个编译器是以很复杂的方法被写出来的,后来的编译器是有形式语言这样的理论指导下发展出来的,因此,解决开发编译器的角度不同,那么所付出的努力也会不同,就像GUI 问题,如果你能换一种眼光去看,,那么或许我们现在所看到GUI就根本不是现在的GUI,,现在GUI的消息机制,也许以另一种思路就能很轻松解决(比如我们将界面逻辑看成XML逻辑,在XML中提供消息机制达到的效果,或者根本不需要一个所谓的消息机制,不同思议的不是问题本身,而正是人类的大脑,任何问题都可以只是想象,然后才有解法),不给平台任何更多的运行负担,,因为没有平台相关的逻辑存在,这样OS的内核就不会越做越大。
  如果能有一种方法,使一种语言机制直接跟OS相应,而且一一对应,,没有其它任何第二种语言建成在这个OS上,换言之,跨平台问题将不存在,,因为只有一种语言产生的逻辑需要考虑移殖,
  编译器的目的是为了产生码,系统语言基于平台逻辑是为了开发平台逻辑,脚本是为了描述应用
  那么解决GUI问题有什么新的方法呢,



1.12 虚拟机与语言
虚拟机是对CPU的模拟(还有模拟中断机制和IO),,,一般提到硬件的“机”就是指冯氏的构架,这个构架中,CPU是首先要谈的东西。。Intel的构架有专门一本书来讲解。汇编语言就是CPU的汇编语言,不同的机器有不同的指令集。。因此有不同的汇编语言,这就是移殖问题最初产生的地方。。

如果你玩过软件的虚拟机就知道了,,市面上存在很多虚拟机..

在设计一种CPU时,总是先实现一个软件的虚拟机来模拟它,这种软虚拟机被运行在一个平台上,运行在这个平台上但对虚拟机编程的编译器叫交叉编译器。在虚拟机设计完全并实现之后,这个编译环境也要移殖到虚拟机上(此时它被实现到目标硬件平台上),,只需要用这门语言产生关于这门语言在新硬件环境下的编译器就可以了。

然后,操作系统就可以在这个基础上开始设计并实现了。。但用虚拟机方式来验证此机器,和提供一个编译器,这是首先要做的事情。。

posix是类unix系统的接口标准,,为了移殖方便而提出的,因为OS实际上是一种系统调用例程的抽象,这个系统调用例程规定了“计算机硬件反映到软件能做的事”,是最初软件逻辑产生的地方,被经过OS封装之后形成了进一步的进程逻辑,sokcet逻辑等。。对系统编程就是考虑到硬件逻辑和OS逻辑的一个综合过程。

C语言作为系统编程的代表语言,它的标准库里面的函数,实际上是站在硬件逻辑和OS平台逻辑上面的一个通用接口。在Windows和类unix上都是这个抽象接口。支持C标准库的OS必须声明它支持这些接口。方能让标准C语言移殖其上。。

unix被产生时,是用汇编写的,汇编有移殖问题,因此unix的作者又创始了C语言,并改写unix,f所以历史上unix上是产生C之前的,C的移殖性超好,这个作者又写了一本书,形成了C语言的第一次标准化。几乎是unix产生之后,类unix系统不断出现,,为了规范这些类unix的兼容性,提出了一个posix,对系统调用例程接口进行了规范,因此产生了posix.

最初的C++语言的实现(就是C++作者写的一个编译器)实际上本质上是一个C编译器,他在C编译器的前端额外加了一个过程,把C++代码映射为C语言作为目标语言。再把此目标语言映射为机器语言。。



1.13 虚拟机与语言
通常用一门语言写出来的程序都被用在诸如X86加OS的架构上运行(平台移殖或编译通过时,我们总是说,在某某某架构上加某某OS上编译通过),当一门语言为了实现跨平台(一般是跨硬件架构,硬件掌有真正的运算资源即芯,而OS核心将硬件能力供软件服务,包括进程,等,对一台裸机编程就是汇编了)考虑时,它首先发展出一个虚拟机(只有这个虚拟机实现了跨软硬平台那么语言代码就实现了跨平台了,因为代码的运行环竟的根本是硬件架构),这样所有用这个语言写出的的代码就由这个虚拟机来解释,这个源代码就不是本地码了(不是供X86+OS这样的本地生成机器码),软件实现的虚拟机往往是一种高级机器(是一种刻意为了适应程序的运行而设置的完美环境,实际硬件上的这样的机器没有),而往往是一种虚拟机能执行的中间码,,当然,虚拟机上的语言如果有特定编译器和编译技术的支持,同样可以为操作系统和硬件生成机器码

一般情况下,软件运行环境=硬架构加软架构
那么虚拟机就是专门为了一门语言而出现的高级软件机器,,它直接在裸机上(加OS?)运行,因此去掉了OS的作用,是语言跟它运行环境的直接结合体(虚拟机是虚拟机,虚拟机里面的编译器或解释器就是它跟语言发生联系的地方),是为了一门语言而出现的优化运行环境
虚拟机的意义远远不只是为程序代码提供一个可运行环境,它真正的意义是使编程工作完全远离操作系统跟硬件架的差别(传统的源程序总是要考虑到移殖,而且要考虑到平台本身给语言造成的复杂性,比如C库的I/O),,在JVM和JAVA下编程,我们无视有一个OS和平台的差别,所有人都面对同样形式的代码..JVM有了这个优势,,JAVA因此成为了工业语言的原因之一.
通常用一门语言写出来的程序都被用在诸如X86加OS的架构上运行(平台移殖或编译通过时,我们总是说,在某某某架构上加某某OS上编译通过),当一门语言为了实现跨平台(一般是跨硬件架构,硬件掌有真正的运算资源即芯,而OS核心将硬件能力供软件服务,包括进程,等,对一台裸机编程就是汇编了)考虑时,它首先发展出一个虚拟机(只有这个虚拟机实现了跨软硬平台那么语言代码就实现了跨平台了,因为代码的运行环竟的根本是硬件架构),这样所有用这个语言写出的的代码就由这个虚拟机来解释,这个源代码就不是本地码了(不是供X86+OS这样的本地生成机器码),软件实现的虚拟机往往是一种高级机器(是一种刻意为了适应程序的运行而设置的完美环境,实际硬件上的这样的机器没有),而往往是一种虚拟机能执行的中间码,,当然,虚拟机上的语言如果有特定编译器和编译技术的支持,同样可以为操作系统和硬件生成机器码

1.14 调试器与汇编器
虚拟机与语言的关系很密码,像jvm跟Java语言的关系就很爱味,有人说,jvm仿佛并非一个通用的软件环境,对jvm编程久了,会觉得像是吃饭的时候去了一家只提供一种口味的冷饮店,因为有些jvm的机制,比如rmi,,只限于用Java语言去开发。

Java语言属于高级语言,实际上当构造了一个虚拟机之后,第一件是给他设计一套汇编语言。除非你不打算给这种虚拟机进行开发,一旦有开发的需要,就需要实现一个汇编语言或一套高级语言。这种必须要实现一个汇编器,,,只有这样,虚拟机才能开始执行,,虚拟机本身并不需要一个汇编器才能开始执行字节码,你可以直接为虚拟机手动写字节码,,提供汇编器只是为了开发的需要。。上面说了。

同样为了开发的需要,,调试器也是需要的,一般的IDE都有调试器,调试逻辑来源于CPU逻辑,intel中有断点中断处理例程,,中断一般来源硬件动作,,但也可在源程序中人为地给CPU下一个软件中断,这就是int指,interrput的前三个字母,int 3这样的指令让CPU产生一个陷阱状态,让机器进入调试状态,此时CPU并不正常执行程序,,而是一步一步地执行程序。。

调试器分硬件级的和软件级的,但都是为了让开发者或机器调试者研究程序在机器内部二进制级别的执行程序,,调试器一般向你显示CPU寄存器状态,符号信息,内存上下文等等。。

汇编语言实际上并不图灵完备,因为它是对指令的一种命称指代,汇编语言没有变量。因此没有编译器里面谈到的变量等符号映射为内存地址的符号化信息,,因为汇编器毕竟不是编译器,解决问题时所面向的源语言和目标语言不一样。。

运行时,从字面上理解为代码运行的环境,无论它是机器套个OS执行引擎,,还是虚拟机这样的软件模拟的运行时,无论如何,代码要得于运行,必须需要内存和CPU指令,,JVM就是JAVA字节码的运行时(并不包括JIT,JAVA的编译器等部件,这些都不是执行字节码必须的,只是负责生成字节码的,而运行时是驱动目标代码运行的环境),C语言代码被编译后到WINTEL上执行时,Windows操作系统加编译器的runtime支持就是机器代码的运行时..fs

1.15 平台之GUI
从C语言的观点来说明桌面,从C 语言的观点可以说明一切逻辑,因为计算机逻辑都是由C开发的,而C本身也最接近计算机的处理逻辑,C抽象了汇编中的一些机器困素,但是它幸好没有做到全部抽象,比如指针,实际上直接跟内存这个部件相关,因此C语言可用来解释很多计算机的东西,,,这是C语言作为教学语方的一个很好的地方.
现在流行的开发一般都分为桌面开发和WEB开发,但是桌面和WEB应该是被整合的,GOOGLE DESKTOP SEARCH,GNOME的名字中,就有NETWORK,甚至于OS内核中就有网络支持,在GNOME中也有BELGEL这样的桌面搜索,,因此网络跟桌面本来就该在一起,只是WEB和HTTP协议这特定的一块的逻辑得到了长足发展(因为跟企业应用紧紧结合),导致了流行的WEB开发,因此,技术都是受商业驱动的..不要以为技术是很高尚的东西
最初的逻辑都是没有GUI接口的,一切都是SHELL跟KERNEL,Unix系统下,MIT X Windows是桌面的CORE,历史上地,SUN公司一直在操作系统方面力图占据桌面(它后来的SUN DESKTOP SYSTEM 简单就是拉及啊),,它的OPENLOOK 后来败给了MEIRF,桌面最著名的模型,就是B/C消息模型,用类C++写的桌面系统才有CLASS,用C写的比如GNOME没有窗口类名这样的概念存在,有的只是关于窗口的指针,句柄(这些逻辑可由QT,GTK这样的来产生,也可由编程语言来产生,比如GTK,在库内就实现了"对象"这样的逻辑,因此它的窗口也有CLASS NAME这样的逻辑)等.
为什么GUI如此重要?
因此,GNOME这样的图形平台标准,或者一个实现,,就影响了桌面应用,,以及桌面上的很多程序的移殖问题,,从浏览器、办公套件、邮件客户端、音乐/视频播放器、CD/DVD刻录工具、BT下载软件、即时通讯工具以及偏门的音频抓轨工具都一应俱全,,这本质也就是我们在前面说到的是逻辑的关联问题,,比如,浏览器所用的HTML逻辑,就被封装在GNOME中,这造成运行在它上面的WEB浏览器各不一样,因此IE核心,跟FOX核心是不一样的..corba甚至用在GNOME实现内,就像新兴的XML一样也被用在GNOME内



1.16 界面的本质应该是命令行功能支持下的配置描述文件
从更大的范围讲,,任何一种设计都是自蚕自缠,,当然,适合当前应用的设计总是存在的
  设计的合理性只能是相对的,我们只能做一种“目前最科学”的有限设计,而且这种设计方案也受到问题本身的影响,并不是所有所有目前合理的设计都要拿来用在同一个设计上
  你以为GUI是什么呢?
  界面GUI这个东西,不是逻辑本身(人们通常以为CUI才是,CUI中提供一些支持界面的逻辑,,然后,界面主体的本质应该是命令行功能支持下的配置描述文件,此时GUI不是逻辑,而是配置),但是界面也可当作逻辑本身来看,,此时,它就不应该是配置
  在操作系统的设计中,那些硬件访问功能,CPU资源转化成进程,永远是最底层,最应该被首先解决的逻辑,,那么界面呢,通常被放到后面,操作系统的设计跟一般稍微中等应用程序的设计(特别是涉及到移殖)时碰到的问题几乎是一样的多,,因为应用程序总有它的关于运行的平台逻辑(CPU+OS),,而不总是那些建立在业务领域的高层逻辑,,在完成这些底层逻辑的过程时,也要涉及到界面,,涉及到线程,,涉及到图形,,涉及到SOCKET,,内存模式的数据结构,等,因为这就是PC的逻辑,除非你不是对PC开发(开发语言本身跟平台结合导致的一系列底层逻辑也大量出现在开发语言本身的机制和细节中,,也出现在开发语言开发出的可复用库中,),,因此只要是复用,都跟平台逻辑相关,,只有像WEB开发+JAVA,这样的组合,才使程序员彻底(也不是彻底,70%吧)不需要了解系统知识,,而可以进行编程(所以有人说WEB程序不是纯正的程序员,因为它们不懂计算机,只懂他们的工作业务,他们做的不是开发,而是逻辑配置),你说写XML文件是开发吗?写XML机制的实现才是开发,,因为涉及到数据结构和内存。。这才是开发,,,lua 作为配置语言的脚本语言就是这个意思,它适合写界面描述和配置数据描述这些高层的东西,不适合描述机器本身..
  可见,所谓程序,就是不同的层完成不同的工作而已,,一个是靠近PC底层的比较难点,,一个是靠近你要做的事的高层,比较容易点,,这一切体现的精神,,就是封装和接口,,封装体现的终极精神是复用,,复用体现的终极精神是人。。
  如何为一个OS设计GUI接口呢(我们以为OS内核都是命令接口的像LINUX就是这样,不像WINDOWS内核那样把GUI集进去)
  首先最底层就是LINUX,然后是SDL逻辑和XML逻辑封装进linux内核,作为CUI中为后来所用的GUI接口,然后在这基础上发展出一些封装SDL的高层库(使得以后的开发不要动不动就访问硬件),,用这个库发展出一个桌面环境,比如argr
然后玩游戏什么的,就直接使用SDL就可以
其它的应用逻辑(除了玩游戏等桌面应用),比如网络应用,因为内核中有SDL和XML,至少它们的界面已经解决了,其它的问题就是其它的问题了


1.17 命令行下编程实践
编程为什么要在命令行下而不提倡在界面下呢,,这是因为命令行下有界面,而界面下人们都往往不觉察到有命令行,而其实命令行才是本质逻辑,界面只是一种GUI,是一种建立在本质逻辑上的用户接口和配置文件(只是一种不必要的表现抽象)。真正的逻辑可以不需要一个界面接口来展现它,但强加了界面接口的逻辑就限制了逻辑本身的表达。。因为命令行是观念里自由的世界,是C这样语言开发时所面向的直接世界,是计算机逻辑发源的真实世界,所有计算机逻辑的起点是命令行的(仅需要文字界面这样的用户接口就行了),图形用户界面逻辑只是后来的后来需要考虑的东西。。所以开发时不免将界面逻辑滞后,而实际上,无论先后顺序还是本质特征,我们始终都要把握一个观念,即其实命令行比(图形)界面丰富有趣得多,我们始终要相信,虽然我们在Windows下长大,但其实命令行才是主流,才是我们的思想和开发根据地。

UNIX下的那种Shell操作,命令行工具和管道理念,远远比Windows菜单有效率(当然,命令行最普遍它的门槛却很高),因为本质上,命令行逻辑的一条语句"ProduceMenu(10000)",屏幕再大,也不可能生成10000个菜单供你使用。。而在命令行下,一条命令可搞定这样的逻辑,因为命令行是没有界面限制的观念世界(图形没有文字表意丰富),形成计算机逻辑的一层一层是是命令行的,,C开发时也是命令行的因为C的库全是命令行的,Windows API接口是C后来的产生GUI的一种方式,C是面向命令行的,它面面系统底层,并不一开始就将界面逻辑纳入所有的逻辑开发中。。

我常用的C开发平台是CH,一种C解释器和命令行模式下的交互编程环境,它用了如下的Msys工具,我们需要深克掌握这些Linux下的开发工具(它们实际上也是系统工具,因为Linux比Windows提供了更原始对开发的支持).

make,这是linux下编译大型程序使用的,很多IDE都集成了它。

vim,,如果能熟练使用VIM,那么你就会发现,它远远比Windows下的记事本,和一切IDE都有用。。

新手编程导论(三)

第2章 语言



2.1 真正的计算模型
下面给出一张图(计算机抽象结构科学)

计算模型就是用户跟计算机交流的抽象(如果这个抽象被解决了,我们就完全不管它而去干其它事,因此你感觉不到它的存在),当然我们希望不仅仅是表层的交流,但是只要实现了与计算机的交流,,这之后的处理我们就不用管了(因为这并不属于计算模型学的内容)

用计算机来识别的语言只能是形式语言而非我们的自然语言,自然语言是一种形式语言的超形式,因为它可以被解析为形式语言,并且带有它作为自然语言的非机器解析元素

形式语言是严格的(不符合条件就出错),自然语言是活拔的(出错范围小并不影响信息的传达)

计算机作为一种机器,给计算机设计它能识别的语言就是指明计算机进行识别我们给他的输入的模型(任何语言都是一个形式上的模型,供识别和基于识别之上的高级功能比如交流用,并且这个语言可用于后来的编程以给计算机下达指令(用语言进行编程给计算机下达命令是跟计算机的一种交流)后来计算机对我们的输入进行计算(注意这个“计算”的范围是广的不只是数值处理)这里说到的计算机对它能识别的语言表达的输入(是一些指令)进行处理,处理已经走过语言的范畴了,虽然是基于语言识别之上但不属于语言的范畴),,

语言和文法
语言分为语言具体和语言形式,语言具体即最终由终止符构成的串,而语言形式即不一定最终由终止符构成的串(它可以是语言具体和语言形式的杂合体)
那么什么叫串呢?
语言有组成它的最小单位即词(较大的单位有句子,段落,文章,卷等等,这些更大一级的单位又是由词或其它单位构成的,,包括词在内,这些语言单位构造的语言具体或语言形式统统称为串,语言的最终目的就是得出语言的一个一个串),和语言单位进行组合(产生串)的规则即文法产生式,任何语言最终都是具体语言材料即词汇和具体语言规则即文法的组合

串是语言材料的模板(串代表一个一个的语言具体或语言形式),,而文法产生式是语言规则的模板(文法产生式产生文法)

语言具体或语言形式的产生是通过串与串的等价替换得出来的(这是串与文法关生联系的地方与手段),,如果存在w0->w1,即串w0可以替换w1,这个串间替换的规则就是“文法产生式”,,用来产生文法

以上到底什么意思?相信现在不难理解了

2.2 开发模型与语言模型
软件界不存在银弹,是因为我们站在老百姓的立场说机器的事。永远只是模拟。而不会真正得到一棵银弹。


编译器的定义中其实少了一个方面,编译器不仅是翻译工具,而且更重要的是形成语言规范才是这个定义的主体。

在我的理解中,至少在图灵机的层次上,图灵机不是智能的。在我看来,智能的唯一条件是机器有自主意识。

在我们现用的PC上提倡英语编程(并用传统的编译理论知识去实作),那是SB,因为这是舍近求远的做法,我们的PC本来就不是语言机。我们需要对PC本身进行改造或重建。

为什么机器模型跟开发模型是紧紧联系的呢?而且历史上存在很多失败的计算机模型(死亡计算机协会),只有冯氏模型活了下来。

因为语言写出来的软件就是为了系统服务,(比如结构化程序语言符合堆栈机),所以这个系统最好就是基于某种语言的,(但冯氏模型太屈就通用的低层实现而不是高层开发抽象),语言逻辑即系统逻辑,这多好啊(从这方面看,图灵机既是计算机模型也是语言模型是很容易理解的),,我们的PC设计成非语言直接对应的机器,是因为开发虽然对于PC很重要,但做其它工作时需要PC提供其它通用机制,因此不能仅仅屈就开发模型(比如还屈就机器实现难度上面谈到)。

虚拟机就是在这样的历史使命下产生的,它是软件上实现的机器,是语言模型跟机器模式折中的产物,因此有些人提出它,目的更多地是为了迎合某种语言的提出(实际上虚拟机并不企图提出一套机器标准,因为硬件上很难实现这样理想的机器,我们的CPU只能是晶体管认识二个状态),,而且这样的机器不必屈从硬件实现(可以基于某种高层理论提出),只要给程序执行提供一套执行路径即可,因此也需要OS设计中的运行时,进程,中断等等。’’

对于开发来说PC史的发展就是一个一直在走弯路的过程,,这个过程中又提倡什么抽象(这是由软件的根本任务决定的因为软件即抽象),但是太晚了,PC一开始就被设计成一个远离人类大脑的东西有它自己的底层机制(再抽象的高级语言开发抽象都受制于冯氏的数据加指令模型)。应该把PC设计成一开始就靠近人的东西,这样后来的开发中才不需要太扭曲的抽象。

想象一下,如果我们人也是电器化的东西,,也有控制器等等,比如一个生物硬盘,那么当人们被接入计算机时,编程的工作,也就变成了用0,1组成代码,,即,机器语言就是高级语言,编译原理这样的知识根本不用去学。因为用不着编译器和汇编器这样的东西。这样的话,如果我们说话表达中存在银弹的话,那么软件开发必然也存在一种银弹。

即如果让CPU一开始就屈从于人,而不是冯氏的屈就底层的模型,那么我们今天的开发就不会是这样的数据加语句。

我觉得所谓网络神经电脑的研究是不必要的(我这里说不必要仅仅是对于开发来说无多大影响),因为那是变更底层,而对于软件和开发者来说,再怎么样的底层都没有用,,我们着重的是能影响开发逻辑的PC的高层模型(如何变更底层来实现这个PC级的高层模型才是重要的,如果它是能学习的就是真正的AI机了)。即开发中我们只需要那些能根本影响开发的PC高层模型(而不是冯氏屈就底层的模型这导致以后开发需抽象),,,而且这样的机器不但是“开发机”,而且也是“通用机”。

所以系统必须是基于语言的某种。。二进制,不,,机(必须基于某种低层啊,日,语言数学)

这样的机器在硬件上不需要内存在软件上不需要OS

即,面向系统的语言,导致面向语言的系统出现s,,因为语言逻辑就是系统逻辑,所以这种机器在产生软件时所需的逻辑很小。软件很快

界面逻辑不需要硬件支持,声音也不需要,全是软件作用。利用空器成像,,光脑是另一个维度上的

这种机器有语法集,词汇集,可以扩展,,解释器就是机器本身,不需要编译后端。

CPU是语法器,,硬件直接支持。编程的理念就是把一切能编程抽象到的问题(当然没有硬件的支持不能为你泡一牛奶当然如果接上配备有语法器CPU的牛奶机是可以的)全部用语义直接来描述(而不必经过编译后端)。。

这样的CPU可以直接集成到声卡等硬件上(或者只是一个单片机),这相当于每一个东西接上了人类的语言和思想(当然语言并不等于思想),可以组成更强大的计算机(或者功能系统)
在复用性方面,,CPU本身提供一个最小内核语言,就像只认0,1一样,它认很多东西(比如图的节点和图的动态边上的信息)。

抽象叫概念--->实例叫功能,软件叫文章,,我们知道第一,计算机不但能控制硬件做事比如调用硬件的功能接口这需要硬件本身提供对应的功能设施然后由类C语言调用,第二,计算机也能表达人的思想,实现抽象里的软体比如你怎么用语言来表达数学公式的计算这需要软件提供相关语言设施。(比如C语言中的指针一方面是底层的,一方面是属于迂回这个“人的思想”的,,所以将指针的前部分用法看成为底层开发,而后面部分看成为抽象开发)。实际上,语言的语义就是一切(计算机的功能),,从语言的语义来看,第一部分是被包含在第二部分的,正是语言,,让计算机被赋于能计算的能力,,计算机的计算能力就是它运行语言语义的能力,因为语言逻辑由人产生,书写,所以计算机被赋于体现人思考的能力,但显然不是AI机(因为人的语义经编译后居然成为机械的二进制码,不再是动态的人的语义而只有人才有产生动态语义的智能),只有计算机能自行产生代码逻辑,(产生语义,而且是动态变化的语义),那么它就是AI的。如何使计算机成为真正的AI机,在这个点上思考才是正确的(即让计算机成为理解人和它自己的语义并自行产生语义的机器,)。如果能理解人脑的机能,再具体实现在这样的计算机上,那么会有多好啊,我们知道人手,人脚只是人的部件,人脑才是人相当于PC冯氏模型的东西(而非人的整个构架)。

存储器是词汇器

《连价语言识别器one d of brain simalte machineodobs机》开发,,注意这个语言识别器是pc模型,而非CPU模型

我们的工作在于尽量将这个实现过程简化成电路设计的简单形式。比如设计成一张电话磁卡(这样整个PC就是一张32k的卡了哈哈,说真的,我很怀疑现在的PC用得着那么复杂吗??,而且可以放入一个手机壳中形成一台真正的有芯智手机)。

CPU的指令如果可以定制就好了。(虚拟中可)

只能是即时发生的,而无记忆功能的机器。神经元?那么学习能力呢(混乱状,无自我意识状)?看来得去看些大脑科学方面的书。形象思维和逻辑思维?

规则和知识的集合导致的专家系统?
CPU和存储混成一体的机器,把寄存器当存储器的机器。少量的寄存器如此记住大量无限信息。因为大脑铺开可以铺满整个半球。图与语言??如何在图节点上分散记忆到的信息??又如何促成学习与自主意识?
因为语言影响思维,所以语言处理中心是me(自我意识),记忆点是围绕着me的图点(但显然我们的念头是先于语言的,语言只是念头的前沿地),那么me究竟是什么呢?是物质欲望?

欲望-me-记忆点-语言

一个东西具备了思考能力,就是智能的,具备了语言能力(那么所有的计算问题能归原到语言处理问题吗,因为计算机所有的问题都是开发问题吗??这样的话就是一台计算机,,用硬件实现这种编程语言??可计算问题不就是图灵问题吗?),像是智能的

或者,逻辑->语言,或者me(单纯记忆体?可复用库,在线快速反应库)-语言(加入了语义发生器的语言,forward到me的语言机制)
这种机器叫,可自学习词语机,self learn vec m ,slvm,,那么开发出来的程序就不能用在x86上了。。所以需要一个VM啊。,,,因为编译原来只需要几种数据结构,因此CPU设计中只需要树,图等存储基地,指令只需要操作它们的几条指令便可。

不需要明白很多背景知识就能编程的语言。。。包括术语。因此语义和符号是宽松的。

提供与C的接口。

CPU能学习是什么意思?它首先不能是封闭的,是跟我们一样直面这个社会的(所以能学习),
是需要时间成长的,如何实现??



指令设置

树,图,字典

数据演化为,从内存中取词,REG26个,其实加减X除这些操作可从软件上模拟到。
语句演化为,指令全是。。所以这二者可淡化掉

这种机器,指令操作26个寄存器。跟内存没关系。图作为数据结构,需要什么寄存器?人家是堆栈机,我这是图机(就跟luatable机一样),,所以需要base,stack,,图就需要node ,link,CPU及其简单,,只有2个寄存器指示器,而且这种语言可自举,,写出的语句无上下文环境。因此(跳转或调用)不需要将状态入stacck。指令creategraph(V,E),insertvertex(),…,没有指令指针,指令不保存在ram(在这种语言的眼光里,ram只是快一些的硬盘), 所有程序是在线执行的(所有的计算不是函数调用,而是图link),而且维护一个由26个寄存器2^26(有关图的指令可用来在这里发挥作用,而且在高级语言的设计中,硬件加速的图操作可用来实现编译原理的一些东西,比如树图,hash 图)维护的临时在线存储器(用于即时编译,以及学习,比如用AOE网记住大量的临时信息,每个边都可存东西,所以利于debug)。

先来想象一下这种语言具体的语言设施。
用自然语言,欠入一个英语字典到编译系统中,




无法把这种语言的汇编语言作为高级语言(机器语言即是它自身,它用高级语言说话就是一方面也可以表达这种CPU的功能)??否则要求低层也是“高级的”,能直接明白高级语言,,否则不行。底层根本不能称为智能的。而且由于需要智能化,因此不能通用地像二进制机一样实现(这样的机器只能是高级软件VM?不能被硬件上被实现?)。

语言组合CPU,如何实现,树图?把学到的东西写入bios?àme库-
论文《一个智能计算机CPU实现====自学习语言导向机》

指令即最小内核语言(这样高级语言开发时就是机器语言了,并且无上下文环境)。指令中的数据和输入是库(me,,即指令可以根据节点升级和动态变化,,非固化指令CPU,而不是跟冯氏一样从内存中取指令,因为程序在那,这种CPU的程序直接动态临时发生在节点构成的大 存储机制内,只维护调试状态寄存器最小状态集)。

英语有多少单词,这个CPU就有多少指令(要使CPU智能,必须要让学习用的存储机制直接存储在CPU中,,即相当于冯氏架构中的内存数据的东西,以后衡量CPU就不只是速度了还在记忆量,如果不得以要用内存,那么内存最好不是线性的,而是节点状的。因此这种机器的内存只存数据,实际数据也是指令,CPU中的寄存器用来构造节点的关系,不过不需要程序员来维护这些节点关系来构造什么数据结构,,否则不还是冯氏模式开发了??)

那么类图灵的抽象作用(可计算问题,严格的数学东西,,话如何是一种算法,有没正确性),如何被这种机器的高级语言体现呢??难道就是编译原理??我们知道编码数字,等等东西到这种CPU机是很容易的,就像冯氏用0,1表示数字一样。

编程时,因为计算机用跟我们一样的语言,所以双方是平等的,,me的库存储了计算机CPU的硬件功能调用,表示附有这种语言的硬件功能库。。。智能网卡也是同样的道理。


。整个CPU就是一个TRI树

2.3 正规表达式与有限自动机
有限自动机是一种抽象的识别装置,,,往往一个文法对应一个有限状态机(一个编译器词法阶段,,语法阶段不属于这个阶段),所以我们来论讨有限状态机

正则表达式(文法产生式)往往对应一个正则集合,


10.2带输出的有限状态机
许多机器,包括计算机的某些部件都可以用有限状态机作为模型,,有限状态机本身也有一些形式,这是由构成它的定义的某些要素组合而变换到的,,无论如何,,一个基本的有限状态机必须具备以下几个基本要素

顾名思义,这种机器必含有一有限的状态集合S(在其中包含一个特定的初始状态S0)
一个输入字母表I
一个转移函数f(这个转移函数实现将“状态与输入对”转移到下一个“状态”)

自动售货机

首先让我们来描述这种自动售货机
这是一种出售饮料的机器,出售桔子汁或苹果汁,,都是30分一瓶,投币口只接受5分,10分,25分这三种币值,如果投入多于30分的币量,则机器退出超过30分的那部分,保留30分,然后提示按红色按钮或按黄色按钮,,如果按红色就得到一瓶苹果汁,如果按黄色就得到一瓶桔子汁

根据以上描述,如果我们要制造这样一种机器或来描述它的整个工作流程,那么必须要考虑进“状态”,“输入”,“输出”这几个要素,
这种机器的状态有:
s0(未投进任何量的币) ,,这是初始状态,,机器在工作前以这个状态作为初始状态
s1(侦察到当前收集了5分),,
s2(侦察到当前收集了10分),,
s3(当前收集了15分),,
s4(当前收集了20分),
s5(当前收集了25分),
s6(当前收集了足额的30分或30分以上,等待用户按钮)
这种机器的输入有:
i0(5分币) 其实也有“输入0分币”的情况,,但是不考虑,我们只以起作用的要素作为划分的证据
i1(10分币)
i2(25分币)
i3(黄色按钮O)
i4(红色按钮R)
这种机器的输出有:
o0(不执行任何操作n)
o1(找零5分)
o2(找零10分)
o3(找零15分)
o4(找零20分)
o5(找零25分)
o6(推出一瓶桔子汁)
o7(推出一瓶苹果汁)
机器工作的过程就是根据“输入”+“机器的当前状态”=>“下一个状态”+“输出”

举个例子假设有人投入了10分,,再投入25分,,,机器退出5分后该人按黄色按钮得到一桔子汁,,

机器的工作过程:
第一步:当前状态为s0(未投进任何币值, 无因输入产生的状态变更,无因输出产生的状态变更,即为初始状态s0)
第二步:当前输入为10分(i1),将当前状态为s2(侦察到已经投入10分,机器状态为“收集了10分”)
第三步:当前输入为25分(i2),,将当前状态改为S6(“用户已经投入足够的币值,等待按钮”)
第四步:输出5分(o1),,状态不变
第五步:输入黄色钮(i3),输出桔子汁(o6),,状态复位为s0

状态表
即对“输入和状态”的每个组合指明“下一个状态和产生的输出”的组合
可以无输入或无输出,但机器状态是贯彻始终的
(实际上,无输入也是一种输入,无输出也是一种输出,,故这三个要素都是贯彻每步的,这里每步是一个转移函数的意思)



表示有限状态机的一种方法就是以上的状态表,另外一种方法就是有向图
构造有限状态机的方法是设计状态和设计输入,再画出其状态表或状态图




10.2.2 带输出的有限状态机
有限状态机的形式定义

定义1 有限状态机M=(S,I,O,f,g,s0)由如下部分组成:一个有限的状态集合S,一个有限的输入字母表I,一个有限的输出字母表O,一个转移函数f,f为每个状态和输入指派一个新状态,一个输出函数g,g为每个状态和输入对指派一个输出,,还有一个初始状态s0.


例7 在某种编码方法中,当一个信息出现了3个连续的1,则信息接收器就知道已经发生了一个传送错误,,试构造一个有限状态机,使得它输出1,并且仅当它所接收的最后3位都是1

要理解题目的意思,,,当且仅当它所接收的最后三位都是一是什么意思呢,,这个信息接收器接受零或一这二种输入,,当最近(也就是最后)接收了三个连续一之后报错,,因为一旦输入了三个连续一就会被信息接收器发现出来并输出一个一,因此,,“最后接收”这个用语是正确的,,,

注意此机只接收1或其它的数,我们这里只用0代表“其它的数”,这对讨论没有影响

可设计三个状态:
s0(前面一个输入不是1)
s1(前面一个输入是1,,但再前面一个输入并不是1)
s2(前面二个输入都是1,,但再再前面一个输入并不是1)
为什么这么设计呢,因为总是考虑最后输入的那个数,,这个数与先前输入的前二个数存在什么关系呢,正是这种关系影响了输出,所以只需考虑当前输入之前的二次输入情况,即,“最后输入的前面一次不是1”,,“最后输入的前面一次输入是1,但再前面一个输入并不是1”,,“最后输入的前面二次输入都是1,,但再再前面一个输入并不是1”这三种情况就行了


2.4 联系编译原理学语言
写一个编译器真正涉及到了设计(把编译器分阶段完成正体现了设计,,编译器理论本身就是吸取乔姆斯基这个不懂计算机的自然语言研究学者研究出的成果,以及后来的一整套编译理论,包括,图灵的机器,正规词法逻辑这些高层逻辑模型),,编码(具体编译器的实现自然离不开编码),,算法(关于语法的bnf设计就是一个大算法,验证图灵机是不是会终止算法),,数据结构(树与递归频频出现,这个层次上的数据结构还是离散数学意义的离散结构,树啊,环啊,图啊,还没有到具体用什么语言编码的层次,也就是说还是通用的理论层次),,,是一个系统工程,从C语言的眼光来看,开发一套编译器这所有的过程,是一套算法加数据结构实现的集中体现(当然JAVA也要开发编译器,只是JAVA有高层设计工具,比如OO等,对于算法加数据结构的体现没有C深,而JAVA也一般不用作开发编译器,因为JAVA对于编译器后端开始的工作几乎力不从心)。。

在这个系统工程前期,,编译器前端(词法,语法,中间代码)大部分理论还是基于编译理论的高级逻辑的,实现方法和图径都比较单一,因为有统一的理论和相关的yacc.lex工具等,在后端时(代码生成,代码优化,运行时环境,错误处理和调试)就得进入平台逻辑了。。这里才是发生分歧的地方和可以无限深入的难点所在。。

一般来说,编译原理就是指前端,,因为后端不再属于编译知识了,,而是平台处理逻辑,,至中间代码生成时,已经完成高级源程序到代码的生成了(虽然是中间代码,虽然还要经过汇编到最终的目标语言,不过这些后来的过程不做也可以,因为我们也可以发展一个虚拟机内置解释器执行这些中间代码,,,后端的动作不属于编译原理,因为视具体平台不同,,后来的过程不是统一的理论了,,比如代码优化,,那更是一个没有定论的过程

正规式和自动机理论统一了词法分析的过程就像大家都用bnf来描述文法一样,它让编写编计器的工作变得科学化和合理化,有人说,nfa是给机器看的,而正规式是给人看的。。这样说比较形象,而实际上直接写语法也是可以的。词法分析可以一点也不涉及到正规式与自动机这样对于人的迂回逻辑模型,代码控制能力强的人可直接写此类逻辑。

什么是词法分析呢,因为我们现在是以”写”作程序的手段的,是面向行(每一行语句都对应中间代码的一个三元式,每一行都是一个编译时给定行标号产生内存地址的)的,因此编译器也是面向文本的,,字符串成为编译器唯一面向的东西,,首先,从一段源程序中读入,,词法分析的任务在于得出一个一个的lex为语法分析所有,,,词法分析直接面向源程序文本,,语法分析面向业已分析好的词,,故前者仅仅产生词(语法单元token),只有语法完成才能产生语言(文法产生式),而语义给业已建立的语法和语法元素(即树和节点)增加属性等语义信息,,语义分析过程的一个实现方法就是这所谓的语法制导翻译.语法分析过后,如果是一遍扫描,那么中间代码几乎产生目标代码,编译几乎完成

而语法分析则采取了文法产生式(而非正规产生式,虽然都是产生串集,但正规式本身表明一个匹配模式,匹配式。。文法产生式指明如何为语言产生一个串,前者产生的语言是词会集,后者产生的是语句集,,词法过程远远没有到底语言的意义,,只有到达语法阶段了,,才能谈得上语言,,因此对于编译原理的几个阶段来说,词法处理只是一个跟语言挂不上钩的很初级的过程),
它接收的输入是词,编译原理语法分析过程产生的输出是串,串的集合即语言,串即语句,不再是词了,而是语法一级的单元,,而词是词法一级的单元,,

正如关于词的逻辑有正规逻辑一样,,关于串的逻辑就是产生式逻辑,关于产生式逻辑有一个就是上下文无关文法(这也就是当今很多语言采取的乔姆斯基的文法了),,而其实存在很多其它文法的

词法分析和语法分析过程都涉及到图灵机,有限状态自动机dfa,无限状态自动机nfa,是一种名叫图灵的抽象的机器模型,,它刚好与正规集形成的语言(由一个正规表达式推导出的串集形成语言,这是指词法意义上的语言)一一对应,,即,对于一个正规语言,有一个dfa能够处理它(这个一一对应关系在离散数学中存在科学的证明方法)。

图灵机涉及到图灵状态与此状态相关的处理,,因为图灵机是一种关于状态与处理的机器,有一些图灵机还提供记忆功能,这就是为什么要在lex工具中写一个C例程对应每一个状态的原因,,我们说语言是图灵完备的主要是指文法产生式的图灵处理过程.

这个过程中所有状态都可以用一种离散结构(或者称为数据结构吧,我认为数据结构都应该提供数据存储地,而图好像可以没有)图来表示和加以方便地研究。。对图灵模型的研究往往借助这种状态图

语法分析产生一段抽象树,,树这个数据结构深刻并与生俱来地与递归相关,,因此你可以看到LR,LL,自顶向上,,自顶向下这些概念,,语法分析的过程产生抽象语法树,供下一阶段的语义分析和代码生成过程用。。

语义分析,就是将语法分析过程中产生的符号赋于语言的意义,即语义(比如此符号具有什么类型,而类型是语义上的东西),语法制导翻译(以之前分析得到的抽象语法树作为推导的翻译过程,为它当中的各个节点建立意义)不属于语法分析阶段,,即不属于上面一段的编译原理语法分析阶段,,注意翻译二字导致意义的差别。。如果说词法分析和语法分析仅仅是分析阶段的话,,那么从语法制导翻译和中间代码表示开始,,就进入了实际的翻译的实质阶段了(编译就是把高级源程序转为汇编目标语言,这个总过程称为编译,,编译,编译,有一个译字,直到这里为止,译字渐渐明朗,编译前端完成,编译完成,因为进入了代码的阶段,虽然这里是中间代码表示),

词,串,,还有一个符号的概念,,词,串是编译原理意义上语言的概念,,符号就是程序意义上的语言本身的概念了,,对应词法分析中的词,语法分析中的终结符等概念,,,这些东西(不光是词,终结符,而且是语句,相关的语句逻辑块比如一个for过程,我们知道在形成三元式时一切都规范化了,成了一行一行的符号加地址的统一形式,) 在被作为中间代码表示时,,就成了实质翻译成的语言(也许此时也不能称为语言,只能称为中间形式)的符号,,词法分析阶段和语法分析阶段绝不仅仅就是分析,,他们还生成和完善以及维护一个记号系统,,前端是一个所有动作的统称

这些符号记号在前端被不断完善它们的属性值,比如变量类型啊,,关键字ID啊,变量编译后的地址啊,

中间代码表示则着眼于产生的串,如何用中间形式(源程序到目标语言的中间形式)表示一个串(一个符合语法规则的句子,),,一般采用三地址格式(这是个形式化了的中间形式表示的串),比如对于算符优先的规则,,一般采用波兰后缀,或逆波兰形式。如果前面分析阶段主要是树和图的方式来说明和处理,那么这里栈式处理数据的方式在这里频频出现。。、
所谓的栈式机概念在这里出现

为什么一门语言的类型机制如此重要呢,答案就在符号系统。

汇编原理说明了机器语言的逻辑。。编译原理后端的那些知识说明了语言代码如何映射到机器。。通过该语言编译后端学习此语言是学习的比较好的途径

为什么控制结构如此重要呢,因为它代表语法和语义方面的要求.比如布尔条件式,语义允许短路的语言会直接编译出条件判断后的结果。。有时如果语法语义允许,语法上布尔运算也可等同算术运算。。因为布尔结果用数值可表示。。C语言就是这样

C语言字符串有大量指针这说明了与C++的抽象字符串不一样,说明C是用底层来描述问题和设计的。

这些说明,要更好地了解一门语言,,最好要上升到他的语法规范。联系编译原理知识来了一解一些东西。以及编译实现时是如何满足这些规范的。

2.6 如何理解运行时
编译后端会对目标平台产生代码,这目标平台包括机器和OS,最主要还是OS,因为OS是封装了系统调用(BIOS中原子基本的几条IO功能接口,可供OS和系统进行以后的抽象)的,在语言看来,封装了的OS和语言的其它库一样,也是一种语言逻辑,有接口就可以调用(语言跟OS并非一种鸡生蛋生鸡的关系,往往是OS出来了,然后有一种特定运行于这种OS下的语言实现即编译器,tiny甚至可以自编译自身),

那么如何在OS下运行这种语言写成的代码呢,它如何从OS中获得空间等运行资源,如何获得system call IO操作文件(比如cstdio中的文件操作实际是封装了system call io逻辑来的)呢,把运行时想象成语言后端逻辑(编译后端代码生成器及其生成的代码)跟OS的逻辑接口就行了。对于OS来讲,它解决的是OS如何运行它,对于代码来说。它解决的是如何从OS中获取资源进行运行,和提供main()这样的接口,,当然特定编译器商甚至也包括一些语言实现std lib dll,因为这毕竟算是开发完整一套编译器嘛。。一般会把std lib dll当成crt,这是不对的和不完整的。

其它语言是没有crt的只有c有,因为C有标准编译器实现必须实现它,这个意义上run time是语言的库逻辑,run time是编译后端,是每种语言包括无标准库的语言都需要的,这个意义上它是系统软件是语言对于系统的接口是编译器后端一套完整编译器实现必须的,(我们知道解释器一般是系统软件,作为编译后端的意义的crt是相当于解释器对于一门语言的意义)

从解释器的角度我们来看一下,的确是这样的,解释器不为特定目标生成代码,它只负责运行语言源代码(虽然这其中会有一个中间代码,但解释器不运行目标代码,它的目标就是解释器自己,目标代码就是中间代码),,解释器就相当于机器和OS,它没有c runtime,,编译环境下的crt相当于额外的中间层。。


再者比如.net的通用runtime,也是这个道理.。
2.7 运行时环境
XML其实是先是面向解决数据异构问题出现的,,如果想理解XML,不理解它这个历史背景的话,那么你得到的认识会以为XML仅是一种新的理论加实践,而不知道它产生的根本和存在的本质。
  
  运行时其实不如叫做运行空,就是程序运行的物质环境,比如CPU架构(里面的寄存器,就是专门为通俗意义上-不只是一门语言的程序,,,的程序而设置的最终运行时,里面的部件全是程序专用的),,当然,运行时,也可以是虚拟机这些软件逻辑上的东西,,其实程序不可能纯粹以硬件作为运行环境(除非电器化的微指令,或者裸体指令,运行时不是纯粹指CPU构架,因为单纯一个CPU提出来,只有指令,并没有程序的概念,必须等OS机制,高级语言的汇编原理出来,然后程序机制出来,才发展出运行时这个说法),还是有OS逻辑来封装CPU硬件逻辑,然后为运行时所用。
2.7 运行时
Dfdsfsdf

6.3 语言的类型系统
高级语言引进类型机制,其意义是重大的,一方面,类型直接抽象了现实生活,比如数值,字符,这使得计算机可直接用来作科学运算,另一方面,广义的类型是程序员用来操作内存的规则,变量是程序员用来操作内存的手段,而变量是建立在类型机制上面的。构建在类型上的数据结构就抽象了计算机存储机制和它能表达的现实事物。程序员可用这种抽象来产生更深层的逻辑和解决计算机能解决的问题(算法是针对数据结构的撒,如果说数据结构是计算机的结构,那么算法就是用来解决计算机问题的,数据结构和算法是计算机的而不是编程语言的科学,这个说法就来源于这里)。

对类型的进一步抽象是很多高级语言在做的事情,比如动态类型语言,比如类,范型,数据结构等。。特别是类的提出,是一种通用类型,这使得类型不仅仅只表示数值和字符,还可以表示更抽象的概念,
编译原理告诉我们,类型机制是编译期的一切,,关系到变量表示这种类型,关系到编译期类型安全和转换机制,关系到一门语言组织更大数据抽象的能力。关系到与它相关的表达式的属性。
动态类型语言不是没有类型,而是其变量在运行时可以自由转型。范型是建立在为一个通用类型上的操作的手段。类是一种定义类型的手段UDT。。。因此除了简单类型之外,还有自定义类型。。否则仅仅靠建立在基础类型之上的复合类型还是不够(C++提供Class这种UDT并导致了基于对象和面向对象,这是C++区别于其它语言最尤为可以拿来类比的特征)。

我们可以从一个很初级的逻辑来讲解类型与变量,在没有计算机出现之前,是用算盘这样的工具来计算,但算盘不能处理,一个世界跟一个猪如何交互,它只能处理1加1等于2。。这就是类型产生的必要。。因为计算机不仅能抽象数值,还能处理人类想象出来并能通过计算机表达的其它抽象。。当然,这一切都是抽象了的。。但算盘根本无法抽象。。所以在计算机开发领域,对类型的抽象是必要的。

1,动态语言是指语言的运行时,其运行环境是动态的,,新的函数可以被引进,,等等
  2,动态类型语言,是指类型可以运行期被改变的语言,,一般来说,类型系统是一个语言的特征之一,如果它都可以是在运行期是动态的,那么该语言就是在运行期动态的
  3,弱类型语言,,,语言有类型,,但是类型之间的转换并非严格,,,字符串指定可以转型后用作数值型
4,无类型,,,无类型是指类型是不需要显式在写代码时声明给它一个类型的语言,,在运行时视给它的值确定类型,而且还可以再变动,即所谓的dukingtype,,,因为没有类型,,所以就没有变量(因为变量就是类型的代表撒),...没有变量只有值,,,值决定一切,,给它一个鸭子走的动作,,如果它像,,那么它就是一只duck

数据结构与算法是属于计算机的,而不是程序设计语言的。更多在出现在计算机实现上。比如计算机图形上。
2.8 编译与解释
CPU+OS的本地给了我们开发的空间,,也就是说,云计算实际上是个老概念,云计算就是=web 2.0,web x.x + nc,我们日常用的计算机会缩水成nc,不必为一个没有CPU,没有OS的“本地”编程和开发软件,,所有的软件都被开发在“远程”的WEB中去了,以及其它服务器,这就是云计算,,以后所有的云计算机才有OS和CPU,,云计算机说将不必为本地开发软件,这除非本地计算机成为一个NC空壳,,否则,如果我们现在在用的计算机,,它有强大的CPU,也有OS,,那么对它的开发工作就会一直存在,,在云计算下,以后所有的云计算机才有OS和CPU这些云计算机仅是服务器,我们人手一台的PC将严格不能成为服务器,比如,因为我们的工作站没有NT操作系统,所以只能作为上网用的broswer这样的简单终端而不能成为提供服务的云计算机,NC很多年前就被提出了,而web2.0是最新的,所以云计算就是不新不老的结合体
CPU+OS的环境给了我们什么样的"本地"编程空间和限制呢,而WEB"远程"计算又给了我们什么样的编程空间和编程限制呢,,,这首先要理解这二者的抽象基础
CPU提供了冯氏架构,因此要有IO,要有寄存器,OS提供了进程,内存管理,API,进而提供了解释器,而编译则是直接面向硬件的,当然无绝对的编译语言或解释语言
编译跟解释
编译可以为特定平台作优化,而解释不行
2.9 运行期与编译期
先说一句题外话..用了Openoffice,我才发现微软技术的封闭性,那种凭空吃硬盘空间和内存空间的历史复杂性,实在是不必要
开始今天的讲解,运行期与编译期
就像有些知识是为了跟人结合而产生的一样(OO这个东西不仅是技术问题而是软工问题,是为了解决编程方式跟人的关系结合产生的),是为了探索一个领域的东西却总是无法不避象涉及到另外领域的事物一样..
我们现在的编程方式,需要以什么方式写流程,,需要处理什么样的异常,不纯是编程的知识,,而是跟程序运行环境有关的,必须先明白编译原理抽象领域的知识,才能用一门编译语言作编程领域的工作
范式就是编程习惯,比如函数编程法,,OO编程法,AOP编程法
这些编程法是由计算机处理数据的方法和内部逻辑所决定的,,比如函数语言就是由lamda演算得来的,函数语言可以用一种其它范式的语言不能用的eavl()过程
计算的原理是一种图灵机的离散形式,,,因此是命令式的,,,只需一个入口,和一堆要处理的数据,就能串行得到另一个结果(这就是串行形式算法,计算机的功能就是状态的改变,未来出现的并行计算和多核会导致编程出现并行范式),这个结果可以被另一个串行处理作为中间结果,,,计算机(堆栈机)或虚拟机(JVM的软件机器)是构建在这上面的另一层抽象,在这种开发方式下,要注意很多算法,,,离散的算法,和数学的算法,拿递归跟迭代来说,迭代就是计算机处理的方式,,,是行动导向的,而递归偏重于目标,,偏重于人的思维,,比如函数语言中,,就用迭代比较好, 而OOP中的语言中,,就用递归比较好因为计算机离散形式处理迭代好,迭代需要一个循环变量而递归不需要

2.9 编译与运行期分开的本质与抽象
其实字符串逻辑,数据结构逻辑都可以完全不跟内存有关,语言都可以站在一个比较高的角度来抽象,甚至如果可以,一门语言可以把字符串抽象为rope(当然还可以是其它东西),因为语法级是设计抽象,可以不跟运行期有任何关系,,编译期后端才负责运行的事,才有运行效益的说法,所以语法级,也就是设计期可以站在一个完全脱离运行逻辑的抽象高度上进行对字符串的抽象,因为语法级是C语言,C++,JAVA这样的高级语言它只负责这方面的高级逻辑的事,而编译前端跟后端是可以分开的,只有到编译后端时才需要映射到汇编语言这样的机器逻辑,

C用底层来表达语法级的设计抽象,,比如它将字符串看待是指针数组,这就是C语言跟其它所有语言不一样的地方,,因此它产生的运行期抽象跟语言级抽象最接近,虽然是运行抽象但几乎等同设计,因此可用在内存有限的地方,因为它抽象小,比如手机等特殊平台上。。

但即使是这样,C的抽象能力也是巨大的,它可以抽象OOP,抽象结构,抽象。。。

而相比之下,C++直接在语法级支持更大的抽象,它沿袭了C的core(流程控制什么的,C被称为最小内核语言),但在比较大一点的抽象上它沿袭了C的指针和预编译这二大抽象模块,并自己发展出一个运行期的OO,接着面向运行期的模板可以产生STL这样的泛型抽象集,面向受限编译期的模板可以产生BOOST这样的元编程抽象集,而其它的第四代语言比如JAVA,RUBY,LUA,PYTHON,它们在语法级直接支持的设计抽象就更多了,因为它们不需要像C一样处处屈就运行期,,而是屈就人,想怎么样设计就怎么样设计,至于运行期,,完全是虚拟机的性能问题。。


2.10 脚本语言
net的虚拟机就是把所有的语言不直接放到CPU+OS这个二次本地上了(纯CPU是一次),而是放到了同一个虚拟机内这个三次平台之上.因此各种语言规范写出来的代码一者相对另一者来说是native的.可以共用一个类库,而Ruby,Java由于是从C发展而来的,它们只视C为native,如果是其它代码要为它们所用,必须改变别的语言中的函数入栈压栈规则,,,通过一种bind的技术改造..IT界的整合与分离无处不在
要知道什么是脚本语言,就必须知道什么是脚本程序,脚本程序简称脚本,是一种程序代码,它是由某个系统(如操作系统)或服务器(如WebServer)或应用程序(如AutoCAD,MS Office等)环境提供的能自动化操作该环境的功能的指令执行序列.而为了合法地编写出脚本程序所制定的形式语法和语义的规范,原则,定义等等总和形成了脚本语言.脚本语言实质上其实是一种用户接口.现在用户接口这个词,多指图形用户接口(GUI),即指软件的视觉和使用设计必须符合用户的习惯.但用户是有层次的,一部分是普通用户,他们通过软件提供的类似菜单选项,工具条选项,以及使用说明和简单配置等功能,来完成他们的日常工作和任务的,这种用户也被认为是最终用户.而另一部分的用户不仅使用这些一般的功能,而且希望软件随时能按照他们的意愿定制使用在菜单工具条选项中不能提供的功能,或者他们能在这个软件平台上做二次开发,将开发后的产品再出售给自己的客户或者自己内部的其他部门.为了能够满足这部分用户的需求,软件开发商就推出一种编程语言,让这部分用户通过编程的方式来使用软件内部提供的功能.这种语言就被称作脚本语言.使用它的这些用户被称为高级用户.
????脚本语言赖以生存的软件环境被称作是宿主环境(host environment).宿主环境可以是操作系统,服务器程序,应用程序,而开发这些宿主环境的程序语言(如开发操作系统一般使用c语言,开发WebServer一般使用c或Java语言,开发应用程序一般使用C++/Java/c#语言)被称作系统开发语言,或用一个更贴切的说法是---宿主语言(Host Language).在软件产业发展初期,软件没有GUI接口,软件供应商提供一些使用该软件的API(应用程序接口),而这些接口一般采用的编程语言是宿主语言.由于宿主语言是功能强大但也复杂的语言,因此使用该软件的用户也是专业性较强的用户.但随着硬件的快速发展,软件业逐渐渗透到其他产业以及用户群体的不断扩大和GUI的出现,逼迫软件开发商必须提供一种比宿主语言功能较弱,但使用简洁,方便的语言来给一些非专业程序员用户使用.这就是脚本语言产生最根本的原因.目前世界上有数以千计的脚本语言形式.在操作系统领域,Linux上有bash, Windows上有WSH(Windows script host),而web上有perl,jsp, php, asp, VBscript, JavaScript. 在应用程序领域, AutoCAD上是AutoLisp, MS Office上有VBA. 3ds MAx上有MAXScript.各种各样的脚本语言极大地丰富了其宿主程序的功能,使宿主程序能满足不同客户的个性化需求.
????因此,目前大多数相当出名的软件都提供有脚本支持,我们国内的软件开发商是否也能考虑以这种形式来提供二次开发的接口呢?因为脚本语言的简单性能够降低二次开发的成本.如果软件是使用Java开发的,提供的二次开发语言也是Java或干脆直接提供源码进行二次开发,会极大地增加开发和维护的成本.而且当今软件行业的竞争日趋激烈,谁能快速满足不同客户的个性化需求,谁就能在竞争中占据有利位置,因此脚本语言的地位也日益突出.可以这样下一个定论,如果软件产品中不提供脚本支持,该软件产品必死无疑.
????脚本语言有多种分类方法,但按照使用方式来划分,脚本语言可被分为两种,一种是独立型(或称宿主型)(stand-alone)脚本语言,另一种被称为嵌入型(embedded)脚本语言(也称作嵌入式脚本语言).独立型脚本语言顾名思义,是指所构建的应用程序的主体程序全部或绝大部分是由脚本语言来编写的,即使用到了系统设计语言,也是非常少的(主要存在于库中),脚本语言与宿主语言的接口也只是由脚本语言调用宿主语言编写功能的单方向调用,极少反过来由宿主语言调用脚本语言功能.由于完全是用脚本语言来编写,因此就可以摆脱宿主语言的束缚,定义出符合需求的脚本类型,如脚本语言中所定义的对象的内存布局不必要与宿主语言中所定义的对象相同(比如Python对象和C++对象在内存布局方面就是不同的,两者要进行通讯必须经过一定地转换).甚至可以忽略具体的类型,设计弱类型语言.独立型脚本语言的代表有Python,Ruby,perl等.嵌入型(也称嵌入式)脚本语言是指构建的应用程序的主体结构由宿主语言(C/C++/C#/Java)来编写(主要是为了性能和效率方面的考虑),但为了增加灵活性和二次开发性,在应用程序内部嵌入一种脚本语言来灵活地操控宿主语言编写的功能,并且宿主语言功能和脚本语言功能之间的双向调用是非常频繁的,而且也是对等的(即指双向调用的机会是相同的),由于是嵌入到宿主系统中,所以要受到宿主语言的一些约束,无论是数据类型还是内存布局,应尽量与宿主语言保持一致,如脚本语言的对象和宿主语言的对象最好能够在内存布局上保持一致,以便两种对象能非常直观快速地相互通讯,而无须进行费时而又冗长的相互转换.当然独立型与嵌入型脚本语言也并不是绝对分得非常清楚的,许多脚本语言既是独立型脚本语言又可作为嵌入型脚本语言使用.如Python和lua.

2.11 灵活性与安全性
系统编程第一要考虑的问题不是方便性和灵活性,而是安全性,特别是类型的安全性,

C++有运行期多态,这集中体现了它的面向对象,,是用虚表来实现的,这就是面向对象的精粹,,

在一门dukingtype语言如RUBY,LUA这样的应用语言而非系统语言中,C++那样的虚表机制实现运行期多态是不需的,因为duking type这字眼已经是多态了,

基于对象是什么意思呢,,这个基于对象不是指"不能从中定义出对象的类本身这种ADT",,,这里的基于对象,,就是指通过运态语言中通过"接口"实现的对象多态这种编程泛式,是动态语言中除了OO范式之外的另一种更为高级的编程泛式..

什么是基于原型呢,,就是说,事物是什么样的就是什么样的,如果它动起来像一只Y子,那么它就是一个Y子,,于是我们就提出一个原型,,比如Y子动的动作,(这就是二进制级的接口,对象行为集,而通常情况下,基于类的OO语言中都是单根继承,语义上的,实际上这种继承是不符合现实的,因为“类别”都是人为加上的概念,class这个字眼与其说是一种ADT,,事物原型,不妨说它更像是一种偏离原型的分类即classification),实际上运行期的行为集接口实现的原型才是事物的本质,,,而编译期事先由单根定好的继承,这种早就分好类,并在运行期泛出来的对象反而是不符合事物原来本质的。。

类型在运行期可以被动态改变,,像Y子一样灵活地编程,那么在编译期类型就得不到被检测,,这极大地放宽了一门语言对于类型的检测机制,要知道如果是对于系统编程的话,类型机制是多么多么地重要,虽然动态类型语言编程灵活,但是其安全性没有一个保障..这就是安全性和灵活的矛盾之处.

上面的基于对象,实际上也就是基于原型,,C++只有运行期的面向对象,却没有类似RUBY的基于对象开发泛式了吗?用C++的模板技术可以办到!!

"C++模板实现的基于对象"是十分可贵的,第一,它提供了类RUBY的基于对象编程的机制(提供了类型多态,),,保证了编程的灵活性,第二,这种基于对象的多态是在编译期被栓测的,,一方面又保证了语言的类型安全性

一个是二进制运行时级的多态,一个语义级的多态

那么这样说,C++的运行期多型可以被抛弃了(我觉得作为系统语言的C++只需要基于对象和模板就够了,虽然C++可同时作为应用开发语言,但是它的运行期实现的OO实在是给系统问题引入了过多的人类OO思维,使系统问题变得多解,很多时候,我要求去掉C++的OO,当我只是希望用C++开发系统相关的逻辑的时候),,因为基于对象的方式提供了编译期多态就够了..而且同时提供了类型在编译期多变了灵活性和安全性

世界上最好的编程语言的组合是,底层用基于对象加模板的C++,千万别用它的运行期虚表实现的OO,,,,,在高层用LUA这样的快速通用脚本语言(虽然LUA更适合开发游戏)
2.12 二进制指令与循环
大多一门语言都提供了流程控制,,形成了各自的语言关于流程语句的语法要素,,语法是一种形式(所以称语言是形式语言),,语义是一种意义,,,
其实循环,跳转这些东西来自于汇编语言,,,高级语言接纳了这些理念(因为汇编语言几乎就是机器逻辑,高级语言提供类汇编语言这样的机制是为了符合平台逻辑,,,况且高级语言最终要编译成汇编语言和平台逻辑,,循环语言要最终被还原成汇编语言的形式,这些处理速度就可大大加快),,,发展出了它们关于循环,跳转封装了的语言机制..
C语言最终要编译成汇编语言和平台逻辑,,循环语言要最终被还原成汇编语言的形式,,这就是调试的由来,,,调试分语法级调试和语义级运行期的错误.
2.13 所谓函数
函数机制往往被作为过程式语言的代表,但其实无论是C这种纯正的过程式语言,还是RUBY这种OO式语言,,与其说函数它是一种语言机制,倒不如说函数是一种接口,更多地来说是一种过程式非过程式语言通用的机制,一种具体的构造逻辑的接口形式(一种单入口多出口,而且可被其它函数无限调用的接口,因为它是一种最接近计算机单路离散算法的逻辑,,这也就是C语言采取的自顶向下的形式,,所以实际上这种简单的形式可构造一切计算机逻辑,就是这种机制,,决定了函数实际上可构造一切逻辑,包括被C++用来构造类机制,注意函数有一特点就是它的返回并不决定函数一定就结束),,虽然这些语言谈到的函数都不是一个意思(C的函数是通常意义上我们说的函数,语言直接支持的第一等公民,而RUBY的函数是另一种运行时支持下的函数变形,比如虚函数,成员函数,COM接口,RUBY并不提倡用函数为主体进行编程)
从源程序到机器码经过了编译和汇编,,这二个过程都是单向过程,对于纯编译语言来说,不可能将机器码还原为源程序,,对于类Java的半解释半编译语言,,有一些工具跟据jvm解释器的逻辑,可从中间代码还原出源程序的形式
反汇编的过程是一个模拟还原的过程,这是一个静态过程(可模拟得出pe载体的机器码,全部函数级算法逻辑,注意,这个过程并非hex查看,,hex查看是打开pe载体,以十六进制形式查看这个可执体进行修改),,有的工具在反汇编方面做得很好,如果ida内置的反汇编器甚至可得出函数名级的标识,而dumpbin做为另一个反汇编工具,仅仅做了初级的这方面工作,比较它们对同一段程序的反汇编结果就可看到,,不同的汇编器对同一个东西得出不同的结果说明反汇编是一个模拟的过程,,影响PE逻辑的最终还是hex修改pe本身(PE载体在外存磁盘上会有一个载体,因为它内部按外存线性地址划分成了几大块,所以被map到内存中时,这些块的相对偏移还是不变的)
如果说调试器是静态过程(分析了一次就形成了结果),那么调试的过程是运态模拟运行的结果(调试器维护一个活动的逻辑过程环境,可根据你的输入和程序逻辑作出反映),这种方式相比反汇编来说,,是一种更加好和互补的寻求PE逻辑的方式
无论是二种方式中的任何一种,可看到,都用了函数作为十六进制级反工程逻辑的最高代表,所以说函数是分析一切机器逻辑,计算机语言形式体现的计算机逻辑的最好接口模块,,当然也是构造的最好方式,如文章开头所说,,
函数有三个部分,,一原型,二类型,三,参数,对一个函数的把握要从以下几方面进行中
首先,原型部分,各个编译器在编译并汇编相应语言的同一段代码时,会产生相同的反汇编序列(这就是progo原型,这成为在汇编逻辑中发现一个函数开头部分并借此发现这是一个函数逻辑的方法)
我们知道基本的原型有C的自右向左和pascal的自左向右方式(其中,由于C函数原型方式下,将执行权交由调用代码来完全,这使得被调用函数中可出现可变参数)
2.14 所谓流程
高级语言在封装机器汇编逻辑上损失了一定的灵活性(因为只有汇编才是与机器逻辑一一对应的)
而高级语言的流程控制,分支逻辑等,,终归是某种抽象品,,只能提供有限的if else形式,这些封装了的高级语言关于流程的逻辑(其实判断,循环都是流程控制逻辑)
这就是封装,抽象,与原来可获得到底层灵活性的矛盾所在
第一个if往往是最基本的条件逻辑,else是一种变相的if逻辑,是针对于已提出的if的反面,,是if正好相反的条件,而其它的if,在一个if存在的条件下,,相当于else if
一定要明白这里的区别,,这些语言逻辑产生的对应的汇编码的绝对性决定我们得明白这些细微的差别
理解这一类汇编逻辑时,我们得理解intel平台的逻辑,条件指令逻辑,,和分支逻辑这二大部分
典型的有,高级语言的条件逻辑转化为汇编逻辑时是它的倒装形式,而且else部分是放在所有分支逻辑前面的。
明白这些,将有助我们理解高级语言汇编出来的逻辑,从而更好明白高级语言的这些关于流程的语言机制。。

2.15 为什么需要数据类型和数据结构
一切语言机制都是为了抽象,,,抽象真的有那么重要吗??
为什么需要数据类型和数据结构
对数据的抽象必要吗,,,
诚然,用C和机器的观点来反映应用就够了,因为屏幕本来是二维的我们却要发展出一个三维游戏,,,有时复杂性仅仅是为了跟人靠近而不是屈就计算机,因此这个绕的弯子是值得的,是一种计算机逻辑向人类逻辑的变换,但最终要变换成计算机逻辑,我们只取OO的中间变换,,,虽然OO最终要变成计算机离散逻辑
因此简单类型是机器的类型,但复杂抽象类型是人的类型,,,计算机的东西是死的,但是却越来越抽象,因为抽象带来的不是复杂性,而是靠近人的简单性,这个要特别明白,,,即,相反的,抽象是简单化而不是复杂化
什么是真正的数据,,什么是抽象了的数据,,,,数据类型就是数据的模板,,计算机和语言用了什么样的形式语言的形式去指代和控制他们?
2.16 数据类型和数据结构是二种不一样的东西
数据类型type是高级语言对汇编直接操作位的“位”的抽象,,而这句话中的“操作”,,也被高级语言抽象为施加在这些类型上的操作,比如,对于整型数,有相加,但是不能对整型数执行浮点数的运算,,一切都是抽象,数据类型的提出本身就是一种抽象,而至于提出什么样简单类型也是一种抽象,数组,指针,都是C的复合类型,实际上C只有int,float,char这三种类型,高级语言提出这三种类型是因为这三种类型抽象了我们的现实事物。比如int,float对应数值意义的抽象,char对应字符意义的抽象。。

而算法和数据类型是建立在type和施加在type上操作的更高级抽象。。是语言,大凡具有类型机制的高级语言通用来的,用来设计应用和解决问题的方法。。

算法提供了一种用语言来进行设计的手段,是设计抽象,当你不知道如何实现一个程序时,先找到数据结构,自然就找到了算法,编码之后程序就实现了,,就是这个道理,所以说数据结构和算法是通用的语言用来进行设计的抽象惯用法。如果说数据结构是非语言级的设计抽象(它也是一种实现相关的设计抽象),那么高级语法机制就是语言相关的设计抽象(也是实现相关的),而我文章中最后一部分谈到的设计就是工程相关的设计抽象(相对实现来说,这是偏向人的)。。

首先,数据结构在type的基础上进行抽象,,它看不到汇编的位,只是考虑如何将type翻来复去地变换形式进行使用,而算法,看不到汇编的指令,只是考虑如何用高级语言的语句来操作这些数据结构,“即算法是对如何操作数据结构的抽象”因此数据结构和其上的操作称为adt,

一句话,type和type上的操作是抽象汇编的,,那么,数据结构和其上的操作是高级语言站在type和type操作上的抽象,一者是高级语言面向汇编,一者是高级语言面向高级语言的type.

函数是这里最能体现这种抽象的机制,首先,函数接纳实参或数据结构这些抽象,,函数体内的代码是“施加在实参上的操作的整合体”这样一种抽象。。
----------------------
C刚开始是作为unix的系纺编程语言出现的,计算机的产生源于图灵的人工智能实验.
2.17 为什么需要变量这些东东
Java中一切对象都是引用,引用即地址引用,操作对象只要操作对它们的引用就可以影响对象本身,因为引用本来就是对象本身存储在内存中的物理表示嘛

地址是变动的,因为一块内存在不同时刻可以存储程序中用到的各种数据,因此称这些数据为变量,数据的本质是类,构造函数就是实际为某种数据分配内存的过程,

在程序中我们常常需要作数据的移动,复制,比如函数的形实演绎

实际上,我们根本不需要操作数据本身(在面向对象的范畴里,类对象就是唯一的数据,比类的成员更能代表数据),因为往往有时候这样的代价太大了,传值的方式就是直接复制一份原来对象的新的对象(需要给产生这个对象的类一个复制的构造函数),这样就会产生一个临时的复制体,现代的应用中,一个应用中有成千上万个对象是很常见的(因为往往是用某种大型库写的),,给系统巨大的时空开销负担,而且很不安全,在没有提供垃圾收集的语言中,涉及到二次删除的问题

而引用方式就不需要作频繁的复制,因为从抽象上来讲,,引用的确可以代表对象本身(因为对象是程序的,而引用是计算机的,引用本质上就是对象嘛),

另外一个方面是,当我们用逻辑操作符比较二个相同的对象时(一个对象和它的复制体),返回的结果居然是False,这是因为实际比较的是它们的引用,,,由于第二个比较对象是原对象的复制体,因此它的地址即引用是与原来的对象不同的,因此比较结果会是假!!

GameObject& rgo = *pa; // rgo 的静态型别是GameObject,
// 动态型别是Asteroid。
为什么说动态呢,因为=赋值时就经过了一个隐式的转换
dymic cast就是对动态型别的转换
注意此时rgo就是gameobject类型,,而不是asteroid类型,,也即,,一个变量的类型永远是它的静态类型而非动态类型
这是什么呢?rgo被声明为一个指向GameObject的引用(引用就是引用,,而非指针,引用就代表对象本身,取地址操作符不是引用,这二个东东根本不一样,虽然可以通过对一个对象取地址就可以将它变为指针),,但其实它就是一个对象______即rgo是一个GameObj对象,因此说rgo的静态型别是gameobj
point to someobj跟ref to someobj是不一样的
refcount就是实现对引用的计数的,
由于引用是变量的别名,,所以,引用并不是一个地址值,而是变量名,,,,注意
必须对一个即存变量进行它的一个引用声明,而不能对一个常数进行引用声明
2.18 面向类型化的设计和面向无类型泛化的设计-OO不是银弹
以数据抽象动作为中心的设计,以类型为中心的语言。这是冯氏下编程的特点

有没有注意到呢,编程首先是将概念进行数据化,以至于将那些不是实体的抽象概念都数据化---比如设计stl时将迭代器template class化,,,脚本语言的变量中,数值可直接表示科学运算用到的元素,其它UDT和ADT可表其它高级的dsl抽象表示。。也即类型。

类型就是数据,编译语言严格这个过程,脚本语言提倡类型即数据。我们可由类型定义出子类型,,也是一种数据类型。。比如typedef int myint,,,这在jass2中广泛使用。

类型即内存中编码了的“计算机数据”,即设计者眼中的“数据”(UDT),(即“OO设计”实际上=“用UDT表达设计,用数据描述设计”)在很多地方,类型化正是设计的死敌,因为它规定了目标对象的产生模式,在整个设计中充满着错综复杂的对象头文件引用。数据间的复合进入设计一旦被固化,用数据表达的设计容易造成偶合。

而这正是范型产生的最佳替代。,范型将设计中出现中的数据默认为一个空的占位符UDT。无论是OO还是泛型开发,都是以数据封装动作为中心的。而过程化设计是以函数封装为模块为中心的。

即第一点:OO以数据封装为中心,这会造成偶合。

第二点:OO容易产生过度OO化的负影响,因为极容易将那些不显式的设计元素封装为对象,这反而带来了负面影响。

第三点:而OO的三重机制中的继承,更是

在抽象类型作为设计手段的方法上,OO和模板是差不多的,然而不同的是模板是“泛化”类型OO是纯粹只用“类型化”来实现设计(对即逻辑的抽象逻辑,我们知道,实现中也离不开设计,设计库的设计就更离不开设计了),,所以OO的设计手段不足,而设计模式是一种新的设计理念,语言并不直接支持,,C++用STL作为语言的数据抽象,,用LOKI作为语言的设计能力(OO,模板相比来说是小规模的设计手段)。

注意,OO的类型化绝对不能说成对象化,OO定义成面对对象是极其错误的译法,,OO是用于设计的,,所以O这个字眼只能是“类型化”的“类型”,而不能译成对象,,因为对象是实现里面,不是OO设计的原义所在。

新手编程导论(四)


第3章 语言之争


3.1 学编程之初,语言之争
最最古老的问题,什么是计算机,这个问题跟什么是人生一样简单但是难解,但是我们可以站在开发的角度,给出一个足够简单的答案,计算机=硬件架构+软件OS
只要了解了硬件架构和软件OS,那么我们就可以骄傲地说我们精通计算机了,你就可以进入第二个问题,什么是编程,编程绝对是个广泛的话题,但站在,编程的根本在于抽象
在C的时代,我们以为语言细节和系统知识才是难点,,在提供了VM式OO语言的时代,我们却发现设计更难.应用更难,如何开发被别人复用更难,如何复用别人的逻辑更难,一个时代有一个时代的问题
在C时代(也就是汇编时代),CPU内置了操作码指令码,又提供了对内存和寄存器的控制,因此程序=数据+操作(换言之,计算机再怎么样,在其内部都是用操作码操作数据的机制).以C中对数据还进行了结构体这样的封装,后来数据经过了CLASS抽象和封装,一样都可以最终解释到这个机器过程.

给二套代码,一套过程C式,一套OO式,让读者发现他们之间的不同(一般C提供的接口都是函数,,一个库应尽量提供少的逻辑(一个接口应尽量简单,提供它自己应提供的逻辑,其它多的都是多余),这些逻辑都是基本的,比如一个socket库只需提供诸如transport(int),transport(float)这样的接口,,那么所有的逻辑都可以建立在它上面了(比如发送大件数据的后来逻辑只需建立在transport()这样的函数基础上就行了),,接口并不总是虚的迂回接口,,比如C++的抽象函数,,也有干实事的函数,,作为可复用的接口应是抽象的,,像上面提到的这些接口都是作为应用接口的,面向应用的,是不首先考虑作为复用接口的,如果是面向复用考虑而设计的接口,一般是干虚事的抽象函数)
第三个问题,什么是语言,我该选择什么语言,又回到可爱的语言之争了,谁也不想浪费精力花费时间却发现学了一门不太讨好的语言.但是千万要相信我,没有通用永远有前途的语言,永远没有最好,只有此时此刻刚刚好,没有通用,否则就是骗自己,很遗憾给了你一个几乎没有用的答案,我们在这里不宜讲解(请在本书进入一个阶段之后看p178)

把C++的语言机制分个类,,,有1,指针,间接访问者实现OO也可,,2,OO,3,访问控制COSNT,STATIC等
深入分析一下全部的C++语言机制,会发现他们都面向抽象,C++没有接口,却轻易能用指针和纯虚类实现JAVA的接口
本书将从计算机这个编程环境,语言是什么
3.2 语言与应用与人(1)
一门语言提供什么机制,,是由它能表达的应用逻辑来决定的,历来是语言适应并决定应用,什么语言开发什么样的逻辑(人,应用逻辑,语言及其机制永远是三个相互依存的矛盾,应用逻辑要求语言提供什么样的逻辑,而人开发应用逻辑也要求语言提供靠近人的机制比如OO,,因此用该语言开发的逻辑都可以用这语言本身的机制来解释),
比如JAVA适合做WEB,LUA适合做游戏(因为它的协程机制),XML适合做数据(因为它提出的元数据理论解决了浏览器文本交换的异构,并一度深入到数据库领域),比如WINDOWS是用C开发的,那么C的那些语言机制就可以用来解释WINDOWS原理的一切,,再举一个例子
我们将语言要面向开发的逻辑称为“问题本身”,“逻辑领域”,WEB领域是最能体现这三者关系(称为软工)的领域,因为这里面存在关于这三者的诸多矛盾和他们的解决之法。
比如虚拟现实领域,,一个“虚拟世界”这个说法是用OO来表达呢,,还是用LUA的具体语言机制来表达呢还是用语言实现的某个库逻辑来表达呢(LUA毕竟还是属于语言,可能在库级逻辑提供对游戏的支持,并不直接在语言机制里,,即它的语法支持里支持游戏逻辑,,因为它还是属于不同于SQL这样的高度面向领域的DSL语言的)
那么“虚拟世界”是用语言语法来表达呢,还是用库来表达呢,,是用语言的OO机制来表达呢,,,还是由语言的一种更好实现的机制来表达呢??(诚然,OO作为语言机制能够表达一切,比如“虚拟世界”,但并不总是直接的好方法,有时对于一个特定应用逻辑,LUA可能提供了一种更好更快速的语言机制,或库级逻辑,,反正要记住,OO并非一切)这就是语言机制基于应用的要求。。

3.2 语言与应用与人(2)
很多语言机制,当你做到用抽象的眼光去看待它时,它才真正算是被你理解了,即如果你能从现实的应用开发这个高度和需求角度,突然想到你可能会需要用到C++的模板(假设你以前未碰过它或只是听说过),那么你才会真正开始明白关于模板的一些东西而不是从学习C++的语法书开始。比如你的应用逻辑中存在一个循环你需要一个循环,而C语言的语法刚好提供了循环语句,那么你会主动去翻C的这种流程控制进行学习它,但这是例外的情况,很多情况下,绝大部分的应用逻辑并不直接对应到一套语法机制,可供你直接采用,完成这个应用到语言的映射。因为语法机制实在有限,更多的逻辑被体现到了这种语言的库中,或其它第三方库中。

即应用才是最终的目的现实问题,我们永远要先行解呈清和解决它,最难的编程是认识你要解决的问题,在编程解决一个问题的整个过程中。语法问题的解决处在编程的未端,应用开发更多地跟语义直接相关,而非语法。无论你看了哪本语言方面的语法书,负责的都会告诉你只学会了基础,的确,比起对应用的理解来说,以及如何用语法表现应用,学语言语法永远都是基础.明白语法只是第一步,你得明白语义,明白语义抽象到现实应用的那些部分。但是有人会说了,应用问题在一定意义上 — 比如提供了复用库,就转化为语言问题了,此时应用问题本身还是要搞清吗?要注意,语言会跟应用发生联系,但应用绝对不会跟语言发生联系,所以应用问题是独立语言和语言问题的要单独搞清的,这里我们谈到了语言与应用的关系,那么我们该如何理解C++的一些常见语法机制呢并联系到应用呢?
在C++的诸多语法机制中。类和模板都是代码结构,是面向人(解决复用和软工问题,处在人和语言之间)而不是面向机器(像数据结构处在计算机和现实问题之间,

解决的是现实问题而不是人的问题),类提供了一个数据化“代码”的统一手段,使编程工作复用维持在这个高度上,但是正如上面所说,它没有解决实现问题。这也就是实现与抽象的区别所在。数据结构解决实现问题,代码结构解决抽象(到语言)问题。
3.3 C与Ruby
编程语言涉及三个东西,1,平台,2,语言,3,要解决的问题

设计模式是OO范式刹车的标志,因为它拉大了计算机与要解决的问题的沟隔,怎么说呢?OO以后的OOP范式太多了,有设计模式,框架等,其实计算机内部的离散形式是死的,但用编程语言解决的目标问题才是巨大的(实际上OO加重了编程的负担和新手的入门难度,用OO来描述现实世界只能做到"对象"层次,如果向模式,向框架发展就越来越庞大和力不从心了),只能越做越复杂,有了OO就应该适可而止了,不要再发展OO以后的东西了,比如设计模式,比如框架,因为OO以后的东西其本质还是计算机内部的离散形式,这二者南辕北辙,造成的结果是适配这二者的负担太重了,叠在裸机上的逻辑太多了,有OS,有虚拟机,有运行时,有PE载体逻辑,有解释器,这只能给机器越来越重的负担,这是第一,第二,计算机底层的离散形式本身就是固定和简单有限的,自从有了OO之后,编程逻辑发展为远离计算机底层(向人靠近)的高级逻辑,而现实问题用OO来解只会越来越复杂,不如用固定的编程范式,,比如C语言规范来表达,这最大的证明就是单例,RUBY可以用极为简单的形式来表达单例,而JAVA要用一大堆OO的思想,,
c有全部的离散而RUBY相对来说就少点,没有指针这个离散因此C产生RUBY,而RUBY只能BindC,RUBY不能写我们现在能看到的WINDOWS(因为它关于C的底层用到指针),而C可以出RUBY,
什么样的应用用什么样的语言,需要达到什么样的逻辑就要选择好语言,因为C几乎能控制计算机内部所有离散(而应用就是计算机离散的向后发展),因此OO反而没有C的直接底层强大(这相对计算机能完成的事来说的,在开发上,OO自然会快点好点)

下面是我提出的一些方法

1,OS用LINUX内核+Ruby shell,比如Ruby,底层开发一律用C,比如驱动程序,游戏底层硬件渲染,RUBY可方便调用C,而且直接用于硬件编程时十分科学,比如embed c,C的运行也十分快
有人会问了,当RUBYBIND C时,那难道C的模块不也成非本地代码了?其实不然,当C模块被BIND时,它只提供一个接口给RUBY使用(只是在RUBY这边产生了一个使用逻辑),真正的执行体(被调用者)还在C模块中,还是nativecode.
2,RUBY与C混然天成,当作高级非系统编程时,应把它变为一种SHELL加通用编程语言..让RUBY也同时成立软工时代标准语言

在学习编程中,我觉得C和RUBY都是要学的(一个是底层,一个是高层,不需要学习C++等中间层的OO语言),,C面向底层,很好地解释了学习一切C泛化出来的其它语言的底层知识,比如字符串,比如IO,比如进程,线程,对学习是极为有利的,而RUBY
3.4 你为什么需要Ruby
C不能满足大规模的开发,因此出现了引进了OO的C变体C++,C++几乎是C的再造,虽然它对C的兼容性是天生的,但是C++又有很多陷阱,这些陷阱一部分来自对OS平台的逻辑,一部分来自C++语言本身的复杂细节,(C++的库并没有把平台复杂性封装得让开发者可以不管它,而且语言细节带来的复杂性比如指针是用C++这样的语言进行开发永远不可避免的)平台逻辑和语言本身细节带来的复杂性是阻碍开发者前进的二个拦路虎因此出现了JAVA,JVM使JAVA开发不需要了解任何我们真实的环境(硬件架构加OS).而且JAVA提供的库已经把JVM的进程资源,SOCKET资源调用逻辑封装得稍微会点OO的人都可以拿来用了.当然我们并不总是作基于平台的开发,,比如对抽象WEB的开发就是另外一个远远高于平台逻辑的开发过程
困此我们说C,和C++是一派,因为它们没有一个虚拟执行环境,面向真正的计算机环境,C可以做C++做的一切事情,但是C明显不能大规模开发需求,当面向本地编程时,当逻辑过大时,一定需要C++这样的语言作辅助.C和C++被禁固在本地上,C++是本地编程的极致了,提倡对本地编程只需要C就够了而排除C++的这种论调是极端的,而C++也并不是做得完美,除非对C++本身进行改造,增加如RUBY那样的新语言的新机制,让C++成为容纳一切范型的多范型,否则它并不能直接实现很多RUBY能干的事情(RUBY构建在RUBY虚拟机上的OO机制和其它语言机制多了去了,比C++简单但比C++的那些范式强大,比如C++的模板,虽然可以让C++带来GP,但是RUBY的GP要科学和有效得多,还比如JAVA用反映和代理技术去模拟RUBY的动态语言性质),JAVA却根本不擅长操作本地,因为这是它的设计目的导致的(JAVA就是为了独立平台,使得开发时可以不管复杂的平台逻辑,而且即使是对JVM的平台逻辑,JAVA的那些库也作了很好的封装)
那么你到底需不需要一个VM式的语言呢,首先如果你是作本地开发,虽然VM式的语言可以通过JNDI这样的本地接口和BIN技术来进行访问,但终归是一种绕之又绕的方法,而且BIND和改造技术也不是万能,有时你并不需要全部的原语言的逻辑,此时你需要定制BIND来调用你需要的部分逻辑,这是第一,第二,带了VM作执行环境的语言过于宠大,当一旦有这种语言写成的源程序当被执行时,其执行效立造成的影响不容小观(比如游戏开发,在一些实时碰撞和消息互发时要求不要有迟缓,WEB除外,WEB的平劲不在IO)
第三点,要搞清RUBY等这些语言的目标,RUBY被设计成通用编程语言,但是更接近一种DSL,它就比较适合搞WEB,
那么你到底需不需要一个动态语言呢??动态语言就是在写代码的运行前编译期不需要为变量指定类型,因此类型宽松,写作起来不需要考虑类型之间的静态期转换,只需像对待普通简单类型变量一样对待抽象CLASS类型(变量),源程序可读性也很强(因为没有很多关于类型约束的逻辑和信息),但就是因为类型信息在运行期,因此在编写时IDE不能获取到正确的类型信息,这给编写造成了一定的麻烦.
那么到底需不需要一个解释语言呢?解释语言就是边翻译边运行,程序可以被实时调用,这几乎是脚本语言的一个通用特征(一般脚本的意思就是通过一个SHELL调用,SHELL就是解释器,脚本这个说法相对于系统来说的,实现比较简单的DSL方面的东西,系统编程语言是通用的,那么脚本往往实现某个领域的逻辑,比如文本处理,但RUBY和JAVA走的都是通用脚本编程语言的路子),类逻辑可以先存在于持久化状态,再被实时加载,每一句新写的源程序都可以被即时执行.
那么到底需不需要一个类RUBY语法机制的语言呢,如果没用到协同线程,那么Ruby中的Coroutine你就用不到.如果这些都刚好对应上你需要的功能,而且上面提到的其它方面都基本满足,那么RUBY还是比其它语言要适合你的.
因为脚本就是要实现一种DSL的东西,所以RUBY即使很强大,Blizaard还是愿意用LUA这样的东西,困为LUA的一些比如协同线程很适合游戏开发,而且LUA不必通用,而且游戏就是要轻量的VM,脚本语言的本质决定了它不好被发展成为一种通用语言.
没有一种通用最好的系统编程语言+通用的最好的脚本语言,不要去寻找它,因为历来都是应用决定语言,而不是语言成就应用..
切记,切记!

3.5 C++还是Ruby
不要去干追逐技术的蠢事,,你只是用户,,只需学会一门工具开发,但是也不要走入另外一个极端,就像我崇沿ubuntu,我并不会动则就把Windows和ubuntu作一个彻底的分离,并一有时间就抵制win并使用ubuntu一样
逻辑指出,,,当人们需要掌握的形式过多时,,,那么它造成的门槛也就会过高,(当然如果是C++的高手,那么这种话可以不说,我只是对编程新手说的)C++终于还是属于一种复杂的语言,它的机器因素太多了,多得初学C++的人迈不开脚,,所以要去的地方还很远,,,,用一门语言来描述要解决的问题,C++只完成了最最初级的抽象,虽然它提供了OO,但是它导致的因为C++的OO机制也很让人头痛..
似乎有人嫌Ruby过简单,而且非C++不能体现他们钻研到底层的精神,要知道语言级的形式才是羁绊,现实问题才是复杂的,如果抱这种习惯久了,就出不来了
3.6 C++与Java
其实就语言来说,没有C++和JAVA这二个语言之间那个语言更强大一点的说法,大凡用其中一方能实现的功能,用一方都完全能够抽象得到,JAVA所关注的WEB编程领域,C++完全可以提供同样的功能实现,但是,JAVA不仅是一门语言,它是一个平台,这指的是它的JVM,(C++却不是一个可移殖的语言,如果要移殖,它的库也不允许,关于C++的库有太多涉及到本地OS,C++与OS藕断丝连,这在一方面导致它并不适合WEB开发,C++与OS挂钩,JAVA代码与JVM持钩,而JVM本身是可移殖的,因此这层抽象也就说明JAVA代码是可移殖的),关于JAVA的WEB应用是发展在这个平台上的,JAVA变为WEB开发语言不仅仅是因为JAVA代码可以一次运行,到处执行,历来不是语言成就应用,而是应用成就语言,就像ROR成就RUBY一样,人们发现J2EE框架库的出现适合WEB开发于是就导致了JAVA作为WEB语言的流行,而且关于JAVA的库和先进的框架越来越适应WEB开发,由于这个历史,天时地利原因,JAVA最终成为WEB开发的主流.
后来XML出现了,XML与WEB的结合可谓是无孔不入,XML甚至渗入到桌面开发的OFFICE中(,当然,这是XML不作为文档意义上另一个维度上的抽象,其实软件开发到最后,桌面与INTERNET或者WEB的抽象间隔会越来越小),WSDL被提交给W3C,JAX,AJAX等等,这些都说明现在的WEB开发和布薯都趋向于与XML紧密相联
XML+J2EE终究太复杂,在证明ROR这种框架比J2EE优秀之后(开发周期短),RUBY语言作为开发WEB的最佳选手身份就被成全了,RUBY是动态语言,它提供的处理HTML的能力使它优于JSP在网页中嵌入TAG的方式,然而这些都不是最最重要的方面,PHP也有这个很强大的处理文本的能力
3.7 .NET与JVM
.NET语言的公共语言运行时就相当于JVM,它们为一种语言或多种语言的代码提供运行的平台(比如运行时为它们分配内存,,普遍认为在.NET的运行库支持下可以运行多种语言的代码,在JVM下可以运行JAVA原生代码
但是要知道,原生不原生是相对的概念,如果能在JVM上实现一个Ruby的解释器,那么Ruby代码也就是原生的Java代码,只有抽象完成,整个Windows系统都可以用Java来写,这就是说,在软件的抽象里,任何事情都可以以抽象叠成的方式来完成.但是显然地,在WINTEL上装个JVM,再实现个Windows,这是个傻瓜行为.
OS跟虚拟机的关系,比如用C(更严格地说是C的一个编译器实现比如MSVC,BC,GNUC)写出来的代码就是直接在操作系统上运行的(由一个叫运行时的抽象层向OS请求内存时空资源比如CLS的托管内存说法),,这相对OS来说,C代码就是原生代码,但是当为一种语言发明一种虚拟机运行平台时,这个抽象就大了,我们不再称这个抽象跨度为原生,而是过度了的原生,也就是说,不是原生,而是相对虚拟机的原生,比如JAVA代码之于JVM的关系
实际上编写虚拟机是编写OS的技术之一(在一台裸机上写出一个虚拟机才能调试代码和执行代码),并且直接在一个业已存在的OS上抽象出一个虚拟机实现也是可以的,,,因为这样可以独立很多平台执行这种代码,,这样做的目的(在业已存在一个OS的情况下)就倾向于"为了语言而创建一个运行平台"也即一定程序上"JVM是为了JAVA而出现的",而本来不需要一个JVM就可以直接在OS上写JAVA语法的代码的
?那么JVM与JAVA解释器的关系又是什么呢?一门语言的最高级公民(first class)往往存在于栈内,比如函数啊,OO体啊,但是JVM又不是JAVA解释器,不属于运行时抽象也不属于OS抽象,而是编译原理抽象,学习的过程中,我们必须格定这种"抽象所属",才能
3.8 你为什么需要Ruby
C不能满足大规模的开发,因此出现了引进了OO的C变体C++,C++几乎是C的再造,虽然它对C的兼容性是天生的,但是C++又有很多陷阱,这些陷阱一部分来自对OS平台的逻辑,一部分来自C++语言本身的复杂细节,(C++的库并没有把平台复杂性封装得让开发者可以不管它,而且语言细节带来的复杂性比如指针是用C++这样的语言进行开发永远不可避免的)平台逻辑和语言本身细节带来的复杂性是阻碍开发者前进的二个拦路虎因此出现了JAVA,JVM使JAVA开发不需要了解任何我们真实的环境(硬件架构加OS).而且JAVA提供的库已经把JVM的进程资源,SOCKET资源调用逻辑封装得稍微会点OO的人都可以拿来用了.当然我们并不总是作基于平台的开发,,比如对抽象WEB的开发就是另外一个远远高于平台逻辑的开发过程
困此我们说C,和C++是一派,因为它们没有一个虚拟执行环境,面向真正的计算机环境,C可以做C++做的一切事情,但是C明显不能大规模开发需求,当面向本地编程时,当逻辑过大时,一定需要C++这样的语言作辅助.C和C++被禁固在本地上,C++是本地编程的极致了,提倡对本地编程只需要C就够了而排除C++的这种论调是极端的,而C++也并不是做得完美,除非对C++本身进行改造,增加如RUBY那样的新语言的新机制,让C++成为容纳一切范型的多范型,否则它并不能直接实现很多RUBY能干的事情(RUBY构建在RUBY虚拟机上的OO机制和其它语言机制多了去了,比C++简单但比C++的那些范式强大,比如C++的模板,虽然可以让C++带来GP,但是RUBY的GP要科学和有效得多,还比如JAVA用反映和代理技术去模拟RUBY的动态语言性质),JAVA却根本不擅长操作本地,因为这是它的设计目的导致的(JAVA就是为了独立平台,使得开发时可以不管复杂的平台逻辑,而且即使是对JVM的平台逻辑,JAVA的那些库也作了很好的封装)
那么你到底需不需要一个VM式的语言呢,首先如果你是作本地开发,虽然VM式的语言可以通过JNDI这样的本地接口和BIN技术来进行访问,但终归是一种绕之又绕的方法,而且BIND和改造技术也不是万能,有时你并不需要全部的原语言的逻辑,此时你需要定制BIND来调用你需要的部分逻辑,这是第一,第二,带了VM作执行环境的语言过于宠大,当一旦有这种语言写成的源程序当被执行时,其执行效立造成的影响不容小观(比如游戏开发,在一些实时碰撞和消息互发时要求不要有迟缓,WEB除外,WEB的平劲不在IO)
第三点,要搞清RUBY等这些语言的目标,RUBY被设计成通用编程语言,但是更接近一种DSL,它就比较适合搞WEB,
那么你到底需不需要一个动态语言呢??动态语言就是在写代码的运行前编译期不需要为变量指定类型,因此类型宽松,写作起来不需要考虑类型之间的静态期转换,只需像对待普通简单类型变量一样对待抽象CLASS类型(变量),源程序可读性也很强(因为没有很多关于类型约束的逻辑和信息),但就是因为类型信息在运行期,因此在编写时IDE不能获取到正确的类型信息,这给编写造成了一定的麻烦.
那么到底需不需要一个解释语言呢?解释语言就是边翻译边运行,程序可以被实时调用,这几乎是脚本语言的一个通用特征(一般脚本的意思就是通过一个SHELL调用,SHELL就是解释器,脚本这个说法相对于系统来说的,实现比较简单的DSL方面的东西,系统编程语言是通用的,那么脚本往往实现某个领域的逻辑,比如文本处理,但RUBY和JAVA走的都是通用脚本编程语言的路子),类逻辑可以先存在于持久化状态,再被实时加载,每一句新写的源程序都可以被即时执行.
那么到底需不需要一个类RUBY语法机制的语言呢,如果没用到协同线程,那么Ruby中的Coroutine你就用不到.如果这些都刚好对应上你需要的功能,而且上面提到的其它方面都基本满足,那么RUBY还是比其它语言要适合你的.
因为脚本就是要实现一种DSL的东西,所以RUBY即使很强大,Blizaard还是愿意用LUA这样的东西,困为LUA的一些比如协同线程很适合游戏开发,而且LUA不必通用,而且游戏就是要轻量的VM,脚本语言的本质决定了它不好被发展成为一种通用语言.
没有一种通用最好的系统编程语言+通用的最好的脚本语言,不要去寻找它,因为历来都是应用决定语言,而不是语言成就应用..
3.9 语言功能上的差别
学习时所一开始站的难度和起点不同,后来的成就也就不同,如果不一开始学编译原理,那么以后学习编程语言中所碰到的问题都会求助于自己的经验,就会产生形而上学的错误,我们能想到的联系都是知识,有一些可以被证明为正确,另一些不正确或即使正确却不受人吹捧的渐渐流失而已,,,知识本多维,如果仅想从IT去认识IT,,那么你会看不到全部,也会不知道很多学习IT需要学到的其它维度的知识(我实际上说,有些IT的问题不纯粹是IT问题,并不能直接用IT原有的知识来解决,就比如,JAVA到底比不比其它语言优越,,这是软件的问题,然而它跟人有关,,跟软工有关)

也即,我们站在跨度太大的抽象间进行一个抽象到另外一个抽象的理解,因为这个抽象太大了,在另一个抽象发生时,那个最底层的抽象可以显得忽略不计,计算机可以记住那么多抽象,而人不能,,就像这个世界已经发明了哲学一样,难道人行事做人动不动就会求索哲学去理解事情吗?问题出现了而过不去,你会发现再高深的哲思也不过几条丑陋的信条,所以哲学有方法论,但是作为哲学的方法论是大抽象上的,并不会深入到指导每个细节,当方法论脱离细节即脱节时,就没有太大的意义
离散数学,,就是把数学理论,,形式化为计算机能处理的东西,什么是离散??计算机本身就是一个巨大的离散概体,,能让计算机来处理数学,就必须把纯理论数学的一些因素形式化为计算机部件能理解的东西,因此会有形式语言的出现,因为很多形式语言的低层知识还是图等数学知识,等它与计算机结合成为形式语言时,形式语言这门学问也就成了离散数不的范围
就像在理解Delphi为什么能提供过程内过程一样,为什么函数语言可以避免不用设计模式,为什么函数范式的语言可提供eval函数(流程控制,等都是语言间通用的),为什么动态语言适用于WEB开发和XML结合,线程和异常.等等,这是因为语言也是抽象品,在它上面可以构建其它高级抽象,但是如果能了解一种语言抽象的得来过程,那么许多问题也就不问自知了(因为抽象跨度很小,中间很多的透明抽象现在可见了).学习一门语言的好方法是学习它的抽象由来..虽然这是个巨大的学习过程,涉及到很多其它非程序语言编制领域的抽象..
当然,如果仅仅是想开发一个应用,那么不必了解这么多,有人不理解STL的设计原理和架构,照样都可以用STL技术快速开发,有人对JAVA不必作如上我们所涉及到的分析,是因为每个人都有自己维度上或科学或不科学的理解(有人甚至因为JAVA是编译器哈),正确的思想级的理解对开发不是一定必要的,所谓死读书也是有用的,不一定要理解到那个层次,能学会使用就够了,细节永远是重要的(有一句很出名的话:别无它,唯手熟耳),与开发应用最结合紧密,但是思想和学习方法也是重要的,我以前看过一本参考书,因为把所有的知识来源都理了一遍,抽象达成从不大的跨度说了一遍,比纯粹的一堆细节性的教科书更能让我明白很多东西,也即,细节不是一切,只是关键,思想仅仅只是重要的.
3.10 C与C++之争
C++教父在他的一本书中透露说,1/3的人反对在C++中加入OO(OO引进了编程向人的思维靠近的理念,是一种信仰和理念),,1/3的人积极推荐他向C++中加入这些功能。
  
C和C++的争论高潮就在这里,C++让系统问题都OO化,使得系统问题都多解化,让人们不习惯,这造成的可复性反而会降低。

可复用不是指修改而用,而是倾向于不用修改也可以用,,因此跟平台相关的那些逻辑,无法通过仅用拿来就用的方法去复用。而且系统逻辑本来就是固定的,死的离散,而C++的OO对他们作了极为灵活的封装变换,因此造成即使对系统的理解,,也变得不够透明,只有懂C,懂编译原理,懂计算机系统,懂网络,才能从一种大局的高度去看待并计划你的系统你的应用。。而C++之后的OO机制不行,这种原因使C++受到了C粉丝的抵制。

OO是不是真的提高了我们想到的可复性?
  
  而脱离平台逻辑的那些领域问题,基本上人人都会有一个解法,宜在这里采用OO,OO最应该出现在脚本语言中,并且面向对象和基于对象也是二种几乎性质的东西,虽然都是设计期,都是对运行期的设计,但其底层支持下的RTTI有性质上的差别,C++作为底层开发语言,它提供的“基于对象开发”还是比较接近C“封据抽象”的风格的,但是它的面向对象,就有点脱离系统编程语言向DSL发展了,一般来说像RUBY这样的语言才提供OO和其它一系列语言机制。因为这些语言机制都是脱离系统编程的高层问题所需要的。而给底层开发提供一个OO,反而让人觉得封装过头,,抽象得太象了,,太象人的思想了,反而不能够让人家了解系统编程语言作为描述底层的透明性要求。。
  
世界上最好的语言是objective C加快一点的Ruby
3.11 我为什么选择C而不是C++及其它语言
我为什么选择学C而不是c++以及其它语言

首先C是最不容易过时的语言。
因为它跟硬件接近最近,可以向后发展,而其它语言只能向后发展却不能向前发展。。即使是在冯氏结构过时之后,c语言的地位也是最好的。嵌入式就说明了这个道理。。

C语言算法解决那些对于计算机实现要迫切解决的问题,比如内外排序,当要排序的对象远远大于内存时,需要设计一种好的算法来解决其对于计算机的实现问题,,
而Java语言的设计,解决的是站在人类逻辑角度的算法问题,比如如何进行架构设计才能更好复用以利于软工的开展,这二者所处的维度不同,决定了他们解决问题的方法根本不一样。。

当要设计机器的时候,Java的那些设计思想根本派不上用处。。

c语言加数据结构算法的方式解释了计算机产生逻辑的根本,只有深克地理解了这些,那些高级抽象就有被准确理解的可能性了,,这个道理就像接口和抽象、、

产生了抽象,提供了接口,但是接口却是大口径的,它隐藏了实现,接口并非刻意去隐藏实现,而是这是它的性质决定的,客户程序不能通过接口去访问接口后面的实现,否则只有它是这个接口的客户,,它就至少无法通过接口向前访问,,它只能利用接口逻辑,去发展后面的东西。。

要知道,你想要明白stl必须要理解一些adt的东西,这是C语言算法和数据结构的一些东西,,C++并没有完全做到adt完全的抽象(没有抽象到你能以一种脱离底层完全不同的眼光去看待adt),,这不是C++的错,,这是你没能学习算法跟数据结构的错。。
不要依赖太多的抽象,因为它们根本做得不彻底,你依然得学习算法跟数据结构才能理解这样的adt.

计算机底层=C语言加数据结构,算法,,,,这二门学科刚好完全地归纳并解释了编程对应于计算机底层的方方面面。。其实C语言很简单,基础的指针用法也很简单,如果学习了C语言版的数据结构,因为这是在学习C的习惯用法,,因此指针的很多抽象用法也会学到,,也就同时学会了C语言和数据结构,,,因此学数据结构是学习C语言最好的方法。。

深克地理解了算法与数据结构,你学习计算机才算到了家。。
3.12 类VB,DELPHI类RAD语言分析
vb在Windows平台上是一个流行的开发环境,在排名上很靠前,VB所体现的理念就是比尔向开发者靠近的态度,一个OS的发展离不开DEVELOPER的支持,比尔算是深刻地做到了这点
并非没有指针的语言功能就一定很弱,并非没有OO的语言功能就一定弱,运用VB,你照样可以用VB开发游戏,系统软件等,解释器并非虚拟机,VB带了一个解释器和运行时库,但是没有带一个软件机器性质的运行时虚拟机,,因此效立也不是很差.
我们关注的是VB的快速开发理念,它的最大特点就是"封装的可视性"和"复用接口的简单性",,,可视性是指逻辑单元总是被发布和开发成一个可视的控件,简单性是指每个控制都有有限和简单的几条属性和方法,,语言做到这个程度已经十分简单了,不需要我们掌握学习C++要学习到的一系列其它细节复杂性,VB把这个年代的程序员更多地做的是一种直接使用别人的库的事情这个理念算是做到家了,只要在上游统一了简单的形式,那么在下游就可以用这种简单性开发一切逻辑,,我们知道,复用就是逻辑组合,逻辑总有接口,对象的属性和方法都是接口(但一般把数据属性封装起来,因为透露数据的接口是危险的接口),构建应用就是组合接口兼容的逻辑单元,但是这个过程明显比不得现实生活中的电脑组装,这种事情.所幸VB也有其另外一种过程式的范式,并不要求用户一定要用这种方式开发.
因为应用是复杂的,软件开发永远不可能做到比VB还简单(永远不可能像现实生活中的找零件配电脑这种过程),,,这是因为应用永远是复杂的,而且有它自己的专门性,,,除非别人发布的库就是为你的应用定制的,,又或者你的逻辑不简单,需要复杂的逻辑(而别人没有提供类似可拿来用的逻辑,或者即使有相似的,但是为了适应你的应用而进行改造,那么复用工作就是纯粹的开发工作了(相对来说就不是复用了,而是开发了)..如果改造工作代价过大,或者对逻辑和接口的调整根本不能进行,那么VB的这种编程理念也是没用意义的.
这就是为什么VB永远不能跟C,C++这样的语言功能强大一样,,毕境,,有限的形式1可视2接口简单并不能直接产生很多复杂可产生的计算机逻辑(就像其它语言能提供的众多范式一样),,,这个道理就像WINDOWS GUI跟UNIX CUI之于开发谁更复杂谁更科学谁更简单一样,WINDOWS对GUI做多了文章,,使得在某些人眼中利用GUI IDE开发根本没有UNIX下SHELL开发简单和自然,甚至于一个VI就可以当成整个IDE..有时候,提供的形式越少,少得简单得最最初级的编程者都知道用控件来搭软件,那么这2种向"可视化"发展的简单形式反而不能描述更更复杂的逻辑,只能将逻辑做到控件层

所以VB这样的东西还是局限性很强的,,不是一门真正的系统编程语言,,像是一种介于系统语言和WEB语言这样的DSL之间的语言,,,往往用于RAD,,,通用性不强
因此VB从来都不是标准,,只是像E语言,DELPHI,一样,只能往RAD的方向发展
正像我所说的,,,如果你想为你的应用计划一个科学的设计
最好是理解整个PC逻辑,,硬件的,软件的,,语言的,,库的,,应用的,,软工的
要注意VB并非通用OS编程语言...因此它的局限性很大
历来都是应用成就语言,,,有了RAD所有就有了VB
离开了快速开发的需求,没有人为VB提供复用控件,VB就没有太多用了..因为下不能入底层,上不能跟VB.NET一样进入高层的WEB开发,是鸡肋了.

新手编程导论(五)


第4章 语言最小内核(C)


4.1 C与C++是二种不同的语言
C语言的最初的版本是起源于B语言的,后来经过发展,又经过了多次标准化(第一次民间标准化是C语言的二个创始者K&R写的一本书,C是最初作为UNIX专用系统编程语言出现的),分裂成了二支,在C89标准上面加上OO,加上Template,就形成了C++(C++是C89的超集,当你手中拿的C++教材没讲基于过象面向对象和模板并且你需要的不仅是一本C的教材时,你可以现在就扔了它),第二支继续独立发展,发展出了C99,目前最新的C标准是C99,支持并实现了它的编译器市面上比比皆是。
关于C89与C99方面的差异有一些是根本性的,因为实现的原理根本不一样(是编译器方面的差别,而不是库级逻辑上的差别),比如C++用Template技术实现的数组版本和C99的数组..
所以,C++除去C89的那部分才是C++的主体(所以说学习C++是学习C语言和C++语言这二个过程,我们知道++是C家族语言中特有的运算符,C++这个表达式表示,第一次的运算过程只首先取C的值,第二次及以后的运算过程才在保留原值的基础上开逐次始加1),C++为了成为多范型语言,先是杂合了C的很多特性,提供了指针,位,函数,流程这些基于过程的开发支持,为了成为基于对象和面向对象语言,又实现了一个运行期OO — 其中有运行期多型机制(这是C++之父"发明"的),再后来是在没有任何原C的编译技术的支持的情况下发展出了一个Template(也是C++之父所在的研究小组"发明"的),是C++首创的独立编译技术(并由此"发现"了C++的编译期多态和实现了Loki,Boost MPL库等),STL就是用了C++的Template而出来的东西,STL是面向运行期的,而Boost MPL库是面向编译期的。
C99的数组跟STL的数组就是大不相同了,,一个是C上面的,一个是C++的模板技术上面的,有着不同的实现原理,编译支持基础..
在C中,数组就是原生Built-in的,,数组就是连续的同类型数据在内存中的分布,,,注意,1,连续空间2,同类型数据,,如果不是连续的空间就不是数组,,比如用指针link起来的就是链表,而不是"数组表",,注意"表这个说法",,数组是一种表,,因为它用索引下标索引数据对象,是一种key:value对,而链表也是2同类型数据,,
在STL中,模拟数组比如Vector,List是用模板来实现的..这二者之间明显存在差别,静态数组一般直接用C的数组,,但是正因为C的原生数组是没有边界检查的,编译器根本不保证这个栓查全部交给程序员来处理,,而STL版本的模拟数组就会非常严格..
这就是二者之间的差别
4.2 C的数组,指针与字符串
(我这里讲的都是C标准)
C只直接支持简单的数据类型,只有简单的Int,Float,Char,Void,这样的数据类型可以直接定义使用(并可作为函数参数传送,但数组就不能直接被传送,需要转成它的指针形式 — 一个32位长整型数被传送,,因为它不是First Class),,其它的高级数据表示比如字符串,数组,都是在简单数据类型之上模拟得到的,,,是库级的逻辑,,编译器并不直接支持..(C编译器没有提供对字符串String的Built-in支持,只有Char和Char *,C把它作为库级逻辑的String来抽象)
C99在库级新增了_Image,_Complex,_String这些数据抽象(相对C++来说)..
所以C的指针类型几乎跟数组,字串这样的逻辑同胞共母,字串本质是Char * 数组,,数组是内存相续的同型数据,用指针变量来表达数组和字串几乎浑然天成(数组名就是指向第一个数组元素的指针,类型为:"(基类型)指针变量名"这样的形式,,而且字串直接被定义成char[]的形式,,数组大小一定要程序员方面保证容纳字串大小+一个/号的大小,字符串函数大量涉及到指针比如strcat,C有一个标准的字串实现,cstring.h,C++有一个STL版的字串,C++还有一个原生版的string.h,特定的编译器也有特定的字串实现),,,C就这样把所有的东西统一于简单的"简单类型"..
当然C也提供了union,struct这样的结构体来表达比简单的类型来抽象一级的类型,其实C也可以表达OO(不过它并不承诺一定要实现运行期多态,继承等OO语言的标准技术,,有一本<<C支持面向对象技术>>的书讲解到了相关的技术)
4.3 C的输入与输出流
C++版本有个Iostream(Cin,Cout就在里面,实际上C++的IO库很大,是一个复杂的基于对象的模板系统),C也有它的Stdio.h(有Inputchar等和Printf()这样的函数),这是C语言的标准(库)独立于所有的硬件和软件环境,是语言本身的因素,,,一门语言的IO机制是很重要的,比如,JAVA的I/O封装得很完善,,它细分成了多个OO表达的"流",极大地方便了程序员。
i/o绝非仅仅输入输出这四个字这么简单,首先这是一种语言机制(就跟语言提供异常处理,这样的语言机制的地位一样重要),是关于这种语言对向它输入和由它输出的所有抽象的全部集合,它涉及到一门语言如何处理与系统的交互,如果将用户输入转成该语言写成的程序所用,,如何看待OS"文件"的概念.并发展出一系统概念,如inputsteam,file steam,等等
在PC的架构中,语言是高于OS的(一般在一个系统的架构中,开发层是高于内核层的),Windows是用C写的,C有它的I/O,是先于Windows的,一门语言并没有具体的数据类型,,比如unsigh,sign这样的区别是来源,起决定作用的还是硬件,,C语言只是对它们提供了一些名称指代,,比如int在有些机子上是32位在有些机子上是16位,这对考虑一种程序的可移殖性是十分重要的。
在C的眼光中,一些输入输出都是某种流,语言接受用户输入,或者程序进行输出,先以内存作为根据地进行缓种,,缓冲分为二种,一种是缓冲文件系统(这是C标准的文件系统,,即不是一次性输入用户数据到程序中,而是输入一次缓冲一次),另一种是非缓冲文件系统(每次输入就完成一次绝对的i/o),这是,C把显示器,打引机像成标准输出,把用户输入键盘,想象成标准输入,,并把它们看成"标准输入出流",,,把OS的文件想象成filesteam.
4.4 C的类型系统与表达式
任何语言都有一个“类型系统”,比如C++直接支持类(UDT)为它的First Class,,Lisp这样的语言支持直接传递函数指针(因为函数是它的类型),,,就C来讲,,它支持primitive types,即普通的Char,Char*,Int,Int *,Float,Float *,Void类型。。
到底什么是类型呢,C++ template的type跟class有什么区别呢。。
4.5 二进制指令看循环
大多一门语言都提供了流程控制,,形成了各自的语言关于流程语句的语法要素,,语法是一种形式(所以称语言是形式语言),,语义是一种意义,,,
其实循环,跳转这些东西来自于汇编语言,,,高级语言接纳了这些理念(因为汇编语言几乎就是机器逻辑,高级语言提供类汇编语言这样的机制是为了符合平台逻辑,,,况且高级语言最终要编译成汇编语言和平台逻辑,,循环语言要最终被还原成汇编语言的形式,这些处理速度就可大大加快),,,发展出了它们关于循环,跳转封装了的语言机制..
C语言最终要编译成汇编语言和平台逻辑,,循环语言要最终被还原成汇编语言的形式,,这就是调试的由来,,,调试分语法级调试和语义级运行期的错误..
4.6 所谓指针:当指针用于设计居多时
指针是C语言中的一种"语言机制",,它导致的差别在于
如果用得一般,指针就是一种普通的工具,仅仅在给函数传地址改变实参,数组的定位本质是指针,,这些课题上达到顶点
而如果C语言的指针用得好,,,C语言就会是另外一种语言,
那会是一种什么语言呢,那会是一种advancedpointer c lanuage(增强型指针C语言,,指针使C变为设计语言就跟C#高级语言一样,而不再仅是普通意义上拥有指针作为底层机制的中间语言)

因为指针是C语言唯一的"抽象语言机制",我们这里提出"抽象语言机制",说明可用于设计,,,比如C++有"OO","范型"等等(很多书上讲解C++没有讲解这是对的,因为C++的语言机制中,只有OO和范型是它自己的,而指针几乎是C语言唯一的抽象语言机制)

指针被用于设计时,,它的用法有哪些?? 这就是学C的最高境界,

首先指针是一种底层实现和设计通吃的语言机制汇编语言中也有指针,比如
mov eax dowrd ptr [某一地址]
mov edx dowrd ptr [某一地址]

这样的结构,,说明指针在这方面是一种内存地址的指针,,然而当指针发展到C的指针和C++的引用时,,又形成了更高层的逻辑,,,
4.7 指针成就的C语言
幸好你没有把C跟C++放在一起学,,C是c99,C++是C89,,可喜可贺

学C,,要学透就学指针,,,要学全,就要学C的标准库

指针不单是控制底层的工具,,,也是C的抽象语言机制
1) 字符串,C的字符串跟指针密切相关.C根本就是用内存来控制字符串的.而C++的Iostream.h你去看看,,全是抽象了的模块,你根本不需要深入内存,二者根本不可同日而语,,这是因为C就是站在底层去构造字符串逻辑的,,而C++隐藏了这些,,不让程序员知道..所以C最适合当系统编程语言,而 C++控制系统的能力却没有C强,,,一个很好的例子就是可嵌入开发,C++根本不行,因为C++更多地是一种应用开发语言
2)数组,C的指针几乎(我说几乎)等同数组,前期的C根本不能把数组当参数传递,,后来的C标准支持了这个观点,,因为数组在C的观点里就是内存地址.因此用指针完全可以控制数组,,函数可以返回一个指针以返回一个数组,,,数组也可以用指针而不用索引来定位并操作它的元素
3)位操作,,就是所谓的bitwise了,,这个跟指针的关系就更不用说了

综上所述,,C是靠指针来进行设计它自身的,,,而且,C是靠指针来解决C能解决的程序问题的..因为C不仅是一种语言手段,,而且是一种语言抽象...
网上有用指针实现C++的OO的文章,,读完它,你就发现C通过指针机制还可成为设计语言,,,什么是设计语言呢,,C++,VB,RUDY,LUA,DELPHI都是设计语言,而C就是底层语言.

祝好

C是系统编程语言,,一般谈到对系统编程,,,就是数据结构加算法(当然,系统编程的真正概念是指:socket,graphics,IO,gui这些平台支持逻辑相关的层面),,,编译器前端的构造就是一个大算法,,,运行时就是一个大数据结构,,,操作系统作为语言运行的环境,,考察它的实现过程,也是数据结构加算法的集中体现,

如果你真要学透C语言,,,,第一要学习C语言是如何来的,,这就是编译后端了(动态运行期的类型信息),,,第二要学习语言本身的机制,,当然重点是指针..因为指针不单是内存地址,,,而且是一种语言的抽象机制,,,C语言只有指针这种抽象机制,,就像C++的OO,范型一样

数据结构加算法是系统的观点,,所以容易跟C语言结合,,,C语言也因此被用来系统编程

而VB是RAD,,抽象了太多底层的东西,无所谓数据结构加算法,,所以被局限于代码组合和快速开发...因为它的接口就是控件属性和方法,,,它的功能模块就是控件..

其实OS不需要一个语言,语言只是为了开发应用而出现的,OS可以不要它(语言编译器也属于系统软件),更确切来说并不需要一个编译器,,它只需要一个
编译器的后端,比如运行时,或者仅仅一个解释器。有了这二个就可以执行源程序了,这二者之间的接口要明白,因为解释器可以直接执行编译器前端生成的中间代码


而且VB的运行时面向的根本就不是真实机器,,VB是一种架空的语言..因此它的语言机制也是构空的,,,不是面向系统的.因此无所谓数据结构加算法

4.8 指针是语言的一种抽象机制
指针是一种语言的抽象机制,而不是仅是语言用来控制低层的手段(指针指向变量,而变量是内存地址)
指针有什么用呢,,指针用"指针本身"来指代"它所指的东西本身"(虽然实质不同, 一个是指针变量一个是指向的变量本身,二者除了意义上指代与被指代这层联系之外,其它方面没有任何联系,从变量的三个方面来看,
1.变量的型别,,每个指针变量都是一个指向某种型别的指针变量,它首先是变量,然后是指针变量,再然后是指向某某某型别的指针变量,第四是指向某具体变量的指针变量,被指的变量可以是其它类型,但指针变量都是一个32 int
2,变量的作用域,指针变量只为索引被指代的变量,如果被指代物被释放了,而指针本身没有被释放,就成了野指针.
3,变量的所占的空间,不用说了吧
虽然存在极大的不同,但是,但是指针的精神要求你把它们二者建立"他们是相同的"这样的认识,这是基于抽象的考虑要求的
比如int* pointoaint;????? //pointoaint就是一个关于int类型的指代,,,
那么这种指代有什么用呢,(相比之下,引用比指代更能体验这一点,引用=它所指的对象本身,它所引用的对象本身,虽然这二个概念不等价,但指针这层抽象的意义就是让我们人脑把指针作这样的理解,)
在单根继承OO语言中(就是所有的语言级的first class OO对象都是从一个Tobject这样的东东继续而来,用户定义的OO对象也需要从这个根对象定义而来一样)实现泛编程的容器中,,比如JAVA的LIST中,一般用指向TOBJECTS的引用再填充这个LIST,这样容器里的对象不是对象实体,而是指向他们的索引对象,,,
很显然,JAVA的LIST中的对象都是索引他们的引用变量,而非对象本身,是假对象.
另一方面,,,人们用索引来看待变量的方法,,这种行为自身也表明,,引用只是一种靠近人脑的抽象,,,是一种语言的抽象机制,,人们说指针是C语言的灵魂,,说的就是这个道理..

4.9 学C千万不能走入的一个误区(其实JAVA比C难)
C语言重点不是为了来开发应用的,,而是为了开发跟计算机本身(远离业务)那一层面的逻辑



C的开发早过时了,,现在是高层逻辑开发时代,,,,

现在的WEB开发,,,提出了一系列新语言,新的框架,,设计哲学,,这些都是远离计算机底层的(比如数据跟算法),,更像是一种逻辑的配置,,然而逻辑的配置比开发更难,,,,这个道理就像:这个时代GUI远远比CUI常见,,但反而CUI才是主流



这就是说,抽象远离了计算机底层,,在高层又变得异常灵活和难于掌握,,



其实JAVA这样的语言因为面向WEB,面向业务,,,它要学习的东西反而更多更难,,,学习JAVA,如果不学WEB,这几乎是件可笑的事情,可是JAVA易学,WEB就不易学了

但是C语言要解决的问题永远是底层,系统永远是简单和形式的,大不了把数结与算法学精.学透,把系统本身学透,,C的开发就到家了
但是JAVA后面的应用问题永远有新的学习需要出现,,因为JAVA后面的东西是异常灵活的高层应用而非固定的底层...面临的问题永远有新的解法,WEB领域永远有新框架新语言的提出..
所以我说,,,其实JAVA比C难..!!!!!



这就跟C一个道理,,C易学,,但C面向的系统问题涉及到大量数据和算法,又显得很难,,,如果学C,却不是为了解决系统,这又是一件可笑的事情,,,永远不要以为学好一门语言就是全部目标了



学好C语言在于将C语言包括指针在内的所有语言机制习惯用法,跟数据结合和算法的结合,,C用于实际问题才是重点要学习的



懂了C,却没懂系统底层一样没有用,不懂数据与算法也没有用,因为C就是面向并开发这些的



其实,JAVA和C都可以发展很多后来的抽象,,JAVA和C 容易学习,是因为它们入口处小,(JAVA和C有有限的语言机制和学习成本)学习和控制就很容易了,,但抽象应该长足发展(WEB问题和系统问题是一个不断可以深入的话题,需要新的语言机制的支持),,JAVA和C解决的问题永远没有一个头,,永远都可以是新的话题..因为应用无形而语言有形



应该相信一些简单的道理,因为它们是道,道统一所有理



无论是什么语言,一门语言写出的程序总是程序=抽象+接口,
(C的抽象在于抽象底层机器因素,提供程序员可见的因素和机制,,这就是抽象,,对于人的靠扰,C的接口在语言级的细微度接口主要还是函数级的接口,,编译器级有.out文件,,,底层=算法加数据..接口提供了功能模块如何交互和复用的机制)
再比如JAVA,,提出了很多语言机制,,也是为了能更好地远离本地发展抽象,,,它把什么都OO化了,这很利于对开发人员的抽象,,,,
它提出的OO,,,一方面既是抽象机制,,,一方面也是接口机制..

4.10 C抽象惯用法
诚然,C只有有限的语言要素,它的语法只是稍微抽象了汇编,它适合描述系统底层,而不适用表达过高的抽象,比如它没有直接站在OO角度上去描述除系统底层问题之外的其它问题,C的那些语法机制是C在语言级能用来表达所有问题的唯一方式(即使这样,C还不失为一种系统编程和应用编程的综合语言,它的表达能力还是巨大的而且C的优点是易于学习进门但进阶难,不像别的语言连进门都难比如OO语言,它的OO机制一时半会是很难被初学者理解的)C虽然没有语言级的OO,不能直接用C的某种语法机制进行OO思维写OO代码,但C的一些简单语法机制同样可以实现模拟了的OO的抽象(作为库级抽象来进行OO)。。这导致“如何用C达成高度抽象语句或程序”这样的问题的产生(不只是OO,C也可以实现C++的template各种各样的抽象来间接解决现实问题而不满足于用C本身来直接解决问题,这里,间接解决现实问题定义OO等库逻辑,就是抽象的意思所在)。
这就是说,抽象并不用C的语法机制直接写应用,而是在进行某种设计,在C模拟的OO中,它企图先在库级完成C模拟的OO,再去解决它要解决的最终问题,当然这只是C抽象的一种,C的抽象主要表现在四个方面,1.用C来解释的数据结构问题本身就是某种抽象和设计,在一些教材讲解到的C版本的数据结构教学中,可以大量看到C写抽象的方式 2,C的指针是C的主要抽象手段(这样说是因为一些其它C的语法要素也跟指针一样用于设计导致高抽象,),C的指针可直接产生很多复杂的思想 3 跟上面一开始谈到的一样,我们想用C构建某种靠近现实问题的抽象,比如我们想用C来实现OO,或者template,这样C的这些逻辑就成了dsl了。再来解决问题。4,我们不想先实现一个OO,template这样的通用解决问题的C抽象,这样的通用抽象称为范式,而是直接面对要解决的问题,用C的语法语言要素去产生一个或一套习惯用法的抽象。相比OO,template这些范式是小规模的抽象惯用法,下面我们来讨论一下C高抽象的习惯用法。。我们还有另一章专门讲解OO范式.
1),,用指针,struct,typedef,,这样的东西可以声明一个链表的结构本质,这在数据结构中大量看出。指针是“通过地址来操作变量”的抽象,struct是建立在简单数据类型上复合数据类型的抽象,typedef是type redefine的抽象。。这些抽象分别使用,或者组合使用可表示很多其它高级抽象。。
2)形参定义为指针,实参向它传地址,实质上是传值然而意义上是传送地址,这种指针的抽象用法,可用来影响调用函数中的实参。
3)void这种类型可作为形参,实现一种多态机制,,void函数也是如此.
4)C语言数组的本质是地址,多行数组是按行优先描述的一维数组,因此其元素地址都可由数组名抽象得到.
5)字符串是一种变长字节,操作字符串的那些函数,也可操作内存块,这就把字符串抽象为一种数据结构的东西。
6)C用void (foo *)(int)这样的结构来表示返回void,以int为参的函数指针foo.这遵守屏看原理.关于函数指针和数组指针还有很多抽象的变形。。来实现回调函数等抽象。
7)C的指针在链表中,实现了一种"指针即变量自身"的指代作用,比如stack->next=stack->next->next,这种指针的赋给,实际上就是指针所指变量在意义上的直接替换。。这也就是C++中为指针新增的引用语法。。
8)typedef实现了换名和子集定义抽象,用旧类型定义出新类型,新类型就是旧类型的别名,或者是旧类型的一个子集,这样的抽象语义。。特别是define实现了一种变量替换式的函数抽象。
9)用const或双层const进行防修改。
10)还有很多很多。。
4.11 C的抽象范式之OOP
其实我觉得C++之父不必把它的c前端版本的C++弄成一个独立的编译器,因为不这样的话,写出来的OO代码也是C的,这样就能极大范围地做到语法上统一,也能将接口保持在C函数的级别供脚本语言使用,而即使在这些模拟中,不少正是直接对应一模一样或几乎一样的C++的本质。无论如何,下面我们讨论一个前端版本的OO模拟,顺便学习一下C++面向对象的内容本质。



先来看看面向对象几种层次抽象
我们知道在C++实现的OO机制中,存在class,class=type,即template中的type,但是我们知道template更像是一种建立在C++类型机制上的脚本语言,其语法古怪,不接近C++本身,,C++用class作为派生type的类型,用type作为template中的类型。

class可用来派生子类型也可用来产生对象(我们用措词上的区别来区分这二者),派生的子类型subclass,此时它也是一个class,,产生的object,我们称一个对象为class的instance.class为object model

这其中存在一些特例,比如纯抽象的class(用来实现C++版本的interface),,或者静态类,,

为了产生逻辑,我们可以继承,也可以组合,继承就是从相对基类中(除了单根继续的语言,基类都是严格相对的)单层继承或多层继续,产生新的类,这样的子类在抽象上继续了父类的所有属性,在底层实现上其实是叠加了子类的代码在基类上的组合代码,是发生在类间的逻辑。也可以由类产生对象以产生逻辑,是发生在类和对象之间的逻辑,,,注意多层继续在现代OO中是不受鼓励的。。

我们将会在下面的RTTI部分讨论以上机制在底层的对应实现。。

而发生在对象之间的逻辑呢,是采用将一个对象作为另一个对象的数据成员来作为产生逻辑的方式,,对象之间交互的方式就称为消息,C开发的函数范式在这里被消息所代替,实际上我们知道消息也是函数调用,只不过这里的函数是加了访问控制和多态的特殊性函数。。

C的模拟OOP主要有以下几个技术,1,用预处理宏定义keyword和定义结构,2,实现rtti.当然,在C++中写多态有三种方式,1,是C++扩展了C的普通多态函数(重载只是多态的简单手段,稍后谈到的2,3才是高级手段),2是OO里面的多态函数,3是模板的泛型多态,,我们只在这里讨论oo里面的东西,,并不打算讨论其它的机制。比如C++所有的3种多态。纯adt的interface机制,也不讨论C++中C没有的异常机制。更不打算讨论templaste。我们只模拟OOP的典型和核心特征。。


首先就class来说,如何模拟呢,
4.12 C的观点:底层不需要直接抽象
抽象是为了简单化,抽象=对机器的简单性=对人的复杂性
C++在语言语法级集成的抽象太多了,而且它的库级抽象也太多了,BOOST,编译期的泛型抽象,运行期的OO泛型抽象等,,OO抽象等,,无一不表示,C++更适合当一门靠近应用的语言(只有高层应用要求抽象,底层开发就要不得太多抽象,因为机器本身就是机器,C就是对应机器的,无抽象必要),,,这造成的结果是要全面掌握C++要求人们掌握的知识太多了,C++的这么多因素使C++变成了多范型,完全密封远离低层(然而你要知道,抽象意味着对底层的迂回,对人类思维的靠扰,这迂回多了,对底层的访问就少了),,,,其实C++也明显没有损耗C操作底层的能力(我这里仅指它OO范型和模板等高级语法机制),,,,但是它提供高级的语言级和库级的抽象反而对人们要求掌握它的成本变得过高了,这就是说,C++对于开发人员提供的"形式,工具"过多(至少比C多).,提出了很多抽象,.在提供的抽象方面,C只有指针,函数指针,结构体,这些有限的东西,而C++呢,有OO,有TEMPLATE(语法级),有STL,有BOOST(库级)这么多
在开发人员这端,C++代码复杂难解,因为一个会读C++源码的人首先要理解语言本身,才能理解作者要想在代码中描述的现实事物(机器逻辑通过C++对于应用逻辑和现实事物的变换),,,而这里面,抽象太多了(甚至过度抽象,过度设计)
用OO表现的现实事物,相对于C用结构和函数表达的现实事物来说,,,后者更接近机器.C本身的那么有限的语法机制,即使离开了语法级的OO,C也可直接用函数级的第一类型(组成的模块)和结构体数据抽象,和指针,,这些东西描述应用领域,比如Windows用C表达消息,,jxta用c表达管道,端点这些概念,,,
语言的选择永远离不开应用,要开发底层,,C比C++好,WEB开发是那些远离底层的逻辑,用JAVA可以,但是C其实也是OO的,它的结构体,本身就是一种数据抽象,比如我要描述一个学生,我就选择性的定义一个struct student,里面加上学分啊,身高啊这些数据,(这是最好的方法,比C++的类都要好,为什么呢,因为C语言的类型机制就是计算机的类型机制,,这些定义数据抽象做到了机器跟应用域事物的一一对应,非常地好)

而定义关于数据的抽象是冯氏语言模型的根本,我们知道可以用设计数据的方式抽象DSL概念词汇,系统编程支持词汇。

JAVA明显不好用来开发底端,,这就是语言适配应用的道理,,但是计算机终久是计算机,如果你想学习计算机本身而不仅仅是想通过掌握JAVA开发WEB,那么C+LINUX永远是最好的选择,因为开源的东西一般出现在LINUX下.而且通常用C开发的,世界上开源项目最多的就是C了.
一句话,C比C++简单,如果想开发图形啊,网络啊,这些跟计算机本身有关的东西,这样的一个想靠掌握语言马上编程的初学者最好去学习C,而不是C++

4.13 指针:间接操作者
type*
这个形式就表示一种类型,,,,如果说type本身也是一种类型的话,那么在type后加一个星号也表示一种类型,,这种类型叫“指向这种type的指针类型(重要的是最后的指针类型这四个字)”,,你可以联想dephi中的ptype类型
所以你就可以这样定义东西
type* someobj;
(*可向type靠近或someobj靠近,C程序员偏向于向someobj而C++程序员偏向于向type靠近,这样的话就有二种等价意义,type *someobj表示*someobj是一种type变量,type*someobj表示someobj是一个指向type的指针)
someobj就是一个指针变量,它代表指向此type的的指针变量,,因此在这里type*是类型(是一种指针意义上的数据类型),someobj是这种类型的一个变量
根本上引用只是一个指针的别名,因此你可以由一个指针得到一个或多个引用(即关于该指针指向的对象的引用)
指针本质上是一个32位longint(因此在Generic progamming领域有“用整型代替类型”的说法),定义一个指针时可定义void*型指针(因为指针并不一定出生时就要指向一个对象或一块内存,而引用要求有一个初始值,因为引用本质是指针的别名),因此它可以被指定为0,即空指针,也可以被重新赋值(此时它就不指向它原来指向的对象或内存了),,换言之,,多个指针可以同时指向一个对象,你可以为一个对象定义多个指向它的指针(但是它并不拥有这一内存,也就是说,当它指向的对象发生了变化,指针仅仅作为指向这个对象在内存中的位置的意义就会失效或过时,它只拥有对象的adress值而非value值),而你可以通过这些指针或引用来操作该对象
一般来说,引用常常跟const在一起(加上const只是为了强制跟保险),那是因为定义出来的引用常常不会改变
type*
这个形式就表示一种类型,,,,如果说type本身也是一种类型的话,那么在type后加一个星号也表示一种类型,,这种类型叫“指向这种type的指针类型(重要的是最后的指针类型这四个字)”,,你可以联想dephi中的ptype类型
所以你就可以这样定义东西
type* someobj;
(*可向type靠近或someobj靠近,C程序员偏向于向someobj而C++程序员偏向于向type靠近,这样的话就有二种等价意义,type *someobj表示*someobj是一种type变量,type*someobj表示someobj是一个指向type的指针)
someobj就是一个指针变量,它代表指向此type的的指针变量,,因此在这里type*是类型(是一种指针意义上的数据类型),someobj是这种类型的一个变量
根本上引用只是一个指针的别名,因此你可以由一个指针得到一个或多个引用(即关于该指针指向的对象的引用)
指针本质上是一个32位longint,定义一个指针时可定义void*型指针(因为指针并不一定出生时就要指向一个对象或一块内存,而引用要求有一个初始值,因为引用本质是指针的别名),因此它可以被指定为0,即空指针,也可以被重新赋值(此时它就不指向它原来指向的对象或内存了),,换言之,,多个指针可以同时指向一个对象,你可以为一个对象定义多个指向它的指针(但是它并不拥有这一内存,也就是说,当它指向的对象发生了变化,指针仅仅作为指向这个对象在内存中的位置的意义就会失效或过时,它只拥有对象的adress值而非value值),而你可以通过这些指针或引用来操作该对象
一般来说,引用常常跟const在一起(加上const只是为了强制跟保险),那是因为定义出来的引用常常不会改变
4.14 真正的typedef
typedef是类型替代名(typedef=type dfine嘛),在理解时可以按以下的方法进行

1.typedef是定义一种类型的子集,,,比如typedef int INT;(这意思就是说,INT是int的一个子集)

2.类extern的这样一种声明(也即仅仅是对类型的向外的一种再声明),如extern int myint;(这意思就是说,作为变量的myint这里作为一种“新的类型”,而且是一种“int类型”,,,这是一种当已有类型的再声明)

也即,extern就是相对于变量,typedef就相对于类型(注意它们二者并不实际等价,也即不会有myint这个实际变量被声明出来)

注意,一般myint要大写,但是这里为了说明还是采用它的小写形式

由第二种理解方式可以导出很多,如typedef int (*myint)(),,,可理解为extern int (*myint)(),,这里的myint也是一种类型,这种类型表示“指向一个返回int类型的函数的指针类型”

4.15 真正的指针类型
type*
这个形式就表示一种类型,,,,如果说type本身也是一种类型的话,那么在type后加一个星号也表示一种类型,,这种类型叫“指向这种type的指针类型(重要的是最后的指针类型这四个字)”,,你可以联想dephi中的ptype类型
所以你就可以这样定义东西
type* someobj;
(*可向type靠近或someobj靠近,C程序员偏向于向someobj而C++程序员偏向于向type靠近,这样的话就有二种等价意义,type *someobj表示*someobj是一种type变量,type*someobj表示someobj是一个指向type的指针)
someobj就是一个指针变量,它代表指向此type的的指针变量,,因此在这里type*是类型(是一种指针意义上的数据类型),someobj是这种类型的一个变量
根本上引用只是一个指针的别名,因此你可以由一个指针得到一个或多个引用(即关于该指针指向的对象的引用)
指针本质上是一个32位longint(因此在Generic progamming领域有“用整型代替类型”的说法),定义一个指针时可定义void*型指针(因为指针并不一定出生时就要指向一个对象或一块内存,而引用要求有一个初始值,因为引用本质是指针的别名),因此它可以被指定为0,即空指针,也可以被重新赋值(此时它就不指向它原来指向的对象或内存了),,换言之,,多个指针可以同时指向一个对象,你可以为一个对象定义多个指向它的指针(但是它并不拥有这一内存,也就是说,当它指向的对象发生了变化,指针仅仅作为指向这个对象在内存中的位置的意义就会失效或过时,它只拥有对象的adress值而非value值),而你可以通过这些指针或引用来操作该对象
一般来说,引用常常跟const在一起(加上const只是为了强制跟保险),那是因为定义出来的引用常常不会改变
type*
这个形式就表示一种类型,,,,如果说type本身也是一种类型的话,那么在type后加一个星号也表示一种类型,,这种类型叫“指向这种type的指针类型(重要的是最后的指针类型这四个字)”,,你可以联想dephi中的ptype类型
所以你就可以这样定义东西
type* someobj;
(*可向type靠近或someobj靠近,C程序员偏向于向someobj而C++程序员偏向于向type靠近,这样的话就有二种等价意义,type *someobj表示*someobj是一种type变量,type*someobj表示someobj是一个指向type的指针)
someobj就是一个指针变量,它代表指向此type的的指针变量,,因此在这里type*是类型(是一种指针意义上的数据类型),someobj是这种类型的一个变量
根本上引用只是一个指针的别名,因此你可以由一个指针得到一个或多个引用(即关于该指针指向的对象的引用)
指针本质上是一个32位longint,定义一个指针时可定义void*型指针(因为指针并不一定出生时就要指向一个对象或一块内存,而引用要求有一个初始值,因为引用本质是指针的别名),因此它可以被指定为0,即空指针,也可以被重新赋值(此时它就不指向它原来指向的对象或内存了),,换言之,,多个指针可以同时指向一个对象,你可以为一个对象定义多个指向它的指针(但是它并不拥有这一内存,也就是说,当它指向的对象发生了变化,指针仅仅作为指向这个对象在内存中的位置的意义就会失效或过时,它只拥有对象的adress值而非value值),而你可以通过这些指针或引用来操作该对象
一般来说,引用常常跟const在一起(加上const只是为了强制跟保险),那是因为定义出来的引用常常不会改变
4.16 真正的函数指针

C和C++都不允许一个真正的函数作为参数,,将函数作为参数传送的办法是将它用一个指针去指向这个函数,,,,

而且,通过函数指针和函数模板,可以很有效地实现业务逻辑跟界面逻辑分开
学习函数指针完全是一种挑战,你能分别出以下的吗?
int *(*(*foo)(int))[5];
这个就表示:foo是一个函数指针(这就是括号带星号的结果-即把*foo括起来的那个括号),它指向一个函数(以指向它的指针foo命名的函数),它带一个int参数(即函数指针foo紧靠右的那个括号和括号内的int),,并返回一个指向数组的指针
4.17 真正的句柄
句柄一般有三种意思
1,Window的资源句柄

一个语言跟操作系统的关系是什么??

一个操作系统可以用一种语言写成,,因此如果语言出现在前(历史原因),那么操系统(或其一个小节)会用这种语言来描述,,,比如Windows的对象,,就是说如果Windows是用C++来实现的,那么这个对象就是CUI(**—对象

根本历史,,一方用另一方的东东来描述的关系

比如文件,,操作系统普通把文件作为硬盘上的东西,,可是文件在语言的观点里根本就不是这么一回事,,,文件只是data flow,,或者一个file map(也即在内存中作引象),,,,

2,包装被指对象为其值的智能指针
3,形如type**的双层指针
4,其实通用意义上的Handle就是句柄的意思,句柄还有一种用处出现在编译原理中
5,如下

如果B是A的一个子类,,,有如下代码

(A)new B


那么以上可以理解为,,,上面产生了一个对象,,这个对象指向B(指针),,,但是其句柄为A

即a handle of A pointing
to a B,

也即第四种指针是用父类来操作其子类对象的一种机制

4.18 真正的循环
for的条件表达式可能短路,,它的update部分可以是任何算式,甚至不要updata部分
一般把最常发生case的情况放在最后面,如果你学过汇编就知道,编译器是从后到前搜索的

注意汇编是没有逻辑运算符的,只有移位运算符(而往往C++中把它们同用)

有三种,,逻辑(与,或,异或,取反),移位(无符号左移,,右移,,高位补0等等),

在汇编中

contine和break的区别,contiune放在循环语句的某一具体层中,当满足条件时,继续执行下一个当前循环(即重新判断条件),而break可以放在一个循环的任何地方,是跳出当前循环(注意因为循环可能是嵌套的,所以这个当前循环是指break所在的那一个嵌套层次),,然后继续执行这个循环外的第一条语句(即结束循环)。
4.19 真正的static
在C++中,一个结构也有它的构造函数,这个构造函数(接口类不应包含构造函数)往往作为初始化该结构的初值使用,因此一般将它们作为static来定义,这就不得不说说static到底给语言增加了什么?
static的成员也不是静态的,它也可以进行自加自减这样的操作,
static是在编译期发生的,因此不能在运行期改变它
在一个函数体内定义的static数据,,相对整个程序来说都是全局的(直到被销毁)
因此static提供了一种把局部自动变量转化为暂时全局性的东东,,比如对于一个在函数体内的常量来说

static也被作为类级的私有成员数据或行为(类属函数),因为类只是定义对象的规范,因此它应该没有一块内存实际存在用于调用它的成员(一般方法是具体化出它的一个对象然后调用对象在类中定义的成员),但是static提供了一种“全局静态”的概念(类级的全局),比如Java中systme名字空间的API就是全局的API(包级的全局),可以直接拿来用

函数退出这个常量就失效了,解决的方法是用static或const
static的作用就在于“保值”时空限度为定义它的整个模块,,这是相对静态局部变量来说的,,还有静态全局变量
要注意,一个函数的原型和它的具体实现是分开的,你可以overroad(复写)一个基类的private的函数声明,但却不能调用它的实现
也即,只能复写声明,而不能调用定义(实现)

在C和C++中,声明和定义是分开的,如int i=9;就是一种定义(因为连初值都赋过了这说明分配了内存),而extern int i;只是一种声明而已(向外界宣告它的存在),而Java中就没有声明跟定义的差别了。上面是对于变量来说的(特别是对动态对象—也即对象变量这种表现更加明显),,对于函数的声明和定义的区别就更加明显了。

4.20 真正的数组索引
指针经常跟数组在一起,因此它也经常跟索引相关
因为字符串也是一个数组(更准确来说是可以用数组来实现的线性表,只不过它的最后一个字符是/0而已),所以形如char*的字符串也一定跟数组有关
如果一个指针指向一个new type[num]形式开辟的数组,那么经常有下面的形式出现,
数组名=指针=数组的索引1的地址=用双引号括起来的一串字符串
char* myvar = “iloveu”是用字符指针指向字符串第一个字符在内存中的位置,char[] myvar2=”iloveu2”也是成立的(字符串往往以字符指针或这种字符数组来表达)。
指针加1等价于索引也加1,但是这其中发生的本质是不一样的,,指针值加了sizeof(type)的值,,而索引只是向后一个索引递进而已
指针只能在堆上存在,,,而不能在一个函数的栈帧上存在,alloca可在一个栈上声明
“同一”与“等价”的区别就在这里出现了
实际上数组的地址的确等于它的第一个元素的地址,,然而数组的地址并不是它的第一个元素的地址,而是这个完整数组的地址开头
mfc的消息机制就是一种表驱动

4.21 类型和屏看原理
BCPL作为C语言的始祖(就连32位汇编语言都引进了变量和类型,类型是一门语言的基础,用来表示为程序所用的内存的抽象,即多少内存,可以在这个类型上执行什么操作),它是没有数据类型的,实际上一切都是抽象,C语言中的基本类型正是抽象了“以何种方式使用内存(更确切地说是使用内存的位,因为这是汇编语言和机器逻辑最终要考虑的问题,在高级语言中得抽象一下不能直接用位组)”,使用内存的方式就成了基本类型,这是一种抽象。。将基本类型发展为Struct聚合类型又是一种抽象,,将数据类型封装为ADT(同时封装了可施加其上的操作)就更是一种抽象了。。基本类型中为什么会有数值,字符就知道了,这完全是一种趋向现实的抽象,这是每一种语言的每一个程序,每一个现实问题最能广泛体现到的抽象,当然,基本类型完全也可以是函数,如Lisp语言,,只要是一门语言直接支持的类型,那么它必定在内存中有一个位存储模式,汇编器规定了什么样的操作可以施加在这个位组合上。,

要深克地理解C语言中的“屏看”原理,比如下面二个例子
void (int *p1,int *p2)
#define mysub(x) x^3/x

在第一句中,参数是p1,而不是int,即不是*p1,我们优先看到p1,p2这样的变量,即参数是变量

在第二句中,define的是mysub(x)这个整体,而不是x,我们优先看到mysub(x),即define语句的整个前半部分

一些名理:

命令行就是观念里自由的世界(编程时从观念里出来,去除表现逻辑和其它逻辑,就事论事,不必输出到gui上,只直接面向输入输出到标准,这些语言级而非应用逻辑级的东西),GUI就是强加了面具做人的不自由

能图灵处理的问题,就是计算机能解决得了的问题,可通过程序手段反映出来。

我们称最小接口为原子操作,因为由它们可以演化得到更高级的接口。

一个源程序模块或二进制模式能运行,一定同时包含了指导CPU的指令和待处理在内存中的数据。。

解释器就是直接运行中间码,不需要完成到目标码的转换。。在编译前端,它还是比较跟编译器一样的。。
4.22 位操作与多维数组指针与元素
首先C语言提供位一级的操作是为了迎合汇编语言,,回忆一下CPU的指令集,,有专门的位操作指令,C只是稍稍地将它们进行了下抽象(这样的话,C有了位操作再加上指针,它就成为系统和控制语言了,抽象不但隔断不必要的细节,而且在另一层维度上,提供了对于人来说更为强大的功能,这就是抽象的二大特征),对比一下我们就知道它们的差别并不大,,CPU还有OF,和CF这二个寄存器来表示移位和运算时发生的进位和溢出现象。。

我们知道,在存储位的时候(更确切地说是一个字节级的东西),Intel的CPU是按高位在前(书写或打印时显示在左),低位在后存储的(在右)。因此位往左边移的时候(因为左边是高位,位到了左边就占了一个高分位,整个二进制会按基数越来越大),就是乘以2的幂,体现在它的十位制表示会越来越大,C语言规定,一个类型的值向左移位的时候(右边会溢出不用),在它的右边低位加0,一直补满这个类型应有的位数。。而这个值向右移位的时候,位到了低分位,整个二进制按基数2越来越小,,移了几次位就相当于除以2的几次幂。。移位的时候,左边补0,如果原数左边是0,是个正数,那么还是补0,如果原数左边本来是1,是个负数,那么根据不同的编译器会采取不同的动作,有的编译器把它看成“简单算术右移”,最左边那个位还是补1,有的编译器会把它看成“逻辑右移”,,会把最左边空出来的位加0..

有人会问了,移位会不会把周围内存的位给挤了,不会的,因为所有的移位操作,只局限于这个类型的这个值,跟内存中其它值(只要它们都不参与此次位移运算)都没有关系。。如果二个不同类型的值用一个二目移位符连接时,编译器会进行对齐操作以使它们有相同的位数。。

位定义了几种运算符,有与(实际上不是逻辑乘,但我们按乘的原理可以得出它的计算方式),或(逻辑加),异或,取反,各种运算用在不同的目的下,与可以用一个屏蔽字屏蔽给定值特定位,就是把它们置0,,,,或可以用一个屏蔽字屏蔽给定值特定位,,就是用1去保留它们。。异或可s以反转特定位。。

这样所有的工作就成了找一个需要的屏蔽字(根据原值,,面向要作什么样的屏蔽要求,这二个因素)。。

在单维数组中,,指针跟"&数组名[索引]"或"数组名+索引"的方式有点相似,,实际上这是C语言语义的二义性,,这是为了让C语言变得灵活而保留的。。而在多维数组中,&数组名[索引]"或"数组名+索引"往往并不是一回事。。这一切都是因为C语言中的多维数组其本质还是单维数组,,因此用单维数组的索引或指针方式用在多维中,就会出现很多语义。。

我们来分析一下,可以得出以下结论:

首先,我们知道数组名并非指针,它只是一个意义上的等同,实际上存在数组名作为指针,,但是不存在"&数组名[索引]"这样的元素,,它表示一个地址(从...开始的地址)而非表示一个元素。。尤其是在多维数组中要分明白。。
4.23 变量与VOID
C语言中有一种void类型,,无类型指针。。我们知道变量是有三个要素的,1变量的名字,2,变量的地址,3,变量的类型,其中3是最重要的,它影响了2,,这也就是说,对于一种变量来说,一谈到这种变量,它的大小就清淅了,,变量的最重要的特性是它类型影响下的大小。。。这是变量的本质

所以,对于变量的指针这种类型来说,,在使用它之前,一定要知道这指针是指向什么变量,,这样编译器才知道从这个地址,以多少大小的范围去操作它,否则void类型的指针是不能被解引用的,只能供调用参数用。。
使用void来作为函数参数时,主要出于这种目的,比如我们想让一个函数处理多型数据,就可传递这种数据的指针,因为是多型,我们不直接定义int *,float *,这样多的类型,而代于void作参,这样处理时带来void解引用后的结果(当然,得cast成int*或float*这样实际的指针变量才能使用得到它指向的数据)。


而cast是一种什么样的过程呢,C语言的隐式转换是编译器的动作,手动转换是程序员的事,但是,无论是这二转换的那一种,都不改变原指针值,它只是在内部作了一份拷贝(这个道理就像传指针也是传值,不过传的是指针值,因此另外一种意义上来说就相当于传地址,,它跟直接传值一样,也是作一份拷贝),你可以将这个拷贝值看成为C++新增的指针语法:变量别名。

新手编程导论(六)

第5章 抽象


某某牛人说过,增加抽象是一切解决问题的方法!!
时代改变了!!以前是手工软件作坊时代,,现在是软件工程时代!!!

5.1 人与软件
应用越来越接近人了,比如web2.0,3.0的出现,这是指软件产品的应用,实际上在软件被作为产品被产生出来时也作了靠近人的调整,编程领域的三个东西,问题域,方案域,人都在相互影响,这种影响产生了一些技术,导致了一些编程的变革,并最终与人们的观念结合,比如OO,比如设计模式,这也将导致架构变成软件的功能性"实现"要考虑的,在某个维度上加深了复杂度,然而却在另外一些维度上提供了简单的形式和更有效的应用
某些东西越来越统一和规范了,这加大了学习的门槛,比如xml出现,就统一了文档交互的格式,并导致了很多逻辑知识,产生了一些新的逻辑,需要被学习,但这是合理的,因为形式更加简单了统一了,并改变了一些应用的形式,比如软件分发delopy的统一形式等,
另外一趋势,应用越来越分布了和趋向web,这实际上是很多年前某些大公司的战略,总有那么一群人(有些人研究应用形成架构,有些人研究编程低层形成架构和思想),先知先觉地认识到一些东西,比如.net的出现,网上的资源服务器越来越变成一般应用服务器,富客户端的flex,silverlight等等,只是它们是慢慢被民间所识所学习.
一切技术都是面向被应用,因此人无论如何都是主导.将反过来最终影响技术的被利用形式而隐藏了低层实现,一些离最终应用跨度太大的低层实现不必知道其原理,靠近人的一端要提供尽量简单的形式,比如xml,比如oo,面向机器的一端永远有它的实现
5.2 软件活动的特点
别空想设计,设计也是源于迫切要解决的问题(设计跟需求分析有关),你说的设计需要可能是一个空想的禁固。。

软件需要你考虑每一个细节,因为这不比盖房子,,做软件需要你从大量小逻辑促成应用(每一个部分都可能需要你造轮子)。
方案域用于表达应用的抽象元素只有类是最大的,库是最大的,这种细小性决定要去的地方,要达到的应用还很远。软件活动是一个真正体现人类心智的地方。是一个真正工程级的活动(程序员作为实现者可以不懂大设计和系统级的设计,但设计师要管)。
而且现实生活中软件活动通常都是变化的:代码在变化,设计在变化,连需求都在变化。
C++为什么不是单一OO泛型而是多泛型的呢,因为设计就是多选题,你无法找到其单一性,这就跟policy based design一样的道理。
实际上,软件的设计可以不具有任何需求的成份,,只是在人类的具体活动中,往往需求会影响设计而已。。

模式分为三种,问题模式,设计模式,代码模式,MVC是一种表达窗口的现实问题模式,此时用mvc复用设计模式就最好表达了。而mvc被policy based design实现了,所以它首先也是一种代码模式了。

设计期聚集于程序员,运行期聚集于用户,,比如当设计深入应用时,语言的设计和运行功能都要很强,,设计时不可能预测用户点了什么菜单。


5.2 抽象与接口
什么是架构,架构是设计的上上层部分..比如网络七层模型,甚至PC的冯氏模型..这些设计的上上层部分深刻影响了计算机后来的东西.不可轻易更改.

不可思议的不是代码,不是编码,而正是人类的大脑,而正是设计,,

在设计一种平台时,语言和OS是一个架构的基础部分(在OS内部更多地存在设计),应用再在这个上面慢慢搭建.

如果你看过google的手机平台设计,就会发现语言这个图灵装备往往是架构的最基础部分。如果说机器语言或汇编语言是严格基于平台逻辑的,那么高级语言基于严格的语言机制和语言语法,具有严格的图灵装备,,负责产生后来的逻辑和用户级的应用逻辑,本来OS是不需要语言的

语言尽可能小,直接面向真实平台不要虚拟机,因为加了虚拟机执行方式会很慢,,而且,语言应作为OS的内核一部分.就像WINDOWS把GUI接口加入core一样,这样的语言执行效益很快.这样的语言也就称为系统开发语言..与之对应的是应用开发语言,或称脚本语言.

在底层,汇编是一种几乎对应机器语言的东西,因为汇编语言无语法.只是名称指称,助记符..C作为中间语言,汇编只需要提供接口给C,C还应在编译期提供一个编译期多态

为什么脚本语言多称为语言粘合剂呢..因为脚本语言往往提供对多种系统编程语言.数据类型的内存模型的封装.提供了对这些语言的多种接口,,,但更主要的,脚本语言被称为脚本语言,,,更在于它面向用户的其它特域相关和特定工作相关的语言机制(比如Lua的多协程常被用来游戏,,而这显然不是通用语言应在语法级应提供的)也许脚本语言只需要提供与其它语言的粘合接口就可以了

在上层,我们应该提供一种"无语法"语言的DSL脚本(比如SQL,好像脚本语言跟解释型语言几乎同意),,这种语言最好还是无类型的,,这样做的目的是方便程序员,但不好用来开发平台逻辑(因为平台语言,要求严格的类型机制)

这种语言是自然语言描述性的,,重复性的句子并不会产生双份的运行负担

设计应该被配置,而不是被编码,设计语言应是一门非图灵完备的独立语言(要考虑到它的通用性而不是DSL就更好了)。

我们看到的大多数语言都是图灵完备的,而我觉得C++应改编成一个解释执行、无语法的语言,因为应用语言往往需要做成即时修改的脚本语言

应该如何设计接口呢,OO=抽象+接口,,那么其体现又是怎么样的??只有深克地分析了应用和计算机本身,你才能分析出通用和最精简的接口机制,而OO正是这样一种机制..因为类=数据加函数,,数据加函数既对应计算机世界,又是解释现实事物的一种机制...当然.OO不单是这种理念,还要学习它的运行期多态,继承等(语言语义)..

学高级语言应不仅学它语法,而且要学它语义..因为被翻译后的代码才是代码最初被产生的地方,是最应该被研究的..

接口应尽量窄,提供尽可能少的东西,,但在概念上一定要广,,以备后来的扩展需要.这样的接口,对于复用来说,也是可以花最少的精力来学习的.什么是抽象呢,,,就是抽取对于人来说象的部分....把多种特征的东西在概念上整合为一个认识,,,抽象出接口就是这个意思...其实,抽象正是为了简单,,而不是复杂化(它只是极力把复杂化的低层细节隐藏,透露给人们一个通用的,可认识的抽象概念,即接口)

deepest concept,lowest interface = min learning

对接口编程即,将需要维持不变的东西,即设计,维护在接口层,即变化的东西,实现,维持在接口层以外的地方。

如何从以上二种机制理解OO呢,,,OO为什么这么流行,,因为OO“很好很强大”..

5.3 过度抽象
C和C++只是抽象多少的问题(对硬件架构和软件系统,构成的系统编程环境进行抽象,以靠近人脑开发),linux之父还跟别人争得起烟,,在Windows的GDI中,用C实现了一个对象系统,连C都抽象了桌面对象,还有什么不能抽象的,,,只不过C++在语言级实现了一个OO而已(而WIN的桌面环境对象系统是在逻辑库级),
一个问题出来了,,抽象显然是为了简单化,但是这是对系统编程环境的简单化,,在高级层次会造成更大的复杂,但至少脱离了大量需要原来人脑去维护的细节, 人们只需要掌握大概就可以去复用了(接口),,,这是一个很大的进步,,,而且有些被实现了被开发过的东西和轮子可以直接被拿来复用,C代码却要基于平台的考虑修改大量细节,这种修改的代价超过了太大,就没有可复用性了,,,所以无论如何,C++比起C,,使人们掌握细节和系统的机会减少了,但需要人们掌握设计和复用上的知识多了(人们更专注应用领域).而且在这个时代,由于JAVA语言这样的语言的出现,语言标准得于统一,因此可复用性首先不用考虑语言本身,更而且,JDK统一,SUN的J2EE规范统一,,可复用性就成了人们之间的契约文化了,可复用性的地址会越来越下降.C的难点就在于系统本身,,系统细节和语言细节都过多,,造成人们掌握它的成本过大,但C的代码没有绕OO的弯子,跟系统逻辑几乎一致,因此效率很好。

什么是过度抽象呢?如果抽象不是提出一个简单模型,而是在封装的基础上提供了一个很复杂的模式,甚至超过了人们学习C和系统的那些知识,那么这种OO模型(或称呈现的供可复用架构)就是不成功的,

模块化也是一个重要方面,设计中的抽象过程固然要求模块化,但这决不是抽象的终点,,因为有时虽然模块化了,但是模块之间却偶合了。所以,科学的对于问题的抽象应该是模块化加松偶合的过程。

5.3 OO为什么不是银弹 - 过度抽象与抽象偏差
C的程序员在用C++表达数据结构与算法时,出现了二种情况: 要么写出来的东西虽然被套一个class名字,实际上还是过程编程 要么虽然能类化相关概念做到真到的面向对象,但是感觉不如C的三种流程来得直观,感到OO很别扭。(参见stl的写法和对stl的使用,还有你能找到的OO实现的数据结构库)

实际上人们已经习惯于用三种流程来表达数据结构,跟类比起来,三种流程加简单数据类型的语言机制已经足够。如果再强制他们使用OO来表现。那么他们实际上做了二件事: 对数据结构和算法能解决的问题做了解决(数据结构) 对代码进行了抽象(代码结构)

我们知道OO是C++带给程序员的抽象机制,对于了解这层抽象的人来说,他可以很灵活地写出质量好,逻辑清楚的类(比如STL的作者,虽然那是template class类,泛类,但我们这里还是把它作为OO的类来看待),但是如果回顾学习过程,大多数人会觉得诸如迭代器,type榨汁器这样的东西是晦涩的东西。 原来C程序员做的事,换到C++程序员(当然,我们这里指的是真正会用OO来写程序的人)来做,他必须完成上面的二件事,对于不理解它的人来说,他觉得简单的语言机制都能解决的问题,为什么硬要套上一个class呢?这就是过度抽象给程序员带来的麻烦。对于了解它会使用它的人来说是利器,对于不会使用它的人来说实际上是障碍。抽象的好处是某一个层面上的相对容易性(比如OO将复用维持在public member,protected member等接口上),但是在另外一些层面会对不理解它的人造成更多层面上的迷茫。

面向对象程序员之间很容易写出质量参差不齐的程序,有的人写的程序用了大量迭代器之类的抽象概念,有的程序员写的程序全是class pig,class cat之类的实体概念。对于前面程序员写的程序,如果不能理解,基本上很难复用(即使有class public member在那里)。后面程序员写的程序,有时又会显得太繁琐(比如他会把一些不该抽象的东西抽象成为猫爪子,而实际上并不需要这层抽象)。

抽象给人带来的好处和坏处并存,对于多人合作的软工项目来说,一些语言的机制是禁忌,比如模板,他越抽象,就会造成越来越大的混乱,OO虽然统一了代码形式为类,但并没有统一设计和抽象的方法(在同一个应用中,有人会写迭代器,有人会写阿猪阿猫)。所以OO并不是银弹,银弹是那些能统一人类思想,形成契约文化,经验的东西(比如我们说小说的那些套路),而不是简单的class这种面向复用的小技俩。 而在软工界,除了OO,实际上还存在很多很多其它的过度抽象。

谨以此文自娱。
5.4 真正的设计与编码
大师的禅语并非无所指,一切都只是因为我们并没有拿到那个水晶球而已!!

比如:

不要拒绝接受所有的观点(当你是某个领域的专家时,你往往是寻求别人的思想跟你已成体系的思想的相合之外,,有取舍地学,,因为你有自己的理念),,也不要不接受任何观点(当你对一个领域还是一无所知的时段,,你的最主要任何是无条件理解和接受别人的思想)
学习的四段决
想,,想,,,还是想(一开始是对细节的学习),,,不要一直想(当你有自己的理念时),,,,

思想本非也不该是高高在上的东西,只是我们没有勇气跟它平起平坐而已,而且求知的你只是缺少一个求知切合点而已(这就是每个人心中的“实践认知”,,某个特别的维度认识)。

因为这三个思想实在太重要了而它们又独立于所有知识,更并且,它们不被任何其它书籍提到并组织,所以在这专门作为一章来讲解:)



软件开发过程分设计和编码,组织这二者(以及对其它细节的工程可控性要求)的工程化方法就是XP啊,UPS啊什么的,设计阶段的工作是可以涵盖整个软件开发过程的,而且可以跟编码同步(一旦已经在写代码,那么你就开始在设计了)或异步(最好还是先想好设计的每个细节)进行,

与设计模式相比,算法体现的是一种更泛化的问题解决方法的说法(或者说它更测重于说明如何实现能不能实现,而设计体现是如何设计,要设计出什么架构来表达什么逻辑达成过程),在《领域数学》节中提到的算法是数学界对于问题的Solution(VC7以上的工作空间被称为Solutiom).

设计的严格意义是广泛的,,
我们这里说的设计是指定义某种思想上的架构(软件架构往往是一种思想构架,然而必须最终在代码上体现出来,,设计模式也可称为架构模式(实际上设计模式可用在大架构或小逻辑上都可)或逻辑模式,),然后在这种架构下编码构建应用(世俗的眼光里好像编码的地位一直要次于设计^^),这不是一种泛思想的活动(虽然严格意义上的设计的确是泛义的),而是面向产生类文件的设计,但我们正对事物或问题进行设计,所以在源端是非常不确知的高构,在目标端是一定要产生类文件,

因此编程界(注意这三个字)的设计有三种
1. 基于通用设计传统的设计,比如采用一切语言级和非语言级的多范型设计
2. 一开始并不直接面向编码的原语设计,后来才转为面向编码的范型设计
2. 技术上用策略来实现的设计
3. 混合了你自己创建出来的一些架构思想的设计

用原语设计去主导你的设计,再用修饰过的原语设计去主导你的多范型设计(有效的设计应分分这二步),最后才产生类文件。。

现在很多领域都向原语发展(人们正在更好地认识这个世界),,比如下面的一张图,左边是设计(偏重思想),右边是实现(编码),左边在不断地靠近右边
1.类是对对象的设计,,,模板是对类的设计,

很多现象表明,编程正慢慢向设计偏移,细节正慢慢向它所属的大领域靠拢



5.5 真正的构件库
构件是运用了组合的思想之一,然而组合是一种更大的思想,有些时候,组合的个体之间并不需要接口,,只是当它们运作起来,它们便突然构成了这个璀璨的世界。就像这个世界,水,火,大海,高山并不是某个神预谋为了某种美感而创立的,神只是分别创立它们(没有留专门的接口吧?),然而它们各自存在而已,然而突然有那么一天,神创建的人类突然发现这简单的组合也是这么美和合理。(请原谅我用了这么一个不严格的神话来描述这种思想,然而作这种泛化思维有好处的)

在编程领域,库跟构件严格上是二不同概念,然而大体上等价,这里不区分二者。

按使用形式分构件一般有五种
1. C的库,可能是运行时库,语言函数库,用户发布的应用库,OS级的API库,等等,这些库由于往往只有函数级的接口,因此只要明白参数调用或入栈规则就可以了
2. C++的库,这种库也可以是上述四种库,这些库由于是用C++写成,可能采用了OO,因此其内可能含有某种架构,我们必须懂得其内置的架构才能更好地使用它们。
3. 接口库,比如COM,这种构件就是严格意义上的构件,因为它直接内置接口定义,需要懂其内置的架构才能更好使用它们,更高级的还有DCOM等
4. 构件套,比如ActiveX(主要用于网络分发)
5. 类JavaBean直接为编程环境服务的组件。

这几种库一般都用DLL来实现,DLL是一种装载机制(它可以在运行期动态装卸,比起静态库的另外一个优点来说就是可以避免双份的库引入),而构件是构件,这种关系要明白

一个很重要的思想,千万不能让库的架构主导你的原先的架构,,你永远有你自己的大智慧(在你的应用中你有自己的架构逻辑),也不要做严格的学院派高手去研究某个库的接口实现(设计一个好的库的架构也是一项大工程),除非你是为了学习,否则不要去探究它的实现,而且库的架构不必复用到你的架构中,你永远只是库挑剔的顾客与使用者。

按组合关系来分库存在有平等关系,上下关系,多对一关系,比如有三个库



如图,库A与B,C是上下引用,也是一种一对多的引用(单向箭头),库B与库C是独立平等的关系(可能BC并不引用各自的逻辑,说它们平等只是相对要引用它们的A来说的)

这种构架就是我们呆会在第四部分提到的总架构,库A是GameGeneric,库B是ShowGeneric,库C是LogicGeneric,当然在每个库下面还有很多库引用关系

什么是上下关系呢?就是说谁更靠近应用谁就是上,平等关系就是它们作为中间逻辑靠近应用的程度是一样的(这主要是因为它们为共同一个库提供逻辑,而那个库更接近应用,比如这里的B,C对A的关系)

按性质来分有架构库和工具库(库都是中间逻辑的封装者,然而这中间逻辑也有“是架构”还是“非架构”之分),非架构逻辑就是或工具函数(实现逻辑),架构逻辑就是对一种思想模型的描述,,对其它库的引用逻辑,使用逻辑(封装逻辑),通过这种方法,将利用外来库的行为封装为自己应用的架构

我们在这里反复提到架构与实现,那么到底什么是架构呢,广义的架构就是中间逻辑互饰以构成最终应用的过程中,出现的一切中间逻辑间的关系(无论这些中间逻辑有没有被封装成为库)这就是架构,,,狭义的架构就是指反映设计中某种物体模型的逻辑层次(比如我提到的GameGeneric由表现和世界逻辑组成云云)

比如上图中,中间逻辑的有穷互饰就会形成最终应用逻辑,但是中间逻辑这个说法永远都是相对(于最终应用逻辑)说法,越靠近最终应用的中间逻辑越是高级逻辑

要知道,,抽象达成到最终应用中,,需要设计的接口(这里主要指函数级的)精度和量度都是巨大的(可以用单逻辑的类文件,也可用库来集成一些逻辑作为二进制的中间逻辑,但是如果是使用别人的库,,那么往往需要作对它的引入逻辑,也即对这个库的使用逻辑,,也即你自己的接口才是你的应用主体,别人的只是拿来用的(并形成你的应用的接口),,比如Yake对其它的库的引入逻辑),为了未来的更好复用性考虑,越远离应用逻辑的接口,,对它的设计应尽量考虑接口上的放大化

GameGeneric向你透露面向游戏和脚本级开发者的那些接口(包括它自己的一些逻辑,和一些对库B,C的封装逻辑也即入接口逻辑,库A向用户封装了B,C的细节而提供一个大接口给用户使用,当然库A可能也有它自己的逻辑和架构),这些接口是直接面向高阶应用和开发的需要而创立的,既然GameGeneric建立在B,C之上,那么在引用A的时候(利用A进行编码实现或在其上面架构更高层逻辑的时候),B,C是不是变得无可访问呢?不是,我们其实还是可以在这个步骤中访问到库B,与C的内节的(库的组合逻辑绝非一方屏蔽一方而是一方对一方的归纳简化和扩展导出)。

这是为什么呢?因为大架构是供你使用架构下的细节的(这后半句话才是重点),,大架构只是逻辑归约以更好面向程序员,它是逻辑简化而非逻辑替换,,而程序员最终要引用的逻辑是架构下的细节逻辑。。

是的,,明白这些有什么用呢??但是反过来想一想,,如果连这都不懂,,,那么那才是你的损失之处呢^^(因为这是实践思想,,很多书都不会讲到)

5.6 大逻辑与小逻辑
SOA,COM,框架,这样的术语着眼于解决大逻辑间的开发,而不像OO语言本身只能将逻辑提供到类这一层次(虽然宠大的类也可称为大逻辑,但我们这里仅指有限属性和行为的类逻辑)
C跟JAVA是二种完全不同理念的语言,因为它们的运行环境不相同,产生的目标码根本不同,前者是为机器加OS这个本地产生目标码,而JAVA为JVM产生目标码,由于这二个平台的不同导致的理念是根本的差别,JAVA不能称为系统编程语言,因为这个系统指代OS,C是编译成本地目标码的,而JAVA码只能称为JVM的系统编程语言.VB5之后有编译为本地码的特征,因此是半系统编程语言.
C跟JAVA是二重天,因此对它们的开发理念完全不同,一个是面向本地,一个是面向JVM,可以不管OS平台的任何差别(这就是说不用学习程序运行环境给语言带来的机制问题和复杂细节,不用学习关于JVM的任何知识来进行JAVA编程,人们只需关注那些他们要完成的逻辑,而平台逻辑可以忽视不顾,因为JVM被移殖到任何地方时,人们将面对毫无差别的JVM和JAVA代码,代码不会因为平台不同而产生新的要解决的问题,,这是指移殖,,而且,要写JAVA代码,也不必了解任何JVM的知识,,因为根本没有必要为JVM编程,,JVM虽然能给你进程,,给你SOCKET资源,但是在库中已经被妥善地封装了,人们对它固定了一个认识,,不必从JVM中再对它们经过原始理解,再引申为JAVA所用,因为关于这些资源的接口足够简单了,减少了人们对系统的理解,一谈到虚拟机的语言,就要明白这跟C,跟C++这样的编译语言是二重天).人们说JAVA并非好的教学语言,因为JVM毕竟不是我们真实的系统,,它是一种架空的虚拟的完美的人工运行环境,而OS加硬件架构这样的本地才是我们要认识的本地,
5.7 什么是范式
在最开始,可将范式想象成一种特别聪明、能够自我适应的手法,它可以解决特定类型的问题。也就是说,它类似一些需要全面认识某个问题的人。在了解了问题的方方面面以后,最后提出一套最通用、最灵活的解决方案。具体问题或许是以前见到并解决过的。然而,从前的方案也许并不是最完善的,大家会看到它如何在一个范式里具体表达出来。尽管我们称之为“设计范式”,但它们实际上并不局限于设计领域。思考“范式”时,应脱离传统意义上分析、设计以及实施的思考方式。相反,“范式”是在一个程序里具体表达一套完整的思想,所以它有时可能出现在分析阶段或者高级设计阶段。这一点是非常有趣的,因为范式具有以代码形式直接实现的形式,所以可能不希望它在低级设计或者具体实施以前显露出来(而且事实上,除非真正进入那些阶段,否则一般意识不到自己需要一个范式来解决问题)。范式的基本概念亦可看成是程序设计的基本概念:添加一层新的抽象!只要我们抽象了某些东西,就相当于隔离了特定的细节。而且这后面最引人注目的动机就是“将保持不变的东西身上发生的变化孤立出来”。这样做的另一个原因是一旦发现程序的某部分由于这样或那样的原因可能发生变化,我们一般都想防止那些改变在代码内部繁衍出其他变化。这样做不仅可以降低代码的维护代价,也更便于我们理解(结果同样是降低开销)。为设计出功能强大且易于维护的应用项目,通常最困难的部分就是找出我称之为“领头变化”的东西。这意味着需要找出造成系统改变的最重要的东西,或者换一个角度,找出付出代价最高、开销最大的那一部分。一旦发现了“领头变化”,就可以为自己定下一个焦点,围绕它展开自己的设计。所以设计范式的最终目标就是将代码中变化的内容隔离开。如果从这个角度观察,就会发现本书实际已采用了一些设计范式。举个例子来说,继承可以想象成一种设计范式(类似一个由编译器实现的)。在都拥有同样接口(即保持不变的东西)的对象内部,它允许我们表达行为上的差异(即发生变化的东西)。合成亦可想象成一种范式,因为它允许我们修改——动态或静态——用于实现类的对象,所以也能修改类的运作方式。在《Design Patterns》一书中,大家还能看到另一种范式:“继承器”(即Iterator,Java 1.0和1.1不负责任地把它叫作Enumeration,即“枚举”;Java1.2的集合则改回了“继承器”的称呼)。当我们在集合里遍历,逐个选择不同的元素时,继承器可将集合的实施细节有效地隐藏起来。利用继承器,可以编写出通用的代码,以便对一个序列里的所有元素采取某种操作,同时不必关心这个序列是如何构建的。这样一来,我们的通用代码即可伴随任何能产生继承器的集合使用。

 

 

新手编程导论(七)


第6章 抽象之数据结构


6.1 所谓数据结构
编译时是编译期的事情,运行时是运行期的事情,这里的时可以理解为期间(运行期间),也可理解为空间(运行逻辑关于使用reg还是stack的逻辑),
一门语言同时提供编译器,和这门语言实现的运行时解释器,一个编译逻辑,一个面向某平台的运行逻辑(针对本地机器的一般称为运行时,针对类JVM机器的一般称为解释器,所以jvm跟解释器是分开的,一个是平台,一个是平台下针对Java代码的runtime实现),

在学习C++的运行期动态OO对象时,我们也要学习一个称为rtti的东西,,一切高级的语言机制,都可在运行期探求它的平台实现细节,如何用stack内存和reg展开,。。这样才是深克理解了高级语言该机制(因为了解了某一平台下具体实现的细节,而且是最细节的汇编逻辑,因此该语言抽象也被在高层次被体现并理解了)。。

比如C++类的成员函数,它实际上是一种变态的函数。。从他的汇编逻辑中可以看出来,,在压参时还压入一个this指针。从这个眼光来看。跟普通的cdel,fast,pascal函数都不一样
所以说,函数作为一种机制,有不同的实作品。

runtime的意思在这里进一步明显,,实际上不只执行函数要用到stack runtime和stack frame,在执行诸如堆栈队列数组链表树这些高级数据表示与组织的内存逻辑时(即运行时逻辑,这也属于运行时逻辑),,不一定直接用到stack runtime机制,,虽然执行函数时的stack机制的是代表机器就是一种堆栈机运行时的典型。。

数组是编译期就指定的(数组大小固定,因此每个成员都被硬编码为内存地址,动态数组并非运行期数组,只是指定数组大小的是一个变量,实际上还是一种静态数组),链表是动态运行时构造的,数组和链表都是一种存储机制,而堆栈队列数据结构是一种数据抽象机制(抽象了如何访问删除数据成员的通用接口),数组可以摸拟这二者,当然链表也可以。。(这个道理表明,数据结构分存储数据结构和逻辑数据结构,中心是数据成员本身处理,不可能有离开数据存储地谈数据结构的道理(所以每一种抽象数据结构,必有存储机制


数组的特点就是可以下标索引,,抽象了对内存地址的需要作的干涉(只需操作索引就可操作数据),从数组中增加删除一个数据是可能的,但数组本身业已变化,数组增删一个元素都要经过大量的重新对齐和移动操作(因为索引得视增加删除的位置重排),,要形成一个新数组,从这个意思上看,,数组是根本运行期不可变的。它的变化只能发生在它的一个复制品上。 因为在静态期被固定了
数组的另一特点是它只存储同型数据

而链表则不一样,因为它内置了指针,没有抽象对内存地址需要做的干涉工作(对于程序员来说,这都是他们的工作,这样可以动态分配,开辟,增加删除成员),,对数据的读写访问(我们知道,这是数据结构的中心存在意义所在)没有抽象上的索引可用,,只能操作指针和底层。。。

对链表的插入和删除是很方便的。因为只需往往改动一个指针,,所有的改变都是在这链表上发生并自更新,

树是一种可用链表和数组摸拟的抽象数据结构,,因为它内含了大小逻辑,,因此可以索引(左右索引,而不是一般数组的前后索引),,也可指针索引
6.2 算法+数据结构的本质
高级语言编译器所涉及到的那些东西,就像OO一样,对于计算机来说,完全是一种迂回。

因为高级语言编译器的出现是为了迎合人类能读懂的文本源程序(面向行的编译器),所以它先提出一套语法,为了这套语法就造出了正规式,自动机,最终到语言本身这中间的诸多逻辑

而OO和框架(FrameWork)也是一样的道理,,计算机直接执行的是二进制,,但人类若要组织这些逻辑,就得用OO思想....因为这是人类工程的需要(对于计算机来说就是迂回了)

编程设计的本质在于将语言机机制逻辑转化为应用逻辑,完成它们之间的变换,

任何语言都有算法+数据结构之说的设计,但C更为突出,因为C只有这种设计范式,相对其它语言众多的语言机制来说(除了基础流程,类型系统那些东西不说),这更像是C的设计全部,为C而生的,在用语言机机制表达应用方面,C++有“类”,有设计模式,有模板,有编译期多态,有范型,当然也有“算法加数据结构”,但C仿佛只有这种““算法加数据结构””,,,是设计的非常原始阶段和手段。。
6.4 算法不是设计
(算法更多的不是代码逻辑的设计,用最小内核的流程控制比如C都可以实现算法跟数据结构),

一种算是通用的解决问题的方法,不限于编程开发
一种算法是算法设计方法,如递归
一种算法是计算机的执行方法,图灵算法,证明算法可行性
一种算法是跟某具体语言结合的数学问题的解答,如鸡免问题等
一种算法是设计算法,架构逻辑
一种算法是综合设计算法,
6.5 函数增长与算法复杂性分析
从这一节开始,我们就慢慢进入算法了,,为什么把算法放在这里而不是放在数据结构那一章呢,,因为算法是属于数学和计算机的,,跟它们的结合要更前于数据结构(虽然也严重与数据结构有关,但是数据结构是进入到计算机以后的抽象,算法是它以前的概念)

,,先讲讲什么是算法,,它的分类与有关证明
递归算法,,,分支定界,,二分法,,贪婪法,等都是用来描述算法的(它们本身不是算法,,,只是用来设计算法的方法)
程序正确性

算法的有效性包含二部分,算法的正确性和算法的复杂性,,这里讨论算法的正确性,,复杂性在前面2.2节函数增长部分讨论过了

对算法的正确性的研究用到逻辑规则,证明技术(如数学归纳法),算法

不考虑语法等其它因素,,,除非测试了所有可能的输入,,程序都给出正确的答案,这就是程序正确性的概念

如何把以上概念分解为形式定义使它具有可操作性呢?

在有输入的情况下,只要第一部分证明:若程序终止,则获得正确的答案,,第二部分证明:程序总是终止,就可以断言这个程序是正确的(二者之一成立是部分正确)

第三章讨论算法的正确性,这里讨论算法的复杂性
在假定输入值一样的条件下,,求算法的空间或时间复杂性
由于空间复杂性跟数据结构有关而本书不涉及太多的数据结构
因此在这里主要讨论算法的时间复杂性,,即步数
运算次数的单位是整数加法,减法,,等基本运算
6.6 数据结构初步引象(1)
一维数组(向量)
二维数组(包含行向量与列向量的矩阵)
表table也是二维数组,它的行是记录,列是字段
List有三种意义,VList,广义表,狭义指linked list,,普通意义下的list表是有序的,typed 的ordered的即线性表(alistis
an orderedcollectionofentities/items),但不一定是sorteded的(Asequenceis 循序而非顺序?another
name, emphasizing the ordering and suggesting that it may not be a linkedlist.)
查找表是同型数据的无序(sorted or unsorted)有限序列。二叉查找树也叫二叉顺序树。
二叉树遍历的本质在于动态地将非线性树转化为线性表。,
线性数据结构就是逻辑上有头有尾的一对一的数据结点组成的结构,,当它用于表时,就是linear list,线性表用顺序存储的方法来存储就是顺序表sequences
list了,注意线性表只指出结点有序(表的意义所在)一一对应(线性的意义所在),至于如何有序它没有作规定,而数组是一种用下标index来体现’有序’的方式,indexied
的线性表即index list(与linked list对应)=vector,因此是一种顺序表的方式(当然index
list就字眼上说并没有完全地指出它应有的限制,准确的说法是index linear
list)。是顺序表最简单最基本的形式(注意数组是一种抽象数据结构)。与链式存储的单链表一样,是最基本的,因此常有sequence stack,link
stack的对比(而其实数组有多维数组,链表有循环链表等)。当然这二种表都是属于逻辑上的linear list.
6.7 数据结构初步引象(2)
数据结构的讨论是在抽象了数据类型之后才出现的(因此数据结构的准确含义是“计算机开发领域中的数据抽象学”),汇编不需要变量是因为程序员包揽了内存分配,而高级语言提供变量,变量的内存分配由编译器或运行时完成,因此可在这个基础上发展基于靠近人的抽象数据结构,而OO既是对数据的一种抽象(当然,它跟数据结构对数据的抽象是站在不同角度的),也是一种对代码的抽象

数据结构中也有文件的概念,实际上当这个文件(数据结构一般处理数据在内存中的模型)放在外存中(被持久化)就成了数据库,数据结构中也大量用到字段,记录,键等数据库概念,,在计算机领域,数据库和数据结构本来就是同根共源的,数据结构的抽象模式就是内模式,,关系数据库与其它数据库的区别在于关系数据库会返回一个集合,而不是一些特定记录,在数据结构的讨论中,频频用到关系代数的概念,比如排序线性表sorted
list.它按照某种顺序来组织数据,比如<,这实际上是一种偏序关系。

List有三种意义1 linked list 2 广义表 3 (ordered) list,其中3就是普遍讲到的“有序线性表”,而sequence
list=sequence order list(以顺序结构存储的线性表,区别于linked ordered
list),,,线性表除了有序线性表这种形式之外,还有sorted ordered list.即排序(线性)表。。

如 binary search tree,= binary sorttree

查找表(lookup table?search
table?)是一种无序的集合,这使得只能以顺序比较方式操作,因此我们必须对它们强加一些关系,形成sorted的,动态查找表必须动态变化以维持它的表序(即它是一种动态查找表,表是自变化的以维持某种利于查找的顺序)。。

键可以是一个记录中的某个字段,或字段的组合,单字段的以这个字段值作为键。。

(一般)树,森林,二叉树之间可以相互变换,,,树可以通过对节点的限制或修饰发展出多种抽象,比如平衡树,AVL树,B+(B_的一种变体),红黑树。。。当然还有很多,因此数据结构实际上是一门可以无限深入的科学

如果说树是一种层次关系(树是严格分父子的),那么图就是一种松散关系,在任何节点间都会产生关系,因此最符合现实模型。

对图的学习涉及到关系代数,线性代数中很多知识

其实要注意各种数据结构的存储结构,图的多重链表,树的最小带权路径,,图的多路查找
等等知识,这些都是高级话题。。

6.8 数据结构初步引象(3)
交换排序是置换顺序,插入排序更理应被称为交换排序。

双端队列是栈和队列的一种泛化(因为它的二头都可以出或入)。栈和队列跟其它数据结构遍历不同的是,对于栈和队列的内部是不可访问的,只有栈顶和队列头
这几个元素可以遍历到。

查找表是一种集合,是同类型数据的集合,,因为集合是一种无结构的无序松散排列(表是一对一更,树是一对多,图是多对多,那么集合对这些都没有规定,实
际上有表,图,集合三种大数据结构,树是表的推广,图是树的推广,但表和树都有作为自身存在的意义和作为树和图的双重意义存在,在所有数据结构中,反而
只有图才是用得最为广泛的。。
)。。

用位向量可以表示集合(这有点像用邻接矩阵表示图),其它方法也可,比如链表等,,但位向量表达法在提供了位操的机器上产生的效益是很高的。

图也有根,还有无序链表unsorted 还是unordered??连通和强连通,,,回路和路径,,环弧边,无向图是无向还是双向图,这些都是微妙
差别的概念。。
.
6.9 数据结构初步引象(4)
线性表是最常见的数据结构和逻辑结构,然而只有图才是最常用的数据结构和逻辑结构。。

其实树状结构有天然的转化成线性结构的优势,因此也有作为线性表的树的存在
(因此说树是树性表的一种推广,教学的时候完全可以从线性表引伸到树,,
树有作为线性表存在的树,树也有作为树本身意义存在的树,和作为树图意义存在的树,这三种情况都需要被讨论。)
此时从树的眼光来看这种原来是树的数据结构它还是存在父子关系,从线性表的眼光来看是变换了的平等关系.
6.10 ordered与sorted
Lists广义列表,list线性表
有些教科书混用ordered与sorted,比如二叉排序树它并非二叉有序树,当然,
为什么又有无序树,有序树呢。

Ordered表示一个接一个,顺序不可改变,如果被改变(比如一个元素后接的元素不再是以前那个了),就不再是以前那个ordered list了,
所以如果一个unosorted表被sorted过了,它肯定不再是以前那个ordered list了(但它肯定是ordered的,因为它要维护它
线性表的特性即ordered不过它当中有些元素的ordered位置换动了而已),而是一个新的ordered且sorted的表。
.
6.11 数据结构与抽象
(1)
程序如何分类呢,从算法和数据结构的角度看我们可以发现,数据结构加算法等于程序。
因为数据结构源于从一套相似的算法中找出操作对象的共性这个现实

而从复用来看呢,,又可以产生设计和接口就等于程序这种说法

因此这完全是不同事物的不同唯度而已。。根本没有可比性。(至少二者都可以产生程序这个概念,于是,程序=机器加电也是正确的)

OO化并不强求以靠近现实的模型来设计应用,而更多地是利用OO的访问控制以同一种方式看待代码与数据(设计代码,定义数据)

在命令式编程语言中,控制结构等等被证明为可以产生一切逻辑。。因此具备三种控制结构的语言都可以成为产生一切逻辑的语言。。
(2)
抽象完成了之后,只要不是过度抽象,那么所有后来的事情都是另外一回事了,比如抽象了数据类型,那么关于数据的逻辑都成了数据结构学了

算法并非代码逻辑,而只是附属于语言和数据结构学交界的那些东西(算法是从属数据结构的),只有设计模式才是代码逻辑和代码抽象学。。

从开发(者)的观点,我们可以把OS看成是提供API的软件抽象机制(这本书主要是从开发的角度抽象地讲解一系列专业概念),同样从计算机开发领域的角度看,我们可以把算法看成是数据结构的附属,,因为数据结构是源于算法的,而数据结构是开发中的数据抽象,,,,因此作为从开发眼光来看的算法是数据结构的附属(因此人们说算法和数据结构是数学,计算机软件,计算机硬件三门学科之间的交叉学科,)。


在程序设计中,抽象无所不在,OO就是一种对数据和代码进行统一抽象的方式。。
在架构中,抽象也是很常见的,比如七层模型,只有解决了前面的问题,才能着手下一层更高级的问题,这是目的也是原因,,是起点也是下一个终点。

我们知道抽象的本质在于对人的简单性,比如OO的三重机制制造的抽象就在于统一数据和代码,于是产生了复用效益。抽象的本质在于远离问题,从靠近人的一
个高层角度去解决更高级的问题。

这就是抽象.

6.12 真正的逻辑数据结构只有二种
字符串这么平常,然而却需要涉及到不浅的数据结构和IO,,这使语言表达字串方面成为学习这种语言的一个重要方面。

当然,数据结构绝非仅仅数值数据结构。数据结构不仅用来研究数值,节点数据可以是任何类型。或adt

其实理解很多数据结构(data set 和data relation set)前要理解的东西就是什么是数据和数据节点,,这才是最重要的,但很多人忽略了它,那么数据节点体现了什么重要概念呢,1是order还是sorted,2是keyvalue的分别,计算机能处理的数据节点是逻辑上存在order的,所以产生无order,即集合,有order,线性list,层次tree,,,另外,计算机能处理的数据节点是key value的,这就是计算机能处理的“数据‘和它能形成数据结构学这样的科学所加的二个traits,,数据结构绝对不是广义数据的结构,而是有一定条件的概念(我们人类目前的科学都是在一定的限制下讨论某具体问题的)。
明白了以上这样我们就可以理解一种特别的数据结构了,即hash table,首先它是key value对,如果不是,就是hash set hash map之类的东西,,这满足第二点条件traits。。。第二,它的hash,是针对数据结构来说的,hash一定要是hash个数据结构出来,而前一些数据结构是uniform的而hash table,map,set之类的都是uniform的而已。这满足第一个traits.

真正的数据结构其实只有二种,表和树,因为按order来看,前者是线性,后者是层次(我认为只有这二者才是划分和形成概念的标准),如果说硬有第三种,那是unordered的,即集合,集合才是取代图的概念,而不是图,图只是逻辑结构居先
6.12 树与图初步引象
树是图的一种特例(没有回路的连通图,树是sparse图),图的树的区别在于,树是严格分层次的(它的前驱是父,后继只能是子,因此产生出父子,节点的高,宽度,树的高,宽度等概念),而图是任何顶点间都有可能发生关系(即存在边,存在边就叫邻接),除了树,图之外,还有森林(相比树对图的定义来说。它少了一个条件,森林是没有环,可能不连通的图,一棵树也可以是林林,但森林不一定是一棵树),,二叉树与一般树的区别在于1,一般树不可以无节点,而二叉树可以没有节点,2,二叉树的某个节点至多有二子节点,即二叉树不但分层次,而且子树也分顺序,因此对于一般树的边历过程有三种,,先(根)边历,中(根)边历,后(根)边历,,但是正是因为二叉树有左右子树之分,因此中序边历是二叉树特有的。

树和图都可以作为数据结构(主要作用在于存数据,附带一定逻辑的数据)或逻辑结构(主要作用在于表逻辑),节点可存任何东西,比如一个条件,一个值或一个结构都可以。边也可以加权表逻辑。。比如最小生成树是最小加权生成树的简称,,注意,树有最小子树,图也有最小生成树。

如何存储呢,因为用图可以用来解释树,因为决定图的特征在于它的节点,因此它也可以用跟图一样的存储结构来表示自己。但是因为树是图的稀疏表示,一般不用邻接数组存储节点的每个所有特征,,引线二叉树才这样做(因为它赖此达到一种维护它访问的能力)。
.
6.13 树初步引象
(1)
如果将树某个节点的从左到右的子树看成有序的(有左右的order之分),那么就是有序树,即ordered树,但却不是sorted的,因为只有二叉排序树binary
sorted tree才是排序树(不过它不是左右子树之间的sorted,而是左右子树与它们的根之间的sortness)。

就特征的逐步放宽而言,存在,二叉树(order),二叉查找树(sorted),N叉树(un
ordered),多叉树(之所以不把N,多叉树这二者等同,是因为我们特别强调N叉树子树间不order,而多叉树根本不考虑这一点,根本不考虑左右子树之间的关系)

树为什么称为递归的呢,就是因为它的子树也是一棵树,,而这棵树的子子树也是一棵树,因此被称为递归定义的。

树的高度就是深度,因为根的深度就是高度。(因为递归定义中非叶节点就是子树,所以非叶节点的高度就是该子树的高度宽度等特征,即用根节点特征来代替子节点高度等特征)

线性表的线性“序order”和树的层次“序order”这二者有什么意义呢,这种意义决定了对于它们的遍历逻辑。搜索逻辑主要跟sortness有关,而不是orderness有关,线性表决定了它的元素顺序是有序ordered的(虽然并不一定经过排序sorted)因此遍历可按前驱的方向或后继的方向但不一定搜索一定要严格按这个顺序因为那是由sortness决定的,因此树的搜索方向可先(根)序,后(根)序。而树的遍历其实任何访问都是从根开始,,所谓中序,其实也是从根开始,只不过它处理数据的顺序是不跟它的访问顺序一样的而已。而且它是只有二叉排序树才有的。

兄弟是同一个节点为父的子节点,而堂兄弟是父节点在同一层的子节点。

通路跟回路,七桥问题解决的是欧拉通路而非回路,

最小生成树是最小加权生成树的简称,但是存在另外一种最小非负加权生成树的东西,,就是最小代价cost生成树了。

在树中,有height balanced tree也有weight balanced tree,这个weight就是一个层上节点的最大值。。

作为图的树的深度优先有时要求回溯,深度优先遍历时,它从一个节点开始往下遍历时,在到达叶节点时需要重新向上“溯树”。
这就需要额外维护一个栈来记录已经访问过的节点。当访问到一开头那个节点时就“标记已访问并存入栈”,下次从这个节点的兄弟作下步向下遍历完成并作回溯时需要将其弹出堆栈,,,以此类推完成遍历(因此它是一个由树的递归特性决定的递归过程)。。

图的广度遍历用到了队列逻辑。
.
6.14 B减树
B-tree就是B减树(因为存在一个B+树所以会导致这样的误解,实际上并没有B减树这个概念存在,-号只是一个连字符而已也可写成B_),也不要跟
二叉树binary tree混淆了。

那么什么是B减树呢,一般说它是Bayer’s tree的缩写(bayer是这种树的创建者之一的名字,另外一种最常见的命名方法是
b=broad,bushy,因为这种树所有“外部叶节点”在同一level上聚集)。它适用于树的内部节点被频繁访问,但又一般不常访问这些节点以下
节点的情况,比如计算机的二级存储器硬盘等设备,常以结点为目录,叶节点为文件,,一般我们频繁访问目录但又不常深入最终每个文件。

实际上b减树也是要求一定的balance的(有人也说B减树的B代表balance,不过这样的话就与普通的平衡树相重意义了所以并不常采用这
种),,它是一种动态查找树,因为需要在每次update后都动态调整它的节点情况。只不过它并不需要作特别频繁的rebalancing动作,因为B
减树将外部节点维护在同一层次上这个特点使它只需要只很少的调整便可保持balanced。

那么B减树到底具体是如何一种数据逻辑呢?它到底如何使保持平衡只需很少的调整呢?更重要的是,B减树到底有什么用呢?

首先,这种树的某个(或每个)“内部非叶节点”的下层子节点(直接子树)是数量可变的,而且在一个区间里变动(我们呆会再来讨论这个所谓的区间),这个非叶节点维护
一堆elements和pointer,其中elments用来区别各个子树的取值范围,比如这个非叶节点有3个子节点为根的子树,那么它需要维护2个
(子节点数-1个)elments,假设为key1,key2,那么第一个子树的所有值都小于key1,中间子树的所有值都在key1和key2之间,
最右边子树的所有值都小于key2(当然,这是N叉树,3叉树,这里不是说二叉树的左右),很显然地,key1,key2这些elments是
sorted有序线性表,那么points部分呢,它指向每个子树..有几个子树(子节点)就有几个pointer.

所以,每个内部节点维护(该节点所拥有子树数-1)个的elements和(子树数)个的pointer.
叶节点在同一层上,没有element也没有pointer.,不带任何信息。

现在来讨论那个所谓的区间,以m为order的B减树(称为B-tree of order m),再设n是一个内部节点允许的最少节点数,那么那么区
间就是[n上界,m下界],以这种区间为表征的B减树一般呈现出以下特性
(1) 每个内部子节点(只要不是作为该子树的根或叶子)都至少有m/2个子节点;
注意,最多m,最少m/2;此时n相当于m/2
(2) 每个内部子节点(如果它就是作为子树的根并且不是叶子)那么最少可以有2个子节点;
这说明,该内部节点最大子elments数可以为m或m-1,最少elements数可以为m/2或m/2-1(n=m/2或n=m/2-1);
(3)每个内部子节点(如果它是叶子),,,那么不带任何elemnets或pointers

所以,“子树数-1个elements”,“子树数个pointer”,“叶节点不带任何信息”,“n-m的区间所体现的上面三点”,,这些都说明了什
么呢,这些特征都赋于了这种树什么样的特性和能力呢?

我们可以看到,如果内部节点不是作为最终叶节点,那么它(要讨论的这个内部节点)的子节点(它的下一级子节点)个数至少是半满half full的,这
意味着,在[m,n]区间内(或称[m/2,m])二个半满的内部节点可以进行合并形成一个合法的新内部节点。一个全满的内部节点可以分离成二个合法的
新内部节点(只要父节点中可以容纳这些新子节点就可以),从这层意义上,的确不需要太多的高度上的平衡。

更多的关于这种树的删除,插入算法可以从这层意义上引申而来.
6.15 图初步引象
在数据结构中,我们一般是从最普通的情况谈到施加了各件条件和限制的情况),比如有向图是无向图的一种特殊情况(施加了有向这个条件,而无向图是一种更
一般的图,因此图论中一般是谈无向图及遍历等操作,再谈有向图及其特性。(特别DAG)。

线性表是数据项平等的集合(抽象数据的最高境界最一般情况是集合,因此在对各种ADT进行定义的时候都涉及有集合),无论对其进行什么操作,只有维护一
个线性关系而不管它是无序还是有序的,就是线性表,而树的各数据项是有层次level的,无论对其进行什么变换,只有作为树的眼光从根向下来看,它总存
在逻辑意义上的父子,这层逻辑意义是作为树的意义存在的,在对树的各种操作中都考虑进去了作为处理时的因素的),

因此从逻辑意义上来说,树是线性表的推广,,,图却不是树的推广,,,因为虽然它也处理数据项集,但它处理的是数据项间连通关系而不是地位关系,在其各
种存储表示和后来高级逻辑中都会加入邻接考虑。

如果说其它数据结构只有数据项集,那么图不但有数据项集,而且有顶点连通关系集。参见对其ad t的定义就知道了,,而显然连通关系并非前驱后继关
系,,在关系代数中,,存在有全序偏序对称自反等关系。。

也即在图中,结点的地位关系我们不考虑,不存在线性平等也不存在树型父子,而是不考虑这样的地位关系,这一点上它像集合,(集合,是不考虑结点之间地位
关系也不考虑连通的),图只考虑一种称为连通关系的不伦不类的关系,这一点上它区别所有的数据结构,它的二个顶点间可以连接,这一点跟树相同,但是顶点
并不经常用作存储数据用,这一点又跟树不同,树的二顶点存在的是地位关系,而图的二顶点要处理的是连通不连通关系。。

因此我们规定,逻辑数据结构只有二种线性和非线性,只要不是线性,那就是非线性,而不管它是节点地位关系导致的非线性,还是其它关系,比如连通不连通导
致的非线性(其实这二者无关,因为图也可用矩阵来存,,而矩阵,虽然是一种非线性结构,但以某种眼光看,它又是线性的)。

数据结构也可是逻辑结构,树可作为数据处理结构也可作为逻辑表达结构比如流程,因此图不但是一种数据结构而且还是一种组合数学中的离散结构(比如拓朴学
就是关于有向图的)。。。.
6.16 树的平衡与旋转
一般教科书把广义表跟(多维)数组放在一起讲解,是因为有时泛意义上的数组就是广义表的一种。

如果你知道函数对于结构化程序设计的重要性的话,那么你也会明白堆栈对于函数调用逻辑的重要性,所以理解结构化程序设计范式的重要手段是理解堆栈这种ADT。

相比线性表O(n)的复杂度来说(输入的元素个数是最主要的影响因子),对二叉树的各种操作(插入,查找删除)效益直接受制于高度,即h = O(logN),即树提供了O(logN)的复杂度,其中N是节点数,(二叉树中)我们并不直接把N作为影响操作效益的因素,h才是,因此需要对height进行balancing才能控制操作效益。

BST的构造是严格取决于待输入系列的,当待输入系列本身就是一个sorted的系列时,那么当这些序列被用来构造BST时,它就是会退化成对应的“链表”。在操作上会失去树的优势。

一般存在“节点的深度”,“节点的高度”,“(子)树的高度”这三种说法,节点的深度是从根开发,到这节点为止的那条唯一路径的长,节点的高度就是从节点出发,到这个节点能到达的某个叶节点的最长路径的长度,因此如果这个节点是内部节点,那么其深度就是其所在层次(叶节点时为0,不存在时为-1),其高度就是从这个内部节点到与它相连通的最长路径的那个叶节点的长度,当为根节点时高度最高,当为叶节点时,高度为0,而树(或子树)的高度就是以此为根的那个树或子树的节点的高度。

实际上avl树只是自平衡二叉树的一种,高度差为一是这种树的最古老定义(self balanceing并不仅仅是heightbalancing 动作。而AVL仅仅是balanacingthe height),还存在其它不以height的调整为其平衡方式的树,比如红黑树,但是只有AVL树是严格用平衡因子去定义的(因此是“平衡”的最正宗意义所在),而红黑树不是,因此红黑树不是平衡二叉树,实际上它故意允许一定程序上的不平衡。

红黑树不是平衡二叉树但它“保证”2*O(logN)的最坏情况下的平衡度(人们往往根据这个原因把它归为跟AVL一类),AVL树是1.44*O(logN),这二种树只“保证”(注意只是保证)最差情况下的下界为1.44*O(logN)或2*O(logN),称它们为平衡树不是因为其内部是不是极力在保持平衡还是不是,重点在这里(最坏情况下保证了多少的平衡度),,综合考虑插入,查找,删除等操作来说,红黑树的效益要好于AVL,因为AVL是严格平衡的因此只对查找intensive。查找时只需向上查找1.44*O(logN)个节点就行(经过AVL平衡的树只需对height bounded at 1.44*O(logN)个节点进行处理)。。

一个节点的平衡因子是左右子树根节点深度之差(注意并非高度之差),因此不要跟这个节点所处的深度本身搞混了,一个只有左子树无右子树的树,它的树根的深度是0(树根的高度越到根越大,树根的深度(相对整树来说)永远是0空树为-1,这个深度并不影响树根的平衡因子),平衡因子是1(只跟左右子树的深度有关),因为右子树不存在,深度为-1,(而左子树深度为0),因此它们差的绝对值为1,,即平衡因子为1.

在建立AVL树(向一棵普通意义下的binary sort tree进入插入一系列key码)时,边插入边判断(插入点的平衡因子是否导致了不平衡),在发现因为插入动作导致的不平衡现象时进行rotation.,以维护其平衡性,直到所有的数据插完,这树依然维持binary sort性质(因此称sort tree为查找树,,即查找特性intensive的,作为数据结构的树,其实它的本名最开始是sort tree然后才是search tree,searchtree一般有二种,第一种是每个节点都含key 和value的,所以它的每个节点都包含数据,而有些search tree只有叶节点含数据,其包括根在内的内部节点都用来search,只包含有key)和heightbalanced 性质。
那么怎么样进行判断并在需要的时克rot(根据情况不同有时是二次rot)呢? 在这之前,我们需要规范一些概念。1.rot所涉及到的主体(插入点和支点privot)2,最小不平衡子树。3, 扁担原理。


最好的方法是举个例子。
比如我们将考虑把{20,35,40,15,30,25}制造为一棵binary sort并heightbalanced tree,,当插入20,35时,20为根(第一个插入的当然作为“整棵树”的根),35为其右子树(因为binary sort要求它作为20的右子),当考虑插入40时,40>35也应该被插到作为35的右子树,此时虽然binary sorted了,但是却不height balanced,因为随着40的插入它将这(整棵)树变成了不平衡的树(明眼人一看就知道这属于明显的四种不平衡情况中的RR型不平衡,当然为了学习的目的我们还是打算仔细地说明一下,而且存在比这样的仅仅由三个节点构成的RR型不平衡的情况更复杂的情况,需要我们考虑一套更通用的处理方法,在这里第一个R是指整树的R,即35,第二个R是指整树的R35的R,即40,显然地,40节点处的深度为2,35节点处的深度为1,20节点处的深度为0,但在这样的树中35没有兄弟,整树没有左子树,而40也没有兄弟,即35没有左子树,这导致三个节点的平衡因子不一样,我们从上到下再一层一层考虑一遍,,首先拿20来说,它的右子树即35深为1,而“整树”左子树不存在为-1(这就是第一个R,因为只要左子树为-1就可能产生深度上不平衡的剧变开始),因此20平衡因子为2,请时克提醒自己牢记这是棵二叉树因此只有L,R的情况需要被考虑,,再看35的平衡因子,40作为35的右节点也作为整树的叶节点,其深度为0,而35的左子树不存在为-1(这就是第二个R,这里讨论的是35的R而非跟前面那个作为整树R的R,,这第二个R的左子树也不存在,因此提出这个R,,因为它也有可能产生深度上不平衡的剧变),因此差绝对值还是在1之内,既然出现了不平衡,而且我们也知道为什么不平衡(就是整树根处平衡因子超出了1违规了,,由于前面那二个RR,终于在这里产生了真正的剧变)。那么接下来就是处理掉这个不平衡了。我们不妨细化这个不平衡到一个称为“最小不平衡子树”的地方。然后再考虑处理之。

即从插入点40的眼光来看,我们可以找出一个 “最小不平衡子树”, 不平衡的地方一定发生在这里(这个“最小不平衡子树与插入点相关,因为它就是距离插入点最近,而平衡因子却产生违规的第一个节点为根的子树”),我们需要对其进行平衡处理主要在这里进行。(在上面的例子中,是“整树“作为“不平衡子树”。因为20处出现了违规而且那是整树的根, 这里提出了20这样我们就可以开始着手解决开篇提到的处理主体的问题,即一步一步导出“最小不平衡子树”这个概念相关的插入点和支点,下面我们谈到扁担原理);

对这个以20为根的“最小不平衡子树”的处理涉及到“扁担原理”,在用扁担挑东西的实践中,如果前面重我们就把支点向后移动,如果后面重我们就把支点向前移动,(虽然都是以“支点”移动,但挑扁担中是移动,支点是我们的肩膀,在这里我们是作二叉树的平衡处理也称为“旋转”,那么树旋转中的“支点”呢,是不是也可以找到能类比的概念呢?是不是就是最小不平衡树的树根呢?),首先我们要明确提出“最小不平衡子树是为了确定作平衡处理的范围”,而找出“支点”却是另外一件事,(与找最小不平衡子树的目的不同,找支点是为确定从何处为轴进行旋转,我们找出了最小不平衡子树并不意味着这里的“支点”就是最小不平衡子树的根,-------其实上面情况中的支点即RR的第一个R35,,我们能找出它,是因为在RR模型中,这样的模型所在的最小不平衡子树中必定存在三个节点,即由“最小不平衡子树的根节点”,“根节点的右节点也即第一个R”,“再就是第一个R的再下面一级R即第二个R”这三者组成的模型中,“支点”就是第一个R(而不是最小不平衡树的根),上面问题中,显然35才是那个“支点”,至此我们找出了支点,接下来判断哪里重的问题),“重”即“支点需要往支点的哪个方向移动”这个逻辑。

那么究竟往哪里移动呢?根据挑扁担原理,当然是往重了的方向移动,但这里有二个“重”的逻辑,首先第一个,我们知道20不是支点,20的右子树深1,而左子树深-1,这里存在一个以20为中心但左重右轻的情况,那么它是不是就是“重”的意义所在呢?显然在这里是右边过重了(平衡的情况是要么左子树大右子树一,要么右子树太左子树一,这里却大了二下,重了二下),如果这里是“重”的意义所在,支点就需要往轻的地方即左子树方向移。

再来看第二个重的意义,在RR模型中,如果说第一个R是支点,那么显然地比起上面提到的第一个“重了”的意义来说,这里的重是指代第一个R的父节点平衡因子过大为2,而R的R即第二个R为0,这也产生了左重右轻,(RR模型中,第一个R的父节点是产生违规的所在,正是它带出了RR,这三者由于旋转的需要又产生了“过重”,又需要找出一个“支点”进行平衡处理,这个道理也很自然,而且找出的支点是严格以平衡因子来决定其左轻右重或右重左轻的),但是显得有点蹩脚,因为这里的支点并不严格相当于现实生活中挑扁担的支点,,这里的支点完全是从一种模型中比如RR模型中选中的第一个R),这虽然成立,然而,以这样的意义导出的支点再层出的“重”不是上面第一个意义的直观的含义,是不太符合现实中扁担水平移动那么简单自然的道理,这里的重是考虑了“R的父节点”+“第一个R”+“第二个R”的,因此是一种有层次的树的模型(而不是对应于扁担加扁担二边的重物那样选肩膀就可以作为支点的模型,因为我们上面谈到的第一层重的意义不是支持支点移动的意义所在,,这里的是只需要从RR模型中选择前一个R作为支点,而且其旋转是一种左倾主义的旋转)产生的“非水平过重(从树的旋转图中可以明显看到)”。。。


但反而这里的“重了”才是“重”的真正意义所在, 在这第二层意义下,我们再看详细的例子就会理解它们了。

这样我们就解决了开篇提到的“扁担原理”,综上所述,我们讨论了RR的情况(如何判断违规以及如何作重平衡处理),,下面谈到的是LR的情况了。请自行理解。.
6.17 完全与满
Vector之所以称为index list,,我们知道index是索引式存储,list是逻辑结构,于是四种存储结构和四种逻辑结构可以组合到16
种组合方式。而index list正好是这其中的一种。

满二叉树的概念容易弄懂,而完全二叉树(二叉树都是从左到右有序的)这个概念实际上并不突出“编号”,它表现的是这样一种树:如果在某一层上,左边的节
点为空,那么(在这个节点)右边就不能有节点,无论是这个节点右边的节点是个兄弟节点还是堂兄弟节点。所以满二叉树一定是个完全二叉树,而完全二叉树不
一定是个满二叉树。。

树的遍历分先序,中序和后序,又分递归遍历法和非递归遍历法,故有6种遍历方法(也许只有前序才能递归?因为它是根,只有根才有递归意义?)。 当把对树的递归遍历转化为非递归遍历时需要一个辅助栈外加几个循环
(我们知道加栈是递归算法转化为非递归的通用手段之一)。

完全二叉树中的完全跟完全(无向)图中的完全以及完全有向图的完全意思是不一样的,前者并不是一种规则形状(比起满二叉树来)而后两者是规则情况。

因为连通图对无向图有意义但对有向图却没有意义,因此对有向图引入强连通分量的概念。
.
6.18 多路234树与红黑树的导出
数据结构间都是有联系的,比如234树其实可以导出红黑树也可导出B树。

红黑树也是一种二叉排序树,因此一方面保证了查找效率(中序遍历时可以快速到达根部),另一方面,它也保证“最深的深度不大于最浅的深度的2倍”,这使得红黑树有一定程度的balance 特性,因此对于查找之外的另外操作,它也有很不错的效率。

红黑树主要是通过为每一个节点着色来达到以上特征的
1. 首先一个节点要么是红要么是黑只有二色可以被用来着色。
2. 根节点默认为黑色
3. 对于叶节点来说,不管它的父节点是黑色还是红色,一律着色为黑色。(每一个叶节点我们都可以假设它是一个内部节点因此有有二个不存在的子节点设为NULL)
4. 对于任何一个节点来说如果它是红色,那么它的二个子节点都为黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点,因为从叶子到根的路径是唯一的,也即它就是从根到这个叶子的路径,因此它是这个叶子的深度)
5. 高度方向上(注意这里不谈到具体的高度,而是指高度方向上),从任一节点到其每个子孙叶子的所有路径都包含相同数目的黑色节点。

以上五条特性导致了

深度上,“最深的深度不大于最浅的深度的2倍”, 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。


我们来推导一下上述性质,并规范一下“从节点到根,从根到节点,从节点到叶节点”这些说法的准确含义。(注意有时候高度就是深度,当一条边的层次决定它有多少个点附着上面的时候,这也就决定了它的高度和深度具有同一性)
.
6.19 快速排序思想
快排是一种交换排序,这种交换发生在待排序列本身之上,首先,将序列按中间位置分为二部分,随便从整个序列中取一个临时成员,这个成员就是“待排”的对象,这个待排对象完成它的一次排序后,那么整个快排也就完成了,因为它最终到了它应该被排在的位置。

取出这个待排元素后,以中间位置为基准,分别以从左趋向中间,从右趋向中间的顺序找二个对象与这个待排对象比较,,,这所谓的二个对象是满足条件的(从左趋向中间找的元素要大于待排对象,从右向中间找的元素要小于待排对象,首先是从右向中间找,然后才是。。),而且可能有多个(显然在二边可能会有多个大于或小于待排对象的值),,所以这个过程要持续几次,,这个待排对象才能最终到他应该去的位置。

那么这是一种什么样的“交换”排序呢,我们知道待排元素所在位置空出后,,从右边找的值填充(右端第一个大于待排对象的值)这个位置后,那么这个值所在的位置我们置空它,到这里为止,待快排对象原来所处的位置被填充,,只是它的值尚未被放入一个确定位置,而且,这里又空出来一个位置,,就是在右端比较的那个值填充待排对象原来空位后空出来的位置。

现在在左端进行与待排对象比较的过程,同样在找到第一个小于待排对象的对象后,将这个左端这个空出来的位置空出来,将这个值填入上面右端比较时空出来的位,那么到现在为止,第一套左右值被找出来了,而且到现在为止,待排对象没有被置入一个位置(因为只是比较了第一套左右值),而且左端又空出来一个位置(但是此时还不是结束快排的机会,因为还存在其它的左右值对可供与待排对象比较并产生新的空位—左边或右边的)。。、

那么在进行第二套,最后一套左右值比较时,一定最终会多出一个左空位或右空位,此时将待排对象置入,,就算完成了快排。
.
6.20 数据结构之数组
数组是一种顺序数据结构,它本身独立作为数据结构来用时,也是一种线性结构,然而它用来模拟树时,或用来实现其它数据结构时,那么这些形成的数据结构就谈不上是线性结构了(当然作为底层的数组它还是顺序的)。。

线性是针对逻辑数据结构来说的,数据结构可按逻辑结构和存储结构分类,当数据结构按逻辑来分时,有一种是线性的,因为数据结构总有结点,线性数据结构中的结点存在线性关系,即每一个结点都有且只有一个前驱和后继,这种现象可用一种关系数学的表达符来表达(关系数学是关系数据库的数学基础,而计算机的数据结构学跟数据库学又是相通的)。。当然,头结点和尾结点除外,与线性对应的当然就是非线性了,比如树,除了根节点作为没有前驱的之外,包括根节点的任何节点却都在逻辑上(作为树的逻辑)有二个后继,还比如图,任何一个节点都在逻辑上可以包括多个前驱和后继,,,逻辑上都有前后之分只是线性的是一个对一个,而非线性的是一对多,或多对一。。

(下面我们用结点来表示逻辑数据结构中的独立项,用节点来表示存储结构中的独立项,用记录表达索引逻辑的独立项,这是三个完全不矛盾的词,),

当数结按存储来分时,有一种是顺序的,即它申请了一块连续的内存来构见自己(至于它是用这内存实现了线性的还是非线性的逻辑上的数结我们不知道),,数组用存储结构的相对位置来表达它的逻辑结构(这就是索引),,数组可以独立成为一种抽象数据结构(这就是普通的以数字为下标索引的数组),,也可以用来实现更高级的抽象数据结构(比如树)。。

按存储来分时,还存在链接型数组结构,索引数据结构,离散(散列)数据结构,其中顺序的当节点没有使用完全部空间,它就显得有点浪费,而其它的还行。。下面一一介绍。。

链式的很普通,索引式的就是系统(你的应用逻辑)维护一张索引表,里面有各个记录(我们称要索引才会有记录)的记录,比如一个记录的长度,存储位置,等,通过查找表,就能找到记录。

而散列的,系统维护一个函数而非一张表,函数体的逻辑计算出“记录的存储位置”和"key(KEY就是索引逻辑面向用户的一层)"之间的对应关系。。

所以无论是顺序,链式,散列,索引表,我们都是在进行一种最终到记录的存储位置的索引工作。。数组名[下标]这样的一个形式,用计算机的眼光来看是内存地址,用人的抽象来看是索引工作。。而无论是顺序数组,还是链式,索引,散列,都是通过某种抽象形式(下标,,全局表,函数)来最终寻找到内存地址。。。
这个道理就像,我们通常不用&变量的形式来获得指针的意义,而是用*,因为&是面向内存的,而*是面向用户的抽象.
6.21 数据结构的抽象名字
在C中提供了很多支持数据结构和算法的元素,比如数组,链表,指针,struct,typedef,相比其它语言,c还提供了比其它语言的更好的支持数据结构的语言机制。。特别是,C中的一些语言机制,如位移(其实大都机器提供的位指令并不直接操作位,而是对整个字节进行位操作,进而间接控制所需要的位,我们常常通过对原字节进行位移的方法得出bit mask,再将这个bit mask跟原字节进行与,或,异或,最后得出需要的效果,比如提取1位,消去0位,颠倒特定位),跟搜索算法中特定一些算法直接相关,,相比之下,其它高级语言的数据结构都是基于高级语法结构的.甚至于像lisp这样的语言就将adt内置为其一级类型。

算法源于用计算机去解决实际问题,对由研究算法过程中出现的数据结构问题的研究也是一个很重要的工作,特定数据结构和作用于其上的算法们,就称为一个adt..

有时,上层的抽象adt可以有自己的一套算法独成一种adt,与它们的子adt一样,,,,有时,adt之间进行某种意义上的组合形成新的变种adt,,,又有自己的一种算法,如果组合了2种adt,那么就是一种带有2种意义的adt,有时,一种adt以另一种adt作为实现,自己作为一种抽象,,这样的方式演化成一种新的adt.我们必须明白这所有抽象叫法之间的关系,明确他们其实所指的东西有很大的不一样。。而且要明确这些叫法之间的特指与泛指的层次关系。。谁由谁通过谁抽象得来。


我们一般称线性表为list,或者sequences,但其实后者比前者意思更清楚一点,因为一般list就是指linkedlist,这字面上的相似给我们理解造成了麻烦,而sequences正好指出了线性表的二个方面,(1),sequences这个词意思是序列,list不但是一种序列,它更是一种有序序列,即ordered list,这就是说,前面一个元素是接着后面一个元素的,可由相邻的节点按一定逻辑到达下一个元素,虽然list不一定是sortedlist(各个节点按数组顺序或字母顺序排列),但它至少是ordered list,(2),sequences很好地与linked list在名字上区别开来,它跟linked list是泛指和特指的关系,除它之外,数组,集合,队列,堆栈这样的adt都是list.



关联数组包括普通数组,关联数组是一种可以被称为广义数组的数组,我们知道关联数组是key value索引对,只是它的key可以是任何类型,而普通数组只是interger,,,关联数组有很多抽象,甚至只是别名,比如在smalltalk,objectivec,.net,python,realbasic中被称为dictionaries,在perl和Ruby中被称为hashes,在C++和Java中被称为maps,在commonlisp和Windows powershell中被称为hashetables,在php中存在关联数组,只是索引被限制成整型和字符串,这就变成普通数组和字典了,在lua中唯一只有关联数组这种数据结构,被称为table,这也是关联数组比较正统的称法之一,

我们知道集合也是一种关联数组,不过它把key value对中的value给忽略了,把key作为value,从有keyvalue对这一点来说它像关联数组,另外,它的索引就是1到n的某个子集,从这一点来说又像普通数组,联系一下bit vecotr.

数组跟向量这个说法的关系是什么呢,其实向量vector一般被实现为动态数组dymic array,我们知道静态数组的空间是在编译期被分配的,那么动态数组就是用malloc函数在运行期构成起来的数组逻辑,我们知道数组能线性时间随机访问,但是不好重建和插入,因为需要移动插入点后的所有元素,比起linked list来它的索引字段并非指针,而是直接对应内存位置的硬编码索引,因此比不上linked list在发生插入时可以仅仅通过转变指向的方式就可以实现数据结构内部的重建。。而动态数组就是这样一种在运行期有优化了的重建能力的数组逻辑。。

vector的学名叫向量,也即数组index list的一种,linked list也即链表而非索引表,跟数组有关的不只向量,还有队列,堆栈关于数组的表示,甚至还有树,图,矩阵(一种多维数组,C语言中把foo[m][n]看成是foo[m*n]的一维数组),关联表等,除了数组是实现之外,,其它的叫法,它们有些不是指同一个东西(比如向量跟矩阵是不一样的东西,数学上的向量是矩阵中某维为1的情况下的子矩阵,是构成矩阵的量,向量是向量空间的概念,而矩阵是线性空间的概念,不过他们同构,于是在运算上涉及到了一起,数据结构上,一般vector就是一维数组,而matrix是多维数组),,有些是一层一层的抽象关系(比如vector是index list的一种,indexlist又是顺序列表的一种,图有一种形式是树,而树又是bst的基础)。都是建交在C数组这个语言要素上的抽象(前者是实现,是存储抽象,后者是高层抽象叫法).
6.22 真正的ADT
在讲解算法与数据结构的教科书,有一种语言抽象机制屡屡被谈到,这就是ADT。

类是真正的数据,,比类的成员数据更能代表数据的意义,我们的程序就是一个一个的数据,类是用用户的观点来表达现实生活中出现的各种各样的数据的最好方式,因此会有面向对象数据库的出现(数据库技术中,关系模式的下表达的数据给人的引象似乎是它是为专门的数值数据而建立的)

ADT就是指封装了事物属性跟作用的指代物本身,它的属性和作用是ADT之所以为ADT的意义所在。即数据类型不再是基本类型,而是结合了数据跟代码的抽象了的模型(是一种数据类型)

人脑往往不适于长辐记忆,因此也需要对象来表达一些数据(类型)或事物.(抽象抽取对象的可用部分),再在这里抽象上构建更为高层的抽象。也即,从问题到解决不是一步而就的,而是一层一层通过OO来建立抽象达成的,这就是多范型的“方案领域”和“应用领域”的概念。

多范型开发让你在高于面向对象的范畴里看待软件工程,所以真正的编程学习过程是要明白设计在先,这是基础的思想,然后去学习编码,,(以上二点都是方案领域)然后再去研究编程领域对现实问题的解法(这就是应用领域)

而且如果你知道可复用和可扩展对于软件工业来说是多么重要的一件事情,,,你就会知道面向对象是多么好的一种机制了(面向对象和面向构件,面向构件可以不用对象的方法,,但是很明显面向对象和面向构件都有一个共同点那就是它们都提供了可扩展,而这就促进了可复用)

数据接口是另一个很重要的概念

接口实际上就是将要用的部分作为某个接口提取出来(就是定制要使用的实现的某个子集而已),而所有实现就是接口下的所有代码,即数据本身,故其实数据与数据接口是分离的

设计模式不致于使我们声明对象的过程变为硬编码,,,这样就使得整个软件的对象生成是动态的,这个过程就如同动态分配内存,寻求一种好的方式来组织类的过程称为设计模式或策略

接口这个概念其实无比自然,无论是为了隐藏实现的安全考虑,还是为了更好地让这些实现为用户所用这个方面来说,接口都是必需的,

接口几乎改变了现今我们开发软件的方式
举个例子,在现实生活中,经理人可以卖票,,,个人可以卖票,国家也可以卖票,,在面向对象的范畴里(注意这里并非指某个具体的面向对象设计),我们一般是把国家,个人,经理人都各声明为一个对象,再为它们各自添加一个“卖票”服务,,但是事实上,我们需要的仅仅是卖票这个服务,(如果这个服务被包含在一个发布的第三方代码库中为我们所用),我们只需提取这个可用部分,,而不是要知道提供这个服务的各个提供者的细节(还好上面只是列举了三个对象提供了这个服务,,然而现实生活是复杂的,还存在成千上万个对象也可以提供这个服务),,因此,对于复用者我们来说,我们需要的仅仅是卖票这个服务,而不需要知道卖票服务这个背后的情况(而且,对于发布这个第三方代码库的第三方来说,这可以更有效地隐藏它的实现细节)

接口就是把我们所需要的对象部分封装起来提供给我们,而把我们压根不需要知道的关于对象的细节隐藏,,,也就是说,,接口是关于一个对象如何能被使用的形式的封装者(一个对象可以有多种形式被使用,因此可以一个对象提供多个接口),是真正的对象(实现)和外界复用的包装器和桥梁,也称适配器(即接口就是直接面向使用的中间件,或封装了给二个不同架构提供适配作用使它们能协作运转的中间逻辑)
.
6.23 Vector的观点
vector就是数组的数组,我们知道逻辑上的多维数组必须转成一维数组的方式被存储,C中的一维数组是地地道道的数组,,这个意义上的数组是实实在在的,,因为它们是按内存线性地址存储的,,所以明显地,,C语言中的其它泛数组的adt都是抽象的,,从C用一维数组实现多维数组这个意思上,可以看出C语言的确是面向底层的,,它企图用底层解释一切。由于它的这种作为,导致了表示方式上的一些多义性,比如多维数组的指针跟元素的表示混淆不错。

C中泛数组有普通多维数组,动态数组和vector,其中又以vector最为典型,我们知道,多维数组要转为逻辑等价的一维数组,,有三种方式,,1,row列优先,这就是C的方式,,比如foo[3][4],它有三行4列row,它先每一行排3个,再4列,,2,行优先,这也就是pascal的方式,但是这二种方式都有局限,因为它们只能实现为方形规则的数组,要突然这种局限就是vector的事情了,

3,,就是vector方式,,C标准库中倒是没有一个vector,我们讲c++的stl中的vector,一门语言要模拟多维数组,必须要达到一种“多维数组就是数组的数组”这样的抽象,而vector就是这种思想的本质,,因为3维数组是2维的,这就是说,3维数组是2维数组的数组,而由3到2,就是把3维中的其中一维标准化了,,缩为1了,,这正是vector的思想,不要把线性代数中的矢量跟这里的混淆了,但在线性代数中这样的比较有巧合性,比如矩阵(对应多维数组)是由矢量(某维缩为1的矩阵)所组成的,,矢量就是数组中的数组了。。比如foo[3][4][5],可以是:

(1)一个下标为3的一维数组,,每个元素都是[4][5]的二维数组;
(2)..
(3)..
6.24 真正的数据结构
数据结构是什么?它是组织内存中对象或基本类型数值(primtive types)的形式,为了更好地组织和使用这些对象而慢慢发展起来的固有形式,惯用法(idioms),

数据分类与ADT

基本类型数值是没有构造函数的,而Java中对于数值类型的封装形式就有构造函数,这是它们二者本质的不同,像C++中用new动态声明一个对象,它一定用到了这个对象所属类的某个构造函数,可以说C与C++的最大区别就是C++用到了对象ADT,而C没用到(因此C压根不需要构造函数来着),STL可以说是一种总数据结构.

很多地方都用到到向量,,在学汇编时,,中断向量表就是一种向量,为什么STL没有arrary而只有vector呢?因为数组一般都用来实现vector,而且数组是语言的内含类型,一定程序上不提供太多的接口(根本因为是作为数值形式的数值没有被wrapper成一个first class因此不能作为函数的参数—因为没有复制构造函数,只能作为指针形式间接来操作它,也因此不能成为一个函数的返回值),而Vector可以提供跟数组类似的结构(也有多维数组)和比数组更高级的使用接口(一般数组类型只能是静态的不可伸缩的,而Vector可作为动态数组动态改变大小)

实际上多维数组并不是在内存中是一个标准的矩阵(学过线代就知道,任何一种矩阵都可以化为它的等价三角矩阵),比如C或C++就用一种行或列优先的方式来索引其元素。

数组是一个静态配置的空间,在命名方面JAVA做得比C++好(个人看法),比如INT MyVar[10];作为数组名的MyVar带了序列,而JAVA中INT[]MyVar;这就有点相当于type *是指向某种type的指针类型,而type[]是某种大小的数组类型一样好记

下面来阐述几个易混淆的概念

顺序线性表,,有序线性表(orderd list),hasp map(可用关联容器来表达)都是列表,比如字典顺序,也即字母顺序的一种关联机制

线性表即通俗意义上的“线性数据结构”,线性的意思是什么呢,它一定要满足几个规则(1,有唯一的第一个元素和最后一个元素,2除了第一个元素都有一个前驱,,3,除了最后一个元素,都有一个后缀)

上述的元素就是数据结点的意思,数据和数据结点之间的关系可表达为B=(K,R)的二元组,满足关系的二个元素一个称为前驱一个为后继

计算机存储数据的方式只有二种,顺序存储与离散存储(又有链式,索引式,Hash式),这是面向计算机端的存储结构,本向用户端的逻辑结构有集合(set),,哈希(hashmap是一种map),,表(table),数组(array),,向量(vector),,图,树,map映射,线性表,节点链式表linked list,多维数据结构(matrix)等等,这种关系就有点像数据库的内外模式之分。逻辑结构间也有高级的演变方式,比如用vector来实现矩阵和table

map是映射,,,set是集合,,都是关联型的数据结构,因此可用关联容器来表达


集合这种数据结构是用位集来描述数据集(可能以一个数组的形式存在),通过移位和位运算

线性表就是不能随机存储的表,像数组就是线性的,堆栈和队列也是线性的,有双向链表的存在(list),,dequece(double end quece),
表table就对应关系数据中的模式,,也即一个记录是一个表,它的各个字段就是表的竖维
聚合数据类型就是像图(map)啊,树啊之类的,树是一种特别的图(每二个节点都有通路)而且是简单图

图的存储结构主要由它的邻接矩阵来表示,或邻接表,而树的存储表示主要由以下三种表示:结点表示法,兄弟子女表示法,等

这就是关联,,关联一定有key和一个value,,它们一同被存入数据结构,然后运用某种机制(可能是hash)通过key来索引value
图是一种离散结构而非一种数据结构
图的边就是顶点之间的关系,这种关系是单向的或者双向的
像MFC的消息系统用的就是表驱动方式(它的消息映射表就是一个静态表)
我们知道,栈是一端开口的,往往从高端压和出栈(称为栈顶)后进先出的(但是后出先进这种说法是不存的,因为当一个栈没有数据时,它就不能出任何东西,因此只能说后进先出,先假设它有至少一个数据存在)因此它有当前指针和栈顶指针这二个元素来表示(当前指针指),栈往往用数组来模拟(++p,p--这样的形式),栈其实是一种跟它的实现形式无关的思想而已(是一种逻辑结构),因此可以说是数组(指针数组)来摸拟(顺序存储的存储结构),可以用数组的一端,当,而队列是一种先进先出的,它二端开口,有三个描述元素(尾,头和当前指针),它往往被实现作为一个管道(因为队列从意义上来讲它也其实就是一端进而从另一端出的数据结构啊)作为缓冲,或forward给其它处理
list是列表的意思,有序列表orderdlist,链表(linked list是一种orderdlist),堆栈,队列都是一种orderd list
就像链表和数组都可以仿真堆栈一样,,树啊,图啊都是思想模型,链表和数组才是实际存储的机制
对数据结构的讨论中经常用到递归,特别是树中,因为树本质就是一个递归结构,在回溯节点时就是回溯同一种节点(因此这个节点可用递归描述 )

递归跟回溯,栈

递推与迭代还是有区别的,递归就是用自己来定义自己,,一般不需要一个循环,,而迭代需要从1开始,将这个循环变量一直自加到最大值(循环不变量?),,需要一个循环,一般来说,迭代比递归更有效率(在某些专门对递归进行了优化的环境中除外)

对一种数据结构的讨论常常不但要明白它们的工作原理,还要明白它们的操作,如查找,排序等,数据结构存储结构和这些操作就构成了ADT

《数据结构C语言版》

前言:这是我在2005.7月 - 2005.9月署假看《数据结构C语言版 - 清华大学出版社 黄国瑜叶艿菁著》时写的读书笔记,现在把它发布出来,希望对大家有用,也算是作个备忘录吧,不科学之处,还望高手斧正.
6.25 堆栈与队列
堆栈与队列都是数据结构(更复杂的还有树,图)在关系上是平等的数据结构,实际上堆栈与队列都是"内含在空间里的数据块",堆栈,队列的本质就是"数据块",不过它们都是包含在特定内存空间里的"有序数据块",如下图(4.bmp,用数组模拟"特定内存空间")
"特定内存空间"可以用数组表示,也可以用链表表示,数组是内存中的线形空间,也就是说,数组可以在内存中开辟一段空间,该空间是线性连续的,对该空间里任一元素的存取要根据"索引值"来进行,这些索引取从0~MS-1(如定义一个intqueue[ms]或int stack[ms])是线性递增的,与数组空间从低地到高地的排列一一对应,数组的每一个空间都可有数据,也可无数据,每个空间的大小为一个int,整个数组大小就是ms个int,但是无论如何,对其中任何一个元素的存取(存是往数组任意位置里存入一个大小为int的空间,或在某个无数据内容的数组空单元空间里赋值,显然,这个数组空间是本来就存在于数组内的,而不同于链表要动态开辟一个新空间,而如果是前一种情况,数组就不再是静态的空间了,因为它的大小由ms变成了ms+1,而这是不可行的,同理,取是释放一个空间单元,这就使数组总空间大小由ms变为ms-1,或在某个空间里赋值0,术语称置0,而事先不管这个空间里有无值,如果有值,有的是什么值),其实,对数组索引的描述不但可用0~MS-1,当然也可用1~MS,但是为了方便考虑,把前一种看作为常用的,专业的方式,当然还可用2~ms+1(3种方式在定义了一个大小为ms的数组的情况下都可行可用),因为数组空间是一定的,对其的表示方式当然可以自由地使用不同的方法,一切的一切,只要保证能正确存取到所需的数据为程序所用为准,因为这是数组这种数据结构要最终达到的作用和总则,另外还要保持易用(像0~ms-1就显得专业并且简单易用,1~ms就人性化,而2~ms+1就什么都不是),上图中的数组示意图都开了"口",是表示只能从数组的开口的那一端存取数据(数组每个元素都是空间里包含的内容值,即数据,请搞清"元素","数组每个空间单元","内容值"等的说法),是一种形象的表示方法,而标准的数组图示方法可如下表示5.bmp)哪为什么要开口呢?一个数组为什么能开口呢?这是因为前面提到,这个"数组开辟的空间"将作"堆栈空间"使用,也就是"用数组模拟堆栈",因此标准数组示意图就要经过一些变形以适应能正确表示堆栈(空间)的要求,首先第一点变化就是"开了口",第二个变化就是还加入了一个top指针,这二个变化适应"能正确地表示堆栈(空间)"而生,其实要说top指针,它还不是标准意义上的指针,指针是一个32位的整型,而这里的top本质还是索引值,而非内存或外存地址单元名称代号,只是因为它发挥了类似指针"寻址"的作用,因此将其看作为"索引型"的指针,数组"堆栈"相比链表"堆栈",数组"堆栈"中的top是索引,而链表"堆栈"中的top才是真正的指针,因此数组的索引本质上是一种线性循序查找,而链表"堆栈"的top才是指针,分散在内存空间非线性不密集,只能由指针指定查找,这里就比较了数组与链表的本质(数组是内存中一段紧密的空间块,各个空间单元的连接是线性紧密的,这是对数组的低层讨论,反应到数组中就是其中的各个元素,将上一个元素的索引加1就可定向到下一个元素的索引位置,将上一个元素的内存空间地址加一个空间单元长度可定向到下一个元素在内存空间的位置,用0~ms-1或1~ms这样的递增性的索引就可表示数组的各个空间并引用它们,存取其中的空间或元素),有高地址和低地址之分,索引由小到大递增的方向就是内存地址低地到高地的线性递增方向,而堆栈是内存中各个分散的节点数据,各个节点数据就是元素或者更确切地讲,各个节点数据中的内容值,或称数据值而非指针值,就是链表的各个元素,相比起数组的空间单元的"连接",各个链表单元空间,也即节点空间的链表使用一种指定式的数值指定法而非数组采用的循序式的查找定位法,各个无素之间分散的链接由内含在各个元素(节点数据)中的指针字段而非内容字段,数据字段来完成,因此对其每个元素的存取前首先确定元素位置时是不能像数组中循序进行的,而是已被指定的,当前元素的内存位置就在上一个节点数据(元素)的指针字段里,而链表结构里,显然就没有高地与低地这种数组里才有的特征.
另外,要注意堆栈和队列中所说,它们都是有序列表,上面讲到,4.bmp中各个空间里的"有序数据块"才是确切意义上的"有序列表",即堆栈,队列这二个词语作为概念所指的实体所在,那么,它们的有序性是靠什么来体现的呢?堆栈靠的是游离于0~ms-1之间的索引值top,那么堆栈就是指0到top的数据块,top有一个特殊情况,top也可等于-1,显然,此时它指向序列为0的空间的更低地址的一个空间,表示堆栈为空(即堆栈中没有元素再供出栈了,出栈<=>从栈中输出一个元素<=>从堆栈中释放一个元素<=>删除一个元素),前面谈到,出口和top的引入都为用一个标准数组变为"堆栈"数组提供了可能,堆栈数据块是出入有序的,因此称为有序列表,那么,堆栈是如何依靠top来实现其空间里数据块元素出入的有序性的呢?在堆栈里,出口是唯一的数据输入输出(压入即输入push)通道,而队列有2个数据出入口,准确的说法是一个出口,一个入口,而堆栈的出口和入口集中在数组空间的一端而已,示意图就是只能从数组空间的高地址方向端输入输出数据(而堆栈有2端前端后端或称头端尾端,即front,end与head,rear),从rear端入栈,从f端出栈,堆栈数据实体就是从f到r的数据块,在数组表示的图示中f在下,r在上,在链表表堆栈中,f在前,r在后(f此时也可称为头,r也可称作后端),在数组表示中,各个数据实体是各个数组空间里的内容值,而在链表表堆栈中,各个数据实体(f到r),堆栈就是各个节点的内容字段链接而成的,这些内容字段链接起来构成的一串数据值就是堆栈数据块实体,从f到r,可见f到r不是从低地到高地,也不是从高地到低地,因为链表整个空间是分散于内存中的"节点空间"链接起来的,各个节点空间是分散布列在内存中的,因此,各个节点空间的value字段也是分散于内存里的,链表的各个空间间实际上没有一条一条的链,这只是示意图中的形象表示,链表的各个空间的链接桥梁就是内含在各个节点空间中的指针字段,链表与数组的一个重要区别与优点就是链表使用指针而非索引来寻址,这样就可以通过改变指针的值来重新形成链表空间或增删元素(实际上也是重新形成链表空间),这样链表就是一种动态配置的空间,而数组就是一种静态配置的空间,数组中的每个空间在数组被定义后就存在了,其中的任一个空间都不能被增删,只能向其赋值内容值0,表示置空此空间单元,这就是静态配置的空间.
图片在我的Q空间的像册里.

数据结构浅探•其它
评论(0)发表时间:2006年6月2日 0时53分
.
6.26 真正的递归
递归是一种思想,只要问题本身满足递归你才能,递归涉及到三个概念,回溯
递归定义
即用递归来定义一些离散结构(集合,函数),3.4递归算法,用递归思想来解决问题的一般化步骤(算法即解决特定问题的一般化步骤,”停机问题”证明不存在解决所有问题的算法)
这种关系就如同二叉树的定义和查找,因为二叉权的定义就是指明它的左右节点存在次序关系,所以可以用这种定义作为思想来对二叉树进行查找,这并不难理解
用对象本身来定义对象就是递归,,这是递归的描述性定义,,是不确定的,,,非形式的,
集合的定义和算法的定义只有在形式语言里面才有形式定义,,(特别是图灵机证明不存在所有问题的一般化算法时用到的集合和算法的概念)


递归是用自身来定义自身这种说法成立吗,先来看一个问题


你能描述以上一副画吗??(如果它的中心是无限循环的)

你可以这样描述,,,一副画的中心区域的内容是它自身(也是一副画,而且具有跟前面描述的一样的性质),这样一副画就是如上的画的定义

显然递归的说法在这个例子是成立的

序列函数的定义比较容易理解,,集合的递归定义常常用来产生一些合式公式

迭代与递归在不同的情况下各有其优势

兔子和斐波那契数

例4 免子和斐波那契数 考虑如下问题,一对刚出生的免子(一公一母)被放到岛上,每对兔子出生后两个月后才开始繁殖后代,如下表所示,在出生两个月后,每对兔子在每个月都将繁殖一对新的兔子,假定兔子不会死去,找出n个月后关于岛上兔子对数的递推关系。



如何理解该月新生兔子即为距该月二个月前的兔子总对数?如6月新生的兔子为4月总兔数,4月新生的兔子为2月总兔数,3月新生兔数即为1月总兔数?

对某个月的讨论要追到与它的前二个月的情况(题目意思如此:每对兔子出生后二月才开始生育),这里是某个月的新生兔子与它二月前兔子总数“相等”的情况(注意这是一种递推说法,在任意差为二的月份间都存在这种联系,比如3-1,4-2,5-3,6-4,而6-2则不能考虑,因为它超出了递推为2阶的阶数2),从第三个月开始,放上岛的第一对兔子A+A-生下了一对兔子B+B-(假设每生下来的一对兔子都是一对公母可生育),在B+B-被生育下来的这个三月份,B+B-并不生育,这对A+A-在第四个月继续生育C+C-,而B+B-在第四个月也不生育,第五个月A+A-继续生育,B+B-终于开始生育出一对兔子D+D-,,

上面描述的可以作为典型,因为从第三个月开始,它就满足“每对兔子出生后二个月才开始繁殖后代”“任意出生在a月份的兔子在二个月后的b月后才生育,b-a=2”的题目要求,作为典型的3-1,5-3的情况被提出,后来处理任意相差二个月的兔子情况都可参照以上的描述了.

这样问题便被缩小到相邻二个月之间的情况,,

(为什么这是对的呢,作为典型的3-1,5-3为什么可代表一切相邻二月的情况,上面说了,这是题目意思告诉我们的,而不是递推,n个月后的兔子跟n-1总数与n-2总数才是递推,,这个递推也是在假设不知道存在这个递推的情况下列出的一个式子,列出之后才发现它是一个线性组合的递归,列出这个式子之间设了an,这并不表明事先就知道an一定是个满足某种递推的通项,只是在列出式子之后才明白是一个递推,而列出式子的过程仅仅依赖于题目意思)

既然已经得知,5月兔子与3月兔子间存在联系,那么这种联系是什么呢?重要的是知道这个联系本身,这个联系可用于任意相邻二月之间(题目意思)

从以上3-1,5-3的描述中容易看出,


汉诺塔问题:



图1 初始状态


图2 柱1n-1个盘移到柱3后的情形

我们的目标是计数移动步数,,,并不是如题目所说得出移动的具体方法和每个书面步骤
因为等我们得出步数这个结论后,就会发现如果具体去得出移动步骤会多傻
而且,尊照规则(一次只能移动一块盘子,最后全部原样移动到柱2而不是柱3,并且最重要的大在下小在上)把n个盘子成功转移,移动的方法也可以千千万万,,所以我们要求的移动步数是最少的移动步数,那么对这个问题(求出最少移动步数)该如何建模呢?
首先,我们设想这样一个步骤:第一步,把柱1的n-1个盘子移到柱3,保持小在上大在下的顺序(如图2所示),保留最下面一个底盘,第二步,把底盘移到柱2,第三步,再把从柱1移过来的n-1个盘按步骤1的方法和顺序移动柱2,至此完成(注意方法和顺序的说法,方法:步骤1是怎么样移盘的这次也怎么移,顺序:保持小在上大在下)

容易看出,使用更小的步数是不可能求解这个难题的

下面具体建模
按照上面设想的所产生最少移动步数的移法,首先,设Sn是把n个盘子从一根柱子移动到另一根柱子的需要的总步数,(问题是什么我们就设什么,,虽然我们并不知道这其实是一个为了满足递推关系的设法,虽然我们也不知道有了以上设法,再用递推关系就可以很好地解此题,因为从形式来看Sn像一个序列的通项,它指明Sn就是把n个盘子从柱1到柱3所需要的Step,S2就是2个,S10就是10所需要的Step,这个设法假定Sn与n之间存在序列关系),按照步骤1,移动次数可用Sn-1来表示,步骤2可用1来表示,步骤3亦为Sn-1,故有
Sn=2Sn-1+1 (Sn为把n盘从柱1移到柱3所需步数或直接就是)
注意Sn为什么不直接说是“设Sn是把n盘从柱1移到柱2所需步数”呢,这样说当然没有错,不过为了严紧性和通用性考虑还是这样设(本题当然是从柱1移到柱3,如果没有规定其实移到柱2作为中转也可,最后求移动到柱3上的步数同样满足Sn=2Sn-1+1,,况且还有很多同类的题目,移奶酪到盘子里什么的,所以为了通用性考虑还是如上设)

最后的问题:我们用迭代方法来求解这个递推关系


5.2 求解递推关系

有一类重要的递推关系可以用一种系统的方法明确地求解,,在这种递推关系中(好像我们见过的都是这样的),序列的项由它前面的线性组合来表示

请注意,递归,递推,迭代在措词上的区别

递归是用自身定义自身的,或过程调用自身,用在序列通项表示上,,形如,an= an-1,用在过程体内,这仅仅是一种思想,而不能说是一种算法,,显然这种思想是成立的,,,递推关系和迭代是用了递归思想的2个概念而已,是用到了递归思想的所有东西的集合中的二个元素,
要对它们三者定性的话,递归是一种思想,递推只是一种说法,迭代是一种算法(常用来求解满足递推关系问题)

递推是一个序列的某个通项可由它的前几项组合推导而来的关系,,强调的是通项和推出它的前几项之间的关系满足这个“递归推导而出”说法,因此它一定用到了递归的思想,满足递归

序列中,递推是发生在某个项和它的相领项之间的关系,,但是指定了初始项和这个递推关系,可以得出所有的项,即通项,,实际上“某个项”的定义一旦被提出,它就表示了通项的意义,,因此序列所有项=由通项关系得出每个项后,这些项的组合=初始项+满足递推的相领的某几项

(我们设这里是二项,而不是前几项)所以,所谓递归只是二个项之间的关系,,但是由于这二个项可以是任意项,,因此,递归是所有项中任意二相领项的关系,可用来求解整个序列每一项,求此也求解所有项

定义X:一切序列可以用到递推要求它满足递归,这永远是前提(实际上并不是所有的序列都满足递推和递归)

迭代是用迭次代换,(常用来求解一个“线性组合”型递推关系的方法,也可求解不满足线性组合的一些递推关系)把一个项逐次展开,,用到一个迭代器i,比如求解an,必须使i从第n项到第k项逐次递减,(k通常情况下是1,即第一项),,迭代也只在几个相邻项之间进行,因为它也借用了递归和递推思想而已,这一切都是因为用到迭代的序列(或其它问题)也要由定义x而来,,不再赘述
.
6.27 树与单链表,图
除了单链表之外还有高级链表,包括双链表和循环链表,但是它们的基础依然是单链表,对单链表的讨论可运用于高级链表,也即无论单,高级链表,本质都是一样的数据结构,都是链表,即链表的本质是节点的单向或双向串连,注意"串连",这是所有链表区别于树这种数据结构的所在,因为树是一种节点的"分支"链接,(树是较线性表,图更为复杂的数据结构,它的任何二个结点间都可以发生联系)它们的共同点就是:这些节点都是分散于内存中的节点,由上一个节点(链表)或父的一方节点(树)的指段字段指向,树和链表的示意图中,圆形就代表节点,包含数据字段和指向下一个节点的指针字段,这是对链表来说的,而对于树来说(这里说的是二叉树)圆形节点就包含数据字段和指向其左右节点的指针字段,或称左右子树,因为这些左右子树是以这个节点为根的子树,注意左右是有顺序性的,不能颠倒,用Left,Right区别.无论是链表还是树示意图中的直线都是指针,有方向的,不是互逆的,这处互逆性决定了对单链表的"首->尾"遍历和对二叉树的"二叉查找"遍历方式(中序,左右序)的单向性,可见树与链表的实体数据都存在节点中,这是节点的主体存储空间,链表,树作为数据结构用于组织程序中要用到的数据,它们的节点单元的Data字段发挥的是主体存储作用,而它们节点的指针字段是辅助性且必要的,用于树,链表的查找,遍历,插入,删除等操作,严格来说,图并非一种数据结构,书中对图的讨论只能称为对"与线长,面积大小"无关的点线面的拓朴讨论,而由点边组成的图形中居然没有一个点或边发挥数据存储作用,而这是一种数据结构首要完成的任务.

3.数组,链表为什么能仿真堆栈?

其实这个问题被提出来一点都不可笑,对这个问题的讨论很有意义,它能让我们明白一些细微而重要的东西.
数组,链表,堆栈,队列,是四大数据结构,在关系是并列的,为什么又有"用数组和链表仿真堆 栈"之说呢,其实数组,链表是较高级的复杂的数据结构,还记得本书序言中的一句话吗?数据结构是人类在长期的编程过程中总结归纳出来的一套科学有序的方法,数组是,链表是,堆栈和队列也是,但是在人类总结归纳这些数据结构并形成术语进而形成数据结构这门计算机科学时,永远是从简单低级的数组和链表开始的,对它的讨论和总结归纳先于堆栈,队列之前进行,当人们总结出数组和链表数据结构时,并得出对数组和链表的删除,复制,插入等操作后,形成了数组链表等数据结构概念及其操作的一整套理论后,人们进一步探索数据结构,发现了堆栈的存在,而堆栈又是基于数组,链表的高一级的数据结构,对堆栈,队列的研究与讨论经历了与对数组链表相同的过程,对数组和链表的研究和技术总结业已成型的情况下,就应该采用现在的知识来描述新出现的知识,因此有以上的说法.
6.28 树
树是一种数据结构,称为树状结构,简称树,树的本质是一个或多个节点的有限集合,树只有一个节点的情况是例外的特殊的情况但也是允许的树的形式,而一般情况下,树都有不止一个的节点,有限集合这四个字指明:一棵树,无论它由多少个节点构成,这些节点的数量都是有限的,可以计数的,也即,从来没有一棵树,它的节点个数为无限不确定的.
在一棵树的所有节点中,它们的地位是不一样的,每棵树必定有一个特定的节点,称为根节点(root),根节点与这棵树的其它节点构成了这棵树(当然这些节点并不是单独地存在无联系的,不然就无法从这些节点中搞出一个为根的地位节点,这些就构不成一棵树),可见,"根"是相对于"树"来说的,根这个概念是树一级的概念,只要有树,便会有根,根的本质是一棵树的"特定节点"(第一个节点,其它节点由它分支而来),而树又是一种特定的数据结构,请记住,根是依附在树概念上的概念,脱离了树便无所谓根概念,总之,根是与树直接挂钩的一对概念,为什么我要在这里这么花心思地说明根是相对于树的概念呢?因为这是理解7.1节中关于树的其它概念的一个基本点,掌握了它,你便不会在理解这些概念中迷失.
说完了根,再来说其它节点,其它节点(实际上这里说的其它节点准确的意义不是说除了根节点外的一棵树的其它所有节点,而是说根节点的下一级节点,可以有0个,可以有n个,0个就是没有下一级节点,而n的大小便决定了这棵树的分类性质,如果n=2,便是二叉树,后面会谈到)是根的子节点.(8.bmp)
从树结构的图示来看,树与现实中的树虽然类似,但是,数据结构中的树是一棵倒树,根是相对于树的概念与树直接挂钩,而子节点的概念直接与根节点的概念挂钩,而与树不挂钩,根节点与子节点的概念仅仅存在于一个节点与该节点下一级节点之间,与树不挂钩,这就像主程序与子程序的概念,主程序与子程序永远是相对的概念,如果前面说的子程序它又有它的下一级程序,那么主程序与子程序有主子关系之外,还可以说前面谈到的子程序与该子程序调用的子程序也有主子关系,此时这里的子程序是主程序而子程序调用的子程序就是子程序,这2种说法都是可行的,因为主子关系是相对的可变的而非绝对的,主子这种说法存在于只要满足一方是调用者而另一方是被调用者之间,而非绝对不变的,再接着上图讲,那么B,C,D,M就是A的子节点,而不能说B,C,D,M是树T的子节点,没有这种说法,树只有根和子树,叶节点与其挂钩,而只能说(因为B作为根节点其下还有P,Q2个子节点)B,C是树T的子树(D,M不是),这里,B,C,D,M作为A的子节点,同时B,C又是树T的子树,子树B(以它的根节点为名称故根节点就是B)的根节点B又有自己的子节点,T的树C的根节点C又有自己的子节点R,若一棵树中的任意一级(根)节点最多有n个子节点,则称这样的树为n元树,二叉树的得名也来源于此.

12.树的本质是什么?

树是一种数据结构,所以树的本质是一种组织内存空间的方法,就像数组是静态配置内存空间的方法,且数组配置出来的空间不但是静态的,而且是紧密相连排列的线性的各个"小空间",是一块内存中的块状数据静态的存储区,由它的一个小空间的地址可以推导出下一个小空间在内存中的地址,而链表是一种动态的配置内存的方法,程序中,数组空间在数组被定义出来时就被开辟,在它的生命期内从此不能更改大小,而链表被定义出来时还要用内存开辟函数malloc()来实际分配内存,所以它开辟出来的空间是动态的,而且这些空间单元不是线性紧密排列的而是分散的,分散于内存中的各个"节点"空间,是不可通过索引下标循序查找定位的,只能通过内含于各个节点数据内的指针字段来"指定查找",形象示意如下1.bmp)那么树呢?树的本质是"节点的非空有限集合",跟链表有一定的相似性,首先,链表与树(基础树,书中所讲为二叉树,这属于简单树而非广度意义上的树,但是对二叉树的讨论可以延用扩展为整个树的范畴)都是动态内存配置的方式,链表空间间连接依赖于各个节点数据的指针字段,而树的节点空间连接的方法为"格式上的约定",树的节点空间和链表的节点空间都是这二种数据结构实际存储实体数据的存储场所,是怎么样一种"格式上的约定"呢?以二叉树为例(其实树各个空间之间没有什么链接,链表各空间发生关系的纽带与桥梁是指针字段,而树的各个节点空间间发生关系的钮带与桥梁为"格式上的约定","约定俗成的方法来格定"),二叉树的每一个节点空间实际各各分散布列于内存中,它们发生联系的手段就在于各个节点的性质,前面谈到,树是节点的非空有限集,如根就是树的第一个节点空间,如果是二叉树,这个节点自然会跟其下一阶的二个子节点发生关系,这就是父子节点关系,当然不止根与其下一阶节点,在其它节点间也存在父子关系,除此之外还有兄弟关系,树->子树关系这种"关系机制"是远远不同于链表依赖指针表示各节点空间联系的手段的,所以这是一种"节点关系"上的"约定",是一种全新的组织节点空间的手段和方法,而没有用到指针,当然可以用指针来模拟和仿真,这就涉及到"用链表来表示二叉树"的知识点,这是后来的内容.

13.图的本质是什么?

要说图是一种数据结构实在很难理解,因为我第一次碰到图的概念,根本就没有发现图形结构中的哪部分用于存储数据,378页对图形的定义"在图形G中包含了2个集合,一个是由顶点所构成的有限非空集,另一个是由边所构成的有限非空集,可用G(V,E)表示",按照这个定义,图形就是顶点和边的有限集合(至少要有一顶点一边),晕死,那么实体数据存在哪?顶点中?不是,边的描述值权吗?好像也不是,署假我只看了这本书17天,所以对图我没太多深入研究.

6.29 真正的散列表
一般抽象到了某个程度,为了获得计算机作为底层的冯氏能力,,就不应该再抽象下去了。
开发模型不需要再变了,数据抽象到数据结构级就是顶级了再抽象就不是开发问题了()

Hash表就是hashed list,,,它是逻辑上的list(所以也有hashmap,hash set等),但按hash方式存储地址作为存储,

Hash是一种利于搜索的启发过程,一般的搜索是对搜索空间uniform的,而hash function是对搜索空间的目标问题建立的一种启发机制的函数。

Hash的最重要的意思不是提供一个映射函数,那反而是hash解决的第二个问题,即地址问题,,它最重要的意义,即第一个解决的问题是基于统计的本质。进行的对搜索空间的一种抽象(即hashtable而不是hashfunction问题,比如元素会出现一次,还是二次,这样抽象对操作元素有好处,但显然此时并不出现hasing function的意思),

一般设计散列时,说的都是设计散列表,然后处理冲突处理,,,如何散列所用的函数是附带问题。

也即其实hash table的第一层意思是table,,即形成数据结构才是他的第一层意义,,,而不是如何hash,,,并提供一套hash 机制,,,
它着手于"在解空间和目标数据空间建立一套具有inform关系"的数据结构,,,这才是它的第一层意思,即名字中的table一词
至于如何hash并解决冲突,,那反而是它解决的第二个问题..即名字中的hash一词
只有理解了这点之后,你才会明白hashtable是如何来的,以及为什么存在,,与其它数据结构作比较时所呈现的那些不一样的特点(比如为什么要进行hash,,为什么要处理冲突,而其它的数据结构则不需要这样的分析过程).
6.30 算法设计方法
摊还分析不仅是解释数据结构性能的工具,而且也是设计时要考虑的因素。就跟NP完全一样(逊于图灵不可计算机的停机问题)。如果复杂度超过了10的确良8次方这个计算机的数量级就没有意义了。

存在很多类型的问题,比如最优化问题,,找点问题,理解诸如贪婪算法的前提是理解这些问题在先,比如最优化问题可以很好地解释什么是贪婪设计。。

迭代是方程求根的一种方法,并且它也体现了一种算法设计方法。

递归体现了分治的算法设计思想,但它提出的子问题一定要跟原问题接口一致。。 递归的终结条件是不需要递归也可直接求解的条件,,因此是终止条件,,当以从下到上的眼光来看时,它是超始条件(直到问题规模n)
其实并不是只有所有明显递归性质的问题才可以用递归来解答,而是只有能把原问题分解为子问题并能制造一个接口的情况下就可以利用递归来求解它。

递归是从上而下,所以有时要求用辅助栈来保存中间结果,而递推只有一个函数,并不发生欠套的函数调用,并没有出入栈的时空开销。复杂的递归结构转化成递推时,需要回SU处理,二个概念仅一字之差,一个归,是向下,一个推是向上,

关键字相当于字典中的单词,,而数据项相当于这个词的词义,造句,等全部的词条信息,这样说你一定不会明白,说实话我第一次看到这样的说话也没能明白。。

实际上就是说,我们要查的是关于某个词的词条义,音等,,但我们是通过某个单词找到该单词的词条义,音的。。在字典中单词和其音义是一同被存入字典的,都是(某记录的)数据项,但单词是作为键的数据项。.

新手编程导论(八)


第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的内容)
l 这样仅仅是一个软件内部的考虑,如果不在源程序的级别,你要提供你的软件为别人所用,或者是一个类或者是函数,你不可能向别人提供源程序,你就要在二进制级别上向别人提供面向对象的特性(即可复用特性),,这通常是建模工具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的综合)


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被定义为constint*,,,因此并不能使用它来修改指向的对象
即,除了类型之外,指针的声明可以与它指向并要操作的对象无关(比如关于操作的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

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是什么意思呢?

首先我们要弄懂实例化
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),这样的结构,,,要绕一些弯子,比如tagdispatch 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 的EnableIf,,从这些字眼上看就直接跟思想和设计已经十分接近了。(不要忘记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++中,写代码就是为运行做设计,程序设计期designtime的就法就因此而来,写代码的本质就是在C的数据封装抽象基础上,C++的基于对象抽象上,C++的OO抽象上这些语言机制逻辑上+操作系统封装了的CPU逻辑上,写最终面向CPU的二进制代码,这就是我们说的,系统编程语言的本质在于基于系统逻辑(并且语言本身也是基于系统逻辑的,我们把计算机系统离散基础,语言实现离散基础,CPU汇编语言所涉及到的那些机器离散基础都称为系统逻辑),写面向系统逻辑的代码。(与它对应的脚本语言更多地是为了直接为应用服务因为它极力让开发者绕过系统逻辑)。程序运行的本质是什么呢,,就是向操作系统和CPU请求资源,CPU执行二进制,(任何我们写出的源程序,在经编译优化后形成目标平台二进制码,我们向PC编程以构造应用的过程只是系统逻辑到应用逻辑的的一个维度变换),这是指比如C++这的以本地平台为运行环境的系统编程语言,而非指自带VM的,向VM编程的脚本语言而言的。
  
  设计期就是写代码期,是用户动作,发生在编译期前,编译期不是用户动作,运行期又不是用户动作。。
  
  模板是一种参数化的泛型,泛型这二个字决定了模板没有一个类型(在写模板函数,或模板类时的设计期,能难想象没有类型的东西能够堂里皇堂的参加运算吧^-^,只有编译器技术到家就可以),,在写出这个函数模板或类模板后经编译时它并不产生代码(机器动作时),只有在接下来实例化时(注意,这个实例化是指在设计期用户动作期,用户为这个泛型占位符指定一个具体类型并写出关于它的代码,这个实例化跟我们在OO程序设计中,由一个class定义一个对象类似,接下来,在编译期机器动作时才会在编译期实例化产生实际代码到中间目标文件中比如obj中(非飞行模式),
  
  什么是实例化呢?注意这个字眼
  
  在OO程序设计中,由一个class定义一个对象并不实际动态分配内存,因为这还是指为运行期作规划的设计期并没有实际进入到运行期并分配了一个内存,如果用了new关键字,还是没有分配内存,编译期永远不分配内存,只是为程序将来在运行期如何获得内存作分配上的控制而已,,
  
只不过一个实例化用了不同的情景而已,实例化并不一定指为运行期实际实例化(也就是说实例化并不一定就是一种“为运行而作设计”的设计,,它还可以是一种“为设计期而作设计”的设计期动作,)它其实就是一种编译期的宏,只不过它有类型的判断,因此远远不是简单的文字替换性质的宏,,而是一种编译技术,模板由于有这个特性,第一,因为它是泛型,所以为C++带来泛型编程机制,第二,模板给C++带来元编译技术。
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 元编程技术
承接我上面一篇文章《为设计产生代码》
  如果实例化不是为运行进行设计,,而是为了为设计期产生代码这样的目的而进行的设计期的设计(很显然,它本身也是一种设计,并不面向运行实例化,这是一种静态实例化,编译期的实例化,即它向前推进了一个意义层次),而且当模板机制的编译技术实现涉及到词法处理,语法分析这样的编译原理知识时,,,那么模板就是一种地道的编译器的编译器了,是一种元编程技术。
  很多时候,理解以上的东西,不但要求你有实践经验,而且还需要你有精深的抽象组织能

新手编程导论(九)



第8章 抽象之设计和领域逻辑


8.1 大设计
范意上的设计是广泛的,不仅限于计算机的,也不限于软工抽象,软件的设计哲学是可以用来解释一切的,因为它是真正的哲学,而真正的哲学并不仅适用软件开发(软工和计算机是二个完全不同的抽象,虽然没有人提出过计算机抽象到底是什么,软工抽象到底里面有哪些抽象存在,我们仅能站在某个或某些维度上给出一个描述性的概念而不是有限集,这也就够了,如果能站在一个大全的维度上说明到软工的全部抽象,虽然这是不可能的,但我们还是给得出的这个结果取个名字,叫范式,范式在意义上是大全而的抽象,然而人类的范式总表现为某些维度上的产物(只是我们在一本无论多么长的书里都写不完而已,如果有那么一种生命力和那么一本书存在,我们也坚信任何一个道理都不可能写完)。

设计并不仅仅面向于创新,有时是形式的重组,而不是内容的创新,应用形式的改观,设计出的产品,要源端是面向人的,因此要提供足够简单的使用和访问形式,在目标端是要达到足够丰富的应用逻辑(比如XML统一文档交换,但可由node,root这些形式导致足够丰富的深层功能,深层这里是指上层),因此越复杂越大而全越好(但是如果没有足够人力,我们应考虑设计出别人想不到的商机),应用形式和应用逻辑作为设计中应主要考虑到的问题,有很多人想到用一种形式来统一既存的东西,比如在jvm搭建Windows和其它一切软件,然而这是多么无聊的事啊?
明白了原理跟实践,细节与思想之间的关系,我们就会有选择性地去学习细节,,一定要研究细节,因为细节是重要的,你得用这种技术细节实现某个东西,不一定要研究细节,还是因为你要不要用到它,然而思想是重要的,明白了这个思想,你就明白有些知识是技术细节,有些思想却是根本,当然,没到考虑思想的水平,技术细节永远也还是重要的,不要企图去钻研一切细节,因为很无聊,除非你是在满足自己,一生学习细节,这又是多少无聊的事
明白了抽象,我们明白一个东西,应做个明确这个东西的抽象所属,即它所在的人类范式.
在设计软件时,我们主要用UML工具,但是这东西是静态语言用的

8.1 什么是设计
我们必须明白,设计这个词是一个表示人类活动的词,比如有建筑设计,艺术设计,,而程序设计只是一种。它可以解读为用程序设计语言提供的基本语法和高级语法,以及独立于程序设计语言但跟语言有关的那些手段(比如Corba,设计模式),来进行对方案域到目标应用域的一个逻辑映射(一种能产生最终产品但并不以产生最终产品为终止条件的人类活动过程,它包括选择语言的考虑,应用设计,模型抽象,维护各个部分的整个综合过程,也即设计等于软工)。故涉及到不止程序设计语言一个方面。下面我们将就这些方面一一进行解说。

l 因为程序设计是人的活动,所以它首先是一种对人类活动本身的设计。设计模型的目的正是在于规范程序设计的各个过程。使开发过程可控化,这就是程序设计模型要解决的问题(XP编程啊,瀑布模型啊)。

l 再次,设计需要对代码逻辑的控制,因为是由人来写代码而且也是由人来维护代码的,所以选择一种能切合人类层次又靠近语言支持的方式是必要的,过程式使人类能以发号施令的方式写计算机逻辑。面向对象单方面站在人类抽象的角度将计算机逻辑封装为一个一个的对象。并不需要考虑对计算机的实现过程(当然在每个class内部也可以有过程式),相对面向过程来说,面向对象是更抽象的。当然,这些都是大的代码逻辑控制手段,和高级语法机制,小的可以小到“流程控制,循环”这样的课题。

l 在语言选择方面,如果是通用编译型语言,因为类型是语言表达客观事物或客观逻辑事物的机制(使之成为语言的数据使语言成为一个DSL),所以代码逻辑的抽象首先是一种类型抽象的过程,C++中有OC(面向类化),模板范型这样的对类型进行抽象的机制,如果是通用脚本解释型语言,它将类型动态化,这样就能更好地表达领域词汇,DSL抽象。相比通用编译型语言来说,这二者都存在设计的抽象和实现(因为都存在类型),但显然脚本语言更靠近实现,,这二者的唯一的区别在于,编译型语言用了严格类型的抽象来表达DSL逻辑,而脚本型语言采用了活动类型的方式来抽象DSL逻辑。

l 程序设计还是一种对应用的抽象过程,因为任何软件的编制在于解决目标问题,面向一个目标领域,无论是架构师还是程序员方面,都需要面对程序设计要解决的问题和领域进行抽象,,提供一个科学组织的抽象框架以更好利用复用或更下一步的抽象,软件产品的用户有三,最终用户,最终脚本用户,程序员用户。其中,2,3是能有机会进入到程序逻辑内部的用户。如果是写代码的那个人来维护程序,实际上就是下一步的抽象,下一步的抽象和复用不过一个词说法的二个方面。

然而,虽然抽象过程并不需要预先考虑软件复用能力,但是科学组织了的,松偶合,紧实现的抽象过程显然可以为整个设计中的所有阶段带来好处。

综上所述,无论如何,设计的最终目的是使软件产品具有更强大的生命力,对其内要做到科学抽象和组织,对其外要做到利用复用,易于重构。

什么是重构呢,重构并非重写,重构是面向为已有系统增加新功能或完善已有功能的需求下,在原有系统已经存在的情况下,变更其设计方式(注意设计这个词),修补式地加进一些新的设计元素或去掉一些旧的设计元素(故往往用到设计模式,因为设计模式可以很好解决此类问题,所以在介绍设计模式的一本书里,总是会有这样的问题和需求发生在先,然而才导出一个个的设计模式),重构往往是解决代码维护问题的一个重要方面,当然,正如上面提到的,软件产品在最初被设计和产生时也要考虑到一个科学设计的过程,这样才有利于后来产生新需求和新问题的重构过程。

这里首先有一个实现与抽象的区别所在(人们往往把设计和实现对立而模糊了对实现的理解,其实,抽象和实现才是一对对立体,找准了这个才能正确地理解实现,就像对脚本语言的理解,如果你一开始知道它是系统编程语言的对立体,那么你就不会产生脚本语言是不是一定是解释语言这样的疑问,因为它一开始是非系统编程语言,其使用解释或编译是它第二个要解决的问题所在),但设计和实现无法分界,这首先是因为语言没能为它们提供一个有效区分的机制(Delphi 的单元文件pas中有接口和实现这样的关键字,C++默认将h文件和cpp文件分开,然而这都不是严格的抽象与实现分开的机制和行之有效的方法,这根本是因为这二者根本就是具体问题具体分析的事,无法在形式上进行区别,但提供h,cpp这样的机制也并非错误无效的方法---请参照选读中的“形式主义”),OC可用于设计中的抽象,也可以用于在先前设计抽象基础上进行的下一层抽象(实现)。

那么是不是可以将抽象分层呢,一部分是DSL类,一部分是字串这样的计算机实现类,将前部分看成设计,后一部分看成实现,,但实际上这样也是不行的。因为无法精确给每一种这样的抽象分层,而且即使分层,也是有很大局限性的。

比如在库的设计和利用库进行实现这二个过程中,都可以写函数(以过程范式编码),显然地,库的设计是高度面向复用的,而实现是一种函数调用,然而为什么库的设计中的函数才是设计,而实现中包括了函数调用的函数体就不是设计呢?他们同样是有函数原型的接口的啊。这就说明了语言本身并不能严格化这个区别。能严格化的就是具体问题中的具体区别方法。

这就说明,存在接口的地方就存在设计(因为可供再被可复用,而设计的一个意思就是为二层用户提供复用逻辑考虑的过程,比如接口,我们在比较大的层次举个例子吧,比如用C实现的工具库和抽象库)。我们应该严格在源程序中标志出哪些接口是作为最终工具使用的接口,哪些是能再供抽象使用的接口。

因为抽象最终要形成源程序,要在源程序中反应出来,而源程序是由自己或别人查看的,所以说抽象对设计是至关重要的,因为抽象是择其事物一方面或某维度进行抽象,所以设计不必大而全,那些“理想”靠近现实事物模型的抽象反而不是最好的抽象(比如严格用OO设计一个游戏的图形世界,把每一个树叶都封装为对象,还把导演,这样的概念抽象出来),因为这样的抽象往往太大了,或不必要带了太多错误的臆想。抽象只能是现实事物的一个或某些方面的变形。甚至带有逻辑模型抽象,而不全是实体抽象。
8.2 编程能力,代码控制能力,复用与接口,轮子发明与使用
你应该知道,真正的编程能力不是使用轮子的能力(即不是复用能力),而是将现实问题用编程语言来解决的能力(这就需要你具备设计能力,即很强的代码控制能力,能把现实问题抽象为代码而不仅仅满足于使用别人现成的库)虽然复用能力与设计能力都貌似语言能力,从语言的眼光来看,复用能力是属于设计能力的,但实际上他们还有很大不同的要求有不同的语言能力,有些人具备在不懂STL原理的基础上使用STL的能力,但它们就是不能用STL来实现一个STL。。这就是上述二种能力的根本区别的证明。

(往往我们把复用能力当成编码实践能力,把设计能力当成广义的编程能力)。


有时难以理解的绝对不是代码本身(语言的语法和语句有限),难理解的代码是其中体现出来的设计元素和应用元素..也就是所谓的算法和设计,,有时是设计模式,有时是现实问题模型的相关抽象(其实算法和数据结构也是计算机开发中历史形成的抽象,,即某个“现实问题”,但一般将它们独立出来)。

要知道,程序的编制者跟程序的使用者有时是不一样的,这集中体现在C++的模板的复用上,C++的模板是用来书写高抽象库的好工具,然而类型的泛化意味着通用,因为像STL一样的东西意味着它能并适合于提供某种抽象上的规范(接口),,我们使用STL就不必处处屈就通用了。我们只需要“使用”,(我们实际上也不必提供太多的轮子方面的东西因为我们正是使用轮子,因此只需开发出使用这些规范的实践,实例),就跟游戏引擎和游戏的区别一样(面向通用的库或面向实现特化的库都有)。。因此这二者涉及到的模板编程技术是有高有低的。会复用并不一定会写。当然,理解stl库的实现过程中使用到的模板理念显然可以帮助我们复用这些规范(接口)进行实例化(使用接口)的过程。

实际上,数据结构,设计模式,现实问题这三个东西跟任何一门程序设计语言都没有关系,因为任何一门冯氏模型决定下的开发语言都满足这些东西,,而且计算机界的一切软硬基础都是算法加数据结构的集中体现(再往人类的软工一点,就有设计模式了),我们编程往往涉及三个能力
1, 语言语法的,比如流程控制,数组,IO,字串,
2, 数据结构和算法的。
3, 设计模式的
4, 现实问题的。
用某种语言来编程,体现的代码控制能力只有1是必要的,然而具备了1并不意味着你就能定出某种数据结构,或设计模式,,或抽象出某个现实问题,,因为那根本是另一些领域需要你掌握的东西(而不仅仅是语言能力)。所以你汇编码能力并不意味你有编程解决现实问题的能力。你有编码能力所以一定有复用能力,但你不一定有实现这些轮子的能力(以及阅读这些代码时认识到这些轮子设计的能力,这样你阅读代码的能力和眼光也会受到限制,只限于简单的复用能力,因为复用能力只要求你拥有接口复用能力,,真正的逻辑不只是接口间的简单复用,而是接口间复合形成什么样的抽象以及如何逻辑上形成这样抽象,这就不仅仅是接口复合作用了)。

所以真正的编程能力在于2,3,4,加1编码能力,,所组成的综合编程能力;
8.3 OO,模板,设计模式与设计
设计即逻辑的逻辑,而且是面向人的控制逻辑的抽象即设计的第四种意思是让抽象以人.需要的方式进行组织,即人影响抽象的能力,

实际上设计无所不在,从你写第一行控制流程代码开始,你就在设计了,只不过是隐式的,机器并不会流程控制,是语言赋于你能以人类看得明白的流程设计逻辑向机器发令的。如果说流程控制设计是细小的,那么OO是一种显式化的设计,当你用OO来写代码时你就在设计,虽然你写的是实现,设计与实现间无明显分界,,,你照样是在设计,因为你用到了OO的三重思想,继承,泛化,封装,,这就是控制逻辑的逻辑,,是设计因素。。一句话,设计无所在不在,即使在C那样的紧实现的语言中也存在设计。。

所以存在三种设计方式在C++中,OO,模板和LOKI。

模板的能力在于抽象类型,所以设计的一种意思是类型控制和抽象能力(动态类型更侧重实现谈化设计所以它首先去掉强类型,而提供弱类型或无类型或动态类型),模板只能只做类型控制,然而LOKI可以做到控制逻辑级。这就是单纯的模板用于设计和LOKI用于设计时的区别。。


在抽象类型作为设计手段的方法上,OO和模板是差不多的,然而不同的是模板是“泛化”类型OO是纯粹只用“类型化”来实现设计(对即逻辑的抽象逻辑,我们知道,实现中也离不开设计,设计库的设计就更离不开设计了),,所以OO的设计手段不足,而设计模式是一种新的设计理念,语言并不直接支持,,C++用STL作为语言的数据抽象,,用LOKI作为语言的设计能力(OO,模板相比来说是小规模的设计手段)。

注意,OO的类型化绝对不能说成对象化,OO定义成面对对象是极其错误的译法,,OO是用于设计的,,所以O这个字眼只能是“类型化”的“类型”,而不能译成对象,,因为对象是实现里面,不是OO设计的原义所在。


设计的唯一特征是站在高阶的泛,这就成为区别实现与设计的根本,而不是大小问题的逻辑包含互饰关系(因为这样的话,设计与实现只有模糊分界),而泛成为区别这二者清楚的界限。

为什么template仅有type as type一项就足于跟OO匹敌呢,因为类型就是语言的一切,提供关于类型的机制就是一门语言的设计能力的一切。。

动态类型语言也有类型,,不过它的类型不需要一个设计期的type来确定指定而已,,其本质也是某类型的某个value.

而动态类型没有type,所以没有对数据的泛化,也即无法抽象数据,也就无法抽象代码和控制(设计里的)逻辑,所以无法设计(设计的一种意思是类型控制和抽象能力,,动态类型语言不存在对类型的抽象,所以至少这种意思的设计在动态类型语言是不存在,因为动态类型是DSL语言不是通用语言无需考虑类型及类型抽象上的设计)。它只能在线控制逻辑的运作。

所以动态类型语言是实现语言。。应该称动态类型语言为在线类型语言。模板是离线类型语言。

我们知道设计是复用导向的,最终用户(领域内用户,脚本用户)和程序员用户(本语言内的复用,语言非通用的)是二级不同的面向级,前者用动态实现语言即脚本语言,而后者用静态语言为宜。

解决可复用银弹问题的根本可以改造开发模型,,也可以将设计逻辑尽量多地沿伸到实现,而且是脚本用户级。因为此时没有编程工作,只有配置工作,银弹问题根本不存在
8.4 设计能力和程序员能力模型
设计能力第一个是结合了数据抽象和代码抽象以及语言映射能力到现实问题的解法的能力,第二个是构架架构的能力(提出一种规范一种抽象模型,就像Java规范和七层模型一样,相比第一种设计能力来说,这是高级的能力)。

设计还有一个方面是面向复用的工程设计。就是开发库的设计意义所在,

理解设计对理解C++新思维那样书里的观点有效。

冯氏模型的局限和优点同时体现在数据结构和设计模式上,它使人无论如何都要最小在这二种子抽象上工作,,优点是,统一了开发抽象,,这也使软件逻辑变得统一。这对软工是尤为重要的。

程序员的能力模型,语言30%,数据结构50%,对现实事实的抽象理解能力10%,,设计模式能力10%。。 ==100%。

真正的编程能力是设计能力!!对思维模型的学习!!而非对细节,对平台编程!!事物的OO解只是事物解空间中的一种而已!!

8.4 自上而下设计和自下而上设计
基本概念

抽象在软件开发中的重要性是不言而喻的。如果一个系统有了正确的抽象,那么这个系统就更容易理解,更容易维护,开发起来也更为高效,最为重要的是也更容易把事情作对。Grady Booch甚至认为抽象是应对软件复杂性最为有效的手段。在面临一个复杂的系统时,往往只要再提升一层抽象层次(当然要是正确的抽象),那么该系统就会立即变得清晰、简单,理解、开发、维护起来也更为容易一些。不过,值得注意的是,虽然有了一个正确的抽象后,可以大大降低理解、开发和维护的难度,但是要想得到一个正确、合适的抽象却是非常困难的。



提起代码自动生成,可能大多数人立即会想到这样一种情形:只要获取了系统的需求,那么就会自动地从这些需求生成系统的代码。这种想法固然很好,但是在目前的科学发展水平(这种技术不单是软件技术的问题,它还和人思维的生物基础有密切关系)下却还无法实现。需求和能够自动转化为代码的精确的形式化规范之间有一个巨大的鸿沟,目前这项转换工作还只能由人来独立地完成。因此,这种技术在目前来说只能是一个神话。我们在本文中所指的代码自动生成是针对比较局限的领域而言的,即需求已经被正确的理解并由人转化为解决方案领域中的抽象模型。代码自动生成并不是为了替代人去完成系统的软件开发的,它只是一种支援抽象的工具而已。



领域特定语言(DSL)

其实,大家在每天的软件开发中都在不经意的使用着这项工具。当我们在使用面向对象语言进行软件开发时,其实我们是在一种抽象的层面上进行工作。有了这层抽象,我们所编写的软件就会更有表现力,更为简洁。比如:当我们写下下面的代码行时:class Cat extends Animal。我们想表达的是Cat is a Animal,面向对象语言为我们提供了强有力的支持。而这个抽象的实现细节则由编译器来完成,我们无需关心。这样我们就能够在一个更高、更直接、更易于理解抽象的层面进行开发和交流,同时也大大降低了出现错误的机会。当我们使用支持泛型或者AOP的语言进行软件开发时,其实我们是在另外一种抽象层面上工作。比如:当我们写下如下代码时:

template

T Add(T, T)

我们想表达的是求两个类型为T的变量之和,而不管T到底是什么具体类型。想想看,如果语言不支持这种表达规范,我们要写多少个雷同的Add方法。有了这种表达规范,我们就可以直接、简洁地表达出我们的意图,而具体的转换工作就有编译器代劳了。还有,如果我们在支持AOP的环境中进行软件开发,那么我们只要使用该环境提供的AOP语言规范定义出我们希望的横切关系(其实就是一种抽象),剩余代码的编写和插入工作就由该环境帮我们自动完成了。虽然编译器或者开发环境在底层生成的实际上也是大量重复的代码,但是这些代码的抽象规范却只有一份,而人们开发、维护、沟通所基于的正是这唯一的一份抽象规范,底层的重复实现细节对开发者来说是不可见的,并且是自动和抽象规范保持一致的。可以说,在开发者的头脑中,是没有重复的。从而有力的支持了“once and only once”和DRY原则。试想,如果语言种没有提供这种描述规范,那么我们要编写多少晦涩、难懂、重复的代码才能描绘我们想要表达的概念。



上面提到的抽象是一些比较通用的机制,因此一般都是语言内置支持的。也正是其通用性使其有效性范围受到了限制。一般来说,上面的一些抽象机制是仅仅针对开发者群体而言的,并且使用这些抽象机制进行的表达也是由编译器来自动生成底层执行代码的。但是,还有一种抽象表达更为重要,它的作用是在开发者和客户之间进行沟通、交流。说它重要是因为它和所要开发的系统是否能够真正满足客户需要密切相关。这种抽象表达更贴近具体的问题领域,因此也称为领域相关语言(Domain-Specific Language(DSL))。比如,如果我们开发的是一个金融系统,那么如果我们能够使用一套金融术语及其关系来刻画金融领域中的一些业务逻辑,那么不但表达起来会简洁、直接得多,更重要的是客户也更容易理解,和客户沟通起来也更为容易。再如,如果我们要开发一个科学计算系统,那么如果我们拥有了一套描述科学计算领域的词汇,那么在表达该系统时不但会容易、自然很多,而且也更加高效。有了这套DSL之后,剩下的工作就是要自己实现一个编译/解释器,来把DSL自动生成为目标语言。由于这个DSL一般都局限于某个特定领域,因此其编译/解释器实现起来也不会有多大困难。



敏锐的读者一定会发现,我们在前面列举的支持面向对象(OO)、泛型(GP)或者面向方面(AOP)的语言,其实也是DSL的一种。只不过它们所针对的是更为通用的,和软件要面临的实际问题领域无关的领域。它们的作用是为了提高一些通用问题描述的抽象层次,并且也为构建更贴近问题领域的抽象提供了基础。



自上而下 还是 自下而上?

写到这里我突然想起一个非常有趣的问题,那就是大家常常争论的自上而下开发和自下而上开发。一般认为,自上而下的开发方法更具目的性一些,也更为自然一些。其实自上而下的方法是有很大风险的,一是往往很多预先的设想很可能本身就是错的,一是这些预先设想落实到底层时要么无法实现,要么无法很好地适配。其结果就是生成一个具有大量冗余、丑陋粘合层代码的系统。而采用DSL思想的自下而上方法则具有很多你可能没有想到的好处。你可以先实现一些DSL中的单个单词,然后再实现一些更大的单元,并试着把这些元素组合为更大的领域逻辑,按照这种方法实现起来的系统往往更加简洁、清楚、干净,甚至更加易于重用。一般来说,如果你想采用DSL的开发方式,动态语言大有用武之地(Python、Ruby都是不错的选择,在www.martinfowler.com/bliki中,Martin Fowler对动态语言和DSL的关系进行了深入、有趣的描述)。



自下而上的做法实际上是在改变、扩充开发语言,使其适合于所面临的问题领域。当你进行软件系统的开发时,你不仅仅只是把你所构思的程序直接映射到实现语言,同时你还不断对语言进行增强,使其更加贴近你所构思的程序,并且表达起来能够更加简单、更加直接。比如:你会想语言中如果有了这个操作符,表达起来就更加清楚、简洁了,那么你就去构建它。这样,语言和程序就会一同演化,直到二者能够形成一种完美的匹配关系。最后,你的程序看起来就会像是用专门为它设计的语言开发的。而当语言和程序能够很好地相互适合时,所编写的代码也就会更清晰、更少、更有效。



值得注意的是,自下而上设计相对于自上而下设计来说,并不意味着用不同的顺序来编写同样的程序。当采用自下而上设计时,常常会得到一个和自上而下设计不同的程序。所得到的不会是一个单一的单片机(monolithic)程序,而是一个具有更多抽象操作符的更“大”的语言和一个用该语言编写的更“小”的程序。此外,这个语言的抽象操作符也很容易在其他的类似的程序中得以重用,所编写程序也更加易读、更加易于理解。



还有一点值得提出,那就是自下而上的开发方法可以尽快地得到来自代码的反馈,可以及时进行重构。我们大家可能都已经知道,设计模式一般是重构的目标,这里我想特别指出的是:DSL往往也是很好的重构目标。



抽象、库和DSL

C++之父Bjarne Stroustrup经常强调的“用库来扩充语言,用库来进行思考”( http://www.artima.com/intv/elegance.html有近期对Bjarne Stroustrup的采访,他再次强调了这个问题),其实就是在强调DSL以及自下向上开发的重要性。库就是DSL的一种形式,Bjarne Stroustrup所列举的科学计算库的例子就是科学计算领域的DSL。构建库的过程其实就是在朝着更加贴近问题领域抽象的方向迈进。明白了这一点,我们就不难理解Bjarne Stroustrup一直强调的泛型在构建一个优雅、高效库的方面的重要性了,因为泛型为构建这种抽象提供了一个坚实的基础。



不仅C++如此,其他一些语言(比如:Java)也拥有众多的库。这些库在我们进行软件开发时,为我们提供了强大的支持。不过,这些库往往都只实现了它所针对领域的一些通用基础性的问题。因此,在某些特定问题上,可能无法直接地使用这些库来进行表达。此时,你就应该考虑建立一个特定于自己问题的特定库了。



结论

作为开发者的我们,该如何做呢?正如本文开始所说的,抽象固然很好,但是要找出适合于自己手边问题的抽象是一件很困难的事。它应该是一个不断从代码编写中得到反馈,然后再修正,接着再编写代码的循环反馈的过程,这也是我为何认为自下而上开发更为有效的原因。我们不应该局限于仅仅会使用某一种工具,某一种开发环境,而应该多想想这些工具、开发环境背后蕴涵的思想。我们应该注重于培养自己发掘抽象的能力,这方面能力的培养既需要很好的和客户沟通的能力,同时还需要有坚实、高超的软件能力。



此外,近期热的不能再热的技术MDA,其核心思想其实就是DSL,不过我对于其试图使用一种语言来刻画所有领域模型的做法并不看好。与其使用它,倒不如逐步演化出一个特定于自己问题领域的Mini DSL和一个DSL编译/解释器,并基于这个DSL来进行开发,这样或许更为直接一些,更为轻便一些,更为清晰一些、更为有效一些,更加具有针对性一些,也更为经济一些
8.5 大中型软件和复用与逻辑达成
我们知道一个库的发布总是带一个samples目录,然而那是小例子,,大中型程序是怎么开发出来的呢,,你该如何着手写一个大型的程序呢?一般来说是找轮子和写实现。

如果开发中存在造轮子的工作,那么很多逻辑都在这里面,,就拿开发游戏引擎来说吧,引擎部分总是要考虑进很多逻辑,有时是复用别人的库,有时是很多预考虑的逻辑,有时是设计模式方面的逻辑,考虑OGRE就知道了。

就复用逻辑(比如一个使用了OGRE的游戏的实际代码)来说,,它往往是OGRE中的samples演示小程序,如果是一个用yake写成的大型游戏,那么起码可能还存在造其它轮子的过程(比如你还用到数据结构,而不想用STL,那么你需要自己设计—这是在造轮子了,或直接复用GLIBC),,逻辑间的大量复合,和与其它维度上逻辑的结合(比如不仅是图形方面的,还有网络方面的代码等),就会构成一个大型软件了。

然而幸好有一个YAKE的程序考虑进了几乎你能用到的很多库,,当然如果你想用其它的库替代,或者加进其它的库逻辑,或者自己在复用中会想到自己开发自己的库逻辑,也是完全可行的。但是我们一般不改造YAKE(因为如果它不提供SRC我们就无法改造,其它很多库都是这样的)。而是拔插使用其内组件库。。

所以大中型软件的形成,,有的是库逻辑和实现逻辑,即设计能力,,有的是复用逻辑。。即编码能力。。因此那些说程序员是简单的复用员的人是可笑的,,因为一个程序员在编程中总会涉及到设计能力(即那么比如设计一种数据结构库的能力)。。。

所以什么是设计能力就出来了,它泛指一种真正的编程能力,即将现实问题映射为语言逻辑的能力,(找轮子是作设计的一个步骤,),那么设计能力要求你有什么样的能力呢,,当然,现实问题的模型你是要清楚的,而且语言逻辑也是你要清楚的,复用的库你要从语言的观点去搞清每一个接口的真正意思(无论用什么语言,,数据结构,设计模式和语言本身这三者的逻辑是不变的,,其它的就是具体问题映射到语言的能力了),在整个设计过程中(映射中),,你必须清楚每一个步骤,,因为软件设计真的不像拼零件(强烈鄙视这种用在小打小闹模式上的接口拼起来的软件上),,大型的软件接口太多,,你必须熟悉每一个功能模块,,和预见每一个功能接口。。这种细节的无限性决定了软件的复杂性和设计实现时的清楚性,,否则就是编译不过,不成软件。。那意味着项目失败

架构师的能力就是设计能力,,,程序员的能力就是编码能力(然而在每个小问题上每个小模块上也存在设计,所以程序员也是一个设计师,不过他不直接面对复杂的大系统而已)

8.6 通用设计与专门设计
像C++,C这样的语言都是被设计为通用的,要通用,因此往往基于某种靠近计算机底层的离散形式,而DSL实现特定领域事情的语言,(相对C++,C来说)不强大,不深入底层,不能控制计算机干任何通用事情,因此往往基于高层模型,,
因此,C++,C这样的语言必须要涉及到汇编原理里面的东东,而DSL可以以任何高层的形式被体现,比如不需要编译的UML图都是,POWERPOINT代码都是DSL,根本不需要编译器这样的图灵完备装备
这就是脚本语言比不上编译语言这样的语言对计算机编程方面的功能强大.因为脚本语言的虚拟机往往是高级机器,根本不像我们的硬件机器那么底级,图灵模型对应我们的硬件机器和架构,而虚拟机往往跟硬件架构差别过大,因此脚本语言和系统语言是为二个不同的机器设计他们干的事,,,,而一般虚拟机作了高级逻辑,比如GC等,而X86不可能在硬件架构级就用了GC,如果CPU芯片可以用硬件加速的方法直接支持语言的垃圾回收机制就好了,这要求语言跟CPU一一对应,,而且OS也提供了大局方面的GC(并不仅仅针对某个语言,比如WINDOWS的操作系统级的资源释放)
C跟C++到底有什么区别呢,我觉得第一个加号是一种理念上的叠加,第二个加才是语言要素上的改变,C跟计算机离散和底层接近,解决的问题是如何实现,专注于计算机对问题的实现,因为C语言就是机器的观点
C++跟人接近,解决的是如何更好地复用,关注于问题本身,脱离了实现逻辑如平台逻辑,人们如何更好地写代码服务工业化,OO就是人类的观点,C是实现域扩展,C++则完成了一个从实现域到问题域的一个维度变换而已,
C和C++解决问题时,,站在二个根本不同的维度而已.
8.7 具象与抽象
抽象源于一个简单的事实,把事物从逻辑上分开,这样就会解偶它们之间的联系。

抽象要适中,不能抽象得太像了,为程序员复用的相对低阶接口变成了为脚本程序员的高阶接口。

复用由二次开发的程序员决定(这个事实决定了你将向它们提供什么大层次上的功能比如ogre的复合模式,大的逻辑或小的接口),所以复用是程序员面向的,纯dp的设计方案要求二次开发的程序员也要了解DP。
对api的调用在此设计的最下层,由二次复用者提供。(即数据由它们提供,代码逻辑由我设计)

其实你可以将复用抬高到场景这样的层次(这就是领域逻辑,当然设计也可不深入到这么高的境界,这就是代码模式,设计模式和现实模式之间的整合设计所在),这是由复用面向决定的即需求决定的。而不仅仅是单方面的设计。

首先来对游戏进行划分。
8.7 架构与应用
因为功能是逻辑叠成的产物,越到底层,它对高层的逻辑依赖应尽量少,因为高层往往是应用逻辑,而底层往往是功能逻辑和业务逻辑(高层和低层通过一个隔离层意义上的逻辑来达到功能上的完成但又不致于增加这二个层次上的复杂性,使得在这二个层上的工作可以分别完成),因此底层核心架构应尽量简小精短(比如linux的core,但其中的应用可以无边无际),但是简小的同时却要提供最大程序上,或全部程序上的可扩展性,这样在完成了整个core后,后面的功能慢慢趋向应用时,就可以不致于改变到core,也即这个core可适应一切小,中,型的平台,不必做重复工作.或者即使需要被改变时,涉及到这个core的修改量也尽量小.
对底层的逻辑应尽量提供迂回用的接口,迂回即抽象,增加了迂回即增加了另种一种维度上的抽象可能性,而维度永远无穷无尽,因此抽象以任何一种姿态被创建都可以是一种新的抽象,我们用代码的形式表达我们所需要的功能,即用计算机能处理代码的本质维度摸拟人的思想功用的维度,这就是"原语设计"的概念所在
逻辑的抽象粒度永远有它关于具体的复杂性,我们不能定义一个具体的逻辑块,指明它为core或者还是应属于core,但是正如这句话说过的,问题有它自身的复杂性,因此我们应具体问题具体分析
这是哲学所指明的,哲学不是人的东西(虽然它是人发现的),?
底层完成了(底层往往是一些首先要解决的逻辑或者在设计意义上具有优先产生其它事物的概念体),其上的应用可以无边无际,但应用应尽量追求形式简单,一个合理的架构和其上发展的应用对于人来说应越来越简单,,,这才是合理的,我们不能掌握的复杂度应尽量隔离.因此我选用没有历史复杂性的open jdk,gnu c,拥护开源的linux
.
8.8 应用与设计
应用越来越接近人了,比如web2.0,3.0的出现,这是指软件产品的应用,实际上在软件被作为产品被产生出来时也作了靠近人的调整,编程领域的三个东西,问题域,方案域,人都在相互影响,这种影响产生了一些技术,导致了一些编程的变革,并最终与人们的观念结合,比如OO,比如设计模式,这也将导致架构变成软件的功能性"实现"要考虑的,在某个维度上加深了复杂度,然而却在另外一些维度上提供了简单的形式和更有效的应用
互联网的最初灵感来自对学术自由和开放的向往,而现在它已成为由企业和运营商控制的商业平台。企业应用与网络的发展密不可分,这二者相互发展,成为影响软件工程界的二大主力
多维这个字眼本身就提倡从多个方面(可见多维就是多方面,当我站在某个维度为我自己说话时,我将同时失去另外其它的维度)
某些东西越来越统一和规范了,这加大了学习的门槛,比如xml出现,就统一了文档交互的格式,并导致了很多逻辑知识,产生了一些新的逻辑,需要被学习,但这是合理的,因为形式更加简单了统一了,并改变了一些应用的形式,比如软件分发delopy的统一形式等,
另外一趋势,应用越来越分布了和趋向web,这实际上是很多年前某些大公司的战略,总有那么一群人(有些人研究应用形成架构,有些人研究编程低层形成架构和思想),先知先觉地认识到一些东西,比如.net的出现,网上的资源服务器越来越变成一般应用服务器,富客户端的flex,silverlight等等,只是它们是慢慢被民间所识所学习.
一切技术都是面向被应用,因此人无论如何都是主导.将反过来最终影响技术的被利用形式而隐藏了低层实现,一些离最终应用跨度太大的低层实现不必知道其原理,靠近人的一端要提供尽量简单的形式,比如xml,比如oo,面向机器的一端永远有它的实现.
8.9 与软件有关的哲学 联系
范意上的设计是广泛的,不仅限于计算机的,也不限于软工抽象(软工和计算机是二个完全不同的抽象,虽然没有人提出过计算机抽象到底是什么,软工抽象到底里面有哪些抽象存在,我们仅能站在某个或某些维度上给出一个描述性的概念而不是有限集,如果能站在一个大全的维度上说明到软工的全部抽象,虽然这是不可能的,但我们还是给得出的这个结果取个名字,叫范式,范式总是某些维度上的产物而不是大全的维度产生,学习计算机的很多哲学思维可以解决其它域的哲学),设计并不仅仅面向于创新,有时是形式的重组,而不是内容的创新,应用形式的改观,设计出的产品,要源端是面向人的,因此要提供足够简单的使用和访问形式,在目标端是要达到足够丰富的应用逻辑(比如XML统一文档交换,但可由node,root这些形式导致足够丰富的深层功能,深层这里是指上层),因此越复杂越大而全越好(但是如果没有足够人力,我们应考虑设计出别人想不到的商机),应用形式和应用逻辑作为设计中应主要考虑到的问题,
在设计软件时,我们主要用UML工具,但是这东西是静态语言用的
.
8.10 与软工有关的哲学 唯物主义
没有绝对科学的东西科学,维度给了我们一切答案,永远有更偏更僻的维度存在,因此拿唯物主义来说,如果它仅仅是表达那么一种"理"(它仅仅只需要说明这点),而没有宣扬它是正确的(实际上唯物论本身站在另外一些唯度上看就是片面的,而片面就是一定程序上的不正确),那么唯物论在这个维度上就算是做到科学了
唯物主义表明事物是不以人的意志为转移的,但是这里面的最基础的一个说法都不科学,什么是事物?哲学都没有从一种形式上去说明这个问题(就像字典内对"强间"应如何定义呢,法律范式内应如何对这个词进行界定呢)
当然我们讨论问题时并不需要把所有涉及到的逻辑都拿来解释一通,但是作为哲学维度的"事物"是应该被深入和专门界定的,而且"事物"这个字眼本身就是不严格的,而对象对象中的OO中的O,在计算机内存中,它是一个运行单元,在表达出的思想所指时,它又是不可形式化的.(在内存中总是那种形式,然而却可以产生不同的逻辑,这就是形式与逻辑的关系)
如果我们曾把生命中一段时间用于考虑这些哲学知识,那么我们就会与常人"分裂",正如写小说的人如果想说出与众不同的小说故事到达到无人深入之境,就必定要付出和得到一些灵魂,,产生排拆他人的想法,但是要知道,世俗的维度是我们生活所在的维度集,我们如果能认识到这种客观性,并尊重它,那么我们就不会分裂,
人只能是简单的,人只能是一个行者,在有生之年接受有限的知识,进行自认为或世俗认为正确的观点和作法并行动.而不可能永远是一个哲学者,没有人有足够的生命力来最终解释自己,得到的解释也只能在一个维度上成立而在另外一个维度显得可笑,,选择需要学习的人类知识去学,不要做一个大而全的学习者,在利用所得知识进行设计自己的应用时,应根据经济学所指,做别人没有的东西,才会显得有优势,你不必在任何一个方面都出色,但一定要在一个方面最出色(实践要达到"别无它,唯手熟而这样的境界"),这就是你的资源优势,可以转化为经济优势
逻辑

很多逻辑的意思都是不可言传的或者难以言传的,所以要看别人的代码时,除非别人在类文件形式的逻辑,组件形式逻辑的命名上直接让你明白很多信息,?否则你就不能有效地明白作者写这些程序时的想法,所以读人家的程序是难的,因此虽然你是面向源程序形式,但你其实是在求索别人的思想,更要命的是,你明白了这个类文件是什么意思,你还不能很有效地告诉别人这个类写了什么东西,,体现了什么逻辑,这是感觉是窒息的
软件的本质就是一种逻辑的叠成物,,某某牛人说过,一切问题和功能的达成都是逻辑!!软件的眼光里,一切皆逻辑,,在这个软工年代,最终的产品是多人合作代码的产物,因此相互之间能明白对方的源程序里到底能提供什么功能是重要的,,在以前的程序="数据结构+算法"的年代,程序员之间很容易在这方面获得沟通,而在现在这个软工年代,程序=数据结构+算法+架构"的年代,你还得明白人家程序逻辑是如何组织的,哪些是功能级的实现,哪些是架构级思想级的东西(这些东西为了以后的可扩展而存在,虽然一定维度上也可以称为功能级的实现),,

所以逻辑该如何命名,,我们只能满足于用特定的,固定的形式去描述性地表达它们,比如用类文件的名字,组件的名字,,设计模式的中间用词,等等.
8.11 真正的设计模式
我讨厌在讲授一些思想的时候提供大量的代码,因为我觉得接受思想的过程应该是一种快乐的如同阅读一本小说的过程,而不是花大量脑力研究某个细节的过程

而且这个世界,往往知识都只是相互转载,国内没有多少人会像那些欧美大师特立独立发明一些新的思想和论述,而我愿意写出我的一些思想与你们共享

每一个设计模式中都出现了一些角色,然而使用某个设计模式的个体(Client)不属于设计模式的某个角色,而只是使用这个设计模式的客户,,设计模式的目的就是为客户提供一个好的对现有对象的访问方法,设计模式是一种高于任何成码的思想和经验模式,因此不能直接用某个工具建模下来,在使用设计模式的过程中,,,总会产生一些新的抽象(而且有时不只一层抽象),,这些抽象隔离和解偶了客户(Client)与现有代码之间的关系,,,在它们中间作为中间抽象出现,而所谓抽象,,往往都以一个类的方式存在,(因为JAVA中一个类默认只承担一项责任或实现一个对象数据描述,因此一个抽象往往就是一个类,当然,抽象有时以方法的形式存在,某个设计模式也会以方法的形式存在,比如工厂“方法”模式,一般来说,设计模式都会形成某几个抽象类,对应该设计模式中的几个角色Actor),,

设计模式终归是一种迂回的方法(因为增加了抽象所以代码变得有点难于理解而且类层次增加这变得运行时变慢了一点),,然而这种方法成全了一种好处,,那就是:它部分或完全都解偶了使用者与现有代码之间(实际上设计模式可用在开发的各个阶段)的关系,,,这使得以后的对软件的维护工作和修改需求变得易管理和易实现,使软件不致于由于当初设计上的欠缺而变得难于修改而濒于死去.
8.12 设计模式与数据结构
对于设计模式,一般有下面几种理解: 重构时的设计模式。修补式的设计模式(client角色浓重) 大设计时的设计模式。全新式的设计模式(可以没有client角色在某一套模式中)

其实。不妨把设计模式称为抽象模式更好(我们知道抽象问题领域是设计中的一个重要步骤),,因为它更多地跟着眼于解决具体事物有关(就跟数据结构一样,不是跟具体语言有关,不是属于某种代码结构。)正如数据结构是择“数据”这个维度来抽象对现实事物映射到计算机解法的做法一样,设计模式是择“模式中的各个角色和关系”来映射对现实事物的模型,从而求得一个现实问题到计算机的解法一样(就目前所提出的一些设计模式来看,他们都是抽象现实事物模型的初步组件,一般有倾向于用面向对象语言来实现的趋势比如四人帮那书)。数据结构和设计模式都不会跟某种语言和语言机制有关,跟“面向对象”这样的代码抽象有本质上的差别,是实现模式,实现结构,而不是代码结构。着眼于如何解决和抽象问题,而不是如何抽象代码以进行更好能被复用这样的软工目的(当然,这二者是不分家的)。

我在《OO为什么不是银弹-过度抽象的利与弊》中谈到,OO并不是银弹,银弹是那些能统一人类思想,形成契约文化,经验的东西(比如我们写小说的那些套路),而不是简单的class这种面向复用的小技俩。 设计模式正是上述所谓“契约文化,经验”之类的初步体现(不可否认,我们所看到的设计模式跟具体现实事物还是有很大距离的),等到有一天,所有的问题都用设计模式来抽象的时候,成千上万的设计模式会被提出来。人们会倾向于用大大小小的设计模式来解决问题。那么设计模式就会到达它的颠峰。

然而对于程序员来说不利的是,数据结构已经被很好地映射到C语言中,而设计模式几乎在C语言中找不到它的影子。这正是它不成熟的地方。也许有一天会有一套“设计模式”专用语言出现。
8.12 设计模式之基础
研究数据结构我们目的从来不是那些底层的东西,,而是抽象的比如优先队列,多重集等。

四人帮的那本书是基于面向对象来谈设计模式的,因此它先提出一些面向对象的知识,比如一个类的class和type(interface)的区别,提倡对接口编程而不是对class定义即实现编程,提倡对象的组合而不是继承。而且它稍后提到的诸多设计模式中,都有对象,职责,请求,之说,这些都是OO里面的知识。。

不要小看了这里的对象组合它实际上是对接口编程的小化说法

8.12 真正的开闭原则
我们应该对扩展开放,的同时(注意这三个字),,,保证对修改的关闭(一个工程,应该在设计时就要考虑到将来修改的需要,而且要保证未来修改时能尽量降低工作量,对于一个真正的工程级的规模,人力管理工程应该尽量简化),
几乎没有接确过设计模式的人(除了完全外行和真正的编程高手外)看到这句话都会感到疑惑,而且会产生一个很普遍的疑问:不修改何来扩展?
高手与低手的差别就在于这里,高手往往看重的思想(即设计能力,,,识别架构和建立构架的能力,但是因为计算机能理解的设计只能是多范型设计,因此高手着重的这种思想往往也是受计算机实现的限制的设计),而低手考虑问题的第一切入点就是源码本身,因此产生“不修改源码何来功能扩展”的疑问也就很自然了
而其实,在高手的眼里,只要定义一些抽象,产生一些迂回就可以解决问题了,这些迂回(实际上就是产生一些高层逻辑,这些逻辑就是具体某个设计模式中的某些角色Acotr)可以让我们clients通过这些高层迂回避免直接接确到低层的实现(虽然我们client无论如何最终是要进入到具体实现的,但我们可以不直接而间接迂回地进入啊!!这些低层的实现就是现有代码了,是实现部分(可能是某个你要使用到的第三方库代码),我们经常要对实现部分修改,或者说对现有代码的修改,而要求要有最少的工作量,而一个没有定义抽象或者没有定义好合理抽象的工程要涉及到很多修改工作),而这,,真真实实就是解偶的意义所在。。

我们再来说这些逻辑,其实这些逻辑都可以称为中间逻辑,,然而这些逻辑的地位又是不同的,,有与具体实现接近的那一端的逻辑,,这些逻辑也是高层逻辑,,但是把与接近client使用者的逻辑看作为相对更高层的逻辑。。

说个故事吧!
《西游记》大闹天空时,要求当天庭大官,太白金星向玉皇献记说让孙悟空当弼马温,太白金星的智慧就体现了开闭原则,一方面,在孙悟空方面,太白向孙悟空说明玉皇已同意他上天(对扩展开放),,另一方面实际上只是给了他一个放马的差,实际上按天庭规则(系统原有结构)孙悟空是不能上天的(),然而迫于孙悟空的力量(修改的需要),太白只是稍微迂回了弯子(增加了一层抽象),就暂时平息了玉皇(玉皇本人不知道如何扩展这个需求,因为这是与天规相背的)和孙悟空二边.
8.13 真正的通米特原则
之所以不称通米特原则为通米特法则是因为在设计模式领域内实在不存在一个法则之说,
通米特法则也称为最小知识原则,一个事物对另外一个事物知道得越少,那么它本身就越安全(这可以联系武侠小说里小人物碰巧目睹了对杀手杀人的整个过程,那么这个小人物就会有杀身之祸,),

这里的安全是指对修改关闭

实际上无论对象组合还是继承都会造成类与类之间的引用,都会造成不可复用的问题,然而,相比继承来说,组合可以极大地减少这种复用的偶合程序,而继承压根就是不可分离的,因为本质上组合是一种Has-A的关系(组合对象与被组合对象),而继承关系是一种Is-A的关系(基类与继承类,或称父类与子类,注意这二个概念还是有点区别的,一般说到父与子关系时就是指父对象与子对象,而说到基类与继承类时往往描述类与类之间关系的用词~~)

还有一种关系是Link-A的关系,这种情况下的不可复用性按情况下来定,,Is-A的准确意思是什么呢(这里的意思指语义)?如B is a A,,那么“B是一个A”,,可能是一个A,但是不一定必定是一个A.而且如果B是一个A,,那么反过来就不能成立(子类化,虽然站在类型转换的场合下可以但是现实生活中这样理解不通)

一个代码的修改量应只取决于它最低层的实现,如果某个低层引用了过多高层逻辑接口的实现,那么这只能说明,对这个实现的解偶还没有规划到家,理想的情况是,应该只让这个实现的修改不触动到任何间接使用它的高层逻辑!!(因为自顶向下的引用对于顶来说,如果底部被修改顶部是不用作任何改变的,而如果是自底向顶引用,那么当底发生改变时,一定要涉及到顶部也要改变,,而这就是不恰当的高层抽象,违背了好莱坞原则和通米特原则)
.
8.14 真正的好莱钨原则
好莱坞原则(不要给我打电话,我会打电话给你们)强调高层对低层的主动作用,即低层应该只管好自己的工作(具体实现),而高层自有它自己的工作(这就是管理低层的逻辑们,或者说从client到具体实现的一系列中间逻辑),在不需要到某个低层的时候,高层并不会调用到这个具体低层,低层永远不需要向高层作出表示,,说它需要被调用,,(即在所有的处于使用者与现有代码的中间的,用于隔离和解偶二者的,那些中间逻辑中,低层逻辑永远不要涉入高层的实现,而只要高层通过某个逻辑去涉入低层的实现,也即低层应不要调用高层,只有高层才会去调用低层,这才是合理的,我们应尽量避免向上调用和相互调用).
8.15 真正的策略模式
Open和close一点也不矛盾,当它用在同一个架构上,open指出这个架构的可扩展性,而close指出这个构架的内敛性,open是相对高层来说的,,而colse是相对内部实现来说的,,,一个构架应对高层open,而对内部实现close,,,

策略模式将可变的行为集封装起来,这符合OO封装“可变部分”的原则,可变部分就是实现,我们修改一个软件直接修改的就是实现,而非抽象(实际上也不应该也没有必要对抽象进行修改,如果你的工程存在对抽象的修改,那就只能说明,当初在定义抽象的时候压根定义的抽象就是不合理的抽象,真正合理的抽象将使用者客户和现有代码极大地解偶,这使得以后的修改工作只需在低端实现进行而无须触动高端).
8.16 真正的观察者模式
好莱坞原则指出,类之间应尽量避免低层(实现)向高层(抽象,逻辑)的引用,
观察者模式中,观察者,被观察者,一个被观察者管理诸多对象(观察者),这些观察者通过
.
8.17 真正的装饰模式
装饰模式就像是一个用类来修饰类的机制(这就添加了新的职责到被修饰的类,,这里说的修饰本质是什么呢?就是类的组合,让一个类被修饰者成为修饰者的一个实例变量),,,这要求修饰类(可能是多个)和被修饰的类有一致的接口(也即它们同共都曾实现implent了某个接口,或者继承了某个有接口作用的抽象类extend,,这样一来,就可以在动态运行时用一方代替另一方,然而客户并不会知晓其发生过内部的替换)

装饰模式可以让很多具有对等地位而且拥有共同接口的类进行有穷互饰,这样可叠合多个类进行某个共同的接口作用,并获得最终的修饰过的这个成员作用
.
8.18 真正的单例模式
某些只能够拥有一个实例的类对象必须通过某些方法来保证它在程序运行期只有一个单例,而且,更重要的,,必须提供一个全局域访问入口,这个入口必须是类层的,,

通过这个全局域访问点,你可以直接调用类的某个机

因为它的产生实例的构造函数是私有的,只能从类的内部去产生和获取这个实例,换言之,你不可以通过继承或组合的方法去获得一个实例,而且这个方法往往被定义为fina,,也就是C++语言中的CONST,即子类不能覆盖它,

因此,可以用类方法(也即静态方法),这种方法下,从继承

.
8.19 真正的迭代器模式
如果你知道什么叫递归和递推,那么迭代器本身这个概念你是很容易理解的,迭代器跟集合(集合就是通俗意义上的对象集合,虽然存在很多不同质的集合,比如用数据结构表达的对象集,或者用函数索引的hash集,但是只要是集合,它的内在总有一些对象及对象逻辑,对象逻辑就是操作这些对象的根据,比如遍历算法,而至于本象本身,可以是无意义的对象,或者同性质的内存节点,或者离散的东东,然而上面说了,这些集合内部必有一种方法作为逻辑可用来遍历他们各自内部的对象,)的关系就是:无论是什么集合,它都可以把一种抽象抽象归纳出来,就是遍历它们各自内部对象的算法
所以,对抽象的提取,往往是找相同的部分,把这些相同的部分提到高层,而用这处抽象来封装可变的部分(这里指各个集合内置的不同的遍历算法),这样就形成了一个所有集合能共享的遍历接口(当然这个接口并不为集合所用,集合自有它们自己的遍历算法,而是为client所用,不同的client都能面向和共享一个共同的,使者这些集合来进行遍历集合的算法,而不必管这些集合自身具体是如何遍历它们自身的元素的)

.
8.20 真正的工厂模式
工厂模式用来实例化对象,,,可被形象理解为一个封装了专门用来产生对象的某种逻辑(这种逻辑可以是一个方法的形式存在-这就是工厂方法模式,也可以是一个类的形式存在-这就是简单工厂模式),因为大凡产生对象的过程都是低层的(调用New方法实际创建实例对象,属于实现),它压根就不应该跟高层(这里的高层指的是需要引用那些实例或间接引用到那些实例的抽象或更高层抽象,由于一个类只能负责一种责任,一个抽象只能被作为一个类,因此当有多个抽象存在时,有必要将它们按职责分成不同的抽象层次,形成不同的层次类放在一起.

这个道理就像:我们生产出一系列的东西(我们当然可以把这个产生过程直接放置到某个未来应用中-这个未来应用要使用到产生过程中产生的对象,这样一来所谓的“某个具体未来应用”就会跟产生对象过程直接挂钩,因此我们把产生对象的过程独立出来,归纳它为专门的产生对象实例的过程,而应用这些对象的一些应用--虽然不知道未来会有多少应用会存在,而这个“不知道”的说法,本身就反应了它符合未来的扩展性--放置到另外一层去),然而会有其它一系列
.
8.21 真正的门面模式
门面模式也称为外观模式,它提供一个易使用的接口作为它的外观,只是为了使现有代码client和要使用到的对象集(往往是多个具有不同行为不同接口的对象)通过这个接口(制造出的目标接口)能被更简单地使用而已,也即打包某些对象行为(并透露出一个基于高层应用逻辑上的接口),常跟适配器模式放在一起被讨论,因为它们都是为了提供接口而存在的,,适配器模式是转换接口为了“能够被使用”,而门面模式是简化接口为了“更好地被使用”(让被适配对象被client被使用,通过一个目标接口-注意这后半部分的说法才最最重要的)

.
8.22 真正的命令模式
将命令本身封装起来作为一个对象,让它的调用者(注意这个调用者不是客户Client,Client是模式之外的使用者,而是命令模式中的一员Actor,是这个命令模式抽象层中的一层)和命令对象通过对象组合的方法

8.23 真正的模板方法模式
模板方法用一套模式作为定义方法和行为的大致框架(注意是大致,而不是全部,这个机制就允许挂钩,和一些需要它的继承子类实现的抽象方法),这跟策略有一点相似之处,因为他们都封装了作为可变部分的行为,然而它们之间还是有差别的,

然而,模板方法使用继承模式,而不对象组合模式,模板方法因为是一个抽象方法,因此如果有子类继承它,那么这个子类必须要实现这个抽象方法

8.24 真正的适配器模式
Adapter,不是接口的意思,它更准确的意义应该是适配,真正的“接口”在不同的应用场景下有不同的意义,现例举如下:
1,Java的一种机制,这种interface语法是一套抽象机制,如果实现
2,接口类,这些接口类往往是抽象类,
3,二进制复用的接口,比如COM,也就是构件接口
4,接口方法,某个class非private的方法(无论是抽象的还是带有实现的都可以称得上是一个接口,一般是指抽象的成员方法)API都可以是一种接口
5,逻辑模型,通俗意义上的“抽取归纳”某个接口,或者说是高层入口,通过这个高层入口,所有的

以上只是为了不跟OO中的接口相混淆,所以强行把适配器模式说成是适配,,其实适配就是适配二个拥有不同接口对象的接口对象(也即这个产生的目标对象“接口对象”也是一个接口,)

8.25 业务与逻辑分开
业务就是你做软工的设计阶段时,所要明确的"逻辑本身",界面就是"表现此逻辑的应用形式"(面向用户的一端),也即逻辑是"我们要搞清的问题"(面向低层的一端),要解决和面向的问题领域,这个所谓的"问题"是严格的,它决定了我们在编码要体现什么样什么维度上的功能.因此在设计中,"搞清你要实现的问题"永远是重要的
另外一个概念是数据,数据处理逻辑要做成独立于界面和逻辑的,此时要提出一个架构,进行新旧系统的分合与整离,让应用统一于某种低层逻辑或界面形式,或由这种架构创建新的应用.
现在的网页,即使它用到xml做数据源,也是不完全的"业务与界面分开",我们应保证"如果一个页面被刷新,那么那些不与数据相关的界面元素根本无须变动",这就是彻底的分开
这种理念可以让网页反映速度提高很多倍,,应用的多元化绝对是可以被统一的,只要你能提出一种合理的架构,架构的提出不仅是一种IT观念改革(对某个抽象有了新的认识),而且是一种极大创新的活动(人们可以由此发展出很多改变了形式的应用).
多少人明白domain这个词的意义呢,如果泛化起来,会是什么意义,任何问题求本溯源就是一个世界,一个领
域)其实domain这个词是在原语领域描述事物分类的,每个事物都有一个name,受某个domain类name来管理,因此,以什么粒度以什么元meta来分类事物并命名以产生一个命名机制呢,就是domainname这个词的由来,(元是老子提出来的,古人希望把世界的本质用元这个形式形式化下来)

Java的源程序文件夹也是这样,你难得找到一种命名为你的所有大大小小的逻辑命名并人为区别,所以sun找了一个domainname形式,,分类学与命名学是对软工尤为有意义的,,只有sun意识到了它
8.26 架构不是功能的要求,但却是工程的要求
所有的术语都可以被重新定义,,游戏是什么,,其实火星人可能也在玩一种叫"游戏"的东西,,我们的BBS论坛也可以和魔兽世界一样被归为网络游戏,,,这就是重新看待一个领域的抽象,,给一个术语重新格定它的含义的范围,如果性质相同或相似,,就整合它们,再发展形成一个架构,将它们发展成此架构下的分支实现,,比如facebook,它以技术的形式统一了很多web2.0的应用,,,这就是说,应用这个东西,,是可以以技术的形式被统一的,,,从架构web2.0的眼光来看,,blog,视频点插,,,诸多web2.0应用,,都可以被看作为web2.0,,,
这种分离与整合现象其实在IT界每天都在发生,,以上facebook是个例子,还有adobe的Javadeveloper ide? ecillpe??,Java?平台开发库将内存流,网络,本地文件都看成"流",,这就是对一个术语重新进行定义,格定它的范围,,而这是合理的,,因为我们对一个术语的定义本来就是历史现象,当历史发展了,,一个术语要么被增加新的内容(量变),要么被完全演变,,成为一个新的术语(独立发展成一个新东西,虽然原来的那个术语也有效)
?我们所看到的概念,,如果重新被设计,,会产生很多新的抽象(在另外的维度上甚至会产生更多抽象,只不过我们是人,有限的生命不能允许我们同时或异时站在多个维度去想东西),,和由此而生很多应用,,这就是sun所玩的游戏,,比如它提出一个"xml",,实际上xml的最高境界就是"文档互换的标准",,由于xml的成功流行,这由于它是符合应用的,它就成了标准,实际上如果随着历史发展,xml就会过时(Jnos出现了),,xml只不过是人类知识的临时品,,,总会有它的代替品出现,xml相对"文档交互的标准"这个说法是个实现,而"文档交互标准"这个说法是个思想,一种思想反映在IT界,可以用代码实现(细节级的),,也可以用构架来形成一个观念上的应用规范,,比如XML规范,,这种思想一定要理解,
火星人也把他们玩的一种东西称为"游戏",,任何到现在为止我们能耳听目见的东西,,其实都不像我们想象的一样简单,,当你学习西红柿的单词时,如果你不能了解到它其实是一种外来词,,这种现象,,这种对一个术语的"历史抽象",,那么你就不能有效地学习它,只能说片面了解了它,,而这对学习是不利的?

8.27 你需不需要一个库
IT开发中,,只有属于底层开发的,,,一般才称为开发,,发明轮子,,,而复用成风的今天,JAVA这样的语言体现的是一种高级逻辑配置式的开发,当然也算开发,,所谓库,是一种面向通用和复用的中间逻辑,,接口逻辑,而非终极的应用逻辑本身,,,库面向应用复用提供接口,而应用逻辑面向应用本身.语言的功能和可复用性,,一个很重要的方面是除了语言自带库之外,还有没有第三方为它开发大量的开源库
  
  所以,如果你不是专门为了通用的目的考虑,就根本不需要开发一个库
  
8.28 可复用与可移殖的区别
在相关书籍中,存在很多相似但其实有很大区别的概念,比如可移殖与可复用,接口与实现,接口与抽象,下面试区别之。

在一个大型软件系统中,抽象是分层次的,,粗略地来说,有的抽象是系统平台相关的抽象,有的是对于目标问题领域的抽象。注意这个区别只是粗略的绝不是精确的(所以也可说是无层次的)。

设计中经常将实现和抽象分开并各自集中,如果抽象中过多地混入了细节考虑(即有硬编码和实现出入的地方),那么它必将在以后的扩展过程中产生麻烦,因为对于一个庞大的软件,其内部逻辑复杂,牵一发而动全身,语言给于实现和抽象形式上的划分方法只有头文件和CPP文件这样的初级方法。实现和抽象的分离从来都高度掌握在源程序的作者手中,我们知道,抽象不全是为目标领域作抽象,有一部分抽象是为接口作抽象,也就是为可复用的有效形式作抽象,即接口是抽象的一个部分,是抽象的简单形态,,其目的是为了给使用它的客户提供一个复用(或实现)的原型和规范,比如库,函数API,这里的客户是程序员用户用户。(但是像虚函数那样的语言内接口,又不完全是为了面向人的复用,而是为了面向程序内逻辑客户的实现。这个客户跟据这个接口产生出一个关于这个接口的model),如果不是库,则不需要提供接口设计,而实现是未端抽象(这就要求设计者具有良好的对系统的可复用考虑的设计能力)。更多的关于接口与实现的区别在文尾有述。


在一个复杂的软件系统中,可移殖逻辑主要集中在那些与系统编程相关的逻辑中,而不是对于问题的领域设计逻辑(虽然如果对问题的领域设计,,抽象得不得体的话,这样同样会导致不可复用问题,但决不会产生不可移殖问题)。比如对某语言密切有关的字串逻辑的依赖,对某平台密切有关的某个socket..鉴于对不可移殖问题的考虑,,我们往往将它与领域逻辑分开。所以可移殖问题只是可复用问题的一部分,二者绝不是同一意思。

所以,应该怎么样做呢?这样才能同时达到可复用,又最大程度地可移殖。(当然,只能是最大程度地这样。)

编程涉及平台支持和目标领域问题。一个用编程语言写就的,用OS运行的,“软件系统”中,必将大量存在这样的“平台编程逻辑实现”,相比之下领域逻辑少得多,(我们将由领域逻辑主导实现逻辑。)。
一种方法就是广为谈到的“抽象与实现分开”,我们需要将各个“实现”按文件物理地分开放置(此文件将会是引用的未端,不被“设计”直接作为头文件引用,而是作为最终可用可弃的实现未端,由它引用目标领域逻辑)。当然这个过程中,我们应注意模块化(所有的编程范式都是模块化的),然而正如上述所说,模块化不能复合,不能高下相互引用,比如“设计”引用“实现”
。(因为设计中有自上而下和自下而下,故也不存在各个模块之间平等不相互引用的情况)但是如果不考虑最终软件系统的实现的话,光就设计来说,确实也存在各个设计模块之间平等绝不相互引用的情况存在。


除了上述不可复用问题来源于不可移殖之外,还存在以下几点不可复用问题产生的源头:

l 解决不可复用问题的方法是增加迂回(一层抽象接口),将实现逼回底层,这个动作出现在二个过程中1,对于目标领域的抽象过程中,2,对于重构时的过程中。

然而,所谓的迂回,其实也是系统中的抽象,,也会对可复用产生障碍,一定意义来说,系统中抽象层次过多,,或数量过大,都会直接对可复用性产生麻烦,这为了解决不可复用问题而设计的另一层抽象正加大了某种程度上的不可复用性。所以是一种以毒攻毒的方法。解决问题的方法正是产生问题方法的来源。

l 不幸的是,语言机制也会造成不可复用,比如模块就没有函数来得可复用性强,然而复用从来都是相对的,存在一个比较时所采用的最小考虑单元,仅在C++语言内部而言,模块是可复用性很高的,在所有语言面前,函数接口和过程式开发无疑是复用性最大的。。这就是linux之父跟别人争吵的源头所在。这也就是说,C++的抽象性能反而带来了不好的地方,越抽象的东西越会阻碍可复用性。
l 当然,最后,抽象的方法不同,产生的抽象结果不一,由此产生的不可复用问题是最严重的。因为复用者一需要理解你的设计抽象,,在理解了之后,才能进行复用。如果你的抽象过于复杂,复用者不会有太多兴趣。

编程能力就是学会如何面向可复用考虑去进行抽象。当我们设计自己的系统时,为了提高它的最大可复用性,我们将它设计为与语言无关,与OS无关,与复用的库无关。这种工作是相当难的。设计中的目标问题抽象永远是自己的。那么如何将这些如上的“实现”逼到未端呢?

首先,要想做到与语言无关,就要用那些最初步的语法机制和开发范式,比如函数过程式。三种控制结构(事实证明它们可以产生一切逻辑)。或者自己开发一套自己的语言,在自己的语言领域之内作“固步自封的复用”。但我们知道,这(当其它语言不存在)实际刚好阻碍了其它语言对其的复用。

其次,要做到与OS无关,当OS不存在吧,不要引用OS的任何东西,,,照样是在自己的语言的基础上发展自己的GUI库,等(这实际上也是很多语言提出可移殖理念最初的出发点),一系统的系统平台相关的库。

要做到与可复用库无关,只有自己开发功能相当的库了。即一切轮子自造,包括语言。然而你可以改造语言,却不能改造英语编程语言,那是一个未知领域,你更不可能在冯氏模型之外发展这样的语言,你不能改造PC模型,也不要指望改造OS,更不要指望改造电脑的能源为光脑。

所以,一切好自为之吧。既然轮子的创造是一个无底洞,,何不直接就复用别人的库呢,用大家都用的语言呢?更重要的,作大家都在做的领域设计方案。这样别人才能理解你的设计。

===================================================================
接口是实现的原型,,一般谈到实现,就是一个复杂系统中的终极未端逻辑,意指其不必为复用留有余地(因为复用就是利用接口作进一步的抽象,复用跟接口密切相关,所以实现也指不必为接口设计作余地),一般谈到接口,意指为下一步的抽象提供统一的原型和形式,即为如何复用提供设计,

8.28 再谈可复用
可复用问题的由来一是实现的不能可复用(还记得在C语言中写上大量的预处理代理来实现跨平台逻辑吗?),二是架构逻辑的不可复用,,,,实现指的就是跟计算机离散相关的平台逻辑,,架构就是人为为程序的可扩展性加上的设计逻辑,,大多数是OO之后的东西,,我觉得提出二门语言,一门C,一门类RUBY的脚本语言来进行程序的编写,,,,在底层用C,在高层用OO脚本,这样的办法很好...因为你在低层不需要架构,,而只要在高层考虑架构问题.,,这样一来,可复用性就是二个阶段的事,前一阶段只管实现,不考虑设计,,这种过程是承接性的,只要先实现了,才能被设计得架构上更科学.

8.29 真正的可复用
可复用到底追求一种什么样的效果,又能最终达到什么样的效果?运行期的效率或重构期的不可复用和窄扩问题(这是二个并非绝对统一的东西),一切都可归究到设计期的问题。

编程界的可复用主要是面向对象和构件复用和设计模式和设计复用,库也是语言内部的可复用(就跟你拥有库的源文件一样.因为有头文件也是一样的,因为你还至少清楚库的构架,这也就跟理解并应用一个库只需了解其API就行了但不需要了解其SRC级的实现一个道理.ode的头文件集却是一个例外),COM的复用就是纯粹的二进制的复用,因为有真正的接口的隔离作用(此时你根本不知道库的构架),在库定义的接口中,你必须透过接口才能深入接口更下面的逻辑(可能是另一个库的实现),因此接口一方面提供了方便性,另一方面也增加了屏蔽性,这是一对矛盾,接口的定义是为了引入某种架构或桥接二种架构使其配合工作,而这种机制在提供了方便性的同时也增加了理解和使用该接口的复杂性和运行时空的代价。

用OO来表达世界的观点,,,物体可以组成世界(所有其它的东西,比如物体之间的关系也是另一种意义的物件),,,因此编程抽象了OO,那么编程就可以用来用计算机模拟世界,这种思想是成立的。

库的组合=功能的组合(类库设计是一种跟语言同级的设计),当然这种逻辑在使用同一种语言下是成立的(不同语言时也可以用Swig等技术来改造或Bind),然而库作为中间逻辑的封装者(库让你跳过库的实现即中间逻辑这些细节而直接面向大逻辑大架构编程,只要引用它们就可以在自己的程序中实现它们),可以一直细化接近最终实现,诚然单逻辑的一个类也可以被封装为一个库但是往往不样做,一个库封装了一套互饰的中间逻辑的有机组合,,这里的中间二字是相对最后的应用逻辑来说的,往往把最终的应用逻辑称为实现,这就是一种实现逻辑了而不再是中间逻辑了(这就是说库可以是一种内含高抽象的架构逻辑或具体的工具函数的实现逻辑,或基于其它库之上的架构逻辑或实现逻辑),库可以直接深入到实现细节,但是我们要控制这种过程,一方面是中间逻辑与最终应用逻辑不可精确定界,另一方面是因为设计与封装是个无底洞,不必做这种深入,第三方面是有其它的库可以plug进来然后在这些“轮子”上实现(库应只提供BaseGeneric这个库构架(此时库本身是一组架构逻辑而非实现集)和对一些其它外来支持库的引入接口(一般接口需实现,逻辑需继承,此时其它库可按需进行plug in 或者out),这就是库引用库,这种情况下有一些未端的实现是不应该加入中间封装的,比较好的作法是用一个库定义架构和基本工具函数集,以及对其它未端工具库的接口逻辑(此时先前定义的那个库就是主库,其它的库是可选的辅库,比如Yake的实现就是这样),实现就是最后一节提到的几个Demo(作为基础的逻辑已经被库封装起来,其它的就是实现了)

像Yake,它提供了一个Base core和很多构架上的接口逻辑,每个接口逻辑都实现了一个对外来库的引用,Base core是工具函数集(也有一些接口逻辑),这是Yake的主体部分,而接口逻辑(Yake也在这里实现了一些工具函数库比如)和对其它库的引用逻辑(也是一些Adapter)才是Yake的重要部分(Yake包括它的base和对其它库的引入逻辑这二大部分,当然还有它的一些工具实现,这样算起来Yake有三大部分).

接口是可复用中一个很重要的概念。

8.30 你能理解XP编程吗
大设计是一种动用一切资源,从整个思想领域去设计计算机应用的过短,,这完全是一种预设计,编码过程变成了纯粹的被设计预见的集成的一个过程,,这种设计往往首先从思想出发,,完全不考虑计算机实现,语言机制对应用的限制或表达能力,,,提出一种标准和理想模型,把思维过程出现的任何一个过程都作为设计的一部分,设计过程中任何动作都不跟计算机和程序语言相关,,最后仅留一点余地作为编码,在编码时考虑其跟计算机实现和语言机制实现的结合,编码的地位很低很终端。。

XP编程出来的时候,,人们大呼设计已死,,因为这种边设计边编码(在编码中形成设计)的方法大大忽略了设计超越“编码”的“预”,,设计变成了跟编码并行的过程。。

实际上,该如何处理设计呢??比如设计游戏。。

我们知道设计是无底的,这种无底性决定了我们应有限地把思维中出现的理想设计和想法体现到计算机逻辑和语言机制能表达的逻辑中,,,而且应尽早地这样做,,任何应用领的逻辑都要最终被转化成计算机逻辑和语言逻辑。。也即,我们不必做超级设计和完美设计。。

游戏是什么呢?如何设计一个游戏呢,,游戏这个字眼可以无限被放大(应用领域可以无限深化),WEB论坛可以是文字游戏,3D游戏也是游戏,,网游也是游戏,,是不是要在你的设计中体现这所有的元素呢(一个具体的设计总是针对某个应用域寻求它在计算机和语言域的对应,如果你知道算法和数据结构你就深刻理解这个说法了,我们总是向语言和OS中寻找某种可能适应我们问题的数据结构,即使再通用的逻辑,比如库的设计,我们也不应),并用一种“设计模式”中的模式来组织这所有的元素呢,,不能,,而且不应该。。你不可能在有生之年把它们(设计中出现的需要组织的逻辑们)的地位作一个组织或你自认为合理的排列。。


你可能会说我不直接提供这些无素的实现,,不直接在设计中体现这些,我只需预见它们,,并在设计中为他们预留接口,,但这样也是不行的

那么最后出来了,,什么是XP编程

预设计,大设计是一种“一次性设计”,企图把应用设计中的大大小小所有过程整合到一个大设计中,,,这样的代价当实际编程开始时如果遇到不能前进的错误会损失很大,而且设计本身花费精力也不少

而XP编程先是提出一个不够完美的框架(针对某个应用,有应用本身和它产生大大小小的其它应用问题,这不够完美的框架是针对整个应用本身来说的),或者不提出思想模型,,,它并不试图分析整个应用,,以及对它们的设计(因为它相信设计不可能是一种大而全的,只能具体问题具体分析设计,人们不应把所有可预见或在后来出现的问题整合到同一个设计中),,并不着手预见可能出现的问题和对它们的大大小小的设计过程,当具体应用问题中的大大小小问题出现时,就着手一个即时设计(比如设计游戏时,这是个具体的大的应用问题,针对游戏本身可提出一个不够完美的框架,,当在他下面遇到有很多小问题,,比如网游时间同步,,我就看语言中提供了什么线程和语言机制,或者如上面说的数据结构或算法,来进行一个小设计)

这就是XP编程的全部意义所在。。

8.31 构件与接口,软工
UML,IDL,MDA,COM.CORBA,WEBSERVEICE,XML软工新潮流
关于接口,也发展出一种语言叫IDL,,语言跟应用的关系是什么呢??因为语言可以接上计算机处理跟人们的应用需要之间的接口,,所以语言和编写语言下的程序成为扩展计算机也扩展自己的手段,,甚至还有DSL为了解决特定问题而产生的一种语言,语言的实现即编译器,语言规范等,,
而MDA是不同的概念,MDA是给定一个领域的描述,,然后写出一个依赖于接口的xml格式的web services.
这直接促成了构件的产生,,,在SOA中,因为要集成新老系统,当构件作为一种比对象还要大的软工逻辑粒度时,,它们共同需要都需要接口,,但是构件
不要小看了这个构件??它几乎可以是一场软工变革,跟OO有相平之处


编程与范型(设计)

初始OO,我不过认为那是一种编程语言支持的工具,,真正懂得它时,我发现我走入另一个迷惑,一个更深的迷惑,,如果OO是一种思想,,,所以我要怎么用语言去联系它?

8.32 设计方法论
“在原语空间内进行设计”前应该是现实世界问题域,在“产生最终类文件”后应该是编程域的OO解(或其它范型解),即设计的FrontEnd面向的是用户,设计的BackEnd面向的最终是计算机,这中间的“原语空间设计”,“多范型表达设计”,“ROSE工具实作范型”都是“设计演化”,即设计是一种从用户到机器的抽象过程(它包括前面提到的三个主要过程,原语设计是从上到下,泛型设计是从下到上,明白这个道理有什么用呢?这至少可以解释为什么好的架构可以扩展出足够丰富的未端实现,因为从架构设计到功能实现是互通的,这二者不是矛盾的相反是统一的,明白这个道理还有什么用呢,这也可以解释为什么Yake的BaseGeneric不是包含架构逻辑的Generic,而是一组与平台native本地有关的DLL引入逻辑,数学函数,Logging机制什么的,这是因为需要先提供这样一些实现,才能独立平台,而这是Yake首先要解决的问题,因此只能把这层逻辑放到最低层再慢慢发展其它抽象,另外,YAKE使用的库中,比如OGRE和ODE就用到了数学函数(并非所有的问题都能靠提供架构和中间抽象来达成并解决,因为有些问题不是要不要封装和不封装的问题,而是能不能实现的问题(能不能用OO来表达跟能不能在算法等级实现是二个不同领域的问题,一个是软件的设计,一个是算法,前面提到了这二者之间的区别),比如一种算法,什么是架构什么是实现,这里是一个很好的区分例子),底层必须先解决并提供这些抽象,以我来看,Yake真正的主体不是BaseGeneric,它是基础而不是主体,它的主体是对其它库的引入逻辑,这才是Yake的架构逻辑,因此说,架构逻辑有时仅仅是被体现,而没有并封装成DLL,也即,架构是否被体现与它是不是要被封装成为一个库是没有必然关系的,哪些没有表现为库的中间逻辑也可以是架构逻辑,明白这个还有什么用呢?原语设计是不受限的面向用户的设计,然而当进入多范型设计时慢慢转入用计算机的观点来看待设计,因此像Yake这种与平台息息相关的表现逻辑必须在底层就解决平台抽象和数学抽象,而LogicGeneric就根本不用考虑这些,因此多范型设计相比原语设计来说,它是从下到上的),而实现是一种从机器到用户的过程(实际上我在这过多强调设计与实现的差别是不对的,因为这二者无法精确定界,然而如果所有中间逻辑都被封装为库,这二者差别就很明显, 库作为中间逻辑可以参与进来以缩小这二个过程差距,把应用架构称为设计,把中间逻辑封装为库(架构也可以表现为库),实际上在这里,中间逻辑与最终实现才是对立的说法的二方,设计与编码才是另外二个对立物(设计就是原语设计而编码就是多范型设计),

设计演化(从问题到类),,实现演化(从类到问题),,前者是从人到机器,后者是从机器到人


逻辑互饰构成的逻辑的巨集组合,就是一个越来越接近应用总逻辑的大逻辑,(上下互饰就是谁更接近应用逻辑的道理,至于最终应用逻辑前面的逻辑,都可称为相对的中间逻辑)

然而设计与中间逻辑不是没有关系,把库外的未端逻辑称为实现,在这种说法下,基于库之上的实现跟设计共享同一些中间逻辑,库使这二者有机结合不产生缝隙,当然作为泛义的库是缩小任何二个架构之间差距的机制),其实在“ROSE工具实作范型”之后还存在一个“设计载体与设计方法”,即UML图,或卡片啊(设计载体),设计方法主要是“找事物的共同点与不同点(就是多范型设计那本书的作者提到的)”,还有就是UML教学中出现的“给出一个句子,找出主语谓语等”(其实这些方法归纳开来就是做列举题和判断题,列举出一些细节,再判断它应属于那个接口中,这在第四部分“确定GameGeneric应提供什么样的高阶接口”那一节有清析的讲解)

实际上我的思想和说法比他们还要超前和规范一点,原语设计三种思想(抽象,原语,组合)就包括上述的说法(找事物的不变点就是指抽象出事物的本质,这是设计过程中一个很重要的能力)

在观察者模式中,

8.33 真正的interface
正如对象的索引才是真正的对象一样(C++中提倡使用引用优于指针),对接口编程才是真正的编程工作,一个程序员大部分情况下只是一个接口粘合者(因为我们不需要重新发明轮子,发明轮子-中间逻辑的工作才是真正的对实现进行编程),发布时我们也是发布接口库和其说明文档,大多数情况下,我们都是利用第三方代码库编程(这是语言之外的接口,程序员也在语言内部编制函数等接口)

接口设计是一个编程工作中常常要考虑到的问题,要考虑提供这个接口的实现会在什么地方会用到(因此它跟需求分析密切相关),以此来设计接口的参数信息,一个接口不单单是一个函数,虽然函数的声明部分在大部分意义下作为接口的意义,,,,

所以delphi的单元中有实现和接口这二个字,,,接口的集大成者是COM,所以borland以它的IDE很好地支持接口而著名
这是语言之外的,
设计一个面向需要程序设计语言永远是不可行的,需要永远是可变的,程序设计语言只能遵守一个固有模式而提出,有固有模式去表达和创立新东西(面向对象就很不错),编程是人的动作,人力对工程(一个大软件就是一个工程)的控制应该尽量简化,

特别是要掌握对象(或称工件与产品)与接口-组件(对象接口与组件接口是不一样的)概念所在,前者是语言内部的,后者是语言外部的
在内部
纯数据对象称为死对象,(可用但无用)纯实现对象称为工具对象,不可用
在外部
纯接口对象称为抽象对象,(可用但无用)纯实现对象称为工具对象,不可用
面向接口,(一个一个的接口,函数称为函数接口)接口归接口,实现归实现,实现不是函数
软件就是包装器,就是一大堆接口的有机组合,不提供继承机制

一个类型的数据可以独立构成一个数据结构
对数据结构的描述包括它的类型,它的结构,它的存取规则

软件设计是一种什么样的过程?
软件就是对象组合,这些对象通过他们的接口按一定逻辑组合成可工作实体
在语言内部的继承是对象,在语言外部的继承是组件

对象的概念,包括函数对象,变量对象,数据也是对象(作为一个元出现在一个特定的数据结构里),因此数据结构也是对象,操作实现也是对象,实现称为处理器,数据或实现的组合也可称为对象,注意,对象的组合只能是被称为对象组合,只有那些能在一起工作的对象组合(compent view)才能称为软件,这里引入工作逻辑的概念(就是ROSE中的logicview)

在内部
纯数据对象称为死对象,(可用但无用)纯实现对象称为工具对象,不可用
在外部
纯接口对象称为抽象对象,(可用但无用)纯实现对象称为工具对象,不能由它派生出实例

面向接口,(一个一个的接口,函数称为函数接口)接口归接口,实现归实现,实现不是函数
软件就是包装器,就是一大堆接口的有机组合,不提供继承机制

一个类型的数据可以独立构成一个数据结构
对数据结构的描述包括它的类型,它的结构,它的存取规则

数据是对象,逻辑也是对象,操作也是对象,一切皆对象的概念,在机器内部一切皆比特,在用户眼中,一切皆对象,因此数据库的数据二字是有通用意义的,,,因此会有面向对象的数据库

至此只是软件内部,那么在软件外部有develpoment view

数据组合接口形成一个数据结构

对象和(包括接口和实现)构成一个组件DLL或LIB,一组对象和一组接口就是一个DLL(称为一个产品),需要一个调用协义接口
.
8.34 真正的对接口进行编程
不存在一个“对接口编程”的真实过程(虽然这是一种过程定义),我们只是说,要为”实现”定义一系列使用它的接口(这样实现才能更好地被使用和被修改),,这种行为才是对接口编程行为,而接口本身是什么还是没有说哈哈

一个为未来扩展而写出的工程中,大部分代码只是框架(抽象的思想模型,也就是为了扩展需要--也是为了使用需要,而定义的一层又一层的抽象),真正的实现部分(调用API啊,用某个算法啊,某个完全具体工作的实体对象,或某个完成某个业务过程的会话对象)很分散,而且分散得很有规律性(因为被抽象接口经过了再组织,所以变得有规律地分散),,这样的分散机制就像把真正的实现逼到最尾端,而最高层往往是使用这些尾端要如何被使用的应用逻辑--被抽象成了一个或某些使用的统一接口形式,而且是高级逻辑,(即接口实际上是关于如何使用这些实现的隔离层,,中间层)

这样抽象也称为为客户调用(或使用)的协议定义

很多时候,在一个工程中,所有的实现都可以由一个Demo直接写出来(写成CONSOLE形式,也可以是一些对象集上面说了),然而,真正形成产品时,我们需要再组织这些实现,让它们最终形成的产品出现(因为一个真正的产品,必须要考虑到未来修改的需要啊)这往往是一个比写实现还要难的过程,因为我们在写“如何使用这些实现,如何把这些实现分散封装到未端”的接口逻辑,而这个逻辑,往往有时比写实现本身还要难!!
8.35 实践方法之极限编程
极限方法只是敏捷开发中的一种,,,软工指明软件开发不只是软件本身,,而是软件跟人的关系,,因此这出现了设计与编码,,,设计与编码是软工的二大主体,,,,好了,,实践方法的出现就是为了解决这二者之间的矛盾
?开放的标准使我们的规范保持中立,所有人都可以接受,而不受某一个开发商的控制,而开源可以使我们得到大家都接受的一个实现,而不受某个开发商的控制,这两者的结合非常有力。这个实现你可以不使用它
开发中的分布要求,产生了二个复杂度,1网络开发,2软件要独立于平台和硬件架构,,这就是WEB的特征
.
8.36 设计模式复用与框架复用
设计模式是可复用策略,,是思想级的,,但不是不可以用代码来表现(编程即换维重现,即将思想级的东西转变为语言级的东西),,软件设计的终极目标就是符合软工,扩展软件的可扩展能力和生命力,,设计模式就是服务这个的,,设计模式因此有一些原则,比如LOC,控制反转原则,不重复自身原则,,这就是设计模式对于软工,所要达到的目的,常见的设计模式有哪些呢?,比如MVC,工厂方法,工厂,单件等,,MVC可以说是一种框架,,也可以说是一种设计模式,,因为MVC是设计模式的组合,,它被作为一种框架时,比如 strcut,spring,也是成立的,,可以说现在的一些WEB开发框架比如STRUCT,SPRING,ROR都是设计模式的实作品,而设计模式是一种思想,,设计模式这种思想,这种设计目标和设计手段,,被体现在代码上,,就是用了设计模式的软件,,或用了设计模式的可复用框架,,比如设计模式表现为一种补丁时,什么时候表现为补丁?就是老总说,某某公司要求我们开发一个软件,但是这个公司提供了一个库,要让我们现有的代码,用一种方法能使它跟这个库协同工作,,因此要利用到设计模式,,就是现有代码,,跟可复用的别人的库,,,,在这二者之间用设计模式进行连接,,,,发展出一种可运行的逻辑,,而非补丁式的设计模式的应用,则是在产品没有出来之前,,不需要适配既存可复用库和要写出的代码这二者,,采用的一种预先的,,大而全的设计方法,,非补丁式的设计模式,,是一种真正的设计,,,此时模式二字反而可删掉,,是一种预先想到可能想到的所有扩展能力,,决定采用什么设计模式来编码,,在这编码之前 ,,决定采用什么设计模式,或采用什么别的方法,,,这就是真正的设计,,预先的,,如果可能,尽量大而全地考虑,,,当然设计不仅是面向可复用,,还面向应用设计,,如何设计应用,,如何设计用户界面,如何分析业务逻辑以便于扩展出关于这个业务逻辑的新逻辑,,不仅是在设计如何编码了,还在于调动计算机资源的能力、思维的建模能力、分解和搭架能力,,很多人以为设计模式是补丁其实是很狭隘的东西,,其实设计模式本来就不是编码,,,只是当人们站在编码角度来理解设计模式时,,他立马就错了,,设计模式是一种流于建筑和软件界,通用的可复用策略,,是思想级的,虽然它不是不可以在代码上被表而而已
引用给了我们什么.

新手编程导论(十)


第三部分 进阶:C,C++代码阅读与控制
如果说在本书第二部分里我力求向你们提出一个思想体系,那么在这本书的这部分我将要讲述的就是代码控制和实践能力了,这是程序设计的基础二部曲。这本书完成之后,你应该具备基础的编码实践能力(或阅读别人代码的能力)。

这部分主体是,C,C++语法,,及各自标准库的分析与使用,stdc,stl,glibc,boost,loki,(设计模式,数据结构,业务逻辑),而且更侧重使用(因为分析出来的是库逻辑,是设计抽象密集的)。比如自己开发例子出来(这才是使用密集的)。
目录可以这样定:轮子分析,再造轮子
1.C,C++基础(语法级的,,数组啊,字串啊,控制结构啊,初级标准库级的,比如IO,,OO啊,模板啊)。
2,数据结构,STL(分析库不如自写来得权威,,但是参照别人的src也是最好的方法)
3,设计模式上的,LOKI,我在这里不把BOOST拿来讨论,是因为我觉得成为语言标准的只需要数据结构作为数据抽明和设计模式作为代码抽象就可以了,,而BOOST有太多的对具体事物的抽象,,实际上随着问题的增多,BOOST会越来越大,,这(对各个事物的抽象解决集)不应该成为一门语言的标准。。而LOKI刚好对应只用模板解决的DP问题。。显然合理。

4,这是放在第二部分最后一节(抽象之领域逻辑)分析的某个库比如OO的OGRE,我在1月2号决定取消这一部分,,那么我作了什么决定呢,我决定写一个实现比如实化版的yake或者是一个yake类的引擎加一个游戏实现(直接把其当成写第六部分的内容,而不再分析并使用别人的游戏引擎,),可能用到其它的库(比如net,mysql,boost),直接产生一个游戏,,

具体如何进行呢,尽量少用轮子,只用OPENGL这样的必要的轮子。考虑进STL和LOKI,做成一个简单的引擎,然后写游戏实现。尽量在写的过程中与ogre,yake对比,突出为什么全的设计是它们,而现在自己在写的是一个克意不用其它轮子的轮子(为了教学的目的)。。

在第四部分,才是一个系统的解决方案(一个中型例子,而不再写库或分析库了,,,应该找什么现成的例子呢还是自写,,在这里要分析出设计的细节,大中型软件如何形成的),提出一个中型程序,,第四部分 一个例子,结合OO,模板,语言,领域逻辑写出的大型逻辑的软件

如果说STL是templaate与C的结合,那么LOKI有些更多靠近OO与template的结合。

第9章 语法与初级标准库


参考书《C++ Primer 第三版》Neckman
9.1 C++的基于过程设计
9.2 C++的基于对象设计: 模板与设计
不幸的是,理解STL深层的原理是需要懂与模板相关的设计的,比如仿函数的本质,迭代器,配接器的本质,模板导致的泛型开发与它提出的这些设计相关的东西可以另外写成一本书。学习STL首先是学习这些设计手法,再学习其数据结构和算法的实现。

泛型有二层意思,第一,基础泛化,它把泛型参数化,用于动态产生关于不同型别组合的相同逻辑(可以联系函数声明和函数定义来理解),这也就是一般泛化了,第二,它把一切设计中可能出现的因素都类型化(template class化),即在templateclass这个字眼中不主要强调template泛化而是class类型化,(只不过它也会用到泛型的第一层基础泛化作用而已)比如,迭代器,仿函数实际上都是(模板)类,这其实更像是C++的概念而不是泛型的概念。(因为class是c++的而stl及它导致的template手法是另外一个人发明的)

为什么需要把指针,函数封装为class呢,这是因为在C++中,class几乎就是一种逻辑粘剂(即将数据成员和函数成员,,当然在模板中也可以是模板成员数据和函数,封装为ADT),在这里并不强调这些Class运行于runtime的那些特征,比如多态,等,而是强调class封装逻辑成adt并提供private,public,protect修饰机制的能力(相比之下C++的struct太简陋因为它只能提供全public,而且不能成为adt,因此没有adt的诸多好处,比如C++的只对class有效的运算符重载,,而class+oper overloading+template class你呆会会看到,,这在泛型设计中是多么有用处的东西。),,所以在C++中,相比面向对象来说,这些基于对象的开发范式也需要被重视。。

一句话,class化可以获得语义级的value, 只要给该class一个copy ctor就可以复制并传统它,给它一个重载的括号就可以成为跟函数动作一样的东西出现在C++语法相容的东西(虽然语义实际跟标准的对应物不一样),,,

template class是泛型设计中的重头武器,因为:
函数+oper () overloading+templateclass化 = function objects,这就是stl中的仿函数。
pointer+oper * overloading(和->)+templateclass化 = smartpointer,这就相当stl中的interate.
Multiple inhert + template class作为另一个template的参数=policy,即loki中的策略。
Struct+template=nested type,,内欠型别。。
元编程里的metafunction


重载是多态的简单形式,,模板特化与实例化是不一样的,,,其实任何一个模板,都存在二套参数,一套是泛用的,在tempalte关键字后面,另一套是特化或偏特化用的,在具体的模板后。特化与实例化的区别在于实例化不需要人去干预。。
9.3 面向对象设计
9.4 泛型开发与初级StdC库

第10章 数据逻辑与STL库


参考书:《STL源码分析》完整版 侯捷
10.1 仿函数
仿函数是C++基于对象编程的典型,,,它把对象class化{也可能是templateclass化},使之具有copyctor可被复制,再给它提供一个重载的小括号这样在语法上就可以跟普通函数一样写了,整个过程并不需要面向运行期(面向对象),,,所谓模板加基于对象的基于对象之说只适合发生于编译期。

STL中为什么需要仿函数呢?它为了成为算法的某种策略,,loki中的仿函数用来实现一种command的设计模式,因为仿函数可以用来封装一系列请求;
10.2 iterater
Stl中,与template相关的一个设计手法就是迭代器,迭代器成为表达各种数据结构以及跟它们有关的各种算法操作的支持概念。当然也是一种泛型手法。Idioms 其实也是一种设计模式,即迭代器模式(traits,adapter泛型设计手法可用于广泛目的,相比之下iterate好像只用于数据结构的设计手法设计模式),

既然是泛型编程,迭代器是在什么样的泛化需求下以什么样的泛化方式被提出来的呢?

我们知道数据结构都是某种区间,把数据结构视为区间这本身就体现了某种泛化(能泛即能提供通用性,可复用性,所以是一种对代码趋近于人的设计抽象),某种抽象,实际上无论是以何种结构形成的关联式(key+value=pair对)还是非关联式数据结构(),,迭代器都将提供一种游动于元素(一般来说元素只是value的说法)或节点(一般来说node=key,value)之间并能对算法提供迭代支持。只不过迭代器作为泛型的型它也可以有多种iterate associated traits而已,有的是input,有的是单向,有的是双向,有的是const,有的是mutable而已。
10.3 adapter
配接器的说法很形象, 你可以联系现实生活中把ps/2鼠标加一个ps/2 2 usb接口,,把它转为USB接口的这样一种动作,,这种动作就是改变接口的机制使之由旧接口变成新接口,一种设计策略(改变原有代码,使之适应某种复用考虑,,所以是人控制代码的过程,,是设计抽象,,这个动作也称为重构,即不改变原有系统设计的情况下,利用设计手段修补式地改造原系统,因此跟DP相关),,往往把它归为专门的一门设计模式。

因为客户(你的电脑ps/2口坏了不能插ps/2鼠标了)只能使用某种接口的东西,所以需要对原有接口(原有代码)进行接口重新封装,使之向现呈现客户能用的接口。这是典型的设计模式应用于给代码打补丁的情形即复用的情形。(当然设计模式也可一开始用于纯设计的地方)

那么stl中的这些配接器都是些什么呢,又怎么样在stl的整个设计中发挥了作用呢??

第11章 高级代码逻辑与LOKI库


参考书:《C++新思维》中文完整版
11.1 typelist
对类型的设计才是设计。

为什么写代码需要设计呢,因为代码是人写给人看的,所以对代码逻辑的控制是需要的,而这就是设计,,设计更多指一种人类活动,比如艺术设计,所以它包括测试,重构等诸多过程组成的与编码相对的过程。设计首先是一种对问题的积极抽象过程,booch甚至说抽象是解决设计问题最有效的方法之一,当然,维护,重构也是,所以说抽象问题只是设计的一部分然而是最重要部分。

在C++中,设计首先是对类型进行设计进行抽象(泛型这个字眼本身就表明了对各种类型其功能通用,所以是一种设计抽象),有OO。有template。OO是类型化即面向对象,template是泛型化即主要用C++的基于对象机制来工作。

泛型编程中对型进行的抽象,有make types to be a list,有mapping type to sth,有get traits from types,尽量在编译期间将型别抽象到应用,形成设计,因为静态语言的编译期间正好提供强大的类型功能,,,而这里谈到的typelist就是一种。。

对类型作了这么多抽象之后,再提出iterate,等设计手法用于stl,提出policy用于policy based design,,学习范型编程,始终要提醒自己把握这个精神(即一般泛型设计会分成三个层次,第一层是型别抽象,第二层在第一层的基础上提出邻域相关的设计手法,第三问题本身,STL和LOKI中都是这样)。。

11.2 traits
Traits是剥离器,是一种设计抽象(往往人们也把它称为concept,,满足concept的实现就是它的一个model,即concept是编译期关于类型的interface),广泛应用于stl,loki,等设计理念中(剥离器一般只用于泛型设计,因为需要从泛化了的型剥离并获得它的traits,实际上这个词更多地强调的是结果),是成就stl,loki等的支撑逻辑。因为类型有“一般type”,作为template class的迭代器,等等,所以也有相应的type traits,iterate traits.即模板的参数可以是什么,那么泛型也可泛化什么,形成相应的泛化意义(比如traits),因此泛型可以将型别泛化,可以将操作泛化,甚至可以将一大类的操作和型别泛化。更甚至,可以将“template template参数”泛化,注意我并没有多打字,现列举可能作为template所有参数的情况,并一一加以解释:
11.2 policy
为什么需要policy呢,,因为我们知道在应用开发中设计往往是做多选题,对应于应用域在解域和语言域中有大量可供选择的方案,所以可复用组件最好是给用户小的设计组件,用户才能借以组合它们形成更强大的设计方案(具体到每个领域,它的设计都应该如此,比如loki,的智能指针,仿函数等具体领域都是策略based的)。因为设计元素只能分解而后才能复合,而不应该是一开始就复合了。。如果一开始就提出一个do it all全功能型的接口,那么往往复用性从一开始就固化了(OO就是如此,单根继承往往涉及大量不必需的东西进入设计,组合才是科学的机制)。这往往很不好。因为它只能向前发展不能向后发展。那么组合大量小policy形成的对某个瓴域的某套组合polices,给了我们后退的空间,我们可以组合需要的去除不需要的,这才是我们需要的,即设计中可以在此做选择题的能力和场所。。

第四部分 一个例子:游戏引擎和实现
War3这个游戏引擎我认为是我见过的颇为先进和完善的引擎,不知你有没看过War3的“铭谢”CS,它提到这个引擎用到了比如ZLIB,TRUETYPE等库,下面我们来实现一个属于我们自己的War3吧,这个War3就叫NotWar3了!

是使用本书前面三部分这些知识的时候了,在对这个框架的设计中,我将向你呈现大部分出现在本书中的思想或细节,比如对外来库的使用,三种思想的运用,建立自己的中间逻辑即库,基于策略的设计(MetaDesignPolicy)等,并给出作者写这个框架过程中的一些合理或不合理尝试,模拟一次真正的软件工程!

第12章 设计(需求分析)


12.1 第一天:接到一个案子

客户要求我做一个类WebGame的游戏,那么这是一个什么样的类WebGame呢?他们用列举的方法来提出了以下几点要求
1. XML统一格式的方法来打包客户端文件,,用xdbm客户端统一打包(他们事先指定用yake和ogre的xml资源,,,model ,,sketeonl,,,scene),
2. 绿色客户端,如果第一条要求是html,,那么这就相当于C/S中的浏览器,,客户端升级接口
3. 实现类war3的对战和游戏内容,类War3的CS过场界面和录像机制
4. 实现脚本机的扩展,,跟War3一样提供一个“护展编程接口”, 玩家发布任务,WorldEditor,
5. 界面设计:游戏界面有三大块,,用一张图来表示,,,input设计要好
6. 利用Jxta(C的Jxta)来构建网络环境,游戏客户端由于集成Game Logic,因此可设置一个UseLocalLogic和UseFarLogic来选对与P2P环境内的玩家对战或连接上一个C/S游戏服务器玩RPG游戏(此时Use Far Game Logic)。
7. 登入时自动下载上次存储在服务端的游戏辅助设置(Input设置,自定义头像和模型)
8. 为了能让这个游戏更好,,任何你能想到的请自由发挥(可斟情增加报酬)

这些需要大部分只是细节要求(很少是架构需要),因为我们是在模拟外行的用户不分巨细向我们提出要求(对于游戏逻辑,都闪闪其词),我们需要整理这些需求以用于后来的设计(说实话以上的需求一般现实见不到)

设计跟需求分析之间的关系很重要,这要求这些需求在设计时就应该被全面地提出来(为了扩展的需要,设计应该在允许的范围内做到全面化和合理化,,但是注意,我在对世界逻辑的设计过程中提到的设计涵盖面是巨大的,这依其说是仅仅面向需求进行分析,,不如说是大而全的通用设计,而一般人则是分析需求,然后依据经验直接简约设计,而这里我是为了纯粹教学,所以请千万明白这里的问题),否则在进入设计期时,就只能通过重构(重构是软工中一个很重要的部分)的手段来添加新功能!

12.2 第二天:需求分析

为什么要提出这么一个过程呢?这(对需求的分析)可作为设计的参考,这即为设计的参考技术总结(设计最终要体现这些,从现在开始就要考虑设计与计算机实现的结合点,因此可以用来指导多范型设计)

在一个需求中,什么是游戏的逻辑部分,什么是游戏的客户端部分,什么是游戏设计中后来才需考虑的细节部分,什么是在进行游戏设计时先考虑到的架构部分,在哪个架构中提供什么接口,一个接口应被置于哪个架构中,这不是一个有人作过专门研究的课题,然而如果给出一个具体的例子,我们应能很快地判断并决定它应放置于客户端逻辑还是游戏逻辑(这就是设计方法论学应解决的问题,游戏逻辑就是业务逻辑,而ClientSideGeneric或ServerSideGeneric就是表现逻辑了,我们也应该能确定需求分析中的哪些是架构哪些是细节),,


9. 利用Lua Bind C++的本地库来进行扩展接口的设计(客户端和服务端面向Developer的编程扩展接口),可以动态测试,不用重启服务器(提供Lua导入功能的界面接口),不过War3的游戏逻辑全部是用脚本写的,而这里用C++写LogicGeneric和DevelopGeneric,然后导出为Lua所用,再在此基础上进行扩展出一个具体游戏(事实上仅仅到这里为止,才进入“游戏”的设计,前面的GameGeneric实际上称为VRGeneric更为合适)
10. 针对界面部分,,好像中国的游戏都是策划主导编程的,,,策划提出来的想法大部分都是表现(数值设定,游戏故事,,等等都是外在表现),,很少是引擎内部架构(我们说原语设计先于多范型设计,那就是说原语设计是对应用内部架构进行设计而不是一开始就设计应用的外部表现),,实际上编程主导策划才是对的(当然如果仅仅从技术上来说是这样的),因为如果在中间逻辑层次提供了足够好的接口,那么外在表现(即实现)可以无限和快速地扩展(然而游戏开发功利的目的使我们往往颠倒了这种关系,这样做出来的引擎只能是。。。)客户端设计DllFromInterLogics,Main.DLL,,,
11. 客户端是瘦客户端,,这往往它的EXE只是一个渲染框架和界面资料(媒体资料),客户端资料比如地图这些大件可以从官方网站或某个下载点直接下到(分文件或一次性全下载),或者(这里才是重点难点)在游戏中进入一个地图时如果检查本地没有此地图即显示First Time Enter This Area,并下载地图,如果取消下载玩家进入一个黑暗区域但是依然会有障碍,故称这是一个类Web的Game(真正的WebGame就是用网页呈现的游戏,通过80端口进行通信,这样的游戏有一点迂腐和小儿科)
12. 对于xml统一打包(比如地图和对战时的P2P文件共享),封装各种二进制的格式(比如客户端的各种格式的文件和Lua代码,Developer的护展发布---一个地图文件封装Lua代码),XML也可作为Jxta的消息通用文档格式,我注意到有些游戏引擎的实现把模型资源封装为XML,这在我个人看来并不是一种好的方法,XML作为Wrapper是好的,然而用它去描述内部某个XML节点数据又是不必要的(个人看法,SQL的拥护者认为XML DB已经超出了数据存储的原语空间,他们认为XMLDB-这里着重指NavtiveXMLDB是一种过度设计)这里用xdbm,,把每一个客户端文件都作为xdbm的二进制格式,,,这样就保证了二进制载入和打包的双重需求
13. 对于绿色客户端,,将GameGeneric置为一个单DLL文件,把Yake的所有中间逻辑封装者即DLLs和LogicGeneric全部编译进一个DLL文件,称为GameEngine.DLL(如果直接在YakeBase中和LogicGeneric中删除DLL加载逻辑这就需要改动源程序尤其是YakeBase中的Logging机制,而且如果删除,以后DLL就不能动态装卸了,而且不利于对GameEngine.dll级的扩展,当然如果不删除,那么DLL就以一系列中间逻辑的样子躺在客户端这样不免难看)这就需要定义另外一种加载插件的逻辑,同样可以做到动态屏蔽和启用(而非装卸)和以后对GameEngine.dll级的的扩展(继承这个屏幕和开启接口就是了),


不要怀疑,我们正是在做软件工程(的需求分析阶段,,虽然只是初步的分析)和面向接口编程(因为接下来我们也要做库和架构)

以上都是需求分析,描述一下接下来我们的开发路线,当然是先设计后编码了(在进入真正的范型设计前,我们先进行一个原语设计,,,整理自己对游戏的认识,,这中间用到外来库因此还得说说它)
设计前工作
14. 游戏原语设计,,表现设计与细节设计(设计界面等细节,实际上这个步骤可以放到最后,因为这是表现逻辑应次于以下的业务逻辑进行,不要被这个主导设计逻辑)
15. 了解要用到的库的架构和接口(Yake表现逻辑)
设计部分
16. 范型设计并实作LogicGeneric(单OO泛型实作设计)
17. 为LogicGeneric写ExtendGeneric
编码部分
Demo编码(这就是实现,其它的实现通过lua去扩展或为三大架构自写Plugable 的DLL)

第13章 设计(领域分析与抽象)


13.1 原语设计

你理想中的游戏是什么样子呢?什么又是游戏(你对游戏的概念直接影响了你对游戏的设计),每个人的看法都会不同吧(反问一下,什么又不是游戏,连BBS也可以是一种文字游戏,但是我们不做这种超级泛化,我们只对3D网游作泛化)?以下就是我对游戏尤其是网游的看法,更准确地说它是框架设计(在完成了这个框架和所有中间逻辑之后,最终的实现编码的过程很简单),,这里把游戏泛化为“可扩展的虚拟现实对战”,并设计出一套思想级的协议(就如同下面的一张图表示的),并用一套具体代码库实现它,但是这显然是一个巨大的工程(就好像根据P2P和一些其它的规范比如XML来实作出Jxta),。。

以下是一个一定程序上反映我的理想的一个框架设计,哈哈


我想象中的一个游戏总原语

由上述原语设计(尚属非常初级的设想)演化到多范型设计或UML实现,中间还需要很多设计上的细节,但我们会一步一步来进行,下面我们继续进行对虚拟现实这块进行细化



我想象中的一个虚拟现实原语


具体介绍一下

世界逻辑管理天气,地理变迁等游戏世界自然变迁逻辑和游戏世界社会关系逻辑(可产生一系列游戏系统GameSystem,用Plugable的DLL来实现),数据逻辑(虚拟世界与现实的接口) ,这所有的逻辑由一个叫WorldLogic的组件来封装(上面的描述),这个组件就是独立于表现逻辑(Show Generic)的游戏逻辑(又称Logic Generic),

游戏世界就是一个社会,因此WorldGeneric就作为一个单例代表整个世界,这个世界分为SocietyGeneric和自然Generic,


图1-3 我想象中的一个虚拟世界逻辑原语

至于表现逻辑就用Yake,注意Yake仅仅是VR的表现引擎,而WorldLogic是游戏逻辑引擎,而且可从这二个组件定义出一个具体游戏(这中间还要通过一个下面要谈到的Game抽象层次和建立在Game之上的扩展接口ExtendGeneric),

这里主要任务是深化对世界逻辑的认识,为以后的设计演化服务
实际上世界逻辑设计可以无限地大(实际上我最后实作出的设计也会很大,而且现在一般的大中型游戏中也的确涉及到了大量的世界逻辑),,,所以现在的游戏引擎一般都是表现引擎,,真正引擎之后的游戏源码部分才会出现一点零散的世界逻辑(而且没有被形成一个架构,因为人们普遍觉得像世界逻辑这样的东西根本不应被用来架构并单独封装,因为这的确是一个无尽的任务,世界逻辑可以无限啊),,,然而,就现在的大中型游戏中出现的世界逻辑引擎来说的确可以总而归为一个架构(我们正是要针对这些作架构,,正如你将会在我的设计中的大量细节),所以在设计中找准问题和思路方向是重要的,用什么范型只是后来的事情

如果一个计设中存在大量细节,,难以分清什么是细节上的,那些是架构上的,,那么一个方法是多范型设计中提到的“变点与不变点”,,怎么说呢,,比如一个游戏逻辑世界,,总有角色和地图吧(在经过分析得出大量设计细节后而难以得到一个满意的分析结果时,这个想法相对来说是比较容易想出来的,历为角色是实际存在的,,这个分析结果就是能提出一个架构,,并串联上所有的设计细节),,,,,这二个不变点就可以引导所有的你不能确定的“变点”(比如各种游戏系统)

实作GameGeneric就是用UML来设计接口和大架构,先来设计一个多范型设计图

这个库我准备将其开源发行,其代号为gvwl=Generic World Logic Libary
GameGeneric为Generic Virtual Reality Lib gvrl
这一个完全与平台无关的库(除了它的DLL形式逻辑,它的内部完全是平台无关的逻辑),

好了,找准了问题之后,现在来归纳问题(世界逻辑到底是什么,有什么,,)明白这些事实对以后的设计演化非常重要,而在原语设计部分提出的那个逻辑只是非常高层的我们需要在这里深入并得出一个确切的设计结果(这样才能依据它得出确切的范型设计)

1. Actor type应有PC(),NPC,道具(又有很多分法),电脑之分
2. Stage就是任务,有世界任务,国家任务,职业任务,组织任务,居民任务,,玩家任务之分,,
3. AI有吸怪AI(AgrossAI),,,电脑群攻AI
4. 时间就是纪元,当一个设定的世界任务stage被完全(世界任务就是隐创的全世界玩家的关卡),,,玩家进入一个新的纪元,,天器和器候机制,,对资源分布和技能发挥,,物品掉落,任务细节,等有影响
5. 这个世界逻辑引擎就是一个Actor_Stage_Driven_Dymic_World(完全可以脱离玩家运作,,需要提供一个初始状态集),,,,有二种干预方式,GM管理方式和gm Player(比如一个国主玩家,,国政系统)二种干预方式(admin adapter)
6. 势力范围演变(个人势力,国家势力,,组织势力),,世界地图,,,资源分布变迁,合成机制
7. War3like 多元故事机制,,多职业机制,,,采用War3的多职业,,技能组合=一种职业,,只有种族之分无明显的职业之分,,但是种族对技能学习有影响,,玩家遗迹系统(声望和称号系统,,匆滥,,声望和称号极其难得,,特殊道具难得)
8. 装备机制,,玩家装备位,,,
9. 少怪多人,,,boss为电脑,,怪物
10. 元素互克系统,,伤害数值(保证适当量度的耐打,少团灭,秒杀),能力数值机制(攻击方式,攻击伤害,,攻频,,视野能力,,白天黑夜之分,记忆系统),,,技能分职业技能和自学技能机制,,技能连携机制,
11. 拍卖行和叫卖场,,,叫卖类宠物(dual box like),,,野外雇佣兵机制,,
12. 打斗机制,,城建系统,国战场,,道具升级系统,,阵法系统,,,Macro和自定义UI的物品/技能栏
13. Actor都有一个id和一个名字集,,
14. 交通机制,,传送机制,,时空广阔,有星外,天上之分,里世界,玩家可转服
15. 此游戏

以上这些细节如果写开来,,就是一份很详尽很占篇幅的策划书,,,,因此我们采取了简单列举的方法,,,,具体的这些设计的细节和对这些设计的范型实现细节在下面一节会讲到。。

那么当设计完成进入多范型之后,我们是用一种自上而下(先提出一种架构然后慢慢发展支节逻辑)还是自下而上呢(先独立考虑各个支节,再在它们上面架构一个大架构,),这里的多范型设计我们使用自上而下(因为我个人认为这样设计得比较全面)

我对Game的看法是,


图1-2我想象中的一个Game原语


那么扩展呢?这里只说脚本机的扩展,Lua扩展并非真正的功能扩展,而是功能的导出,(当然写Lua之后的代码的确可进行功能扩展,但这里是相于Lua之前的中间逻辑来说的),脚本机的作用在于动态调试和发展用户逻辑(就是类似War3WorldEditor的开关编辑器了)

1,作为GameGeneric一部分时


2,独立于GameGeneric之上时



什么是扩展出来的某具体游戏呢?它可以是单机(比如做类War3的战役任务以熟悉游戏,这就要求游戏逻辑在本机),或对战(类War3的对战地图游戏),或C/S式的RPG(游戏逻辑也可以在服务端),这样的游戏并不局限于FPS或RPG或其它类型,这是一种泛RPG游戏,形象来说就是服务端的为客户端和服务端共享这个最终的游戏就是一个Lua写成的GameGeneric的派生,可动态测试,Lua解释器就是一个启动Shell,因为启动游戏等表现逻辑都在ShowGeneric内),这样的游戏,通过它的扩展接口,你可以在一张地图上设置赛道,这样就是一个赛车游戏,可以通过扩展接口,改变游戏视角,提供一些特别的地图,就可以做成一个FPS游戏,但它大体上是一个RPG(游戏在道理上本来就是一个完整的世界),因此说它是一种泛RPG游戏(这再一次体现了,通过某些很好的设计,可以整合某些细节或绕过它们,因此说设计是优于编码的)
这面即为NotWar3的原语图


我想象中一个具体游戏原语
13.2 了解Yake
以上谈完了原语设计部分,这一节讲要用到的库,就是YAKE了,这一节可能要占很长篇幅,因为Yake本身是一个很大的架构


我发现Yake是一个很好的游戏引擎(它自称是VR和游戏引擎,,但是它明显没有提供虚拟世界的内部逻辑,而只是提供了VR和Game对于计算机的表现逻辑而已,为什么选择Yake呢?就因为它功能全面至于运行效益也要考虑),它集成了我们以上提到的多数组件(而且DLL形式的组件可以选择不编译而且各个DLL之间没有相互引用关系对于Yake主体来说完全是Plugable的)(但是我们将按需去掉一些组件,将它集成到GameGeneric上去,比如将LuaBind逻辑和Lua提到GameGeneric上去,把YakeBase中的DLL封装逻辑和Logging机制去除,因为我们将把Yake编译成一个静态库再为GameGeneric所用,把oSceneLoaderLib去除,因为根本不需要这个功能-不需要把场景图中每个节点都利用XML展现出来,我们只想用XDMB的二进制Wrapper场景文件(场景文件有它自己的非XML形式的加密格式),而用XDMB去封装场景调入逻辑),我们应了解它的架构先,并理解其存在哪些API,但是并不深入探索它们是如何实现的,因为我们仅仅是想使用它 (这就表明,我们仅仅要做库的顾客而不是让它去主导我们自己的架构)

对库的理解要深入到函数API级(只是了解一下并不探索其实现因为我们只是顾客只满足于调用它并在自己的逻辑中封装它,但是要当高级顾问时却要了解它与其它API的联系,即该API的实现,因为此时我们调用到了该API的低层,这势必要清楚它与其它API的联系),而不仅仅满足了解其库的架构(了解Yake总体大架构还是基本,如果能完成理解它的主体即引入其它库的那些逻辑就更好拿来调用了),对Yake的研究可以让你学到不少东西

我为什么要强调“我们只是顾客呢”,人们并不真正理解那些从一出生就耳闻目见的字眼的真正意义,我想信如果不是我加括号,很多人也很难理解出括号内这层意义出来(而显然这是在我们前面反复讲到的)

能写出像Yake之类高抽象的代码(除了要懂模板和OO这二种范型的各种技术细节之外,还有策略,可以称为是OO和模板之上的抽象泛型),更重要的是具备我上面提到的那些设计思想以及对游戏的认识,流行于程序员之间的用卡来表达设计的方法,最初就是思想的活动,提出一些思想字眼(设计用词),然后再形成具体类文件的名字写到卡片上,最终形成一个计算机观点能接受的范型设计。

正如Yake所言,它是一个VR引擎,又是一个Game引擎,它的Base部分提供了DLL装卸机制和Log机制,这就一定意义上对应我们接下来要谈到的“可复用”策略(ORDL),它除了RAF,Client,Server之外的表现部分是VR的表现部分,而Raf,Client,Server这些都是Game Show部分(显然Yake没提供VRLogic和Game Logic部分),它的LuaBind就是脚本级扩展接口,而Base的DLL和Log机制就是程序员级的扩展接口,下面我们来深刻了解一下这个Base Generic并探索它可以被如何完善的地方,首先来看一个概念

面向复用的设计库(Ori Reuse Design Lib),这个库是一个策略库(我们把用模板范型和OO范型来表达设计的技术实现统称为策略,这样策略就不只表示DP了),Generic Design Poliy Lib(通用策略设计库),那么这是一个什么样的库呢,这个库基于以下思想


这个库的另一个别名是:GenericInterLogicPolicyLibrary,ComplilerTime 实现与Design Devidded Policy Lib,或Complier Time face可复用 Plugable Pocidy Lib(请原谅我用这么长的描述,但这不是标题,而且如果缺少任何一个字眼都不完整),如果把Yake的Base Generic进行完善,它就会这么一个库

这个Policy用了一些语言内的范型,比如OO和模板,还用了语言外的范型,比如Plugable的DLL,这样的库为设计服务(注意这句话),它主要达到一种什么样的目的呢, 通过在设计期就分开实现和设计,这样就达到了在设计期就达到充分的可复用(而可复用显然无论是设计还是实现都最先考虑的问题,OO和面向构件都是为了可复用而设计,因此这个库实际上就整合了这二个方面,语言之内是OO,语言之外是构件),(你可以看到这个库为VREngine和NotWar3的各个中间抽象层次所用)

与设计期有关的东东常称为元,比如MetaProgramming,强调设计期的作用,还比如MetaData(强调对数据的描述而不是存储,关系数据库的拥护者认为存储才是数据库真正要考虑的东西,而存储是具体数据库的事,这个道理就像我在本书最后一部分提到的Game与VR的关系,到底具体数据库才是数据库,还是对数据的描述才是数据存储?(什么是游戏,具体的游戏是游戏,还是游戏这种泛型说法才是游戏)这是鸡生鸡蛋生蛋的问题,以我看,鸡蛋问题并非不可解,答案就是先是鸡后是蛋,因为在问题中,鸡字蛋字都是具体的)因此上述库还可称为MetaDesignPolicy


运行期=计算机实现=泛型实现(RTTI),因此可复用的最大限度是你的泛型实现

图的左边是“与平台有关的表现”,称为表现Generic(需用与计算机平台有关的多范型表达),右边是“不与平台有关的纯思想”,这样就做到了在设计期就充分考虑了实现与逻辑分开(设计与编码是统一的,设计期就充分考虑了可复用,那么在编码时自然会继承这个优势,一切编码和实现级的的不可复用问题和难于复用问题都可在设计期找到答案,设计与实现本来就是统一多于其矛盾的,他们的矛盾之处在于泛型设计与原语设计的矛盾,比如在设计期把游戏中的一片叶子当成一个对象有时是一种好设计,然而当被用于多泛型实现时,在运行时就会造成效率的低下,这就是不成功的多范型设计,当然,实现与逻辑分开这个说法绝非这么简单,因此我们在SHOW和LOGIC内部也需要考虑将其OO化或策略化以增强其内部的实现与逻辑),诚然,OO可以一定程序上做到“实现与逻辑分开”,但是还有策略,然而这是语言内部的,在语言外部是库(Plugable的DLL形式发挥了重要的作用),这个技术也可提供设计时的可复用(当然可复用永远是相对的,在应用上述图作出的应用架构,在各个Genric外至少是实现与逻辑分开的,当然各个Generic的SHOW或LOGIC部分也要做到可复用),这样就是

1. 要保证在实现与Logic内部也是实现与逻辑分开的和充分的Plug化
2. 要保证Logic部分绝对是思想而没有与平台相关的细节
3. 在每一个Genric层次,可以无Logic,但不可没有Show

以的图可对应到NotWar3的原语设计部分,而它们共同的基础就是这个面向复用的策略库

1. Extend Generic=Game Generic+Lua Bind Generic(或者GameGeneric自成一个Generic,此时就要求Lua Bind不是仅对GameGenric进行Bind,而是对下面各个Genric进行Bind)
2. VR Generic=Yake+World Logic Generic
3. Game Generic
4. 它们的下面是Yake Base

即见下面这个总原语图


请注意到,上述图中很多用词显然仅属于思想过程的中间描述(即仅仅是描述原语的用词,比如GameGeneric中的Generic这个词就表示它可能是一种架构逻辑也可能是实现逻辑,这里就用Generic来整个描述这个层次抽象, 即Generic=”一切中间逻辑”代词,它并不一定被封装成一个库也并不一定要在后来的多范型设计中要求被体现,可能会在源程序中用名字空间来表达,C++中的名字空间表达了一种什么样的本质呢?一个名字空间就是一个应用架构中某个子架构,或称为抽象层次的总称,而我们这里提出了四个即1.Design Policy Generic2,VR Generic3,Game Generic4,Extend Generic,其中3,4是特别的,如果3是为4服务,那么3,4实际上可以合并,如果Game有它自己的架构,那么3,4是分开的,Generic,Generic就是一种组织所有某个抽层的架构和实现的形式,名字空间也使我们的源程序能够很好地硬盘上被组织,Java的源程序包中的文件夹形式就是一种表现),因为我们此时在进行原语设计,不必考虑面向代码将其反映出来,这样我们考虑问题的范围会更广一点(实际进行多范型设计时要反映出来的就是那些能实际作用代码的用词,像上面大部分后带Generic饰词的中间逻辑集都要被体现,我们在实作部分会给出一个经过修饰的原语图,从原语设计到多范型设计到UML设计我们称为设计的演化),但无论如何要明白,一些对设计过程中用到的思想辅助用词是不必考虑进设计的代码实现的。原语设计跟多范型设计中间存在比较大的距离(真正的原语设计不是超级原语,虽然它名为原语,但它是在可控范围内权衡做到的原语,真正的多范型也并不是一团没有根据的计算机实现,它要也要面向前面的原语设计得出的结论,它们之间的联系要优于它们之间的区别被我们考虑)

不必拘泥以上的思想模型(任何人构架的逻辑模式和逻辑用词都可以不一样这不是严格的,但最终的应用反映在代码上要能工作这却是严格的),你画出的原语设计图同样正确,再简单也没关系,也不一定要成档,因为这只是严重参杂了个人看法的东东(只有当后来的多范型设计时才慢慢走入编程的束缚,因为这是参照编程界现成方案的设计,因此大家做出的设计都差不多一样)

这里再谈一下可复用与“逻辑实现”分开之间的联系(主要出现在设计期和重构期)

我们一直提倡逻辑与实现分开(这是设计的基本准则),这样设计有没有做到呢?,这里要说一下实现与逻辑分开,然而即便是这个说法本身也有点含糊,说实话这绝对是一个具体问题具体分析的活,(就以上面的设计来说,我至少可以找出几个逻辑与实现的说法,比如,但是大体上可以分为二类,Generic外部的和内部的,反正我自认为上图已经体现了足够好的逻辑与实现分开,如果你不这么认为,那么你一定有比我更好的方案,不妨说出来听听或指出我的缺点,对于实现与逻辑分开,我曾认真思考过为GameLogicGeneric中的每个个体增加一种联系到GameShowGeneric,但是这个设计我后来放弃了,这样做看起来合理实际上只会增加实现与逻辑的偶合性而且在实现上也是很复杂的,而且压根就不需要这样做因为GenericInterLogicPolicyLib已经是一种很好的方案,已经是一种根本的实现与逻辑分开策略,,需要作一层复杂的中间封装逻辑,这层抽象可能被集成到GameGeneric中)一个游戏GameGeneric就是LogicGeneric加上ClientSideGeneric的总和(逻辑上下或平等互饰构成逻辑总和,应用于是被建立起来)并由此定义出一个游戏比如NotWar3(这才是真正的编码与实现部分,就是几个Demo中要谈到的),或者我们可以把实作出的LogicGeneric置于ClientGeneric的底层(,这正是我们要采取的方式,作为中间逻辑的库之间应如何架构,它们之间的逻辑应如何,这也是我们要考虑的)。

我的一些不成熟的尝试和不成熟的思想导致的设计缺陷

1. 我曾设想Lua部分写Game.dll,我把总架构做到了扩展逻辑之后作为Lua代码,因为我认为那是具体游戏的事而不是GameEngine的事了,因为我还觉得“具体游戏才是游戏”,而这里的GameEngine似乎称为“用计算机模拟游戏世界的引擎”(ComputerSimutWorldEngine)更为合适,因此GameEngine.DLL最好的说法是VREngine,但是另一方面,也有另外一种做法那就是把扩展逻辑和VR逻辑中间放置一个Game总架构逻辑,这样G总架构逻辑就在Lua之前了,会是C++的代码,这样也是一种非常好的选择)游戏是一个对等或C/S的模型,因此分PeerSideGeneric和ServerSideGeneric,(我一开始设计时GameGeneric曾把这二个设计原语加了进去,说实话这就不是一个好的设计,因为只要在具体游戏的逻辑中才会出现客户端和服务端之说,而GameGeneric管理一些更为低层的逻辑,这样做就犯了设计的大忌,过早把实现混合成架构做进了设计中)

2. lua扩展是不是限制了lua级扩展的瓶颈(要深克明白脚本机扩展导出与动态修改的作用区别),因为导出的逻辑有限?当然这个扩展接口并不能导出全部的中间逻辑,实际上也没有必要(LUA级的扩展不需要导出从一开始到GameGeneric的全部中间逻辑)这里要深刻理解这个扩展接口(因为多范型设计与细节有关,只要明白各个中间抽象层次应有的细节,才能依据它来进行多范型设计),扩展接口架构在GameEngine之上(但并不需要导出这个构架),但是扩展逻辑不只是封装GameEngine这个中间逻辑层的某些接口(即它不是一个纯粹架构于GameGeneric之上的中间逻辑,虽然在原语设计图中它是,然而在范型实现中并不是这样),而是分别对其它中间逻辑层次有引用(即它必须对OIS有导出逻辑才能在扩展接口集中提供一个屏蔽鼠标的LUA接口,必须对RAF作导出逻辑才能在扩展集中提供启动一个游戏的功能,必须对CAMERA作导出才能在LUA级提供一个变换视角的游戏—而这个功能正是LUA级扩展必要的,,因为玩家写地图时总要调用到变换视角的功能,,比如通过LUA扩展出的一个泛RPG的FPS游戏,LUA护展级应提供什么功能和接口,可以参照WAR3WORLDEDITOR中的全部开关功能)LUA级的扩展全部是函数级的接口,而不是OO级的架构,,一方面是因为用LUA编程的玩家你不能要求他们有OO范型的思维,另一方面是因为实际的通过LUA扩展的过程只要用面向过程就够了并不需要形成一个架构,无非就是转动视角,控制游戏开启这些简单对外的逻辑,对内的扩展只能通过为Game.DLL写DLL来扩展----(而且LUA本身支持OO也不是很好)当然也存在另外一种做法,这就是把GameGeneric这整个抽象层次作为为Extend Generic服务的基础,也即它并不指代一个“Game”,并不打算把它设计成一个架构,而只是简单的对要导出的库(比如VREngine的方方面面)扩展封装,这样GameGeneric实际上就是1,GameGeneric加2,Extend的综合了


13.3 GVWL1.0开发
荒世:一个简单游戏引擎的实现

这一部分的目的就是为了产生一个游戏引擎可复用库,向读者展示的是设计能力。。

这里的设计在不同的情境下有二种意思,1,面向复用的工程设计,2,用语言映射现实问题的解法。。

当然,这不是游戏引擎,,仅仅是个可称为图形引擎的东西(当然,限于教育目的,我并不打算把它写得跟ogre一样面面具到不提供很多接口过多选择,比如GL,DX)。然而,通过增加网络逻辑,等其它方面的逻辑,它就可以形成为一个游戏引擎(作业:重构里面有相应的作业)

这个游戏引擎充分利用了stl和loki逻辑。而且还复用了OPENGL,我始终相信stl和loki才是标准库,为了最大清楚程序地呈现利用C++和这二个语言标准库来设计应用,,,我没有引入太多的库,,我克意不引进太多的第三方库,(所以很多小逻辑我都直接发明轮子以避免使用到第三方库),,因为这是基于前述清楚教育的目的而且我个人能力也有限,,(比如下面我不会的碰撞检测逻辑所以我刚好省掉它);,所以这个游戏引擎也是省略了很多功能的(比如碰撞等瓴域逻辑,本来一般图形引擎和游戏引擎是需要ODE的但我省了),。但这个库作为游戏引擎,最最基础的功能它还是都包括的,你完全可以二次开发,加入更多复用逻辑或自己的逻辑,,将其扩展成大全的游戏引擎。

这个产生的库要求很强的扩展能力(很强的被复用能力,所以要求基于复用的设计元素),所以我用了LOKI,OO方面的东西用得比较少(因为我相信template 加loki的设计能力和理念作为设计标准库,就该善用它,而OO更应该用在非设计的实现中,因为用OO设计复用时需要大量的学习成本,不应用OO 的眼光对设计分层级)。

当发展具体游戏也即实现部分时应该远离设计, 毕竟复用是设计复用,而不是实现复用,,但这个实现也有这个实现意义上的设计(因为二者根子上都是软件抽象的本质),设计与实现这二者是在某个范围相对的,而且只有模糊分界,,面对整个系统时架构师有划分,而且在程序员某模块中也有小设计,所以C++的各项语言能力,都可用来设计或编码,它是应用语言也是设计语言,,只是OO不太适合出现在面向复用的设计中):不要提供很大的跟OO一样的logics,,,只有通过设计模式串联起来的才是大逻辑(这才是银弹,OO维度太低了),,其它的都是小逻辑,就跟boost里面的库一样。。


设计中主要解决的大大小小的瓴域逻辑有如下(因为这些库逻辑最终要进入实现,所以我只在设计中含有最基础最原子的那些功能和接口,比如图形方面的逻辑,动画啊什么的,(这是业务领域的轮子,,外来轮子才是另外轮子,源程序树应该另外轮子各占一文件夹,主就放在根文件夹内),更高阶部分才发展业务领域逻辑,发展更高阶的逻辑,比如场景管理,资源管理,,

13.4 范型设计与实作
那么以上多范型设计图中,“面向用户和开发者”的高阶接口应提供什么呢,这并不是一个有标准答案的问题(这不可能是一个形式的活动,只能是一个感性的活动),根据“设计的方法学”,我们只需做列举题与选择题,比如以下:
1, 是不是应提供一个“”,我以前想到用Actor封装,Stage封装
2, 如果要提供播放CS的逻辑中间件,(因为CS可能要获取网络)那么是不是应提供一个类,这个类接受网络参数,这样就有引用到Yake 的Net,不,,绝对不能这样,,,这样会增加表现与逻辑的联系,,这样做出的类(集)接口就是不好的接口设计,,,想要什么样的应用逻辑总是可以用逻辑互饪的方法达到,,而基础的中间逻辑件部分只应提供属于它自己职责的独立逻辑。最好不要与其它逻辑交叉(往往将远离应用的中间逻辑称为真正的接口,,,而靠近应用的实现逻辑,,或应用逻辑本身称为真正的实现,,只要不过早地将实现逻辑做进或混入接口逻辑,,那么你就做到了实现与逻辑分开,,,也即这里“真正的接口”主要就是发挥桥接作用的接口逻辑,因为接口逻辑也算是中间逻辑,应在这些抽象层次上提供少量逻辑,,,,后来的大量实现逻辑靠前面的接口逻辑来扩展,,)

我们要写什么样的实现呢,注意我们还得为实现构造一个架构(实际上这个还可做到GameGeneric之上,但是这个应该属于具体游戏的逻辑了,因此我们还是做到NotWar3的Lua代码构架中去)

纯逻辑的库ODE,也跟LogicGeneric一样吧,,不需考虑平台相关,Logic级应提供什么功能接口,应参照WAR3的JASS的二个基本文件来设计,那里有大部分游戏世界逻辑(但JASS用了过程,这里是C++用OO来实现),,应考虑到游戏世界这个相对它的Plugable dll的“高阶”中间逻辑层次应提供什么逻辑跟接口(很明显,这里面应有一个能容纳Plug逻辑的引入逻辑的架构(但是YAKE的引入库逻辑是引用现有的库,而这里的总架构是先于PLUG的,因此更为灵活和自由,应提供类ADDAFORCE之类的总扩展接口,才能为PLUG留足够好的对主LOGICL架构的扩展空间),和一个类YAKEBASEGENERIC的实现集,因为需要用到DLL和LOG机制,),

下面设计出一个UML图,这步开始,我们就进入设计的编码实现了,这步过后,设计就完成了

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值