多态
多态分为运行时多态和编译期多态,也被称作动态绑定和静态绑定或者晚绑定和早绑定。
一、编译期多态
编译期多态主要是通过函数重载,运算符重载还有模板函数,模板类来是实现的。
- 函数重载和运算符重载,通过在编译时对函数进行name mangling,将具有相同返回值、相同名称仅参数列表不同的函数在编译时进行不同的签名,避免调用时的二义性。在编译时,根据函数参数列表来确定应该调用哪个版本的函数,在编译完成后就确定了应该调用哪个函数版本。
- 类模板函数还有STL模板库,也是使用的编译期多态,在编译之前,并不知道应该调用哪个版本的函数,在编译时,编译期根据模板类型,还有实参的类型来确定应该调用哪个参数。同样,在编译期就已经确定了调用哪个参数。
二、运行时多态
主要依靠类的继承和虚函数来实现。
-
当在使用类的继承时,如果想让派生类的某些方法与基类的不同,并且能够通过基类的指针或者引用根据实际指向的对象来调用属于基类和派生类不同的方法。就可以使用vitrual来修饰基类中被派生类重写的函数。这就是虚函数。
如下例子中,我们在基类中声明了三个虚函数,但在派生类中只有
void f()
被重写了。
class Base {
public:
virtual void f() { cout << "Base::f()" << endl; }
virtual void g() { cout << "Base::g()" << endl; }
virtual void h() { cout << "Base::h()" << endl; }
};
class Derived :public Base{
public:
virtual void f() { cout << "Derived::f()"; }
virtual void f1() { cout << "Derived::f1()"; }
};
测试代码:
int main() {
Base a;
Derived d;
Base* ptr_d=&d;
Base& ref_d=d;
ptr_d->f();
ref_d.f();//调用的是指针和引用实际指向的对象中的函数
return 0;
}
但是使用基类指针或引用指向派生类对象来调用派生类对象的虚函 数有几点值得注意:
1、这个虚函数必须是基类和派生类所共有的(重写与否不重要)。即不能通过基类指针或引用指向派生类来调用派生类独有的成员函数(虚函数和普通成员函数都不行)
2、使用基类指针或引用指向派生类对象来调用的普通成员函数,那么编译器将根据指针类型来决定调用那个成员函数,而不是根据指针实际指向的对象来决定调用哪个函数(因为普通函数不存在多态)。
3、想要使用基类指针或者引用来调用派生类的方法的话,可以将类型向下转换,即使用static_cast
(不安全)或者dynamic_cast
(安全)将基类指针或引用转换成派生类指针或引用,以此来调用派生类专属的成员方法。
关于虚函数还有几点需要注意的:
- 构造函数不能是虚的(为什么?因为没有意义),因为派生类不会继承基类的构造函数,而是在派生类的构造函数中调用基类的构造函数来构造派生类的基类部分。不会继承就是不会重写,不重写的虚函数是没有意义的。
- 基类的析构函数必须是虚的,这样才能保证在使用基类指针或引用来释放派生类资源是,能够根据指针或引用实际指向的对象来调用析构函数,释放派生类中的资源,以免内存泄漏。
- 在建立派生类对象是构造函数的调用顺序是
base::constructor --> derived::constructor
。 - 在析构派生类对象是析构函数调用的顺序是
derived::destructor --> base::destructor
。`