主页
文章
分类
系列
标签
简历
【C++ Primer(edition 5) 15】多态
发布于: 2021-10-27   更新于: 2021-10-27   收录于: Cpp
文章字数: 150   阅读时间: 1 分钟   阅读量:

OOP的核心思想是多态性(polymorphism)(动态绑定是多态性得以实现的重要因素)

一、静态类型和动态类型

  • 动态类型: 变量或表达式表示的内存中的对象类型,直到运行时才可见
  • 静态类型: 变量申明的类型或表达式生成的类型,在编译时就已知
  • 可以将基类的指针或引用绑定到派生类对象上。
  • 如果不是指针也不是引用,则它的动态类型和静态类型永远保持一致
  • 不存在从基类向派生类的隐式类型转换。
  • 派生类向基类的自动类型转换只对指针或引用类型有效,对象之间不存在类型转换。对象之间的转换会导致某些对象被切掉(sliced down)。所谓的切掉,就是当派生类和基类之间通过拷贝构造,赋值构造等进行转化时,有一些成员会因为数据成员的不同导致成员丢失,有点像被切掉了。

二、虚函数与动态绑定

1、如何实现多态

基类通过在其成员函数的声明语句前加上关键字virtual使得该函数执行动态绑定。 任何非静态函数都可以是虚函数。关键字virtual只能出现在内内部的声明语句之前,而不能用于类外部的函数定义。如果基类把一个函数声明成虚函数,则该函数在派生类中隐式的也是虚函数。如果成员函数没有被声明为虚函数,则解析过程发生在编译时而非运行时。

当且仅当对通过指针或引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型才有可能与静态类型不同。

  • 派生类必须在其内部对所有重新定义的虚函数进行声明。可以在函数之前加上virtual关键字,也可以不加。
  • 派生类函数如果覆盖了某个继承而来的虚函数, 它的形参必须与被它覆盖的基类函数完全一致,返回类型必须与基类函数匹配,除非返回类本身的指针或引用时,不要求一致,但是要求要求两个类
  • 如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。之间可以进行类型转化

2、override和final关键字

  • C++11新标准允许派生类显式地注明它将使用哪个成员函数改写基类的虚函数,即在函数的形参列表之后加一个override关键字。
  • 如果我们想覆盖某个虚函数,但不小心把形参列表弄错了,这个时候就不会覆盖基类中的虚函数。加上override可以明确程序员的意图,让编译器帮忙确认参数列表是否出错。(在派生类中申明一个除了参数列表之外与基类中虚函数一致的函数是合法,但是不要这样做,为了防止不是刻意构造这样的函数,而是写错了,设置了override关键字)
  • 如果某个函数被指定为final,则之后的任何尝试覆盖函数的操作都将引发错误

3、禁止动态绑定

在某些情况下,我们希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本。使用作用域运算符::)可以实现。

1
double undiscounted = baseP->Quote::net_price(42);

三、多态实例

  • 编译产生的代码将在运行时确定使用虚函数的哪个版本,判断的依据是该指针所绑定对象的真实类型即动态类型。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Base{
public:
	virtual itn fcn();
};

class D1:public Base{
public:
	int fcn(int);
	virtual void f2();
};

class D2:public D1{
	int fcn(int);
	int fcn();
	void f2();
}

Base bobj ; D1 dlobj ; D2 d1obj;
Base *bpl = &bobj, *bp2 = &dlobj, *bp3 = &d2obj;
bpl->fcn (); 
bp2->fcn (); 
bp3->fcn ();

D1 *dlp = &dlobj; D2 *d2p = &d2obj;
bp2->f2();	
dlp->f2() ;	
d2p->f2() ;	

四、抽象基类

  • 纯虚函数(pure virtual):清晰地告诉用户当前的函数是没有实际意义的。纯虚函数无需定义,只用在函数体的位置前书写=0就可以将一个虚函数说明为纯虚函数。
  • 含有纯虚函数的类是抽象基类(abstract base class)。不能创建抽象基类的对象。

五、广义的多态

1.重载多态(编译期间)

1.1 函数重载

1.2 运算符重载

2.子类型重载(运行期间)

2.1 虚函数

见上面内容

3.参数多态性(编译器)

3.1 类模板

3.2 函数模板

4.强制多态(编译期间、运行期)

4.1 基本类型转换

4.2 自定义类型转换