#include <iostream>
using namespace std;
class A
{
public:
A(int aa): a(aa) {}
virtual ~A() { cout << "destructing A" << endl; } // 基类的析构函数声明为 virtual
void Output() const { cout << "a=" << a << endl; }
virtual const char* GetName() const { return "A"; }
virtual const A* GetThis() const { return this; }
virtual const A& GetThis2() const { return *this; }
protected:
int a;
};
class B: public A
{
public:
B(int aa, int bb): A(aa), b(bb) {} // 子类不能直接初始化从父类继承过来的数据成员,必须使用父类的构造函数初始化这些成员,因此,只能在使用构造函数初始化列表初始化这些成员。
~B() { cout << "destructing B" << endl; }
void Output() const { cout << "a=" << a << ",b=" << b << endl; }
const char* GetName() const { return "B"; } // 重写的父类的 virtual 函数在子类中默认也是 virtual
virtual const B* GetThis() const { return this; }
virtual const A& GetThis2() const { return *this; }
protected:
int b;
};
class C: public B
{
public:
C(int aa, int bb, int cc): B(aa, bb), c(cc) {} // 子类构造函数只能初始化它的直接父类。
~C() { cout << "destructing C" << endl; }
void Output() const { cout << "a=" << a << ",b=" << b << ",c=" << c << endl; }
const char* GetName() const { return "C"; }
virtual const C* GetThis() const { return this; } // 重写的虚函数参数类型和返回值类型必须和基类保持一致,但返回类型有一个例外,就是当返回类型是类指针或类引用时,返回类型既可以跟基类一样,也可以是本类的指针或引用类型。
virtual const A& GetThis2() const { return *this; }
virtual void Joke() const = 0;
protected:
int c;
};
void C::Joke() const
{
cout << "I'am C" << endl;
}
class D: public C
{
public:
D(int aa, int bb, int cc, int dd): C(aa, bb, cc), d(dd) {}
~D() { cout << "destructing D" << endl; }
void Output() const { cout << "a=" << a << ",b=" << b << ",c=" << c << ",d=" << d << endl; }
const char* GetName() const { return "D"; }
virtual const D* GetThis() const { return this; }
virtual const A& GetThis2() const { return *this; }
virtual void Joke() const { cout << "I'am D" << endl; }
private:
int d;
};
int main()
{
D dClass(1, 2, 3, 4);
B &rB = dClass;
cout << "rB is a " << rB.GetName() << endl;
rB.Output();
const A *pA = dClass.GetThis();
cout << "pA is a " << pA->GetName() << endl;
const A &rA = dClass.GetThis2();
cout << "rA is a " << rA.GetName() << endl;
const C *pC = dClass.GetThis();
pC->Joke();
pC->C::Joke(); // 指定调用的版本
// 这里是为了演示为什么要把基类的析构函数声明为 virtual
C *pC2 = new D(11, 22, 33, 44);
delete pC2;
return 0;
}
术语约定
声明(declare):
virtual void Joke() const = 0;
int a;
定义(definition):即函数或类的实现
本例子中出现的 const
本例子中出现了很多 const, 有些地方有 const 和没 const 编译器的报错区别是很大的,可以去掉部分 const 编译程序,对 const 增加理解,关于 const 的小结见这里:
http://blog.csdn.net/duyiwuer2009/article/details/8219754
子类如何初始化从父类继承的数据成员
a. 子类不能直接初始化从父类继承过来的数据成员,必须使用父类的构造函数初始化这些成员,因此,只能在使用构造函数初始化列表初始化这些成员(见例子)。b. 子类构造函数只能初始化它的直接父类(见例子)。
虚函数(virtual 关键字)
a. 子类并不一定非得重写父类的 virtual 函数,可重写也可不重写;
b. 重写的父类的 virtual 函数在子类中默认也是 virtual, 如 GetName();
c. 重写的虚函数参数类型和返回值类型必须和基类保持一致,但返回类型有一个例外,就是当返回类型是类指针或类引用时,返回类型既可以跟基类一样(GetThis2()),也可以是本类的指针或引用类型(GetThis())。
为什么需要虚函数?虚函数的特殊功能是什么?
上面的问题的另一种表述是:重写非 virtual 成员函数(Output() 函数)跟重写 virtual 成员函数(GetName() 函数)的区别是什么?
重写 virtual 成员函数可以实现动态绑定(dynamically bound),当指针或引用时基类类型时,它根据所指向的对象类型动态选择是调用基类的成员函数还是调用继承类的成员函数,这是在运行时(runtime)确定的(GetName() 函数);
而重写的非 virtual 函数直接根据指针或引用类型就确定是调用哪一个版本的成员函数,这是在编译时(compile time)就确定的(Output() 函数)。
不能用基类指针引用基类中不存在而父类中存在的成员函数
Joke() 在 class C 中才开始出现,所以只能用 class C 或 class D 的指针去引用 Joke()
虚函数如何摆脱动态绑定?
pC->C::Joke()
这种方式就可以指定函数版本,是在编译时就确定的。
抽象基类
a. 含有纯虚函数的类即为抽象基类(class C 为抽象基类,纯虚函数为 Joke(), 在声明后面加上 "=0");
b. 纯虚函数可以有定义,也可以没有定义(即只有声明),如果有定义,只能在类外定义;
c. 如果子类不重写父类的纯虚函数,那么子类默认也是抽象基类,子类重写父类的纯虚函数,定义可以在类内,也可以在类外(class D 重写 class C 的 Joke() 函数);
d. 抽象基类不能直接实例化。
为什么要把基类的析构函数声明为 virtual
把上例中 class A 的析构函数前面的 virtual 去掉(根据前问的原则,如果 A 的析构函数声明为 virtual, B 的析构函数即使不显式声明,默认也为 virtual, 一次类推,C、D 的析构函数也为 virtual),会发现 new 出来的 class D 析构时是不会调用 D 的析构函数的,原因就是上文的“为什么需要虚函数”的答案。
References
C++ Primer, 5th Edition. Chapter 15. Object-Oriented Programming.
http://www.learncpp.com/cpp-tutorial/122-virtual-functions/