多态是c++中很重要的一环。多态可以分为以下几个层面来剖析:

    1.对象的类型

    2.多态

    3.虚表


    先说第一点对象的类型,这个非常简单。比如说、

int a;

    那么我就定义了一个int类型的变量a。再来看下面的代码

class Base
{

};

class Derive:public Base
{

};

    这里我写了一个Base类和一个Derive类,并且Derive类是派生于Base类

Base b;
Derive d;

Base* pb=&b;
pb=&d;

    上面的代码实例化了一个Base类类型的对象b,Derive类类型的对象d,Base*类型的指针pb。

pb的静态类型就是Base*类型,我们也可以让pb指向d,Derive*就是pb的动态类型。


    下面来说说第二点,多态。

    

int Add(int left,int right)
{
    return left+right;
}

double Add(double left,double right)
{
    return left+right;
}

    上面这两个函数构成了函数的重载,传进去int类型的参数就调用上面的,double类型的参数就调用下面的。这也是一种多态,称为静态的多态。还有一种泛型编程也是静态的多态。静态多态就是在编译器编译期间完成的,编译器根据函数的实参的类型(可能进行隐式的类型转换),可推断出到底要调用哪个函数,如果有对应的函数就调用该函数,否则就会出现编译错误。

    

    那么动态多态(也叫动态绑定)就是指在程序执行期间判断所引用对象的实际类型,根据其实际类型调用相应的方法。

    使用virtual关键字来修饰函数,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。

    所谓虚函数就是指在类中被声明为virtual的成员,基类希望这种成员在派生类中重定义。除了构造函数外,任意非static成员都可以为虚成员。保留字 virtual 只在类内部的成员函数声明中出现,不能用在类定义体外部出现在函数定义上。

    看一段代码:

class Base
{
public:
    virtual void FunTest()
    {
        cout<<"Base::FunTest()"<<endl;
    }
};

class Derive :public Base
{
public:
    void FunTest()
    {
        cout<<"Derive::FunTest()"<<endl;
    }
};

int main()
{
    Derive d;
    d.FunTest();
    
    Base b;
    b.FunTest();
    
    Base *pb=&b;
    pb->FunTest();
    pb=&d;
    pb->FunTest();
    
    return 0;
}

    在这一段代码里面,我定义了两个类一个是Base,另一个是他的派生类Derive。Base类里面有一个虚函数FunTest(),Derive类里面也有一个FunTest()。并且在主函数里面实例化了两个类的对象,并且调用了FunTest函数,下面也定义了Base*类型的指针,先指向b,然后调用了FunTest函数,之后指向d,然后调用FunTest函数。这段代码运行结果会是什么样呢?

wKioL1cl1quARdN6AAANKMNh70U673.png

    正如我们所看到的调用派生类里面的函数他有他就调用他自己的,他没有再去基类里面找。 

    那么动态绑定实现的条件是什么呢?第一,必须要是虚函数。第二,要通过基类类型的引用或者指针调用。

class CBase
{
public:
	virtual void FunTest1(int _iTest)
	{
		cout << "CBase::FunTest1()" << endl;
	}
	void FunTest2(int _iTest)
	{
		cout << "CBase::FunTest2()" << endl;
	}
	virtual void FunTest3(int _iTest1)
	{
		cout << "CBase::FunTest3()" << endl;
	}
	virtual void FunTest4(int _iTest)
	{
		cout << "CBase::FunTest4()" << endl;
	}

};


class CDerive:public CBase
{
public:
	virtual void FunTest1(int _iTest)
	{
		cout << "CDerive::FunTest1()" << endl;
	}
	virtual void FunTest2(int _iTest)
	{
		cout << "CDerive::FunTest2()" << endl;
	}
	void FunTest3(int _iTest1)
	{
		cout << "CDerive::FunTest3()" << endl;
	}
	virtual void FunTest4(int _iTest1,int _iTest2)
	{
		cout << "CDerive::FunTest4()" << endl;
	}
};

int main()
{
	CBase* pBase = new CDerive;
	pBase->FunTest1(0);
	pBase->FunTest2(0);
	pBase->FunTest3(0);
	pBase->FunTest4(0);
	return 0;
}


    上面是一个例子,CBase类是CDerive类的基类。之后FunTest1()是一个虚函数,FunTest2()不是一个虚函数,FunTest3()也是虚函数,FunTest4()虽然是虚函数但是在子类里面重新实现给了两个参数。所以运行结果是这样的:

wKiom1cmuYKwahkaAAALt2t1hLw612.png

    假如我们想调用CDerive里面的FunTest4(),我们就要用CDerive类的对象了。就像下面这样:

        CDerive d;
	d.FunTest4(0, 0);

    我们这里有一个图片,能看明白继承体系中同名成员函数的关系:
wKioL1cmvB2z9w6hAACZa0goxnk476.png

    这里还需要注意:构造函数是不可以定义为虚函数的,因为构造函数是用来构建我们的对象 的,构造函数没有执行完我们的对象就是不完整的。假如我们要调用构造函数,是需要通过我们的基类对象来调用的,但是我们的对象都没有构造完,所以是不能这样的。

    静态函数和友元函数也同样不可以用virtual来修饰。因为这两种函数都没有this指针。


    这里还有一个东西:

class test
{
	virtual void Test() = 0;
};

    这段代码定义的类叫抽象类。它不能够实例化产生对象,它只是提供一些接口。它里面的那个函数后面跟了一个=0,表示它是纯虚函数,它表示它的派生类要对它这个函数进行重写。


    最后来说虚表和虚指针。

    当我们求sizeof(test)时,我们得出的结果是4。为什么呢?对类求大小的时候不应该是它的成员的大小吗?这里就是有一个虚指针。那这个虚指针指向那里呢?是指向的虚表。虚表里面存的就是虚函数的地址。

    举一个例子:

class test
{
public:
	virtual void FunTest1()
	{}
	virtual void FunTest2()
	{}
	virtual void FunTest3()
	{}
	virtual void FunTest4()
	{}
};


    这个类里面只有四个虚函数,那么sizeof(test)等于多少呢?

wKiom1crAcuxar5bAAAJ0p4dP7s313.png

    再来看看t中到底有什么:

wKiom1crAgey11AWAAAhAUsZcj4440.png


    所以当我们要调用虚函数的时候,编译器是先找到我们的虚表地址,之后找到对应的虚函数。