我们知道,C++中的派生类继承了基类的所有数据成员以及函数:
对于基类数据成员的继承,体现在了占用内存上面。
而对于基类成员函数的继承,则不应该从占用内存的角度区去理解。应是继承对基类成员函数的调用权(子类可以调用父类的公有函数,这种行为体现了经典的面向对象编程,此子类函数或这种做法被称为Template Method,此处Method 套用了Java中的函数method)
根据是否需要在派生类中重新定义(override,覆写)基类的成员函数,可以决定基类成员函数的使用形式:
1)如果不希望derived class中重新定义基类的成员函数,则使用non-virtual函数
2)如果希望在derived class中对它进行重新定义
a)如果基类中已经有对它的默认定义,则使用虚函数。
b)如果基类中的函数因其抽象性而无法定义,则使用纯虚函数。此时所在的类称为抽象类。
从上面可以看出,在使用虚函数的时候,利用了类型兼容规则。
类型兼容规则,是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代基类对象。所指的替代包括三种情况:
class Base{...}
class Devried:public Base{...}
Base b, *pb;
Devried d;
1)派生类的对象转化为基类的对象。
b = d;
2)派生类的对象初始化基类对象的引用。
Base &rb = d;
3)派生类对象的地址转换为指向基类的指针。
pb = &d;
类型兼容规则的引入,使得基类及其公有派生类的对象,可以使用相同的函数对它们统一进行处理。因为当函数的形参为基类的对象(或引用、指针)时,实参可以是派生类的对象(或指针),而没有必要为每一个类设计一个单独的模块,大大提高了程序的效率。可以说,类型兼容规则是多态性的重要基础之一。
下面是一般虚函数的使用:
#include
using namespace std;
class Base1
{
public:
virtual void display() const;
};
void Base1::display()const
{
cout<<"Base1::display()"<display(); //只有通过基类的指针或引用调用虚函数时,才会发生动态绑定
}
int main()
{
Base1 base1;
Base2 base2;
Derived derived;
f(&base1);
f(&base2);
f(&derived);
return 0;
}
输出结果如下:
一般虚函数中还有虚析构函数:
#include
using namespace std;
class A
{
public:
~A();
};
A::~A()
{
cout<<"A destructor"<
纯虚函数:
#include
using namespace std;
class Base1 //带有纯虚函数的类是抽象类
{
public:
virtual void display() const=0; //纯虚函数,不需要给出定义,为整个类族提供了通用的外部接口语义,派生类中再对同名函数进行具体实现
};
class Base2:public Base1
{
public:
void display() const; //覆盖基类的虚函数
};
void Base2::display()const
{
cout<<"Base2::display()"<display(); //只有通过基类的指针或引用调用虚函数时,才会发生动态绑定,注意 "->"
}
int main()
{
Base2 base2;
Derived derived;
f(&base2);
f(&derived);
return 0;
}
输出结果为:
以上均体现了C++中类的多态性,这也是C++语言常盛不衰的经典之处。
既然提到了“虚”,那就By the way一下虚基类:(不体现多态性)
#include
using namespace std;
class Base0
{
public:
int var0;
void fun0()
{
cout<<"Member of Base0"<
程序输出:
Member of Base0
所以,被声明为虚基类的Base0的成员var0和fun0只有一份副本,而通过两条派生路径继承,更加节省了内存空间。
以上类族的相关类成员的UML图形表示如下:
如果没有声明虚基类,则是:
此时,若通过Derived的对象去访问fun0和var0(要避免造成二义性),则是:
main()
{
Derived d;
d.Base1::var0=1;
d.Base1::fun0();
d.Base2::var0=2;
d.Base2::fun0();
}