好文档就是一把金锄头!
欢迎来到金锄头文库![会员中心]
电子文档交易市场
安卓APP | ios版本
电子文档交易市场
安卓APP | ios版本

被误解的C——汉尼拔.docx

8页
  • 卖家[上传人]:宝路
  • 文档编号:20163615
  • 上传时间:2017-11-21
  • 文档格式:DOCX
  • 文档大小:30.24KB
  • / 8 举报 版权申诉 马上下载
  • 文本预览
  • 下载提示
  • 常见问题
    • 被误解的 C++——汉尼拔by 莫华枫 公元前 216 年 8 月 2 日,意大利东部平原,一个叫做坎尼的地方,两支大军摆开阵势,准备决一死战一方是由保罗斯和瓦罗两位执政官率领的罗马人,另一方则是伟大的军事天才汉尼拔 *巴卡率领的迦太基军队及其同盟罗马人超过 8 万,而迦太基仅有 4 万余人然而到了傍晚,罗马人被彻底击败, 7 万人被杀,仅有少数得以逃脱这就是著名的坎尼会战经此一役,(外加先前进行的特利比亚和特拉西梅诺湖会战),罗马人元气大伤,成年公民损失达五分之一部分城邦背叛罗马,西西里也发生起义罗马已经到了摇摇欲坠的地步 汉尼拔的这些胜利,完全得益于先前的一次异乎寻常的远征公元前 218 年,汉尼拔率领军队,从新迦太基城(西班牙)出发,翻越比利牛斯山,进入南高卢地域在他面前有两条路可走,翻越阿尔俾斯山,或者沿海岸进入意大利但是,当时罗马人已在沿海地区部署了两支部队,准备拦截汉尼拔而且,罗马人的海军优势,使得他们可以在任何时候将一支部队登陆在他的背后而翻越阿尔俾斯山,则是一条及其艰险的道路,更何况是在冬天 汉尼拔选择了阿尔俾斯山他甩开了罗马人,从小圣贝纳德和日内瓦山之间越过阿尔俾斯山,进入意大利境内。

      此时,罗马人便失去了战略纵深,一把尖刀已经深深地插入他们的腹内 ... C++的发展史上,也有着如同汉尼拔翻越阿尔俾斯山远征一切还得从 C with Class 时代说起 Bjarne 曾经反复强调,他创建 C++为的是将 Simular 的抽象能力同 C 的性能结合起来于是,在 C 语言的基础上,诞生了一种拥有类、继承、重载等等面向对象机制的语言在这个阶段,C++提供了两个方面的抽象能力一种是数据抽象,也就是将数据所要表达的含义通过类型以及依附于类型上的成员表述另一种则是多态,一种最原始的多态(重载) 数据抽象,通过被称为“抽象数据类型(ADT)”的技术实现ADT 的一种方案,就是类类所提供的封装性将一个数据实体的外在特征,或者说语义的表述形式,同具体的实现,比如数据存储形式,分离这样所增加的中间层将数据的使用者同数据的实现者隔离,使得他们使用共同的约定语义工作,不再相互了解彼此的细节,从而使得两者得以解耦 多态则是更加基础更加重要的一种特性多态使得我们得以用同一种符号实现某种确定的语义多态的精髓在于:以一种形式表达一种语义在此之前,我们往往被迫使用不同的符号来代表同一种抽象语义,为的是适应强类型系统所施加的约束。

      比如: //代码 #1int add_int(int lhs, int rhs);float add_float(float lhs, float rhs);很显然,这两个函数表达的语义分别是“把两个 int 类型值加在一起”和“把两个 float 类型值加在一起”这两个语义抽象起来都表达了一个意思:加 我们在做算术题的时候是不会管被计算的数字是整数还是实数同样,如果能够在编程的时候,不考虑算术操作对象的类型,只需关心谁和谁进行什么操作,那么会方便得多当 C++引入重载后,这种愿望便得以实现: //代码 #2int add(int lhs, int rhs);float add(float lhs, float rhs);重载使得我们只需关心“加” 这个语义,至于什么类型和什么类型相加,则由编译器根据操作数的类型自动解析 从某种意义上说,重载是被长期忽视,但却极为重要的一个语言特性在多数介绍 OOP 的书籍中,重载往往被作为 OOP 的附属品,放在一些不起眼的地方它的多态本质也被动多态的人造光环所设遮蔽然而,重载的重要作用却在实践中潜移默化地体现出来重载差不多可以看作语言迈入现代抽象体系的第一步。

      它的实际效用甚至要超过被广为关注的 OOP,而不会像OOP 那样在获得抽象的同时,伴随着不小的副作用 随着虚函数的引入,C++开始具备了颇具争议的动多态技术虚函数是一种依附于类(OOP的类型基础)的多态技术其技术基础是后期绑定(late-binding)当一个类 D 继承自类 B时,它有两种方法覆盖(override)B 上的某个函数: //代码 #3class B{public:void fun1();virtual void fun2();}; class D:public B{public:void fun1();void fun2();};当继承类 D 中声明了同基类 B 中成员函数相同函数名、相同签名的成员函数,那么基类的成员函数将被覆盖对于基类的非虚成员函数,继承类会直接将其遮蔽对于类型 D 的使用者,fun1 代表了 D 中所赋予的语义而类型 D 的实例,可以隐式地转换成类型 B 的引用 b,此时调用 b 的 fun1,则执行的是类 B 的 fun1 定义,而非类 D 的 fun1,尽管此时 b 实际指向一个 D 的实例 但是,如果继承类覆盖了基类的虚函数,那么将得到相反的结果:当调用引用 b 的 fun2,实际上却是调用了 D 的 fun2 定义。

      这表明,覆盖一个虚函数,将会在继承类和基类之间的所有层次上执行覆盖这种彻底的、全方位的覆盖行为,使得我们可以在继承类上修饰或扩展基类的功能或行为这便是 OOP 扩展机制的基础而这种技术被称为动多态,意思是基类引用所表达的语义并非取决于基类本身,而是来源于它所指向的实际对象,因此它是“多态”的因为一个引用所指向的对象可以在运行时变换,所以它是“动”的 随着动多态而来的一个“副产品”,却事实上成为了 OOP 的核心和支柱虚函数的“动多态”特性将我们引向一个极端的情况:一个都是虚函数的类更重要的,这个类上的虚函数都没有实现,每个虚函数都未曾指向一个实实在在的函数体当然,这样的类是无法直接使用的有趣的是,这种被称为“抽象基类” 的类,迫使我们继承它,并“ 替它”实现那些没有实现的虚函数这样,对于一个抽象基类的引用,多态地拥有了继承类的行为而反过来,抽象基类实际上起到了强迫继承类实现某些特定功能的作用因此,抽象基类扮演了接口的角色接口具有两重作用:一、约束继承类(实现者)迫使其实现预定的成员函数(功能和行为);二、描述了继承类必定拥有的成员函数(功能和行为)这两种作用促使接口成为了 OOP 设计体系的支柱。

      C++在这方面的进步,使其成为一个真正意义上具备现代抽象能力的语言然而,这种进步并非“翻越阿尔俾斯山” 充其量也只能算作“ 翻越比利牛斯山” 对于 C++而言,真正艰苦的远征才刚开始,那令人生畏的“阿尔俾斯山”仍在遥远的前方 同汉尼拔一样,当 C++一脚迈入“现代抽象语言俱乐部”后,便面临两种选择或者在原有基础上修修补补,成为一种 OOP 语言;或者继续前进,翻越那座险峻的山峰C++的汉尼拔——Bjarne Stroustrup——选择了后者 从 D&E 的描述中我们可以看到,在 C++的原始设计中就已经考虑 “类型参数”的问题但直到90 年代初,才真正意义上地实现了模板然而,模板只是第一步诸如 Ada 等语言中都有类似的机制(泛型,generic),但并未对当时的编程技术产生根本性的影响 关键性的成果来源于 Alex Stepanov 的贡献Stepanov 在后来被称为 stl 的算法-容器库上所做的努力,使得一种新兴的编程技术——泛型编程(Generic Programming,GP)——进入了人们的视野stl 的产生对 C++的模板机制产生了极其重要的影响,促使了模板特化的诞生。

      模板特化表面上是模板的辅助特性,但是实际上它却是比“类型参数”更加本质的机能 假设我们有一组函数执行比较两个对象大小的操作: //代码 #4int compare(int lhs, int rhs);int compare(float lhs, float rhs);int compare(string lhs, string rhs);重载使得我们可以仅用 compare 一个函数名执行不同类型的比较操作但是这些函数具有一样的实现代码模板的引入,使得我们可以消除这种重复代码: //代码 #5template int compare(T lhs, T rhs) {if(lhs==rhs)return 0;if(lhs>rhs)return 1;if(lhs int compare(T* lhs, T* rhs) {if(*lhs==*rhs)return 0;if(*lhs>*rhs)return 1;if(*lhsstruct comp_impl{int operator()(T lhs, T rhs) {if(lhs==rhs)return 0;if(lhs>rhs)return 1;if(lhsstruct comp_impl{int operator()(T* lhs, T* rhs) {comp_impl()(*lhs, *rhs);}};template int compare(T* lhs, T* rhs) {comp_impl()(*lhs, *rhs);}当我们将指针的指针作为实参,调用 compare 时,神奇的事情发生了: //代码 #8double **x, **y;compare(x, y);compare 居然成功地剥离了两个指针,并且正确地比较了两个对象的值。

      这个戏法充分利用了类模板的局部特化和特化解析规则根据规则,越是特化的模板,越是优先匹配T*版的comp_impl 比 T 版的更加“特化”,会得到优先匹配那么当一个指针的指针实例化comp_impl,则会匹配 T*版的 comp_impl,因为指针的指针,也是指针 T*版通过局部特化机制,剥离掉一级指针,然后用所得的类型实例化 comp_impl指针的指针剥离掉一级指针,那么还是一个指针,又会匹配 T*版T* 版又会剥离掉一级指针,剩下的就是真正可以比较的类型——double此时,double 已无法与 T*版本匹配,只能匹配基础模板,执行真正的比较操作 这种奇妙的手法是蕴含在模板特化中一些更加本质的机制的结果这种意外获得的“模板衍生产品”可以算作一种编译时计算的能力,后来被一些“好事者” 发展成独立的 “模板元编程”(Template Meta Programming,TMP) 尽管 TMP 新奇而又奥妙,但终究只是一种辅助技术,用来弥补 C++的一些缺陷、做一些扩展,“捡个漏”什么的不过它为我们带来了两点重要的启示:一、我们有可能通过语言本身的一些机制,进行元编程;二、元编程在一定程度上可以同通用语言一起使用。

      这些启示对编程语言的发展有很好的指导意义 模板及特化规则是 C++ GP 的核心所在这些语言特性的强大能力并非凭空而来实际上有一只“幕后大手” 在冥冥之中操纵着一切 假设有一个类型系统,包含 n 个类型:t1,...,tn ,那么这些类型构成了一个集合 T={t1,...,tn}在当我们运用重载技术时,实际上构造了一组类型的 tuple 到函数实现的映射:->fj()编译器在重载解析的时候,就是按照这组映射寻找匹配的函数版本当我们编写了形如代码#5 的模板,那么就相当于构建了映射: ->f0() 而代码#6,以及代码#7 中的 T*版模板,实际上是构造了一个->fp()的映射这里 Tp是 T 的一个子集:Tp={t'|t'=ti*, ti∈T} 换句话说,特化使泛型体系细化了利用模板特化技术,我们已经能够(笨拙地)分辨浮点数、整数、内置类型、内置数组、类、枚举等等类型具备为类型划分的能力,也就是构造不同的类型子集的能力 现在,我们便可以构造一个“泛型体系”:G={T} U T U Tp U Ta U Ti U Tf U Tc ...其中,Tp 是所有指针类型,Ta 是数组,Ti 是整数,Tf 是浮点数, Tc 是类等。

      点击阅读更多内容
      关于金锄头网 - 版权申诉 - 免责声明 - 诚邀英才 - 联系我们
      手机版 | 川公网安备 51140202000112号 | 经营许可证(蜀ICP备13022795号)
      ©2008-2016 by Sichuan Goldhoe Inc. All Rights Reserved.