多态满足条件
- 有继承关系
- 子类重写父类中的虚函数
多态分为两类
静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名。
动态多态: 派生类和虚函数实现运行时多态。
静态多态和动态多态区别:
静态多态的函数地址早绑定 - 编译阶段确定函数地址。
动态多态的函数地址晚绑定 - 运行阶段确定函数地址。
创建一个父类:
class Animal
{
public:
//void speak() 不加virtual时
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
当不加virtual时:sizeof(Animal)
可以发现占用一个字节。(这是因为此时Animal类中只有一个成员函数,相当于一个空类)
为什么空类只占用一个字节?
在C++中空类会占一个字节,这是为了让对象的实例能够相互区别。具体来说,空类同样可以被实例化,并且每个实例在内存中都有独一无二的地址,因此,编译器会给空类隐含加上一个字节,这样空类实例化之后就会拥有独一无二的内存地址。如果没有这一个字节的占位,那么空类就无所谓实例化了,因为实例化的过程就是在内存中分配一块地址。
总而言之一个字节是占位用的。
为什么成员函数不占用类或者对象的空间?
成员函数可以被看作是类作用域的全局函数,不在对象分配的空间里,只有虚函数才会在类对象里有一个指针,存放虚函数的地址等相关信息。
成员函数的地址,编译期就已确定并静态绑定或动态的绑定在对应的对象上,对象调用成员函数时,编译器可以确定这些函数的地址,并通过传入this指针和其他参数,完成函数的调用,所以类中就没有必要存储成员函数的信息
当加上virtual时:sizeof(Animal)
可以发现占用四个字节。(此时内部包含一个名为vfptr
(虚函数指针)的指针去指向vftable
(虚函数表),表中记录父类speak函数地址)。
cl /d1 reportSingleClassLayoutAnimal "test.cpp"
子类继承父类:
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
当子类没有内容的时候(也即没有重写父类的虚函数的时候),子类会继承父类所有的内容,包括父类的虚函数表。
当子类重写父类的虚函数表时,子类中的虚函数表内部会替换成重写后的子类的虚函数地址。
cl /d1 reportSingleClassLayoutCat "test.cpp"
当父类的指针或引用指向子类的对象的时候,会查找对象的虚函数表,进入重写后子类虚函数表中的函数地址入口,从而就会调用子类的Cat作用范围下的speak函数地址,从而实现多态。
Cat cat;
Animal &animal = cat;
animal.speak();
此时animal
指针指向的是cat
的子类对象,因此去找cat
的speak地址,即在运行阶段发生动态的多态。
以上整理于b站黑马程序员C++视频的某一讲。https://www.bilibili.com/video/BV1et411b73Z?p=136