C++的封装、继承、多态

如题所述

定义:封装就是将抽象对象的属性和行为特征结合,形成类。数据(属性)和函数(行为特征)都是类的成员,目的将对象的使用者和设计者分开,提高软件的可维护性和可修改性。


特性:



    结合性,属性和方法结合


    隐蔽性,利用接口机制隐蔽内部实现细节,只留接口给外部调用


    实现代码的重用



定义:继承就是新类从已有类那里得到已有的特性。类的派生指从已有类产生新类的过程。原有类成为基类或父类,产生的新类称为派生类或子类。


单一继承:继承一个父类,这种继承称为单一继承。一般情况尽量使用单一继承,使用多重继承容易造成混乱易出问题。


多重继承:继承多个父类,类与类之间要用逗号隔开,类名之前要有继承权限,假使两个基类都有某变量,在子类中调用时需要加上类名限定符,例:c.a::i = 1;


菱形继承:多重继承掺杂隔代继承1-n-1模型,此时需要用到虚继承,例如:B、C虚继承于A,D多继承于B、C,否则会出错


继承权限:继承方式指定了子类从父类中继承的成员访问权限


继承可以扩展存在的代码,目的是为了代码复用


继承也分为接口继承和实现继承:



    普通成员函数的接口总是会被继承:子类继承一份接口和一份强制实现


    普通虚函数被子类重写:子类继承一份接口和一份缺省实现


    纯虚函数只能被子类继承接口:子类继承一份接口,没有继承实现



什么是菱形继承?菱形继承:多个类继承了同一个公共基类,而这些派生类又同时被一个类继承。


D的对象模型里面保存了两份base,当我们想要调用从base里继承的fun时就会出现调用不明确问题,造成数据冗余的问题。


如何解决这个问题?



    使用域限定我们所需访问的函数


    虚继承



让A和B在继承base时加上vritual关键字。


A和B不在保存base内容,而是保存了一份偏地址,将base保存在一个公共位置,保证了数据冗余性降低的同时,也能直接使用d.fun()调用base里的函数。


定义:一个接口,多种实现方式;多态有两种形式:静态多态和动态多态


动态多态:



    是指在程序运行时才确定函数和实现的链接,此时才能确定调用哪个函数,父类指针或者引用能够指向子类对象,调用子类函数,所以在编译时无法确定是调用哪个函数。


    优点:OO设计重要的特性,对客观世界直觉认识;能够处理同一个继承体系下的异常类集合


    缺点:运行期间进行虚函数绑定,提高了程序运行开销;庞大的类继承层次,对接口的修改易影响类继承层次;由于虚函数在运行期才绑定,所以编译器无法对虚函数进行优化



定义:用virtual关键字修饰的函数,本质:由虚函数指针和虚函数表控制某个虚函数入口地址,就实现了多态,作用:实现多态,虚函数可以被子类重写,虚函数地址存储在虚函数表中


虚表:虚表中主要是一个类的虚函数的地址表,这样表解决了继承、覆盖的问题,保证其真实反映实际的函数,当我们用父类指针来指向一个子类对象的时候,虚表指明了实际所调用的函数。基类有一个虚表,可以被子类继承(当类中有虚函数时该类才会虚表,该类的对象才会有虚指针,子类继承时也会继承基类的虚表),子类如果重写了基类的某虚函数,那么子类继承于基类的虚表中该虚函数的地址会相应的改变,指向子类。自身的该虚函数实现,如果子类有自己的虚函数,那么子类的虚表中就会增加该项,编译器为每个类对象定义了一个虚指针,来定位虚表,所以虽然是父类指针指向子类对象,但因为此时子类重写了该虚函数,该虚函数的地址在子类虚表中的地址已经被改变了,所以它实际调用的是子类的重写后的函数


一旦虚指针被初始化为指向相应的虚表,对象就知道他自己是什么类型,但只有当虚函数被调用时这种自我认知才有用。


每一个带有virtual的函数都有一张相应的虚表,当对象调用某一个virtual函数时,实际被调用的函数取决于该对象的虚指针所指向的那个虚表--编译器在其总寻找适当虚函数指针。


效率漏洞:我们必须明白,编译器正在插入隐藏代码到我们的构造函数中,这些隐藏代码不仅必须初始化虚指针,而且必须检查this的值(避免operater new返回0)和调用基类构造函数。放在一起这些代码可以影响我们认为是一个小内联函数的调用,特别是,构造函数的规模会抵消函数调用代价的减少,如果大量的内联函数调用,代码长度就会增长,而在速度上没有任何好处;当然,也许不会立即把所有这些小构造函数都变成非内联,因为它们更容易写为内联构造函数,但是,当我们正在调整我们的代码时,请务必去掉这些内联构造函数。


虚函数时用:将函数声明为虚函数会降低效率,一般函数在编译器其对应地址是确定的,编译器可以直接生成imp/invoke指令,如果是虚函数,那么函数的地址是动态的,譬如取到的地址在eax寄存器里,则在call eax之后的那些已经被预取到流水线的所有指令都将失效,流水线越长,那么一次分支预测失败的代价越大,建议若不打算让某类成为基类,最好不要使用虚函数。


纯虚函数:含有至少一个纯虚虚函数的类叫做抽象类,因为抽象类含有纯虚函数,所以其虚表是不健全的,在虚表不健全的情况下是不能实例化对象的,子类继承抽象类后必须重写父类所有纯虚函数,子类才能实例化对象,纯虚函数只声明不定义。例如:virtual void fun()=0;


静态多态:是在编译器就把函数连接起来,此时即可确定调用那个函数或模板,静态多态是由摸板和重载实现的,在宏多态中,是通过定义变量,编译时直接把变量替换,实现宏多态。


优点:带来了泛型编程的概念,使得C++拥有泛型编程于STL这样的容器;编译器完成多态,提高运行期效率;具有很强的耦合性(两个功能模块之间的依赖关系)


缺点:程序可读性降低,代码调试带来困难;无法实现模板的分离编译;无法处理异质对象集合


调用父类指针创建子类对象,那么父类应该有虚析构函数,因为如果父类没有虚析构函数,那么在删除这个子类对象的时候会调用错误的析构函数而导致删除产生不明确行为

温馨提示:答案为网友推荐,仅供参考
相似回答
大家正在搜