动态多态以及多态调用过程

        多态分为静态多态与动态多态。静态多态包括函数重载,泛型编程。动态是虚函数的使用。

        静态多态是指编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用的那个函数,如果有对应的函数就调用该函数,否则会出现编译错误。

        动态多态,我们在这里主要说明的是动态多态。

        动态绑定:在程序执行期间(非编译器)判断所引用对象的实际类型,根据其实际类型调用相应的方法。使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。动态绑定的条件是基类中的函数必须是虚函数,且派生类一定要重写基类的虚函数;第二点是通过基类类型的引用或者指针调用虚函数。

        纯虚函数:在成员函数的形参列表后面写上"=0",则成员函数为纯虚函数,包含纯虚函数的类叫做抽象类,也叫接口类,抽象类不能实例化出对象,纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。

         eg: virtual void Display() = 0;

       继承体系同名成员函数的关系

        重载:在同一作用域,函数名相同,参数不同,返回值可以不同。

        重写(覆盖):函数名相同,参数相同,返回值相同(协变除外);基类函数必须有virtual关键字;访问修饰符可以不同。

        重定义(隐藏):在不同的作用域中(分别在基类和派生类);函数名相同;在基类和派生类中只要不构成重写就是重定义。

        总结(以下是有关动态多态的总结)

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

        2.基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。

        3.只有类的非静态成员函数才能定义是虚函数,静态成员函数不能定义为虚函数。

        4.如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。

        5.构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆。

        6.不要再构造函数和析构中调用虚函数,在析构函数和构造函数中对象是不完整的,可能会出现未定义的行为。

        7.最好将基类的析构函数声明为虚函数。

        8.虚表是所有类对象实例共用的。

        多态实现原理

        带有虚函数的类比正常的类多了四个字节,里面保存了一个指针。如下图所示:

      

        派生类虚函数表的创建过程是要创建一个派生类的对象先要调用基类的构造函数,此时虚表指针为基类的虚表指针,基类构造函数调用完成后,再把基类的虚表指针替换为派生类的虚表指针,从而创建派生类对象。在派生类构建虚函数表时,可以看成将基类的虚函数拷贝了一份,若在派生类中虚函数重写,则基类的虚函数被派生类中重写的虚函数所替换掉,若没有构成重写,将不会被覆盖,且没有重写的虚函数在派生类虚函数表中的位置不变,若派生类中存在新的虚函数,则按照定义顺序跟在已经被覆盖的基类虚函数表的后面,此时派生类的虚函数创建成功。

        代码实现:

 

class Base
{
	virtual void Funtest1()
	{
		cout << "Base::Funtest1()" << endl;
	}
	virtual void Funtest2()
	{
		cout << "Base::Funtest2()" << endl;
	}
	int _b;
};
class Derived:public Base
{
	void Funtest1()
	{
		cout << "Derived::Funtest1()" << endl;
	}
	void Funtest2()
	{
		cout << "Derived::Funtest2()" << endl;
	}
	void Funtest3()
	{
		cout << "Derived::Funtest3()" << endl;
	}
	virtual void Funtest4()
	{
		cout << "Derived::Funtest4()" << endl;
	}
	int _d;
};

typedef void (*Fun)();

void Printvpf(Base& b)
{
	int* addr = (int*)*((int*)&b);
	Fun* pFun = (Fun*)addr;
	while (*pFun)
	{
		(*pFun)();
		pFun = (Fun*)++addr;
	}
}

int main()
{
	Base b;
	Derived d;
	Printvpf(b);
	Printvpf(d);
	return 0;
}

        如下图所示:

        

         普通虚拟继承的对象模型:

              

         在这里,若派生类中没有自己新加的虚函数,则不会创建第二个虚表指针。

         多继承的虚拟继承与上面的普通虚拟继承对象模型类似,如果派生类中新加了虚函数,则新加的虚函数会放在第一个继承基类虚表的后面。

         菱形虚拟继承:

class B
{
public :
    virtual void FunTest1()
    {
        cout<<"B::FunTest1()"<<endl;
    }
    int _b;
};
class C1:virtual public  B
{
public :
    virtual void FunTest1()//重写B
    {
        cout<<"C1::FunTest1()"<<endl;
    }
    virtual void FunTest2()//新加的虚函数
    {
        cout<<"C1::FunTest2()"<<endl;
    }
    int _c1;
};
class C2:virtual public B
{
public :
    virtual void FunTest1()//重写B中虚函数
    {
        cout<<"C2::FunTest1()"<<endl;
    }
    virtual void FunTest3()//新加虚函数
    {
        cout<<"C2::FunTest3()"<<endl;
    }
    int _c2;
};
class D:public C1,public C2
{
public :
    virtual void FunTest1()//重写C1和C2中的虚函数
    {
        cout<<"D::FunTest1()"<<endl;
    }
    virtual void FunTest2()//重写C1中的虚函数
    {
        cout<<"D::FunTest2()"<<endl;
    }
    virtual void FunTest3()//重写C2中的虚函数
    {
        cout<<"D::FunTest3()"<<endl;
    }
    int _d;
};
void Test1()
{
    D d;
    d._b = 1;
    d._c1 = 2;
    d._c2 = 3;
    d._d = 4;
}
int main()
{
    Test1();
    return 0;
}


              

         上图则为菱形虚拟继承的对象模型。

          

          

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值