文章参考:
1. 多态性概述
所谓多态性,就是不同对象接收不同消息时,产生不同的动作。这样就可以用相同的接口访问不同的函数,从而实现一个接口,多种方法
。
从实现方式上看,多态性分为两种:
- 编译时多态:
- 在C++中,编译时多态与
连编
(把函数名和函数定义连接在一起)有关。静态连编时,系统用实参与形参进行匹配,对于同名的重载函数便根据参数上的差异进行区分,然后进行连编,从而实现了多态性。 - 优缺点:在程序编译时就知道调用函数的全部信息。因此,这种连编类型的函数调用速度快、效率高,但缺乏灵活性
- 实现方式:主要通过
函数重载
和运算符重载
实现。
- 在C++中,编译时多态与
- 运行时多态:
- C++中通过动态连编实现。动态连编在程序运行时生效,即当程序调用到某一函数名时,才去寻找和连接其程序代码。
- 优缺点:降低了程序的运行效率,但增强了程序的灵活性。
- 实现方式:主要通过虚函数实现。
2. 虚函数
定义:
虚函数的声明是在基类中进行的,通过virtual
关键字将基类的成员函数声明为虚函数。语法如下:
virtual 返回值 函数名 (形参表){
函数体;
}
当基类中的某个成员函数被定义为虚函数时,就可以在派生类中对该虚函数进行重新定义,并使用基类调用派生类的实现。
EG:
-
代码:
#include <iostream> #include <string> using namespace std; class Person{ private: string flower; public: Person(string flower = "鲜花"): flower(flower){} string getFlower(){ return this->flower; } virtual void show(){ cout<< "人类喜欢:"<< this->flower<< endl; } }; class Man: public Person{ public: Man(string flower = "油菜花"): Person(flower){} // 派生类中的virtual可加可不加 virtual void show(){ cout<< "男人喜欢:"<< this->getFlower()<< endl; } }; class Woman: public Person{ public: Woman(string flower = "西兰花"): Person(flower){} void show(){ cout<< "女人喜欢:"<< this->getFlower()<< endl; } }; int main(void){ Person *p = nullptr; Person per; Man m; Woman w; per.show(); p = &m; p->show(); p = &w; p->show(); return 0; }
-
输出:
人类喜欢:鲜花 男人喜欢:油菜花 女人喜欢: 西兰花
注意:
- 在派生类对基类中声明的虚函数进行重新定义时,关键字virtual可以写也可以不写。如不写,这时系统就会遵循以下的规则来判断一个成员函数是不是虚函数:该函数与基类的虚函数是否有相同的名称、参数个数以及对应的参数类型、返回类型或者满足赋值兼容的指针、引用型的返回类型。
- 虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为虚函数调用要靠特定的对象来决定该激活哪个函数。
- 内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的。即使虚函数在类的内部定义,编译时仍将其看做非内联的。
- 构造函数不能是虚函数,但是析构函数可以是虚函数,而且通常说明为虚函数。
3. 虚析构函数
问题:
假设现在有一个派生类B,其基类为A,我们建立一个A类型的指针p,再new
一个B类型的对象,并令p指向B的地址。那么当使用delete
释放指针p所指向的内存时,系统只会执行基类的析构函数,不执行派生类的析构函数
。
这是因为当撤销指针p所指向的空间时,采用了静态连编的方式,只执行了基类A的析构函数,而不会执行基类B的析构函数。
解决方法:如果希望程序执行动态连编时,先调用派生类的析构函数,再调用基类的析构函数,可以将基类的析构函数声明为虚析构函数。格式为:
virtual ~类名(){
函数内容;
}
EG:
-
代码:
#include <iostream> #include <string> using namespace std; class Base{ public: virtual ~Base() { cout << "调用基类Base的析构函数..." << endl; } }; class Derived: public Base{ public: ~Derived() { cout << "调用派生类Derived的析构函数..." << endl; } }; int main() { Base *p; p = new Derived; delete p; return 0; }
-
输出:
调用基类Base的析构函数... 调用派生类Derived的析构函数...
注意:
- 虽然基类和派生类的析构函数名不一致,但
如果将基类的析构函数声明为析构函数,那么其派生类的析构函数也都会变成虚函数
。 - 只有在上述情况下,才会出现只调用基类,而不调用派生类析构函数的情况。如果使用的栈空间,或者直接用派生类类型的指针,就不会出现这种问题。
4. 纯虚函数
纯虚函数是在声明虚函数时被“初始化为0的函数”,声明纯虚函数的一般形式如下:
virtual 函数类型 函数名(参数表) = 0;
声明为纯虚函数后,基类中就不再给出程序的实现部分
。纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要重新定义。
5. 抽象类
如果一个类至少有一个纯虚函数,那么就称该类为抽象类
。对于抽象类的使用有以下几点规定:
- 因为抽象类中包含一个没有定义功能的纯虚函数。因此,抽象类只能作为其他类的基类使用,不能家里抽象类对象。
- 不允许从具体类(不包含纯虚函数的类)中派生出抽象类。
抽象类不能用作函数的返回类型、参数类型或是显式转换的类型
。- 可以声明指向抽象类的指针或引用,此指针可以指向它的派生类,进而实现多态性。
- 如果派生类中没有定义纯虚函数的实现,而派生类中只是继承基类的纯虚函数,则这个派生类仍然是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。
6. 实例
问题:
利用C++的多态性,计算三角形、矩形的面积。
代码:
#include <iostream>
#include <string>
using namespace std;
// 定义一个抽象类
class Figure{
// 使用protected,便于派生类直接访问
protected:
double x;
double y;
public:
Figure(double m, double n): x(m), y(n){}
// 使用需析构函数,避免调用析构函数时只调用基类的
virtual ~Figure(){
cout << "Figure is destroyed..."<< endl;
}
// 纯虚构函数
virtual double get_area() = 0;
};
class Triangle: public Figure{
public:
Triangle(double b, double h): Figure(b, h){}
~Triangle(){
cout<< "Triangle is destroyed..."<< endl;
}
double get_area(){
return x*y/2;
}
};
class Square: public Figure{
public:
Square(double b, double h): Figure(b, h){}
~Square(){
cout<< "Square is destroyed..."<< endl;
}
double get_area(){
return x*y;
}
};
int main(void){
Figure *p = nullptr;
p = new Triangle(5, 10);
cout<< p->get_area()<< endl;
delete p;
p = new Square(5, 10);
cout<< p->get_area()<< endl;
delete p;
return 0;
}
输出:
25
Figure is destroyed...
Triangle is destroyed...
50
Figure is destroyed...
Square is destroyed...
标签:函数,多态性,基类,纯虚,析构,派生类,抽象类
From: https://www.cnblogs.com/beasts777/p/17875455.html