先看一个例子:
1 #include<iostream>
2 using namespace std;
3
4 class Liberation {
5 public:
6 Liberation(int a):capability(a)
7 {
8 cout << "Liberation 构造函数
";
9 }
10 int combat()
11 {
12 return capability;
13 }
14 private:
15 int capability;
16 };
17 class Liberation_adv :public Liberation
18 {
19 public:
20 Liberation_adv(int a) :Liberation(a), capability(a)
21 {
22 cout << "Liberation 构造函数
";
23 }
24 int combat()
25 {
26 return capability;
27 }
28 private:
29 int capability;
30 };
31
32 class Enemy {
33 public:
34 Enemy(int a):capability(a)
35 {
36 cout << "Enemy 构造函数
";
37 }
38 int combat()
39 {
40 return capability;
41 }
42 private:
43 int capability;
44 };
45 int main()
46 {
47 Liberation L1(10);
48 Enemy E1(20);
49 Liberation_adv L2(30);
50 if (L1.combat() < E1.combat())
51 {
52 cout << "一代军队战败
";
53 }
54 else
55 {
56 cout << "一代军队胜利
";
57 }
58 if (L2.combat() < E1.combat())
59 {
60 cout << "二代军队战败
";
61 }
62 else
63 {
64 cout << "二代军队胜利
";
65 }
66
67
68 return 0;
69 }
然后我们引进多态,看看代码升级的威力;
1 #include<iostream>
2 using namespace std;
3
4 class Liberation {
5 public:
6 Liberation(int a):capability(a)
7 {
8 cout << "Liberation 构造函数
";
9 }
10 virtual int combat()
11 {
12 return capability;
13 }
14 private:
15 int capability;
16 };
17 class Liberation_adv :public Liberation
18 {
19 public:
20 Liberation_adv(int a) :Liberation(a), capability(a)
21 {
22 cout << "Liberation 构造函数
";
23 }
24 int combat() override //派生类重写的函数不用加virtual也是virtual的,建议加上,方便后续继承这个类的文档阅读,c++11增加关键字
//override告诉编译器和读者,这是个被重写的函数
25 {
26 return capability;
27 }
28 private:
29 int capability;
30 };
31
32 class Enemy {
33 public:
34 Enemy(int a):capability(a)
35 {
36 cout << "Enemy 构造函数
";
37 }
38 int combat()
39 {
40 return capability;
41 }
42 private:
43 int capability;
44 };
45
46 void play(Liberation &L, Enemy &E)
47 {
48 if (L.combat() < E.combat())
49 cout << "敌军胜利
";
50 else {
51 cout << "我军胜利
";
52 }
53 }
54 int main()
55 {
56 Liberation L1(10);
57 Enemy E1(20);
58 Liberation_adv L2(30);
59 play(L1, E1);
60 play(L2, E1);
61 return 0;
62 }
用多态的好处:调用一个函数,简化代码结构,关键在于可维护性好。如果现在我又派生了一个类L3,只用调用函数的接口,就可以仅仅增加一个类定义的情况下,完成项目的维护。
实现多态三要素:
1、要有public继承;
2、要有虚函数重写;
3、要有基类的指针或者引用绑定到派生类。
满足上诉要求,可以实现动态联编(运行时类型才可知),否则是静态联编(编译时类型就已知)。
C++中的多态(polymorphism)是指由继承而产生的相关的不同的类, 其对象对
同一消息会作出不同的响应。
多态性是面向对象程序设计的一个重要特征, 能增加程序的灵活性。 可以减
轻系统升级, 维护, 调试的工作量和复杂度。
多态是一种不同层次分类下的重要联系, 是一种跨层操作。
多态实现的前提
赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。
赋值兼容是一种默认行为, 不需要任何的显式的转化步骤, 只能发生在 public
继承(其他private和protected继承,多态无意义,因为不能在类外访问属性或方法了)方式中, 是多态实现的前提条件。
赋值兼容规则中所指的替代包括以下的情况:
A、 子类对象可以直接赋值给父类对象
B、 子类对象可以直接初始化父类对象
C、 父类引用可以直接引用子类对象
D、 父类指针可以直接指向子类对象
当使用父类指针(引用) 指向子对象时, 子类对象退化为父类对象, 只能访问父类中定义的成员
父类也可以通过强转的方式转化为子类, 但存在访问越界的风险。
子类中可以重定义父类中已经存在的成员函数, 即函数重写。
当函数重写遇到赋值兼容时, 编译器只能根据指针的类型判断所指向的对
象, 根据赋值兼容原则, 编译器认为父类指针指向的是父类对象, 只能调用父类
中定义的同名函数。
面向对象编程中, 通常需要根据实际的对象类型判断如何调用重写函数。 当
父类指针指向父类对象, 则调用父类定义的函数; 当父类指针指向的是子类对象
时, 需要调用子类定义的函数; 当父类引用对父类对象进行引用时, 调用父类对
象定义的函数; 当父类引用对子类对象进行引用时, 调用子类定义的函数。
C++通过 virtual 关键字对多态进行支持, 被 virtual 声明的函数被重写后具有多态属性。
虚函数的声明语法如下:
virtual 函数声明;
虚函数的使用规则如下:
A、 在父类中用 virtual 声明成员函数为虚函数。 类外实现虚函数时, 不能再加 virtual。
B、 在派生类中重定义父类中已经存在的成员函数称为函数覆写, 要求函数名,
返值类型, 函数参数个数及类型全部匹配, 并根据派生类的需要重新定义函数体。
C、 当一个成员函数被声明为虚函数后, 其派生类中完全相同的函数也为虚函
数, 派生类中的虚函数可以加 virtual 关键字, 也可以不加,建议都加上,为了代码扩展和阅读(如果不写virtual(虽然不写也是virtual的),
之后有一个类继承这个派生类,只看这个派生类的声明,不能一眼就看出这个派生类重写了该派生类的直接或者间接父类的函数)。
D、 定义一个父类对象指针, 使其指向其子类的对象, 通过父类指针调用虚函
数, 此时调用的是子类的同名函数,引用同理。
E、 构造函数不能为虚函数, 在构造函数执行完毕后虚函数表指针才能被正
确初始化。 析构函数可以为虚函数, 一般来说需要将析构函数声明为虚函数。 构造函数执行时, 虚函数表指
针未被正确初始化, 因此构造函数不可能发生多态; 析构函数函数执行时, 虚函
数表指针已经被销毁, 因此析构函数也不可能发生多态。 构造函数和析构函数中
只能调用当前类中定义的函数版本。
纯虚函数的声明语法如下:
virtual 函数声明 = 0;
纯虚函数的使用规则如下:
A、 含有纯虚函数的类, 称为抽象基类, 不可实例化, 即不能创建对象, 存在
的意义就是被继承, 提供类族的公共接口。
B、 纯虚函数只有声明, 没有实现。
C、 如果一个类中声明了纯虚函数, 而在派生类中没有对纯虚函数进行实现,
则该虚函数在派生类中仍然为纯虚函数, 派生类仍然为抽象基类。
虚函数的使用规则如下:
A、 只有类的成员函数才能声明为虚函数。 虚函数仅适用于有继承关系的类对
象, 所以普通函数不能声明为虚函数。
B、 静态成员函数不能是虚函数。 静态成员函数不受对象的捆绑, 只有类的信息。
C、 内联函数不能是虚函数。
D、 构造函数不能是虚函数。 构造时, 对象的创建尚未完成。 构造完成后, 才能算一个名符其实的对象。
E、 析构函数可以是虚函数且通常声明为虚函数
F、 含有虚函数的类, 析构函数也必须声明为虚函数。 在 delete 父类指针的时候, 会调用子类的析构函数。
多态的意义如下:
A、 在程序运行过程中展现出的动态特性
B、 函数重写必须多态实现, 否则没有意义
C、 多态是面向对象组件化程序设计的基础特性
D、 多态是一种跨层操作
静态联编是在程序的编译期间就能确定具体的函数调用, 如函数重载。
动态联编是在程序实际运行时才能确定具体的函数调用, 如virtual函数重写。
虚函数是为了实现动态多态, 是当程序运行到虚函数时才会从虚函数表里找
实际执行的虚函数; 而函数重载实现的是静态多态, 是在程序编译时就能找到
函数地址。