C++多态(编译期多态和运行时多态)笔记

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
//
// main.cpp
// CPP_Polymorphism(多态)
//
// Created by echo on 2020/10/7.
// Copyright © 2020 echo. All rights reserved.
//

#include <iostream>
#include "main.h"

/*编译期多态(静态多态)
*由编译器决定,实现的是函数重载(同名函数不同参数列表)
*实现原理:在编译器生成代码时会给函数加上类似于funtionname_int之类的后缀实现重载
*/
void P_func(int num){
cout<<"the function is Int NUM1"<<"\n";
}

void P_func(int num1, int num2){
cout<<"the funciton is Int NUM2"<<"\n";
}

void P_func(double num1){
cout<<"the funciton is Double NUM1"<<"\n";
}

void P_func(double num1, double num2){
cout<<"the funciton is Double NUM2"<<"\n";
}

template <class T>
void P_f(T num){
cout<<"the template T NUM1"<<"\n";
}

template <class T, class T2>
void P_f(T num1, T2 num2){
cout<<"the template T & T2"<<"\n";
}

template <class T>
void P_f(T num1, T num2){
cout<<"the template T NUM1 & NUM2"<<"\n";
}

void P_f_Test(){
// P_f(20);
// P_f(20, 20.05);
// P_f(20, 20);
}

void P_func_Test(){
P_func(20.5, 20.5);
}


class PF_class{
public:
virtual void pure_Func() = 0;
virtual void vir_Func(){
cout<<"PF_class vir_Func"<<"\n";
}
};

class PS_class:public PF_class{
public:
void pure_Func(){
cout<<"PS_class pure_Func()"<<"\n";
}
void vir_Func(){
cout<<"PS_class vir_Func"<<"\n";
}
};
class PC_class:public PS_class{
public:
void pure_Func(){
cout<<"PC_class pure_Func"<<"\n";
}
void vir_Func(){
cout<<"PC_class vir_Func"<<"\n";
}
};

void pClassTest(){
PF_class * p = new PS_class;
p->pure_Func();
p->vir_Func();
}

int main(int argc, const char * argv[]) {
pClassTest();
return 0;
}
/*运行时多态:通过虚函数实现
*虚函数与纯虚函数的区别:虚函数是之类可以重写此方法实现运行时多态,在调用基类指针时会根据所分配的派生
*类所重写的虚函数来调用此方法。
*纯虚函数:拥有纯虚函数的类叫抽象类(不可实例化),只有实现其中的纯虚函数才能实例化,
*纯虚函数实现后会自动转为虚函数(依然可以重写),此时派生类可以进行实例化。
*抽象类的作用:抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,
*由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。
*所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,
*子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。
*/
/*、纯虚函数声明如下: virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。
包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
2、虚函数声明如下:virtual ReturnType FunctionName(Parameter);
虚函数必须实现,如果不实现,编译器将报错,错误提示为:
error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"
3、对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。
4、实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。
5、虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
6、在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。
7、友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
8、析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。
有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。
定义纯虚函数就是为了让基类不可实例化化
因为实例化这样的抽象数据结构本身并没有意义。
或者给出实现也没有意义
实际上我个人认为纯虚函数的引入,是出于两个目的
1、为了安全,因为避免任何需要明确但是因为不小心而导致的未知的结果,提醒子类去做应做的实现。
2、为了效率,不是程序执行的效率,而是为了编码的效率。
*/

class F_class{
public:
int F = 20;
};

class CA_class : virtual public F_class{//虚继承,不使用虚继承时CB无法使用F
public:
int CA = 21;
void fJudgementA(){
if (F == 20)
cout<<"F_class is father"<<"\n";
}
};
class CB_class : virtual public F_class{
public:
int CB = 22;
void fJugementB(){
if(F == 20)
cout<<"F_class is father"<<"\n";
}
};
class CC_class : public CA_class, public CB_class{
void fJudgementC(){
if(F == 20)
cout<<"F_class is father"<<"\n";
}
};
/*
这段代码使用虚继承重新实现了上图所示的菱形继承,这样在派生类 D 中就只保留了一份成员变量 m_a,直接访问就不会再有歧义了。

虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。

现在让我们重新梳理一下本例的继承关系,如下图所示:

菱形继承和虚继承
图2:使用虚继承解决菱形继承中的命名冲突问题

观察这个新的继承体系,我们会发现虚继承的一个不太直观的特征:必须在虚派生的真实需求出现前就已经完成虚派生的操作。在上图中,当定义 D 类时才出现了对虚派生的需求,但是如果 B 类和 C 类不是从 A 类虚派生得到的,那么 D 类还是会保留 A 类的两份成员。

换个角度讲,虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。

在实际开发中,位于中间层次的基类将其继承声明为虚继承一般不会带来什么问题。通常情况下,使用虚继承的类层次是由一个人或者一个项目组一次性设计完成的。对于一个独立开发的类来说,很少需要基类中的某一个类是虚基类,况且新类的开发者也无法改变已经存在的类体系。

C++标准库中的 iostream 类就是一个虚继承的实际应用案例。iostream 从 istream 和 ostream 直接继承而来,而 istream 和 ostream 又都继承自一个共同的名为 base_ios 的类,是典型的菱形继承。此时 istream 和 ostream 必须采用虚继承,否则将导致 iostream 类中保留两份 base_ios 类的成员。

虚继承在C++标准库中的实际应用
图3:虚继承在C++标准库中的实际应用
虚基类成员的可见性

因为在虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性。此外,如果虚基类的成员只被一条派生路径覆盖,那么仍然可以直接访问这个被覆盖的成员。但是如果该成员被两条或多条路径覆盖了,那就不能直接访问了,此时必须指明该成员属于哪个类。

以图2中的菱形继承为例,假设 A 定义了一个名为 x 的成员变量,当我们在 D 中直接访问 x 时,会有三种可能性:
如果 B 和 C 中都没有 x 的定义,那么 x 将被解析为 A 的成员,此时不存在二义性。
如果 B 或 C 其中的一个类定义了 x,也不会有二义性,派生类的 x 比虚基类的 x 优先级更高。
如果 B 和 C 中都定义了 x,那么直接访问 x 将产生二义性问题。

可以看到,使用多继承经常会出现二义性问题,必须十分小心。上面的例子是简单的,如果继承的层次再多一些,关系更复杂一些,程序员就很容易陷人迷魂阵,程序的编写、调试和维护工作都会变得更加困难,因此我不提倡在程序中使用多继承,只有在比较简单和不易出现二义性的情况或实在必要时才使用多继承,能用单一继承解决的问题就不要使用多继承。也正是由于这个原因,C++ 之后的很多面向对象的编程语言,例如 Java、C#、PHP 等,都不支持多继承。*/