面向对象程序设计的核心思想是数据抽象(类的接口与实现分离)、继承和动态绑定
基类
虚函数:基类希望派生类各自定义适合自身的版本的函数
在c++中,当我们使用基类的引用或指针调用虚函数时将发生动态绑定。
基类通常都应该定义一个虚析构函数。C++规定:用不带有虚析构函数的基类的指针来删除一个派生类对象(基类指针指向派生类对象,delete该指针),这个对象的派生类部分没有被析构,造成内存泄漏。
注意:
1.应该为多态基类声明虚拟析构函数。如果一个类有一个虚函数,那么它也应该有一个虚析构函数
2.如果一个类不是被设计为基类或者它们并不是按照多态的方式来使用的(不是用基类指针或引用来指向派生类对象),不要为它们声明虚析构函数 ,vptr及virtual table的存在使得内存空间浪费
任何构造函数之外的非静态函数都可以是虚函数,virtual只能出现在类内部的声明语句之前而不能用于类外部的函数定义。如果基类把一个函数声明成虚函数,则该函数在派生类中隐式地也是虚函数。
构造函数不能是虚函数的原因:
1.从存储空间角度
如果构造函数是虚函数,就需要通过 vptr来调用,vptr是存储在对象的内存空间中,可是对象还没有实例化,也就是内存空间还没有,怎么找vptr呢?所以构造函数不能是虚函数。
2.从使用角度来看
虚函数作用是通过父类的指针或引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数
static函数不能是虚函数的原因:
调用虚函数需要类的实例,static函数作为类函数,不属于类的实例,自然无法实现多态了。
派生类
派生类能访问public protected成员,不能访问private成员。
如果派生类没有覆盖其基类中的某个虚函数,则该虚函数的行为类似其他的普通成员,派生类会直接继承其在基类中的版本。c++11允许派生类显式地注明它使用某个成员函数覆盖了它继承的虚函数,在声明语句的最后添加override.
派生类必须使用基类的构造函数来初始化它的基类部分,通过构造函数初始化列表将实参传递给基类构造函数(否则使用默认构造函数)。首先初始化基类的部分,然后按照声明的顺序依次初始化派生类的成员。
派生类的声明包含类名但是不包含它的派生列表,派生列表必须与类的主体一起出现。
如果我们想将某个类用作基类,则该类必须已经定义而非仅仅声明。派生类中包含并且可以使用它从基类继承而来的成员,为了使用这些成员,派生类需要知道它们是什么,因此一个类不能派生它本身。vs中会报错:不允许使用不完整的类型
防止继承的发生:
c++11提供了一种防止继承发生的方法,即定义时在类名后跟关键字final。
当我们用一个派生类对象为一个基类对象初始化或赋值时,只有该派生类对象中的基类部分会被拷贝、移动或赋值,它的派生类部分将被忽略。
----------------------------------------------------------------------------------------------------------------
class ClxBase { public: ClxBase() {}; virtual ~ClxBase() {}; virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; }; }; class ClxDerived : public ClxBase { public: ClxDerived() {}; ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; }; void DoSomething() { cout << "Do something in class ClxDerived!" << endl; }; }; int main() { ClxBase *pTest = new ClxDerived; pTest->DoSomething(); delete pTest; system("pause"); }
输出结果是:
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
如果把类ClxBase析构函数前的virtual去掉,那输出结果如下:
Do something in class ClxDerived!
上述例子中会先调用基类的构造函数,再调用派生类的构造函数;而析构时,先调用派生类的析构函数,再调用基类的析构函数
一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。
基类的析构函数一般都是虚函数是为了当删除一个指向派生类对象的基类指针时,派生类的析构函数会被调用。由函数的调用流程可以得知,先确定指针的静态类型,然后在静态类型对应的类中查找该函数,当找到后,如果该函数是虚函数,那么会在运行时根据指针的动态类型来调用函数,否则编译时直接生成调用当前类中的该函数的代码。所以若析构函数不是虚函数,当删除一个指向派生类对象的基类指针时不会调用派生类的析构函数。