目录
- 1.菱形继承
- 1.1.菱形继承的问题
- 1.2.解决办法
- 2.虚函数与多态
- 2.1.普通函数不能实现多态
- 2.2.虚函数(子类重写)+ 父类指向子类——实现多态
- 2.3.多态原理
- 3.c++内存模型
- 4.参考
1.菱形继承
先看下面的例子,SheepTuo
同时继承了Sheep
和Tuo
,而他们同时继承Animal
类
#include <iostream>
using namespace std;class Animal
{int mAge;
};class Sheep : public Animal {};
class Tuo : public Animal {};
class SheepTuo : public Sheep, public Tuo {};int main()
{SheepTuo st;////// 1.报错,"SheepTuo::mAge" is ambiguous,mAge成员在两个子类都存在,二义性// st.mAge = 18; ////// 2.可以声明作用域,避免成员的二义性st.Sheep::mAge = 18;st.Tuo::mAge = 100;////// 3.但是在SheepTuo类中mAge成员会复制两份,造成内存浪费return 0;
}
1.1.菱形继承的问题
- 共享成员二义性——增加作用域可以解决
- 内存复制两份-浪费——虚继承解决
1.2.解决办法
#include <iostream>
using namespace std;// 虚基类
class Animal
{
public:int mAge;
};// virtual --> 虚继承
class Sheep : virtual public Animal {};
class Tuo : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};int main()
{SheepTuo st;////// 1.不会报错了// st.mAge = 18;////// 2.这样写,下面的mAge都会是100,因此虚继承后,成员不会被复制,只在基类中有一份,子类中维护vbptr(管理不同的偏移量)指向它st.Sheep::mAge = 18;st.Tuo::mAge = 100;return 0;
}
SheepTuo
继承了Sheep
和Tuo
的虚基类指针vbptr
,这俩的指针会指向他们的虚基类表vbtable
,虚基类表中存着其vbptr的偏移量,通过偏移量可以帮助子类正确找到从虚基类继承来的数据
2.虚函数与多态
2.1.普通函数不能实现多态
#include <iostream>
using namespace std;class Animal
{
public:void Speak(){cout << "动物 在说话" << endl;}
};class Cat : public Animal
{void Speak(){cout << "小猫 在说话" << endl;}
};
class Dog : public Animal
{void Speak(){cout << "小狗 在说话" << endl;}
};// 常对象只能调用常函数
// void DoSpeak(const Animal &animal)
void DoSpeak(Animal &animal)
{animal.Speak();
}int main()
{Cat cat;DoSpeak(cat);return 0;
}
输出:动物 在说话
上面的Animal
中Speak
是一个普通成员函数,虽然有继承的条件,但是编译器在编译阶段,会DoSpeak
函数中把animal.Speak();
的animal
绑定为Animal
的地址,无法实现多态
2.2.虚函数(子类重写)+ 父类指向子类——实现多态
#include <iostream>
using namespace std;class Animal
{
public:virtual void Speak(){cout << "动物 在说话" << endl;}
};class Cat : public Animal
{void Speak(){cout << "小猫 在说话" << endl;}
};
class Dog : public Animal
{void Speak(){cout << "小狗 在说话" << endl;}
};void DoSpeak(Animal &animal)
{animal.Speak();
}int main()
{Cat cat;DoSpeak(cat);return 0;
}
输出:小猫 在说话
和2.1.节相比,只增加了virtual
关键字,使得父类普通函数变为虚函数,使得DoSpeak
函数中animal.Speak()
的animal在运行阶段才会指向真正调用的对象,实现了多态
其运行流程先说明:对象指针->取其虚表指针->取其虚表中函数->call调用
2.3.多态原理
- case1.普通函数的基类所占内存大小, sizeof(Animal) = 1
class Animal
{
public:void Speak(){cout << "动物 在说话" << endl;}
};
- case2.虚函数的基类所占内存大小, sizeof(Animal) = 4
class Animal
{
public:virtual void Speak(){cout << "动物 在说话" << endl;}
};
case1和case2说明了增加virtual
会在类中多用内存,增加的是虚函数指针和虚函数表
- case3.子类继承虚基类,并且子类重写虚函数
class Animal
{
public:virtual void Speak(){cout << "动物 在说话" << endl;}
};class Cat : public Animal
{void Speak(){cout << "小猫 在说话" << endl;}
};
Cat
中的虚函数表发生覆盖
- 总体来看看
3.c++内存模型
C++类中内存存储情况比较复杂,涉及成员数据、函数、静态成员、虚函数等情况
记录结论,详情参考C++类的内存布局
4.参考
C++类的内存布局
C++多态剖析