C++中的封装、继承、多态
封装(encapsulation):就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成”类”,其中数据和函数都是类的成员。封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,特定的访问权限来使用类的成员。封装可以隐藏实现细节,使得代码模块化。
继承(inheritance):C++通过类派生机制来支持继承。被继承的类型称为基类或超类,新产生的类为派生类或子类。保持已有类的特性而构造新类的过程称为继承。在已有类的基础上新增自己的特性而产生新类的过程称为派生。继承和派生的目的是保持已有类的特性并构造新类。继承的目的:实现代码重用。派生的目的:实现代码扩充。三种继承方式:public、protected、private。
继承时的构造函数:(1)、基类的构造函数不能被继承,派生类中需要声明自己的构造函数;(2)、声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化,自动调用基类构造函数完成;(3)、派生类的构造函数需要给基类的构造函数传递参数;(4)、单一继承时的构造函数:派生类名::派生类名(基类所需的形参,本类成员所需的形参):基类名(参数表) {本类成员初始化赋值语句;};(5)、当基类中声明有默认形式的构造函数或未声明构造函数时,派生类构造函数可以不向基类构造函数传递参数;(6)、若基类中未声明构造函数,派生类中也可以不声明,全采用缺省形式构造函数;(7)、当基类声明有带形参的构造函数时,派生类也应声明带形参的构造函数,并将参数传递给基类构造函数;(8)、构造函数的调用次序:A、调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右);B、调用成员对象的构造函数,调用顺序按照它们在类中的声明的顺序;C、派生类的构造函数体中的内容。
继承时的析构函数:(1)、析构函数也不被继承,派生类自行声明;(2)、声明方法与一般(无继承关系时)类的析构函数相同;(3)、不需要显示地调用基类的析构函数,系统会自动隐式调用;(4)、析构函数的调用次序与构造函数相反。
同名隐藏规则:当派生类与基类中有相同成员时:(1)、若未强行指名,则通过派生类对象使用的是派生类中的同名成员;(2)、如要通过派生类对象访问基类中被覆盖的同名成员,应使用基类名限定:基类名::数据成员名。
虚基类:作用:(1)、主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题;(2)、为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝。
继承、组合:组合是将其它类的对象作为成员使用,继承是子类可以使用父类的成员方法。(1)、A继承B,说明A是B的一种,并且B的所有行为对A都有意义;(2)、若在逻辑上A是B的“一部分”,则不允许B从A派生,而是要用A和其它东西组合出B;(3)、继承属于”白盒”复用,组合属于”黑盒”复用。
多态(Polymorphic)性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数。C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖或者称为重写。而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。
多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并产生代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译期间确定,需要在运行时才确定,这就是属于晚绑定。
封装可以使得代码模块化,继承可以扩展已存在的代码,它们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说不论传递过来的究竟是哪个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“= 0”。为了方便使用多态特性,常常需要在基类中定义虚函数,在很多情况下,基类本身生成对象是不合情理的。为了解决这些问题,引入了纯虚函数的概念,将函数定义为纯虚函数,则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚函数的类称为抽象类,它不能生成对象。由于纯虚函数所在的类中没有它的定义,在该类的构造函数和析构函数中不允许调用纯虚函数,否则会导致程序运行错误,但其它成员函数可以调用纯虚函数。
C++支持两种多态性:(1)、编译时多态性(静态多态,在编译时就可以确定对象使用的形式):通过重载函数实现;(2)、运行时多态性(动态多态,其具体引用的对象在运行时才能确定):通过虚函数实现。
C++中,实现多态有以下方法:虚函数、抽象类、重载、覆盖、模板。
函数重载(Overload):指在相同作用域里(如同一类中),函数同名不同参,返回值则不用理会,不同参可以是不同个数,也可以是不同类型。效果:根据实参的个数和类型调用对应的函数体。
函数覆盖(Override)(函数重写):指派生类中的函数覆盖基类中的同名同参虚函数,因此作用域不同。效果:基类指针或引用访问虚函数时会根据实例的类型调用对应的函数。
函数隐藏(Hide):对于子类中与基类同名的函数,如果不是覆盖那就成了隐藏。两种情况:(1)、同名不同参;(2)、同名同参但基类不是virtual函数。
派生类的构造函数使用说明:(1)、在派生类构造函数中,只要基类不是仅使用无参的默认构造函数,都要显示的给出基类名称参数表;(2)、基类没有定义构造函数,派生类也可以不定义,使用默认构造函数;(3)、基类有带参构造函数,派生类必须定义构造函数。
虚函数的重载函数仍是虚函数。在派生类重定义虚函数时必须有相同的函数原型,包括返回类型、函数名、参数个数、参数类型的顺序必须相同。虚函数必须是类的成员函数,不能为全局函数,也不能为静态函数。不能将友员说明为虚函数,但虚函数可以是另一个类的友员。析构函数可以是虚函数,但构造函数不能为虚函数。一般地将,若某类中定义有虚函数,则其析构函数也应当说明为虚函数。特别是在析构函数需要完成一些有意义的操作,比如释放内存时,尤其应当如此。在类系统中访问一个虚函数时,应使用指向基类类型的指针或对基类类型的引用,以满足运行时多态性的要求。当然也可以像调用普通成员函数那样利用对象名来调用一个函数。若在派生类中没有重新定义虚函数,则该类的对象将使用其基类中的虚函数代码。
抽象类:如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类。抽象类不仅包括纯虚函数,也可包括虚函数。抽象类中的纯虚函数可能是在抽象类中定义的,也可能是从它的抽象基类中继承下来且重定义的。抽象类有一个重要特点,即抽象类必须用作派生其它类的基类,而不能用于直接创建对象实例。抽象类不能直接创建对象的原因是其中有一个或多个函数没有定义,但仍可使用指向抽象类的指针支持运行时多态性。派生类中必须重载基类中的纯虚函数,否则它仍将被看作一个抽象类。从基类继承来的纯虚函数,在派生类中仍是虚函数。
虚函数表:虚函数是通过一张虚函数表来实现的。简称为V-Table,在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得有无重要了,它就像一个地图一样,指明了实际所应该调用的函数。
一个多态的例子:
#include <iostream> using namespace std; class A { public: void foo() { printf("1\n"); } virtual void fun() { printf("2\n"); } }; class B : public A { public: void foo() { printf("3\n"); } void fun() { printf("4\n"); } }; int main(void) { A a; B b; A* p = &a; p->foo();//1 p->fun();//2 p = &b; p->foo();//1 p->fun();//4 B* ptr = (B*)&a; ptr->foo();//3 ptr->fun();//2 return 0; }
参考文献:
1、 http://www.docin.com/p-589112223.html
2、 http://blog.csdn.net/hackbuteer1/article/details/7475622
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。