声明:本篇博客中的所以代码及解释都是在vs2013下的x86程序中实现的
1.多态的概念
多态:多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。可以理解为去完成某个行为,当不同的对象去完成时,会产生出不同的状态
2.多态的定义及实现
在继承中构成多态有两个条件:
- 必须通过基类指针或者引用调用虚函数
- 被条用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
那什么是虚函数,什么是重写呢?
虚函数:被virtual修饰的类成员函数
class Base
{
virtual void Func()//虚函数
{
cout << "hehe" << endl;
}
};
虚函数的重写:派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型,参数列表,函数名字完全相同),称派生类虚函数重写的基类的虚函数。
class Base
{
public:
virtual void Func()//虚函数
{
cout << "hehe" << endl;
}
};
class Derive : public Base
{
public:
virtual void Func()//返回值,函数名,参数列表相同,构成重写
{
cout << "haha" << endl;
}
virtual void Func(int)// 参数列表不同,无法构成重写
{
cout << "haha" << endl;
}
};
注意:派生类中的成员函数可以不加virtual修饰,但这样派生类函数的派生类就无法构成多态,所以一般都会加上virtual
虚函数重写有两个特例:
1>协变(基类与派生类虚函数的返回值类型不同)
派生类重写基类虚函数时,基类虚函数返回值为基类基类对象的指针或引用,派生类虚函数返回值为派生类对象的指针或引用,称为协变。
class Base
{
public:
virtual Base *Func()
{
cout << "hehe" << endl;
return new Base;
}
};
class Derive : public Base
{
public:
virtual Derive *Func()
{
cout << "haha" << endl;
return new Derive;
}
};
void Test(Base &b)
{
b.Func();
}
int main()
{
Base b;
Derive d;
Test(d);
return 0;
}
2>析构函数的重写(基类与派生类析构函数的名字不同)
如果基类析构函数为虚函数,此时派生类析构函数只要定义,无论是否加上virtual关键字,都与基类析构函数构成重写。
class Base
{
public:
virtual ~Base()
{
cout << "~Base()" << endl;
}
};
class Derive : public Base
{
public:
virtual ~Derive()
{
cout << "~Derive()" << endl;
}
};
int main()
{
Base *p1 = new Base;
Base *p2 = new Derive;
delete p1;
delete p2;
return 0;
}
3. 多态的原理
1>虚函数表
先看一道题:下面代码打印结果为多少?
class Base
{
public:
virtual void Func()
{
cout << "Func()";
}
private:
int _a = 1;
};
int main()
{
Base b;
cout << sizeof(b);
return 0;
}
结果为 8,在内存和监视窗口中,可以发现,对象除了_a成员外,前面还多了一个_vfptr,对象中的这个_vfptr指针叫做虚函数表指针
虚表指针指向虚函数表(虚表),那单继承关系的派生类中,这个虚表的内容是什么呢?
修改一下代码
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
};
class Derive : public Base
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base b1;
Base b2;
Derive d;
return 0;
}
此时派生类对象模型:
多继承关系的派生类虚表又是什么样?
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d1;
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
cout << " 虚表地址>" << vTable << endl;
for (int i = 0; vTable[i] != nullptr; ++i)
{
printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
VFPTR f = vTable[i];
f();
}
cout << endl;
}
int main()
{
Derive d;
VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
PrintVTable(vTableb1);
VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
PrintVTable(vTableb2);
return 0;
}
此时对象模型为:
通过上面代码,我们可以发现:
- 同一个类的不同对象共享一张虚表,b1与b2的虚表指针一模一样
- 派生类对象d中也有自己的虚表,并且没有与基类共享虚表,而是将基类虚表拷贝一份
- 如果派生类重写了基类某个虚函数,就用派生类自己的虚函数地址替换(重写,覆盖)虚表中相同偏移量位置的基类虚函数
- 单继承关系的派生类中新增加的虚函数按照其在派生类中的声明顺序依次增加到虚表的最后
- 多继承关系的派生类中新增加的虚函数按照其在派生类中的声明顺序依次增加到第一个虚表的最后
- 对象中存放虚表指针,虚表指针指向虚表,虚表是函数指针数组,里面存放的是虚函数的地址,在vs2013下,虚表存放在代码段
2>多态的实现原理
3.抽象类
概念:在虚函数后面写上 =0,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象
class Car
{
public:
virtual void Drive() = 0;
};
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
class BMW :public Car
{
public:
virtual void Drive()
{
cout << "BMW-操控" << endl;
}
};
void Test()
{
Car* pBenz = new Benz;
pBenz->Drive();
Car* pBMW = new BMW;
pBMW->Drive();
}