C++之类与对象 [1]: 多态
多态性
- 目的:不同对象在接收到相同消息时(操作/函数)做不同响应
- 现象:对应同样成员函数名称,执行不同函数体
多态性的实现
- 虚函数:使用
virtual
关键字声明成员函数 - 声明格式:
virtual 函数返回值 函数名称(参数列表);
(派生类重新实现时加不加virtual都行) - 指向派生类的基类指针会调用对应派生类实现的虚函数
- 如果派生类不实现虚函数,则继承基类的虚函数实现
- 当类中有虚函数的时候,会维持一个虚表指针,虚表指针指向虚表中对应到该类的虚函数的函数入口地址(运行时确定),这叫做函数的动态绑定(非虚函数是静态绑定)
-
静态类型:对象在声明时采用的类型,在编译期既已确定;
-
动态类型:通常是指一个指针或引用目前所指对象的类型,是在运行期决定的;
-
静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;
-
动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;
-
对象的动态类型可以更改,但是静态类型无法更改;
-
要想实现多态,必须使用动态绑定;
-
在继承体系中只有虚函数使用的是动态绑定,其他的全部是静态绑定;
-
建议:绝对不要重新定义继承而来的非虚(non-virtual)函数(《Effective C++ 第三版》条款36),因为这样导致函数调用由对象声明时的静态类型确定了,而和对象本身脱离了关系,没有多态,也这将给程序留下不可预知的隐患和莫名其妙的BUG;
class A
{
public:
/*virtual*/ void func(){ std::cout << "A::func()\n"; }
};
class B : public A
{
public:
void func(){ std::cout << "B::func()\n"; }
};
class C : public A
{
public:
void func(){ std::cout << "C::func()\n"; }
};C* pc = new C(); //pc的静态类型是它声明的类型C*,动态类型也是C*;
B* pb = new B(); //pb的静态类型和动态类型也都是B*;
A* pa = pc; //pa的静态类型是它声明的类型A*,动态类型是pa所指向的对象pc的类型C*;
pa = pb; //pa的动态类型可以更改,现在它的动态类型是B*,但其静态类型仍是声明时候的A*;
C *pnull = NULL; //pnull的静态类型是它声明的类型C*,没有动态类型,因为它指向了NULL;pa->func(); //A::func() pa的静态类型永远都是A*,不管其指向的是哪个子类,都是直接调用A::func();
pc->func(); //C::func() pc的动、静态类型都是C*,因此调用C::func();
pnull->func(); //C::func() 不用奇怪为什么空指针也可以调用函数,因为这在编译期就确定了,和指针空不空没关系;- 如果注释掉类C中的func函数定义,其他不变,即
class C : public A
{
};
pa->func(); //A::func() 理由同上;
pc->func(); //A::func() pc在类C中找不到func的定义,因此到其基类中寻找;
pnull->func(); //A::func() 原因也解释过了;- 如果为A中的void func()函数添加virtual特性,其他不变,即
class A
{
public:
virtual void func(){ std::cout << "A::func()\n"; }
};
pa->func(); //B::func() 因为有了virtual虚函数特性,pa的动态类型指向B*,因此先在B中查找,找到后直接调用;
pc->func(); //C::func() pc的动、静态类型都是C*,因此也是先在C中查找;
pnull->func(); //空指针异常,因为是func是virtual函数,因此对func的调用只能等到运行期才能确定,然后才发现pnull是空指针;- 建议:绝对不要重新定义一个继承而来的virtual函数的缺省参数值,因为缺省参数值都是静态绑定(为了执行效率),而virtual函数却是动态绑定。
class E
{
public:
virtual void func(int i = 0)
{
std::cout << "E::func()\t"<< i <<"\n";
}
};
class F : public E
{
public:
virtual void func(int i = 1)
{
std::cout << "F::func()\t" << i <<"\n";
}
};
void test2()
{
F* pf = new F();
E* pe = pf;
pf->func(); //F::func() 1 正常,就该如此;
pe->func(); //F::func() 0 哇哦,这是什么情况,调用了子类的函数,却使用了基类中参数的默认值!
} -
纯虚函数
- 充当占位函数,没有任何实现
- 派生类负责实现其具体功能
- 声明格式:
virtual 函数返回值 函数名称(参数列表) = 0;
- 纯虚函数的虚表指针是存在的,只不过该指针指向0地址
抽象类(纯虚类)
- 带有纯虚函数的类
- 作为类继承层次的上层
- 不能构造抽象类的对象,但可以存在抽象类的指针或引用
虚析构函数
- 保持多态性需要虚析构函数,以保证能够正确释放对象
- 当一个类有子类时,该类的析构函数必须是虚函数,否则可能会有资源释放不完全的情况(因为非虚函数是静态绑定的);
class A |
- 需要给父类的析构函数加上virtual
class A |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 旭穹の陋室!
评论