1、定义
多态通俗来说就是多种形态,即目标去实现某一行为,当对象不同时会产生不同状态。
2、多态构成条件
(1)继承关系父子的两个虚函数,要保证三同(函数名/参数/返回值)。
(2)必须通过基类的指针或者引用调用虚函数(修饰词virtual,只能修饰成员函数)。
(3)被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。
破坏上面条件可能会造成普通函数调用,而不是多态调用。
class A
{
public:
virtual void f()//vitual修饰虚函数
{
cout << "A::f()" << endl;
}
};
class B : public A
{
public:
void f()//派生类实现虚函数重写即覆盖,派生类可以省略修饰词virtual
{
cout << "B::f()" << endl;
}
};
void func(A& a) {
a.f();
}
int main() {
A a;
B b;
func(b);//对象a调用则输出A::f(),b调用则输出B::f()
return 0;
}
3、虚函数重写的两种特殊形式
(1)协变
基类和派生类虚函数的返回值类型不同,基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用。
class Person {
public:
virtual A* f() {
cout << "Person::f()" << endl;
return new A;
}
};
class Student : public Person {
public:
virtual B* f() {
cout << "Student::f()" << endl;
return new B;
}
};
(2)析构函数的重写
因为基类与派生类的析构函数的名字不同,因此编译器对析构函数的名称多了特殊处理,编译后析构函数的名称统一处理成destructor。如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写。如果不考虑下面这种情况会出现内存泄漏问题。
class A
{
public:
virtual ~A() {
cout << "~A()" << endl;
}
};
class B : public A
{
public:
~B() {
cout << "~B()" << endl;
}
};
int main() {
A* a = new A;
delete a;
a = new B;
delete a;
//这里希望释放的是对象B,遵循先释放基类再释放派生类的原则
//如果不加虚函数则只会释放基类,因为这里对象本身是A类型,赋值只是截取
return 0;
}
4、两个特殊修饰词
(1)final
该修饰词用于处理不允许重写的函数,基类中一经final修饰,则派生类中无法重写该函数。
class A
{
public:
virtual void func() final {
}
};
(2)override
该修饰词用于检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译会报错。
class B : public A
{
public:
virtual void func() override {
}
};
5、重载、重写、隐藏的区别
重载 | 重写(覆盖) | 隐藏(重定义) | |
作用域 | 两个函数必须在同一作用域 | 一个函数在基类,一个函数在派生类 | 一个函数在基类,一个函数在派生类 |
函数名 | 相同 | 相同 | 相同 |
参数 | 不同 | 相同 | 无要求 |
返回值 | 相同 | 相同(协变除外) | 无要求 |
6、抽象类
纯虚函数是在虚函数后面写上=0,包含纯虚函数的类称为抽象类(也可称为接口类),抽象类不能实例化出对象。只有重写纯虚函数之后,派生类才能实例化出对象。
class A
{
public:
virtual void func() = 0;//纯虚函数的定义
};
class B : public A
{
public:
virtual void func(){//重写
cout << "B-用来吃饭" << endl;
}
};
class C : public A
{
public:
virtual void func() {//重写
cout << "C-用来喝水" << endl;
}
};
int main() {
B b;
C c;
A& a = b;
A& aa = c;
a.func();//引用调用基类
aa.func();
return 0;
}
输出结果:
注意:虚函数的继承是一种接口继承,派生类继承的是虚函数的接口,目的就是为了重写,重写的是函数实现方法,函数中的缺省参数值并不会重写。而普通函数的继承,继承的是函数实现方法。另外如果不实现多态,建议不要把函数定义成虚函数。