[C++]浅述多态

什么是多态

多态(polymorphism)一词最初来源于希腊语polumorphos,含义是具有多种形式或形态的情形。
我们把具有继承关系的多个类型称为多态类型。
引用或指针的静态类型与动态类型不同这一事实正是c++语言支持多态性的根本所在。
1.类型:静态多态和动态多态
静态多态主要是函数重载和泛型编程,而动态多态是虚函数和指向基类的指针和引用
静态多态:
编译器在编译期间完成,编译器根据实参的类型(可能会发生隐式类型转化),可推断出要调用哪个函数,如果没有对应的函数,则会出现编译错误。

动态多态:

在程序执行期间判断所引用对象的实际类型,根据实际类型调用相应的函数。
动态多态的绑定:
①基类中必须要有虚函数—->在派生类中必须重写基类中的虚函数;
②必须使用基类的指针或引用去指向派生类的对象。
所以通过基类的指针或引用调用虚函数时,调用基类还是派生类的虚函数,要在运行时根据指针(引用)指向(引用)的类型确定,当调用非虚函数时,无论指向的是哪种类型,一定调用的是基类的函数。
例如:

class Base
{
public :
     Base()
          :_b(3)
     {
          cout << "Base()" << endl;
     }
     void Show()
     {
          cout << _b << endl;
     }
private :
     int _b;
};

class Derived :public Base
{
public :
     Derived()
          :_d(4)
     {
          cout << "Derived()" << endl;
     }
     void Show()
     {
          cout << _d << endl;
     }
private :
     int _d;
};

void Print(Base* p)
{
     p->Show();
}

void Test()
{
     Base b;
     Derived d;
     Print(&b);
     Print(&d);
}

int main()
{
     Test();
     return 0;
}

测试结果都为3这是因为Show函数不是虚函数。从而验证了动态绑定的条件
这里写图片描述

2.而在派生类中进行重写(覆盖)的虚函数必须满足函数原型相同(参数列表,函数名,返回值类型(协变除外))。
协变的概念:基类中的虚函数返回值类型为基类类型的指针或引用,并且派生类中重写的虚函数返回值类型为派生类类型的指针或引用。

例2

class Base
{
public :
     Base()
          :_b(3)
     {
          cout << "Base()" << endl;
     }
     virtual Base* Show()//返回值类型为Base*
     {
          cout << _b << endl;
          return this;
     }
private :
     int _b;
};

class Derived :public Base
{
public :
     Derived()
          :_d(4)
     {
          cout << "Derived()" << endl;
     }
     virtual Derived* Show()//返回值的类型为Derived*
     {
          cout << _d << endl;
          return this;
     }
private :
     int _d;
};

void Print(Base* p)
{
     p->Show();
}

void Test()
{
     Base b;//3
     Derived d;//4
     Print(&b);
     Print(&d);
}

这里写图片描述

总结虚函数注意要点:
①不要在构造函数和析构函数内部调用虚函数,在构造和析构函数中,对象是不完整的,可能会出现未定义的情况。
例如:在构造函数中,类中有3个成员变量需要初始化,假如在初始化了一个变量后就调用了虚函数,则可能会出现未定义情况。
在析构函数中,假如释放了一块空间后,调用虚函数,也会导致未定义的情况。
②在基类中定义了虚函数,则在派生类中该函数始终保持虚函数的特性。
在派生类中重写虚函数时也可以不显示写出virtual关键字,这时编译器会默认该函数为虚函数,为了程序看起来更加清晰,则最好加上virtual关键字。
③如果在类外定义虚函数,则只在类中声明时加virtual关键字,在类外定义是不能加virtual关键字。

提问:构造函数为什么不能定义为虚函数?
因为调用虚函数的前提是:对象一定构建成功,获取虚表地址必须通过对象的地址,如果对象还没有构建成功,那么无法获取虚表地址, 所以构造函数不能定义为虚函数。构造函数未执行完时对象是不完整的。
同理静态成员函数也能定义为虚函数。
因为静态成员函数是该类的所有对象所共享的,可以通过类名和作用域限定符调用,也就是可以不构建对象,但是虚表地址必须要通过对象的地址才能获取。

注意:虽然operator=可以声明为虚函数,最好不要这样做。
可能会导致错误(由于和赋值兼容规则所矛盾)。
我们用下面的例子来进一步说明:

class Base
{
public :
    virtual Base& operator=(const Base& b)
    {}
    int _b;
};
class Derived :public Base
{
public :
    virtual Derived& operator=(const Derived& b)
    {
    }
    int _d;
};

void Test1()
{
    Base b1;
    Derived d1;
    Base& b2 = b1;
    b2 = d1;//会调用基类的赋值运算符重载虚函数
    Base& b3 = d1;
    d1 = b2;//这样就会编译错误(虽然可能调用派生类的赋值运算符重载虚函数),赋值兼容规则不允许。
    }
    ...

4.在类的六个默认成员函数中,只有析构函数需要定义为虚函数。

class Base
{
public:
     Base()
          :_b(3)
     {
          cout << "Base()" << endl;
     }
     ~Base()
     {
          cout << "~Base()" << endl;
     }

private:
     int _b;
};

class Derived :public Base
{
public:
     Derived()
          :_d(4)
     {
          cout << "Derived()" << endl;
     }
     ~Derived()
     {
          cout << "~Derived()" << endl;
     }

private:
     int _d;
};

void Test()
{
     Base* pb = new Derived;
     delete pb;
}

int main()
{
Test():
return 0;
}

这里写图片描述

可知,当使用基类指针去指向一个派生类的对象时(即将派生类对象的地址赋给基类的一个指针),相当于构造了一个派生类的对象,先调用派生类的构造函数(在初始化列表中调用基类的构造函数),执行派生类的构造函数,当析构时,只调用了基类的析构函数(因为pb为Base*类型,所以只会调用基类的析构函数(析构函数不是虚函数时))——–所以可能会存在内存泄漏,当派生类中动态申请了一些空间时,资源泄露。

那声明成虚函数又会怎么样

class Base
{
public:
     Base()
          :_b(3)
     {
          cout << "Base()" << endl;
     }
     virtual ~Base()
     {
          cout << "~Base()" << endl;
     }

private:
     int _b;
};

class Derived :public Base
{
public:
     Derived()
          :_d(4)
     {
          cout << "Derived()" << endl;
     }
     virtual ~Derived()
     {
          cout << "~Derived()" << endl;
     }

private:
     int _d;
};

void Test()
{
     Base* pb = new Derived;
     delete pb;
}

这里写图片描述

把基类的析构函数声明为虚函数后,很好的解决了上面遇到的内存泄漏或者资源泄漏的问题。
可这里还有一个问题:那就是派生类重写的析构函数和基类定义的虚析构函数函数名并不相同,这和重写虚函数的规则好像有点矛盾,其实这里是编译器在派生类在进行重写析构函数时做了处理。

5.派生类重写基类的虚函数实现多态,要求函数的原型(参数列表,函数名,返回值(协变除外))完全相同。
验证协变:

class Base
{
public :
     virtual Base* FunTest1()
     {
          cout << "Base::FunTest1()" << endl;
          return this;
     }

};

class Derived:public Base
{
public :
     virtual Derived* FunTest1()
     {
          cout << "Derived::FunTest1()" << endl;
          return this;
     }
};

void Print(Base& b)
{
     b.FunTest1();
}

void Test1()
{
     Base b1;
     Derived d1;
     Print(b1);
     Print(d1);
}

6.多实现的条件:
①必须继承;
②派生类中重写(覆盖)基类中的虚函数。

示例

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

class Base
{
public :
     virtual Base* FunTest1()
     {
          cout << "Base::FunTest1()" << endl;
          return this;
     }
     virtual ~Base()
     {
          cout << "~Base()" << endl;
     }

};

class Derived:public Base
{
public :
     virtual Derived* FunTest1()
     {
          cout << "Derived::FunTest1()" << endl;
          return this;
     }
     virtual ~Derived()
     {
          cout << "~Derived()" << endl;
     }
};
void Test1()
{
     cout << "-------------Test1()----------" << endl;
     Base* pb = new Derived;
     pb->FunTest1();
     delete pb;
}

void Test2()
{
     cout << "------------Test2----------" << endl;
     Base* pb = (Base*)new Derived;
     pb->FunTest1();
     delete pb;
}


int main()
{
     Test1();
     return 0;
}

这里写图片描述

故动态绑定时和类型无关,只和绑定的对象有关,根据绑定的对象去调用绑定对象的类中的虚函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值