xib自定义cell 重用纯虚函数的目的是为了重用其算法代码吗

qinying727 的BLOG
用户名:qinying727
访问量:3115
注册日期:
阅读量:5863
阅读量:12276
阅读量:418309
阅读量:1106329
51CTO推荐博文
1. static在c,c++中有什么不同点2. 堆和栈的区别3. 纯虚函数4. 指针和引用的区别5. 如果构造函数出错,如何处理?6. 对设计模式是否熟悉,用过哪些?7. c++如何使用c中的函数,为什么?
静态数据成员/成员函数,C++特有
在面向对象的C++语言中,虚函数(virtual function)是一个非常重要的概念。因为它充分体现了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广。有人甚至称虚函数是C++语言的精髓。
定义一个函数为虚函数,不代表函数为不被实现的函数 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数 定义一个函数为纯虚函数,才代表函数没有被实现 定义他是为了实现一个接口,起到一个规范的作用,规范继承这个 类的程序员必须实现这个函数。
有纯虚函数的类是不可能生成类对象的,如果没有纯虚函数则可以。比如: class CA { public: virtual void fun() = 0; // 说明fun函数为纯虚函数 virtual void fun1(); }; class CB { public: virtual void fun(); virtual void fun1(); }; // CA,CB类的实现 ... void main() { CA // 不允许,因为类CA中有纯虚函数 CB // 可以,因为类CB中没有纯虚函数 ... }
虚函数在多态中使用: 多态一般就是通过指向基类的指针来实现的。 有一点必须明白,就是用父类的指针在运行时刻来调用子类。父类指针通过虚函数来决定运行时刻到底是谁而指向谁的函数。
有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。 定义纯虚函数就是为了让基类不可实例化。
-------------------------------------------
一、引入原因:  1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。  2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。  为 了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。  二、纯虚函数实质:  类中含有纯虚函数则它的VTABLE表不完全,有一个空位,所以,不能生成对象(编译器绝对不允许有调用一个不存在函数的可能)。在它的派生类中,除非重载这个函数,否则,此派生类的VTABLE表亦不完整,亦不能生成对象,即它也成为一个纯虚基类。  三、 虚函数与构造、析构函数:  1、构造函数本身不能是虚拟函数;并且虚机制在构造函数中不起作用(在构造函数中的虚拟函数只会调用它的本地版本)。  想一想,在基类构造函数中使用虚机制,则可能会调用到子类,此时子类尚未生成,有何后果!?。  2、析构函数本身常常要求是虚拟函数;但虚机制在析构函数中不起作用。  若类中使用了虚拟函数,析构函数一定要是虚拟函数,比如使用虚拟机制调用delete,没有虚拟的析构函数,怎能保证delete的是你希望delete的对象。  虚机制也不能在析构函数中生效,因为可能会引起调用已经被delete掉的类的虚拟函数的问题。  四、对象切片:  向上映射(子类被映射到父类)的时候,会发生子类的VTABLE 完全变成父类的VTABLE的情况。这就是对象切片。  原因:向上映射的时候,接口会变窄,而编译器绝对不允许有调用一个不存在函数的可能,所以,子类中新派生的虚拟函数的入口在VTABLE中会被强行“切”掉,从而出现上述情况。  五、虚拟函数使用的缺点  虚函数最主要的缺点是执行效率较低,看一看虚拟函数引发的多态性的实现过程,就能体会到其中的原因。
----------------------------------------------4.
指针与引用看上去完全不同(指针用操作符“*”和“-&”,引用使用操作符“. ”),但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢? 首先,要认识到在任何情况下都不能使用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。 “但是,请等一下”,你怀疑地问,“这样的代码会产生什么样的后果?” char *pc = 0; // 设置指针为空值 char& rc = * // 让引用指向空值 这是非常有害的,毫无疑问。结果将是不确定的(编译器能产生一些输出,导致任何事情都有可能发生)。应该躲开写出这样代码的人,除非他们同意改正错误。如果你担心这样的代码会出现在你的软件里,那么你最好完全避免使用引用,要不然就去让更优秀的程序员去做。我们以后将忽略一个引用指向空值的可能性。 因为引用肯定会指向一个对象,在C++里,引用应被初始化。 string& // 错误,引用必须被初始化 string s("xyzzy"); string& rs = // 正确,rs指向s 指针没有这样的限制。 string * // 未初始化的指针 // 合法但危险 不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。 void printDouble(const double& rd) { cout && // 不需要测试rd,它 } // 肯定指向一个double值 相反,指针则应该总是被测试,防止其为空: void printDouble(const double *pd) { if (pd) { // 检查是否为NULL cout && * } } 指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。 string s1("Nancy"); string s2("Clancy"); string& rs = s1; // rs 引用 s1 string *ps = &s1; // ps 指向 s1 rs = s2; // rs 仍旧引用s1, // 但是 s1的值现在是 // "Clancy" ps = &s2; // ps 现在指向 s2; // s1 没有改变 总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。 还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是操作符[]。这个操作符典型的用法是返回一个目标对象,其能被赋值。 vector&int& v(10); // 建立整形向量(vector),大小为10; // 向量是一个在标准C库中的一个模板(见条款M35) v[5] = 10; // 这个被赋值的目标对象就是操作符[]返回的值 如果操作符[]返回一个指针,那么后一个语句就得这样写: *v[5] = 10; 但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用。(这有一个有趣的例外,参见条款M30) 当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针。
使用异常捕捉,具体问题具体分析。
设计模式基本思想
好的系统设计追求如下特性:
可扩展性( Extensibility ):新的功能或特性很容易加入到系统中来;
灵活性( Flexibility ):可以允许代码修改平稳发生,对一处的修改不会波及到很多其他模块;
可插入性( Pluggability ):可以很容易地将一个类或组件抽出去,同时将另一个有相同接口的类 / 接口加入进来。
具有如上特性的系统才有真正的可维护性和可复用性。而可维护性和可复用性对一个持续接入新需求,现有功能逐步完善,新功能不断丰富,版本不会终止的大型软件产品来说至关重要。
传统的复用包括:代码的 copy 复用,算法的复用,数据结构的复用。
在面向对象领域,数据的抽象化、封装、继承和多态性是几项最重要的语言特性,这些特性使得一个系统可以在更高的层次上提供可复用性。数据的抽象化和继承关系使得概念和定义可以复用;多态性使得实现和应用可以复用;而抽象化和封装可以保持和促进系统的可维护性。这样,复用的焦点不再集中在函数和算法等具体实现细节上,而是集中在最重要的宏观的业务逻辑的抽象层次上。复用焦点的倒转不是因为实现细节的复用不再重要,而是因为这些细节上的复用往往已经做的很好(例如,很容易找到并应用成熟的数据结构类库等),而真正冲击系统的是其要实现业务的千变万化。
本质上说,如果说一个软件的需求是永不变更或发展的,该软件也就不需要任何设计,怎么编码实现都行,只要需求满足,性能达标。但事实上,软件的本性就是不断增强,不断拓展的,不断变化的。我们可以控制指尖流淌出的每行代码,但控制不了奉为上帝的用户的需求。编码结束,测试全部通过,用户在试用过程中才发现原来的需求有问题,需要变更或提出新需求,怎么办?向用户抗议:需求总在变,没法做!?平抑心中的抱怨,加班加点大量的修改代码,疯狂的测试,依然是时间紧迫,心中没底?抑或了然于胸:这个变更或小需求合理,系统很方便纳入;于是坦然地和用户协商下一个交付时间点?
要使系统最大程度的适应需求的变更或新增,就必须在其要实现的宏观业务逻辑的抽象复用上下功夫。而设计模式就是综合运用面向对象技术和特性来提高业务逻辑可复用性的常用方法和经验的提取和汇总。
掌握 23 种设计模式的关键是理解它们的共通目的:使所设计的软件系统在一般或特定(系统将来在特定点上扩展的可能性大)场景下,尽可能的对扩展开放,对修改关闭。即面对新需求或需求变更时,容易开发独立于既有代码的新代码接入到现有系统或对现有代码做可控的少量修改,而不是在现有代码基础上做大量的增、删、改。为了这一目的, 23 种设计模式贯穿了面向对象编程的基本原则:
面向接口或抽象编程,而不是面向实现编程。
一个对象应当对其他对象有尽可能少的了解,即松耦合。
纯粹的功能复用时,尽量不要使用继承,而是使用组合。使已有的对象成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。
OO设计模式和设计原则
1.1 设计正在“腐烂”的征兆(Symptoms of Rotting Design)
&&& 有四个主要的征兆告诉我们该软件设计正在“腐烂”中。它们并不是互相独立的,而是互相关联,它们是过于僵硬、过于脆弱、不可重用性和粘滞性过高。
&&& 1. 过于僵硬 Rigidity 致使软件难以更改,每一个改动都会造成一连串的互相依靠的模块的改动,项目经理不敢改动,因为他永远也不知道一个改动何时才能完成。
&&& 2. 过于脆弱 Fragility 致使当软件改动时,系统会在许多地方出错。并且错误经常会发生在概念上与改动的地方没有联系的模块中。这样的软件无法维护,每一次维护都使软件变得更加难以维护。(恶性循环)
&&& 3. 不可重用性immobility 致使我们不能重用在其它项目中、或本项目中其它位置中的软件。工程师发现将他想重用的部分分离出来的工作量和风险太大,足以抵消他重用的积极性,因此软件用重写代替了重用。
&&& 4. 粘滞性过高 viscosity有两种形式:设计的viscosity和环境的viscosity.当需要进行改动时,工程师通常发现有不止一个方法可以达到目的。但是这些方法中,一些会保留原有的设计不变,而另外一些则不会(也就是说,这些人是hacks)。一个设计如果使工程师作错比作对容易得多,那么这个设计的viscosity 就会很高。
&&& 环境的viscosity高是指开发环境速度很慢且效率很低。
&&& 2 面向对象的类设计原则
&&& 2.1 开放关闭原则The Open Closed Principle (OCP)
&&& A module should be open for extension but closed for modification.一个模块应该只在扩展的时候被打开(暴露模块内部),在修改的时候是关闭的(模块是黑盒子)。
&&& 在所有的面向对象设计原则中,这一条最重要。该原则是说:我们应该能够不用修改模块的源代码,就能更改模块的行为。
&&& 2.1.1 动态多态性(Dynamic Polymorphism)
&&& 2.1.2 静态多态性(Static Polymorphism)
&&& 另外一种使用OCP的技术就是使用模板或范型,如Listing 2-3.LogOn函数不用修改代码就可以扩展出多种类型的modem. 2.1.3 OCP的体系结构目标(Architectural Goals of the OCP)
&&& 通过遵照OCP应用这些技术,我们能创建不用更改内部代码就可以被扩展的模块。这就是说,在将来我们给模块增添新功能是,只要增加新的代码,而不用更改原先的代码。 第 3 页,共 17 页使软件完全符合OCP可能是很难的,但即使只是部分符合OCP,整个软件的结构性能也会有很大的提高。我们应该记住,让变化不要波及已经正常工作的代码总是好的。
&&& 2.2 Liskov 替换原则The Liskov Substitution Principle(LSP)
&&& Subclasses should be substitutable for their base classes.子类应该可以替换其基类。
&&& 如下图2-14所示。Derived类应该能替换其Base类。也就是说,Base基类的一个用户User如果被传递给一个Devrived类而不是Base类作为参数,也能正常的工作。
&&& 2.3 依赖性倒置原则The Dependency Inversion Principle (DIP)1
&&&& Depend upon Abstractions. Do not depend upon concretions.依赖抽象,不要依赖具体。
&&& 如果说OCP声明了OO体系结构的目的,DIP则阐述了其主要机制。依赖性倒置的策略就是要依赖接口、或抽象函数、或抽象类,而不是依赖于具体的函数和类。这条原则就是支持组件设计、COM、CORBA、EJB等等的背后力量。
&&& 2.3.1 依赖抽象Depending upon Abstractions.
&&& 实现该原则十分简单。设计中的每一个依赖都应该是接口、抽象类,不要依赖任何一个具体类。
&&& 显然这样的限制比较严峻,但是我们应该尽可能的遵守这条原则。原因很简单,具体的模块变化太多,抽象的则变化少得多。而且,抽象是“铰链”点,在这些位置,设计可以弯曲或者扩展,而不用进行更改(OCP)。
&&& 2.4 接口隔离原则The Interface Segregation Principle (ISP)
&&& ‘Many client specific interfaces are better than one general purpose interface多个和客户相关的接口要好于一个通用接口。
&&& ISP是另一条在底层支持组件如COM技术的原则。没有它,组件和类的易用性和重用性都会大打折扣。该原则的实质很简单:如果一个类有几个使用者,与其让这个类载入所有使用者需要使用的所有方法,还不如为每一个使用者创建一个特定的接口,并让该类分别实现这些接口。
&&& 3 包体系结构的原则Principles of Package Architecture
&&& 类是必不可少的,但对于组织一个设计来说还不够,粒度更大的包有助于此。但是我们应该怎样协调类和包之间的从属关系?下面的三条原则都属于包聚合原则,能对我们有所帮助。
&&& 3.1 包聚合原则
&&& 3.1.1 发布重用等价原则The Release Reuse Equivalency Principle (REP)1
&&& 重用的粒度就是发布的粒度。The granule of reuse is the granule of release.一个可重用的元件(组件、一个类、一组类等),只有在它们被某种发布(Release)系统管理以后,才能被重用。用户不愿意使用那些每次改动以后都要被强迫升级的元件。因此,即使开发者发布了可重用元件的新版本,他也必须支持和维护旧版本,这样才有时间让用户熟悉新版本。
&&& 因此,将什么类放在一个包中的判断标准之一就是重用,并且因为包是发布的最小单元,它们同样也是重用的最小单元。体系结构师应该将可重用的类都放在包中。
&&& 3.1.2 共同封闭原则The Common Closure Principle (CCP)2
&& 一起变化的类放在一起。Classes that change together, belong together.一个大的开发项目通常分割成很多网状互联的包。管理、测试和发布这些包的工作可不是微不足道的工作。在任何一个发布的版本中,如果改动的包数量越多,重建、测试和部署也就会越多。因此我们应该尽量减少在产品的发布周期中被改动的包的数量,这就要求我们将一起变化的类放在一起(同一个包)。
&&& 3.1.3 共同重用原则The Common Reuse Principle (CRP)3
&&& 不一起重用的类不应该放在一起。Classes that aren‘t reused together should not be grouped together.对一个包的依赖就是对包里面所有东西的依赖。当一个包改变时,这个包的所有使用者都必须验证是否还能正常运行,即使它们所用到的没有任何改变也不行。
&&& 比如我们就经常遇到操作系统需要升级。当开发商发布一个新版本以后,我们的升级是迟早的问题,因为开发商将会不支持旧版本,即使我们对新版本没有任何兴趣,我们也得升级。
&&& 如果把不一起使用的类放在一起,同样的事情我们也会遇到。一个和我们无关的类的改变也产生包的一个新版本,我们被强迫升级和验证这个包是否影响正常的运行。
&&& 3.1.4 包聚合原则之间的张力Tension between the Package Cohesion Principles
&&& 这三条原则实际上是互斥的。它们不能被同时满足,因为每一条原则都只针对某一方面,只对某一部分人有好处。REP和CRP都想重用元件的人有好处,CCP对维护人员有好处。CCP使得包有尽可能大的趋势(毕竟,如果所有的类都属于一个包,那么将只会有一个包变化);CRP尽量使得包更小。
&&& 幸运的是,包并不是一成不变的。实际上,在开发过程中,包的转义和增删都是很正常的。在项目开发的早期,软件建筑师建立包的结构体系,此时CCP占主导地位,维护只是辅助。在体系结构稳定以后,软件建筑师会对包结构进行重构,此时尽可能的运用REP和CRP,从而最大的方便重用元件的人员。
&&& 3.2 包耦合原则The Package Coupling Principles.
&& 下面三条原则主要关心包之间的关系。
&&& 3.2.1 无依赖回路原则The Acyclic Dependencies Principle (ADP)1
&&& 包与包之间的依赖不能形成回路。The dependencies between packages must not form cycles.因为包是发布的粒度。人们倾向于节省人力资源,所以工程师们通常只编写一个包而不是十几个包。这种倾向由于包聚合原则被放大,后来人们就将相关的类组成一组。
&&& 因此,工程师发现他们只会改动较少的几个包,一旦这些改动完成,他们就可以发布他们改动的包。但是在发布前,他们必须进行测试。为了测试,他们必须编译和连编他们的包所依赖的所有的包。
&&& 3.2.2 依赖稳定原则(Stable Dependencies Principle,SDP)
&&& 朝稳定的方向依赖Depend in the direction of stability.虽然这条原则看起来很明显,但是关于这方面还是有很多需要说明的地方,稳定性并不一定为大家所了解。
&&& 稳定性是什么?站在一个硬币上,这稳定吗?很可能你说不。然而,除非被打扰,硬币将保持那个位置很长时间。硬币没有变化,但是很难认为它是稳定的。稳定性与需要改动所要做的工作量相关。硬币不稳定是因为只需要很小的工作量就能把它翻过来。换个角度,桌子就要稳定得多。
&&& 对于软件这说明什么?一个软件包很难被改动受很多因素影响:代码大小、复杂度、透明度等等。这些我们先不说,可以肯定的一点是,如果有很多其它的包依赖于一个软件包,那么该软件包就难以改动。一个包如果被许多其它包依赖,那么该包是很稳定的,因为这个包的任何一个改动都可能需要改动很多其它的包。
&&& 3.2.3 稳定抽象原则( Stable Abstractions Principle ,SAP)
&&& 稳定的包应该是抽象包。Stable packages should be abstract packages.我们可以想象应用程序的包结构应该是一个互相联系的包的集合,其中不稳定的包在顶端,稳定的包在底部,所有的依赖方向朝下。那些顶端的包是不稳定而且灵活的,但那些底部的包就很难改动。这就导致一个两难局面:我们想要将包设计为难以改动的吗?
&&& 明显地,难以改动的包越多,我们整个软件设计的灵活性就越差。但是好像有一点希望解决这个问题,位于依赖网络最底部的高稳定性的包的确难以改动,但是如果遵从OCP,这样的包并不难以扩展。
假设某个C函数的声明如下:
void foo(int x, int y);
该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字用来支持函数重载和类型安全连接。由于编译后的名字不同,C++程序不能直接调用C函数。C++提供了一个C连接交换指定符号extern“C”来解决这个问题。例如:
extern “C”
&& void foo(int x, int y);
&& … // 其它函数
extern “C”
&& #include “myheader.h”
&& … // 其它C头文件
这就告诉C++编译译器,函数foo是个C连接,应该到库中找名字_foo而不是找_foo_int_int。C++编译器开发商已经对C标准库的头文件作了extern“C”处理,所以我们可以用#include 直接引用这些头文件。
数据结构:
8. AVL 平衡二叉树
操作系统:
9. 进程和线程的区别
10. 进程间通信的方法,两个进程,socket通信,一个进程将一个指针发送过去,另一个进程是否可用linux
11. /proc下的文件是干什么用的?
12. 可执行程序的结构是什么样的?bss中有些什么?
13. Linux下定时程序用过没,怎么使用?
14. Linux下如何调试程序?程序core dump后怎么办?
进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。进程和线程的区别在于: 简而言之,一个程序至少有一个进程,一个进程至少有一个线程. 线程的划分尺度小于进程,使得多线程程序的并发性高。 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
信号:信号处理器&& 处理信号集中的信号
信号量:P、V操作&& 同步互斥
消息队列:进程间传递消息,可被多个进程共享(IPC实现基础)
/proc动态文件系统是一种特殊的由程序创建的文件系统,内核利用它向外界输出信息。/proc下每个文件都被绑定一个内核函数,这个函数在此文件被读取时,动态生成文件的内容。
可执行程序结构一般分为三个段:
.text:存放程序二进制代码
.data:存放全局的已初始化变量
.bss:存放全局的未初始化变量(Block Started by Symbol)
linux系统下让程序定时自动执行:crontab
格式 :&&minute hour day month year command
# Mail the system logs at 4:30pm every June 15th. 30 16 15 06 * for x in /var/log/*; do cat ${x} | done
# Inform the administrator, at midnight, of the changing seasons. 00 00 20 04 * echo 'Woohoo, spring is here!' 00 00 20 06 * echo 'Yeah, summer has arrived, time to hit the beach!' 00 00 20 10 * echo 'Fall has arrived.& Get those jackets out.& :-(' 00 00 20 12 * echo 'Time for 5 months of misery.& ;-('
注意该指令会输出到一个标准出口 (亦即. 一个终端机 ),像是上面使用 "echo" 的例子会将输出寄 给 "root" 帐号。如果您想要避免它,只要像下面将输出导引到一个空的设备 :& & 00 06 * * * echo 'I bug the system administrator daily at 6:00am!' &/dev/null
crontab -e 重新编辑定时执行程序
了这篇文章
类别:┆阅读(0)┆评论(0)114网址导航欢迎加入我们,一同切磋技术。 &
用户名: &&&
密 码: &
共有 4644 人关注过本帖
标题:C++多态性基本概念 包括虚函数和纯虚函数
等 级:新手上路
帖 子:36
结帖率:66.67%
&&问题点数:0&&回复次数:24&&&
C++多态性基本概念 包括虚函数和纯虚函数
C++编程语言是一款应用广泛,支持多种程序设计的计算机编程语言。我们今天就会为大家详细介绍其中C++多态性的一些基本知识,以方便大家在学习过程中对此能够有一个充分的掌握。
  前几天笔试的时候碰到考C++多态性的题目,因为不是自己的专业不是纯做软件开发,C++学习不是很好,做得有点混乱。回来以后立刻查了相关资料,大概明白了一点,可能以后解题的时候不会乱了。
  先摘下一些网上的书上的基本概念。
  多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。多态(polymorphisn),字面意思多种形状。
  C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。(这里我觉得要补充,重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性)而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。
  多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。
  那么多态的作用是什么呢,封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
  最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。
笔试的题目
#include &stdio.h&
&&& public:&&void foo()&&
&&&&&&&&printf(&1&);
&&& virtual void fuu()&&
&&&&&&&&printf(&2&);&&
class B:public A&&
&&& public:&&void foo()&&
&&&&&&&&printf(&3&);&&
&&& void fuu()&&
&&&&&&&&printf(&4&);&&
int main()
&&& A *p = &a;
&&& p-&foo();
&&& p-&fuu();&&
&&& p = &b;
&&& p-&foo();
&&& p-&fuu();
&&& return 0;
&&& 第一个p-&foo()和p-&fuu()都很好理解,本身是基类指针,指向的又是基类对象,调用的都是基类本身的函数,因此输出结果就是1、2。
  第二个输出结果就是1、4。p-&foo()和p-&fuu()则是基类指针指向子类对象,正式体现多态的用法,p-&foo()由于指针是个基类指针,指向是一个固定偏移量的函数,因此此时指向的就只能是基类的foo()函数的代码了,因此输出的结果还是1。而p-&fuu()指针是基类指针,指向的fuu是一个虚函数,由于每个虚函数都有一个虚函数列表,此时p调用fuu()并不是直接调用函数,而是通过虚函数列表找到相应的函数的地址,因此根据指向的对象不同,函数地址也将不同,这里将找到对应的子类的fuu()函数的地址,因此输出的结果也会是子类的结果4.
  笔试的题目中还有一个另类测试方法。即
  B *ptr = (B *)&a;&&ptr-&foo();&&ptr-&fuu();
  问这两调用的输出结果。这是一个用子类的指针去指向一个强制转换为子类地址的基类对象。结果,这两句调用的输出结果是3,2。
  并不是很理解这种用法,从原理上来解释,由于B是子类指针,虽然被赋予了基类对象地址,但是ptr-&foo()在调用的时候,由于地址偏移量固定,偏移量是子类对象的偏移量,于是即使在指向了一个基类对象的情况下,还是调用到了子类的函数,虽然可能从始到终都没有子类对象的实例化出现。
  而ptr-&fuu()的调用,可能还是因为C++多态性的原因,由于指向的是一个基类对象,通过虚函数列表的引用,找到了基类中foo()函数的地址,因此调用了基类的函数。由此可见多态性的强大,可以适应各种变化,不论指针是基类的还是子类的,都能找到正确的实现方法。
//小结:1.有virtual才可能发生多态现象2.不发生多态(无virtual)调用就按原类型调用
#include &iostream&
class Base
&&& public:
&&&&&&&&virtual void f(float x){ cout && &Base::f(float) & && x && }
&&&&&&&&void g(float x){ cout && &Base::g(float) & && x && }
&&&&&&&&void h(float x){ cout && &Base::h(float) & && x && }
class Derived : public Base
&&& public:
&&&&&&&&virtual void f(float x){ cout && &Derived::f(float) & && x && }//多态
&&&&&&&&void g(int x){ cout && &Derived::g(int) & && x && }//覆盖
&&&&&&&&void h(float x){ cout && &Derived::h(float) & && x && }//复制
void main(void)
&&& Base *pb = &d;
&&& Derived *pd = &d;
&&& // Good : behavior depends solely on type of the object
&&& pb-&f(3.14f); // Derived::f(float) 3.14
&&& pd-&f(3.14f); // Derived::f(float) 3.14
&&& // Bad : behavior depends on type of the pointer
&&& pb-&g(3.14f); // Base::g(float) 3.14
&&& pd-&g(3.14f); // Derived::g(int) 3 (surprise!)
&&& // Bad : behavior depends on type of the pointer
&&& pb-&h(3.14f); // Base::h(float) 3.14 (surprise!)
&&& pd-&h(3.14f); // Derived::h(float) 3.14
&&& system(&pause&);
//---------------------------------------------------------
C++纯虚函数
&&纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
&&virtual void funtion1()=0
二、引入原因
&&1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
&&2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
&&为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
三、相似概念
&&1、多态性
&&指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
&&a.编译时多态性:通过重载函数实现
&&b 运行时多态性:通过虚函数实现。
&&2、虚函数
&&虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态重载
&&3、抽象类
&&包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
[编辑本段]
&&void f1();
&&virtual void f2();
&&virtual void f3()=0;
&&virtual ~A();
&&class B : public A
&&void f1();
&&void f2();
&&void f3();
&&virtual ~B();
&&int main(int argc, char* argv[])
&&A *m_j=new B();
&&m_j-&f1();
&&m_j-&f2();
&&m_j-&f3();
&&delete m_j;
&&return 0;
&&f1()是一个普通的重载.
&&调用m_j-&f1();会去调用A类中的f1(),它是在我们写好代码的时候就会定好的.
&&也就是根据它是由A类定义的,这样就调用这个类的函数.
&&f2()是虚函数.
&&调用m_j-&f2();会调用m_j中到底保存的对象中,对应的这个函数.这是由于new的B对象.
&&f3()与f2()一样,只是在基类中不需要写函数实现.
虚函数和纯虚函数
在面向对象的C++语言中,虚函数(virtual function)是一个非常重要的概念。因为它充分体现了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广。比如在微软的MFC类库中,你会发现很多函数都有virtual关键字,也就是说,它们都是虚函数。难怪有人甚至称虚函数是C++语言的精髓。
那么,什么是虚函数呢,我们先来看看微软的解释:
虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。
——摘自MSDN
这个定义说得不是很明白。MSDN中还给出了一个例子,但是它的例子也并不能很好的说明问题。我们自己编写这样一个例子:
#include &stdio.h&
#include &conio.h&
class Parent
char data[20];
void Function1();
virtual void Function2(); // 这里声明Function2是虚函数
void Parent::Function1()
printf(&This is parent,function1\n&);
void Parent::Function2()
printf(&This is parent,function2\n&);
class Child:public Parent
void Function1();
void Function2();
void Child::Function1()
printf(&This is child,function1\n&);
void Child::Function2()
printf(&This is child,function2\n&);
int main(int argc, char* argv[])
Parent *p; // 定义一个基类指针
if(_getch()=='c') // 如果输入一个小写字母c
p=& // 指向继承类对象
p=& // 否则指向基类对象
p-&Function1(); // 这里在编译时会直接给出Parent::Function1()的
入口地址。
p-&Function2(); // 注意这里,执行的是哪一个Function2?
用任意版本的Visual C++或Borland C++编译并运行,输入一个小写字母c,得到下面的结果:
This is parent,function1
This is child,function2
为什么会有第一行的结果呢?因为我们是用一个Parent类的指针调用函数Fuction1(),虽然实际上这个指针指向的是Child类的对象,但编译器无法知道这一事实(直到运行的时候,程序才可以根据用户的输入判断出指针指向的对象),它只能按照调用Parent类的函数来理解并编译,所以我们看到了第一行的结果。
那么第二行的结果又是怎么回事呢?我们注意到,Function2()函数在基类中被virtual关键字修饰,也就是说,它是一个虚函数。虚函数最关键的特点是“动态联编”,它可以在运行时判断指针指向的对象,并自动调用相应的函数。如果我们在运行上面的程序时任意输入一个非c的字符,结果如下:
This is parent,function1
This is parent,function2
请注意看第二行,它的结果出现了变化。程序中仅仅调用了一个Function2()函数,却可以根据用户的输入自动决定到底调用基类中的Function2还是继承类中的Function2,这就是虚函数的作用。我们知道,在MFC中,很多类都是需要你继承的,它们的成员函数很多都要重载,比如编写MFC应用程序最常用的CView::OnDraw(CDC*)函数,就必须重载使用。把它定义为虚函数(实际上,在MFC中OnDraw不仅是虚函数,还是纯虚函数),可以保证时刻调用的是用户自己编写的OnDraw。虚函数的重要用途在这里可见一斑。
再看下面的
-----------------------------------------------------------
摘自:C++中虚函数和纯虚函数的概念,差别和分别存在的原因
首先:强调一个概念
定义一个函数为虚函数,不代表函数为不被实现的函数
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数
定义一个函数为纯虚函数,才代表函数没有被实现
定义他是为了实现一个接口,起到一个规范的作用,规范继承这个
类的程序员必须实现这个函数。
对继承的影响:
普通的类(没有虚函数,纯虚函数)就可以被继承,而且工作的相当好
关于这个问题有以下疑问:
纯虚函数难道就是为了实现接口?接口存在的意义?
我实在弄不懂,我干嘛要预先定义好?未来的事情本难料
就等有一天我的类中需要使用某个函数,在添加一个函数
不久可以?
关于实例化一个类:
有纯虚函数的类是不可能生成类对象的,如果没有纯虚函数则可以。比如:
virtual void fun() = 0; // 说明fun函数为纯虚函数
virtual void fun1();
virtual void fun();
virtual void fun1();
// CA,CB类的实现
void main()
CA // 不允许,因为类CA中有纯虚函数
CB // 可以,因为类CB中没有纯虚函数
---------------------------------------------------------------
虚函数在多态中间的使用:
多态一般就是通过指向基类的指针来实现的。
mydogwangwang.born();
一定是返回“dog”
myhorsepipi.born();
一定是返回“horse”
也是多态呀?
/////////////////////////////////////////////////
有一点你必须明白,就是用父类的指针在运行时刻来调用子类:
例如,有个函数是这样的:
void animal::fun1(animal *maybedog_maybehorse)
maybedog_maybehorse-&born();
参数maybedog_maybehorse在编译时刻并不知道传进来的是dog类还是horse类,所以就把它设定为animal类,具体到运行时决定了才决定用那个函数。
也就是说用父类指针通过虚函数来决定运行时刻到底是谁而指向谁的函数。
////////////////////////////////////////////////////////////////////
//用虚函数
#include &iostream.h&
class animal
~animal();
void fun1(animal *maybedog_maybehorse);
virtual void born();
void animal::fun1(animal *maybedog_maybehorse)
maybedog_maybehorse-&born();
animal::animal()
animal::~animal()
void animal::born()
cout&& &animal&;
class dog: public animal
virtual void born();
dog::dog()
dog::~dog()
void dog::born(){
cout&&&dog&;
class horse:public animal
virtual void born();
horse::horse()
horse::~horse()
void horse::born(){
cout&&&horse&;
void main()
a.fun1(&c);
//output: horse
/////////////////////////////////////////////////////////////////
//不用虚函数
#include &iostream.h&
class animal
~animal();
void fun1(animal *maybedog_maybehorse);
void born();
void animal::fun1(animal *maybedog_maybehorse)
maybedog_maybehorse-&born();
animal::animal()
animal::~animal()
void animal::born()
cout&& &animal&;
class dog: public animal
void born();
dog::dog()
dog::~dog()
void dog::born(){
cout&&&dog&;
class horse:public animal
void born();
horse::horse()
horse::~horse()
void horse::born(){
cout&&&horse&;
void main()
a.fun1(&c);
output: animal
[ 本帖最后由 lateraware 于
22:53 编辑 ]
搜索更多相关主题的帖子:
等 级:贵宾
威 望:14
帖 子:852
专家分:1317
谢谢分享。。。
学习到了。。。
用心做一件事情就这么简单
等 级:青峰侠
帖 子:1160
专家分:1797
这种复制黏贴的帖子有意思吗?
你以为你真的就理解多态了? 我随便问你一个问题你还是回答不上来。
等 级:贵宾
威 望:14
帖 子:852
专家分:1317
回复 3楼 Devil_W
起码别人可以学到东西,你每天到这里说别人的帖子。
你又给别人带来什么了。
反正我是没有看到过。。
别人复制,也要用CTRV+C
给别人带来一点帮助我都觉得很好。。。
用心做一件事情就这么简单
等 级:贵宾
威 望:13
帖 子:302
专家分:972
以下是引用Devil_W在 10:32:48的发言:
这种复制黏贴的帖子有意思吗?
你以为你真的就理解多态了? 我随便问你一个问题你还是回答不上来。还请兄弟问问,让大家也能有进一步的认识。真心的,多交流讨论才能学到更多东西
我的群: C/C++/Assembly 喜欢交流的朋友进,进群请写消息
等 级:新手上路
帖 子:36
回复 2楼 小鱼儿c
共同学习相互进步 呵呵!
等 级:新手上路
帖 子:36
回复 3楼 Devil_W
鄙人确实是初学者,以后还望仁兄请多多指教 呵呵!
来 自:四川
等 级:贵宾
威 望:37
帖 子:2011
专家分:5959
我觉得这篇贴应该发在C++版里。
My life is brilliant
等 级:新手上路
帖 子:36
回复 7楼 lateraware
没错 呵呵 我忘了 下次记得了
等 级:贵宾
威 望:103
帖 子:3280
专家分:12654
回复 3楼 Devil_W
嗯。我也同意只是复制资料意义不大。但如果配合了你的问题,那就不同了,这将成为一个不错的讨论贴。
重剑无锋,大巧不工
版权所有,并保留所有权利。
Powered by , Processed in 0.047490 second(s), 9 queries.
Copyright&, BCCN.NET, All Rights Reserved}

我要回帖

更多关于 swift 自定义cell重用 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信