-
Notifications
You must be signed in to change notification settings - Fork 272
/
(纯)虚函数和多态
152 lines (125 loc) · 5.74 KB
/
(纯)虚函数和多态
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
#include <iostream>
using namespace std;
/****************************************
C++ 虚函数和多态
1. 虚函数是在基类中使用virtual声明的函数。当在编写类函数代码的时候,不确定调用
的基类的类函数代码还是派生类的类函数代码,即需要在运行时刻确定(动态绑定),所以定义为"虚"函数。
2. 当在基类中没有具体的实现、但要求每个派生类必须实现此类函数时,需要纯虚函数。其在基类中声明但不实现,且在函数原型后必须加"=0",
如 virtual void function()=0;
3. 多态就是多种形态、动态绑定。多态和虚函数紧密相连,其意味着:当对象调用类函数时,根据对象类型来执行不同的函数。
更多需要注意的点:
1. 在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理,
所以需要纯虚函数以实现多态。含有纯虚函数的类称为抽象类,它不能生成对象;
2. 友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数;
3. 析构函数需要定义为虚函数,这样在实现多态时才能调用子类的析构函数;否则只会调用父类的析构函数
*****************************************/
///////////////////// 第一个例子 ///////////////////////
class CBase{
public:
//explicit CBase(){std::cout << "constructors()" << std::endl;}
CBase(){std::cout << "constructors()" << std::endl;};
//CBase() = default; //等价于CBase(){};
CBase(int a_, int c_):a(a_), c(c_){std::cout << "constructors(CBase)" << std::endl;}
virtual void foo(){std::cout << "CBase" << std::endl;}
//void foo(){std::cout << "CBase" << std::endl;}
int a;
private:
int c;
};
class CChild:public CBase{
public:
CChild(int d_, int e_):d(d_),e(e_){std::cout << "constructors(CChild)" << std::endl;}
void f(){std::cout << "CChild" << std::endl;}
void foo(){std::cout << "测试" << std::endl;}
int d;
private:
int e;
};
int main()
{
CBase base(54,6); //单独定义基类
CChild child = CChild(2,3); //单独定义子类
//base.c;
child.a;
base.foo(); //此时没有相互影响,各自调各自的成员
child.f();
child.foo();
CBase b; //会调默认的构造函数
CBase b2(); //不会调默认的构造函数,这个声明了一个函数而非对象
CBase *b3 = new CChild(1,2); //和生成CChild对象一样调一次父类的默认构造函数和子类的构造函数,CBase *b3不会调基类的构造函数
b3->foo(); //如果基类中有虚函数并且子类重写(即多态)了,此时才会调用子类的函数,
//否则其他情况下(非虚重写、继承重载都是隐藏)调用基类的成员,取决于等号左边的对象
//b3->f(); //出错
//b3->d; //出错
CBase b4 = CChild(45,6); //和生成CChild对象一样调一次父类的默认构造函数和子类的构造函数
b4.foo(); //CBase
b4.d; //出错
}
///////////////////// 第二个例子 //////////////////////
#include <iostream>
using namespace std;
class Test
{
public:
Test() :a(0) { cout << "基类构造" << endl; }
virtual ~Test() { cout << "基类析构" << endl; }
int a;
};
class Test2:public Test
{
public:
Test2() :b(1){ cout << "子类构造" << endl; }
virtual ~Test2() { cout << "子类析构" << endl; }
int b;
};
int main()
{
Test t1;
Test2 t2;
t1 = t2; //这块没多态,t1基类构造,t2先调用基类构造然后子类构造,且发生切片现象
//t2 = t1; //错误,基类不被允许赋值给子类
Test *pt1; //指针不会调用构造,且不显式delete的话,也不会调用析构
Test2 *pt2;
//pt2 = new Test(); //和下行一样都不被允许,因为基类不可细化到子类
//pt2 = pt1;
pt1 = new Test2(); //调用基类构造然后调用子类构造,指针不会发生切片现象
delete pt1; // 调用子类析构然后调用基类析构
return 0; // t2调用子类析构然后基类析构,t1基类析构
}
///////////////////// 第三个例子 ///////////////////////
class Fruit{
public:
Fruit(){cout << "父类构造函数" << endl;}
virtual ~Fruit(){cout << "父类析构函数" << endl;} // 析构函数需定义为虚函数
virtual void foo(){ //实现多态必须是虚函数,如果基类不是虚函数,则只能实现重写,不能实现多态
cout << "Fruit::foo()被调用" << endl;
}
//virtual void foo() = 0; //纯虚函数,此时类不能定义对象
private:
string name;
double weight;
string color;
int number;
};
class Banana:public Fruit{
public:
Banana(){cout << "子类构造函数" << endl;}
~Banana(){cout << "子类析构函数" << endl;}
void foo(){ //重写实现多态
cout << "Banana::foo()被调用" << endl;
}
};
int main()
{
//多态
Fruit *fru1 = new Fruit();
Fruit *fru2 = new Banana();
fru1->foo();
fru2->foo();
//调用析构函数,分析虚构函数为什么需要是虚函数
//如果基类析构是虚函数,则多态时delete基类指针所指派生类对象,会先调用子类析构再调用父类析构
//否则,依旧可以实现多态,但是delete时只会调用基类析构,不会调用派生类析构函数
delete fru1;
delete fru2;
return 0;
}