C++ 多态的定义及实现
1. 多态定义的构成条件
多态是在不同继承关系的类对象,去调同一函数,产生了不同的行为。
就是说,有一对继承关系的两个类,这两个类里面都有一个函数且名字、参数、返回值均相同,然后我们通过调用函数来实现不同类对象完成不同的事件。
但是构成多态还有两个条件:
- 调用函数的对象必须是指针或者引用。
- 被调用的函数必须是虚函数,且完成了虚函数的重写。
说了这么多,怎么实现呢?我们先来看一段代码。
#include <iostream>
class Person
{
public:
virtual void BuyTicket(int)
{
std::cout << "Adult need Full Fare!" << std::endl;
}
};
class Child : public Person
{
public:
virtual void BuyTicket(int)
{
std::cout << "Child Free!" << std::endl;
}
};
void fun(Person& obj)
{
obj.BuyTicket(1);
}
int main(void)
{
Person p;
Child c;
fun(p);
fun(c);
return 0;
}
- 调用函数就是这里的
fun
,参数int
没有实际意义,就是为了体现函数重写必须要返回值一样、函数名一样和参数一样。
- 被调用的函数必须是虚函数,也就是说必须要在两个产生多态的函数前面加
virtual
关键字。
- 调用函数的形参对象必须是基类对象,这里是因为派生类只能给基类赋值,会发生切片操作。基类不能给派生类赋值。
- 调用函数的参数必须是指针或引用,因为派生类改变了虚表,那么这个虚表就属于派生类对象,赋值的时候只会把基类的成员给过去,虚表指针不会给。所以在调用函数的时候会发生语法检查,如果满足多态的条件,就会触发寻找虚表中虚函数地址。如果不满足条件,则会直接用基类对象调用基类函数。
上面牵扯出两个概念:
-
虚函数:虚函数就是在类的成员函数前面加virtual
关键字。
-
虚函数重写:虚函数的重写:派生类中有一个跟基类的完全相同虚函数,我们就称子类的虚函数重写了基类的虚函数。
完全相同是指:函数名、参数、返回值都相同。另外虚函数的重写也叫作虚函数的覆盖
虚函数重写有一个例外错误:协变
重写的虚函数的返回值可以不同,但是必须分别是基类指针或引用和派生类指针或引用。
这种情况在VS
会报错,但是在linux
的G++
下不会
#include <iostream>
class A
{
public:
virtual A* fun()
{
return new A;
}
};
class B : public A
{
public:
virtual B* fun()
{
return new B;
}
};
不规范重写行为:
就是在派生类的重写函数加了virtual
关键字,但是在派生类的重写函数前不加。
这样不会报错,因为继承的原因,将这个virtual
的性质继承了下来,但是这样写不规范,
如果两个函数构成重写,那么要在两个函数前都加上virtual
关键字。
2.析构函数重写问题
基类中的析构函数如果是虚函数,那么派生类的析构函数就重写了基类的析构函数。这里他们的函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor,这也说明的基类的析构函数最好写成虚函数。
将析构函数定义为虚函数的原因:
因为基类指针可能指向派生类,当delete
的时候,如果不定为虚函数,系统会直接调用基类的析构函数,这个时候派生类就有一部分没有被释放,就会造成可怕的内存泄漏问题。
若定义为虚函数构成多态,那么就会先调用派生类的析构函数然后派生类的析构函数会自动调用基类的析构函数,这个结果满足我们的本意。
所以!在继承的时候,尽量把基类的析构函数定义为虚函数,这样继承下去的派生类的析构函数也会被变成虚函数构成多态。
3.抽象类
在虚函数的后面写上 =0 ,则这个函数就变成纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
这个纯虚函数的作用就是强迫我们重写虚函数,构成多态。这样更加体现出了接口继承。
#include <iostream>
class Person
{
public:
virtual void Strength() = 0;
};
class Adult : public Person
{
public:
virtual void Strength()
{
std::cout << "Adult have big Strength!" << std::endl;
}
};
class Child : public Person
{
public:
virtual void Strength()
{
std::cout << "Child have Small Strength!" << std::endl;
}
};
4.C++11 override 和 final
override:
override
是用来检查函数是否重写,是在virtual void fun() override {}
这里加上,然后来检查的。实际中,建议这样写。
final:
final
是在class A final {};
这里加上,目的是为了不让这个类被继承。
或者,在一个函数后加,表示这个函数不能被重写。void fun() final {}
。