在完成对C语言的学习后,我最近开始了对C++和Java的学习,目前跟着视频学习了一些语法,也跟着敲了一些代码,有了一定的掌握程度。现在将跟着视频做的笔记进行整理。本篇博客是整理C++知识点的第十六篇博客。
本篇博客介绍了C++的多态。
本系列博客所有C++代码都在Visual Studio 2022环境下编译运行。程序为64位。
目录
多态是C++面向对象的三大特征之一。多态分为静态多态和动态多态。
函数重载和运算符重载属于静态多态,静态多态的函数地址早绑定,编译阶段就确定了函数地址。
子类和虚函数实现属于动态多态,动态多态的函数地址晚绑定,运行阶段才确定函数地址。
在函数前加virtual表示虚函数。
多态满足的条件是有继承关系,子类重写父类的虚函数。多态的使用条件是父类指针或引用指向子类对象。
函数的返回值类型,函数名,参数列表完全一致称为重写。
- #include
- using namespace std;
- class animal
- {
- public:
- void eat(void) {
- cout << "Animal can eat something" << endl;
- }
- };
- class dog :public animal
- {
- public:
- void eat(void) {
- cout << "Dog can eat something" << endl;
- }
- };
-
- class cat :public animal
- {
- public:
- void eat(void) {
- cout << "Cat can eat something" << endl;
- }
- };
-
- int main(void)
- {
- animal an;
- cat c;
- dog d;
- animal& a = c;
- a.eat();
-
- animal& b = d;
- b.eat();
- return 0;
- }
父类animal的成员函数eat输出Animal can eat something,两个子类dog和cat重写了eat函数,分别输出Dog can eat something和Cat can eat something。main函数实现了多态,并分别调用eat成员函数。
程序的输出是:
Animal can eat something
Animal can eat something
此处animal类的eat函数未用virtual修饰,因此实现多态会调用父类的成员函数。
- #include
- using namespace std;
- class animal
- {
- public:
- virtual void eat(void) {
- cout << "Animal can eat something" << endl;
- }
- };
- class dog :public animal
- {
- public:
- void eat(void) {
- cout << "Dog can eat something" << endl;
- }
- };
-
- class cat :public animal
- {
- public:
- void eat(void) {
- cout << "Cat can eat something" << endl;
- }
- };
-
- int main(void)
- {
- animal an;
- cat c;
- dog d;
- animal& a = c;
- a.eat();
-
- animal& b = d;
- b.eat();
- return 0;
- }
这段代码和上面的差不多,就是把animal类的eat函数用了virtual修饰,变成了虚函数。
程序的输出是:
Cat can eat something
Dog can eat something
父类的函数变成虚函数,因此调用的是子类成员。
每个类有虚函数表,记录虚函数的地址。子类的虚函数表内部会替换成子类的虚函数地址。
- #include
- using namespace std;
- class animal
- {
- public:
- virtual void eat(void) {
- cout << "Animal can eat something" << endl;
- }
- };
- class dog :public animal
- {
- public:
- void eat(void) {
- cout << "Dog can eat something" << endl;
- }
- };
-
- class cat
- {
- public:
- void eat(void) {
- cout << "Cat can eat something" << endl;
- }
- };
-
- int main(void)
- {
- cout << sizeof(cat) << endl;
- cout << sizeof(animal) << endl;
- return 0;
- }
这段代码的类和上面两个例子一样,程序输出父类和子类的大小。程序的输出是:
1
8
子类存放了一个虚函数表指针,大小为8字节。(32位下为4字节)
多态有很多优点,可以使代码组织结构清晰,可读性强,利于扩展和维护。
下面需要实现一个计算器类,实现简单的加减乘除。
- #include
- using namespace std;
- class calculate
- {
- public:
- int a;
- int b;
- int add(void) {
- return a + b;
- }
- int subtraction(void) {
- return a - b;
- }
- int multiple(void) {
- return a * b;
- }
- int divide(void) {
- return a / b;
- }
- };
-
- int main(void)
- {
- calculate cal;
- cal.a = 8;
- cal.b = 6;
- cout << cal.add() << endl;
- cout << cal.subtraction() << endl;
- cout << cal.multiple() << endl;
- cout << cal.divide() << endl;
- return 0;
- }
这段代码不使用多态。calculate类有两个成员变量a和b,用于运算。下面是四个成员函数,执行加减乘除。main函数创建了一个calculate类对象cal,并执行加减乘除操作。
程序的输出是:
14
2
48
1
但是这一段代码不便于维护,因为如果要增删功能,需要在类中增删函数。下面的代码使用多态实现。
- #include
- using namespace std;
- class calculator {
- public:
- int a;
- int b;
- virtual int getresult(void) {
- return 0;
- }
- };
- class add:public calculator
- {
- public:
- int getresult(void) {
- return a + b;
- }
- };
- class subtraction :public calculator
- {
- public:
- int getresult(void) {
- return a - b;
- }
- };
-
- class multiple :public calculator
- {
- public:
- int getresult(void) {
- return a * b;
- }
- };
- class divide :public calculator
- {
- public:
- int getresult(void) {
- return a / b;
- }
- };
-
- int main(void)
- {
- calculator* cal = new add();
- cal->a = 6;
- cal->b = 8;
- cout << cal->getresult() << endl;
- delete cal;
- cal = new subtraction();
- cal->a = 6;
- cal->b = 8;
- cout << cal->getresult() << endl;
- delete cal;
-
- cal = new multiple();
- cal->a = 6;
- cal->b = 8;
- cout << cal->getresult() << endl;
- delete cal;
-
- cal = new divide();
- cal->a = 6;
- cal->b = 8;
- cout << cal->getresult() << endl;
- delete cal;
- return 0;
- }
calculator类有两个成员变量a和b,和一个虚函数getresult,该函数返回0。add subtraction multiple divide类都以public的方式继承了calculator类,并重写getresult函数实现加减乘除。main函数通过多态进行加减乘除。程序的输出是:
14
-2
48
0
这样如果要增删功能,通过增删继承calculator的类即可。
多态中,通常父类虚函数的实现是无意义的,用的都是子类中的内容。因此可以将虚函数改为纯虚函数。
纯虚函数的语法是 virtual 返回值类型 函数名(参数列表) = 0;
如果类中有了纯虚函数,则称为抽象类。抽象类无法实例化对象。子类必须重写抽象类中的纯虚函数,否则子类也是抽象类。
- #include
- using namespace std;
- class father
- {
- public:
- virtual void show() = 0;
- };
- class son:public father
- {
- public:
- void show() {
- cout << "This is a func" << endl;
- }
- };
-
- int main(void)
- {
- father* f = new son();
- f->show();
- delete f;
- return 0;
- }
父类father的show函数是纯虚函数,子类son重写了show函数,输出This is a func。main函数使用了多态,并调用show函数。程序的输出是:
This is a func
- #include
- using namespace std;
- class drink
- {
- public:
- virtual void first(void) = 0;
- virtual void second(void) = 0;
- virtual void third(void) = 0;
- virtual void fourth(void) = 0;
- void show(void) {
- first();
- second();
- third();
- fourth();
- }
- };
-
- class coffee :public drink
- {
- void first(void) {
- cout << "This is the first step to make a coffee" << endl;
- }
- void second(void) {
- cout << "This is the second step to make a coffee" << endl;
- }
- void third(void) {
- cout << "This is the third step to make a coffee" << endl;
- }
-
- void fourth(void) {
- cout << "This is the fourth step to make a coffee" << endl;
- }
- };
- class tea :public drink
- {
- void first(void) {
- cout << "This is the first step to make a tea" << endl;
- }
-
- void second(void) {
- cout << "This is the second step to make a tea" << endl;
- }
- void third(void) {
- cout << "This is the third step to make a tea" << endl;
- }
- void fourth(void) {
- cout << "This is the fourth step to make a tea" << endl;
- }
- };
- int main(void)
- {
- drink* makedrink = new coffee();
- makedrink->show();
- delete makedrink;
-
- makedrink = new tea();
- makedrink->show();
- delete makedrink;
- return 0;
- }
drink类有first second third fourth四个虚函数,show成员函数依次调用这四个函数。两个子类coffee和tea重写了这四个虚函数。main函数通过drink类指针指向coffee和tea类对象,并调用show函数。程序的输出是:
This is the first step to make a coffee
This is the second step to make a coffee
This is the third step to make a coffee
This is the fourth step to make a coffee
This is the first step to make a tea
This is the second step to make a tea
This is the third step to make a tea
This is the fourth step to make a tea
在使用多态时,如果子类有属性开辟在堆区,那么父类指针释放时无法调用子类的析构函数。解决方法是将父类的虚函数改为虚析构或者纯虚析构。这两个都可以解决父类指针释放时无法析构子类的问题。如果子类没有堆区的数据,可以不写虚析构或者纯虚析构。
如果用纯虚析构,那么父类是抽象类。纯虚析构需要类外实现。
虚析构的语法是:
virtual ~类名(){}
纯虚析构的语法是:
virtual ~类名() = 0;
类名::~类名(){}
- #include
- using namespace std;
- class father
- {
- public:
- father(void) {
- cout << "A" << endl;
- }
- virtual ~father(void) {
- cout << "Z" << endl;
- }
- };
-
- class son : public father
- {
- int* number = new int(0);
- public:
- son(void) {
- cout << "a" << endl;
- }
- ~son(void) {
- delete number;
- cout << "z" << endl;
- }
- };
-
- int main(void)
- {
- father* s = new son();
- delete s;
- return 0;
- }
子类son有数据开辟在堆区。son类的构造函数输出a,析构函数输出z,并释放堆区数据。父类father的构造函数输出A,虚析构函数输出Z。main函数实现了多态,程序的输出是:
A
a
z
Z
- #include
- using namespace std;
- class father
- {
- public:
- father(void) {
- cout << "A" << endl;
- }
- virtual ~father(void) = 0;
- };
-
- father::~father(void)
- {
- cout << "Z" << endl;
- }
-
- class son : public father
- {
- int* number = new int(0);
- public:
- son(void) {
- cout << "a" << endl;
- }
- ~son(void) {
- delete number;
- cout << "z" << endl;
- }
- };
-
- int main(void)
- {
- father* s = new son();
- delete s;
- return 0;
- }
这段代码和上一段大致一样,但是将father类的析构函数实现为纯虚析构,将实现移至类外。程序的输出是:
A
a
z
Z