C++ 面向对象编程:多态相关面试简答题
母文章:✨C++ 多态:化身千万的代码魔法
在母文章的讲解里,我们通过代码和具体的样例,针对面向对象三大特性之一的多态进行了详细的讲解,接下来在这篇子文章中,我们会进行多态相关面试题的简答题的汇总。
-
什么是多态?
多态是指不同类型的对象能够有一个统一的调用接口,或者一个统一的关键字能够构成多种不同的行为,能够通过同名函数调用实现不同效果的行为就叫做多态。
-
什么是隐藏(重定义)、重写(覆盖)和重载?
- 隐藏(重定义)是指基类和派生类之间具有同名的成员函数/成员变量,就会构成隐藏。如果是派生类实例化的对象区调用构成隐藏的成员,则只会调用派生类自己作用域的成员,如果要调用基类对应的成员,则必须声明类域。
- 重写(覆盖)需要在满足成员函数隐藏的条件下,要在基类的函数前加上virtual修饰使其变为虚函数,并且派生类在重写(覆盖)基类的虚函数时还要满足参数的相同,一般返回类型也必须相同(返回类型如果不同,那么则构成协变关系)。
- 重载是指在同一作用域下函数名相同的情况,参数列表必须不同,对返回值类型不作要求。
-
多态的实现原理是什么?
多态的实现原理是借用虚函数表,在运行时根据不同的对象来判断具体调用哪一个虚函数。
在调用虚函数时,主要是经历一下三个步骤:
通过对象的vptr(虚函数表指针)找到其类的vtable(虚函数表)。
在vtable中找到对应函数的地址。
调用该函数,并隐式地传入一个this指针,该指针指向调用它的那个具体对象。
这个过程被称为“动态绑定”或“晚期绑定”。
-
静态成员函数能不能被设计为虚函数?
静态成员函数是不能被设计为虚函数的。
静态成员实际上算是存在于类作用域的普通全局函数,并不是被成员对象指针或者引用进行调用的,并且是在编译时直接就确定好函数地址的。
所以在调用静态成员函数时,函数内部是没有this指针的(违背了多态的构成条件:必须是基类的指针或者引用区调用虚函数),就无法构成虚函数的调用。所以不能被设计为虚函数。
class Device { public:// 虚函数:统一的“开启”接口virtual void turnOn() {std::cout << "设备已开启" << std::endl;}// 虚析构函数(很重要)virtual ~Device() {} //如果static前加了virtual会报错:error C2216: “virtual”不能和“static”一起使用static void func(){cout << "static void func()" << endl;} };
-
inline内联函数能不能被设计为虚函数?
内联函数是可以被设计为虚函数的。
- 在构成多态调用的情况下,编译器会自动忽略内联属性,不会直接在代码段展开函数。
- 但如果是一般对象直接调用内联虚函数,不构成多态,编译器会根据内联属性展开代码段。
-
构造函数可以被设计为虚函数吗?
构造函数不能被设计为虚函数的、,因为对象的虚函数表地址的链接是在构造对象时才被确定的,无法被设计为虚函数。
-
析构函数可以被设计为虚函数吗?
析构函数是可以被并且被建议设计为虚函数的,这样的有助于进行析构对象时的多态行为,因为编译器会把析构函数的名词统一变为destructor构成多态行为,防止在析构派生类对象时出现内存泄漏的问题。
-
对象访问普通函数更快还是访问虚函数更快?
- 如果不构成多态行为,对象访问普通函数和虚函数是一样快的。
- 如果构成多态行为,访问虚函数的效率会比普通函数慢一些,因为调用虚函数,会通过虚函数表地址找到虚函数表,然后再访问对应的虚函数地址。
-
虚函数表是在什么阶段编译生成的?存放在内存的哪片区域?
虚函数表在编译阶段生成,并最终存放在进程内存常量区的代码段中。
-
菱形继承容易造成的问题?虚继承的原理是什么?
菱形继承容易造成数据冗余和二义性问题。
虚继承的原理就是在继承对象时,编译器会把虚继承的基类子对象存放在对象的末尾,并且基类子对象在继承的过程中只保留一份,这样无论派生类继承多少次,都只会存在一份基类子对象。
-
什么是抽象类?抽象类的作用是什么?
含有纯虚函数(只有声明没有实现的虚函数)的类就叫做抽象类。
抽象类的作用是变相强制要求派生类对纯虚函数进行重写,因为抽象类无法实例化出对象。
就像车这一概念和车品牌的关系(is-a继承关系),车这一概念无法单独存在,现实存在的车子必须是有品牌的。