C++多态与继承
C++中多态分为静态多态和动态多态,
- 静态多态:编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推 断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
- 动态多态:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。实现动态多态的条件:1、必须是虚函数,派生类中必须重写虚函数 2、必须是通过基类类型的引用或者指针调用虚函数
C++多态的实现原理:
当类中声明虚函数时,编译器会在类中生成一个虚函数表
虚函数表是一个存储类成员函数指针的数据结构
虚函数表是由编译器自动生成与维护的
virtual成员函数会被编译器放入虚函数表中
存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)
通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。
出于效率考虑,没有必要将所有成员函数都声明为虚函数
C++中关于重写,重载和重定义概念的区别:
名称 | 作用域 | 参数 |
---|---|---|
重写(覆盖) | 作用域不同 | 函数名/参数/返回值相同(协变例外) |
重载 | 同一作用域 | 函数名相同/参数必须不同,返回值随意 |
重定义(隐藏) | 不同作用域 | 函数名相同/在基类,派生类中只要不是重写就是重定义 |
协变:在C++中,只要原来的返回类型是基类类型的指针或引用,新的返回值类型是派生类的指针或引用,覆盖的方法就可以改变返回类型,这样的返回类型称为协变返回类型。
协变返回类型的优势在于,总是可以在适当程度的抽象层面工作。目前,一般认为,返回值可以协变,参数则不可以。
因此,在C++标准的虚函数中,返回值协变,参数不变。
多继承,菱形继承模型
类中有虚函数编译器编译时就会为虚函数建一张表,多继承是将所有虚表都继承下来。如果派生类对基类中的虚函数有重写则派生类中重写的虚函数替换基类中被重写的虚函数。派生类自己的虚函数加在第一张表中。
菱形继承,单继承加组合的模型和多继承一样。
虚拟继承模型:
class Base1
{
public:
virtual void funtest1()
{
cout << "Base1::funtest1" << endl;
}
virtual void funtest2()
{
cout << "Base1::funtest2" << endl;
}
virtual void funtest3()
{
cout << "Base1::funtest3" << endl;
}
int _b;
};
class Derived :virtual public Base
{
public:
virtual void funtest4()
{
cout << "Derived::funtest4" << endl;
}
virtual void funtest2()// 在基类中是虚函数,派生类中重写构成多态
{// 通过基类指针或引用访问派生类时调用
cout << "Derived::funtest2" << endl;
}
virtual void funtest5()
{
cout << "Derived::funtest7" << endl;
}
int _d;
};
typedef void(*VFPTR)();
void test1(Base& b)
{
VFPTR* vfptr = (VFPTR*)(*((int*)(&b)));
while (*vfptr)
{
(*vfptr)();
vfptr++;
}
}
虚拟继承派生类对象模型以及虚表中的内容:
在虚拟单继承模型中派生类也给自己建了一张属于自己的虚表,同时在虚表向高字节偏移四个字节的地方是存放偏移量表的指针。偏移量表格中存放的是偏移量表指针相对派生类首地址的偏移量(所以0xfffffffc是-4的补码)和相对基类首地址的偏移量。
派生类对象通过基类的指针或引用调用时,基类funtest2相应位置被派生类funtest2替换。重写的funtest2函数出现多态现象。
菱形虚拟继承:
class Base
{
public:
virtual void funtest1()
{
cout << "Base::funtest1" << endl;
}
virtual void funtest2()
{
cout << "Base::funtest2" << endl;
}
virtual void funtest3()
{
cout << "Base::funtest3" << endl;
}
int _b;
};
class C1:virtual public Base
{
public:
virtual void funtest4()
{
cout << "C1::funtest2" << endl;
}
virtual void funtest5()
{
cout << "C1::funtest4" << endl;
}
virtual void funtest6()
{
cout << "C1::funtest5" << endl;
}
int c1;
};
class C2 :virtual public Base
{
public:
virtual void funtest4()
{
cout << "C2::funtest1" << endl;
}
virtual void funtest5()
{
cout << "C2::funtest6" << endl;
}
virtual void funtest6()
{
cout << "C2::funtest7" << endl;
}
int c2;
};
class Derived :public C1, public C2
{
public:
virtual void funtest4()
{
cout << "Derived::funtest4" << endl;
}
virtual void funtest2()
{
cout << "Derived::funtest2" << endl;
}
virtual void funtest5()
{
cout << "Derived::funtest7" << endl;
}
int _d;
};
typedef void(*VFPTR)();
void test1(C2& b)
{
VFPTR* vfptr = (VFPTR*)(*((int*)(&b)));
while (*vfptr)
{
(*vfptr)();
vfptr++;
}
}
菱形虚拟继承派生类对象模型和所继承虚表的内容:
菱形虚拟继承其实可以分为两部分来看,第一部分是C1,C2的虚拟单继承Base,第二部分是派生类Derived多继承自C1,C2。
虚拟单继承基类的对象模型永远只有一份而且在派生类对象的底部(相对于图来说)放置。再看上面多继承的模型,只是单单的将它们的对象模型拿下来在底部(相对于图来说)加上自己的成员,派生类的虚函数重建在多继承的第一张表中。