多态常见面试问题

1、什么是多态?

多态(Polymorphism)是面向对象编程中的一个重要概念,它允许同一个接口表现出不同的行为。在C++中,多态性主要通过虚函数来实现,分为编译时多态(静态多态)和运行时多态(动态多态)。
多态的类型:
编译时多态(静态多态):在编译阶段就能确定调用哪个函数,通常通过函数重载、运算符重载和模板来实现。特点:在编译期决定函数调用,效率高,但灵活性相对较弱。

//示例函数重载
class Printer {
public:void print(int i) { cout << "Printing int: " << i << endl; }void print(double d) { cout << "Printing double: " << d << endl; }
};int main() {Printer p;p.print(10);     // 调用 print(int i)p.print(3.14);   // 调用 print(double d)
}

运行时多态(动态多态):在运行时根据对象的类型决定调用哪个函数。通过虚函数和继承实现。
特点:在运行时通过基类指针或引用指向派生类对象,从而动态地决定函数调用,灵活性高,但效率比静态多态低一些。

/*虚函数实现动态多态 在这个例子中,基类 Animal 的指针根据对象的具体类型,动态决定调用 Dog 的 makeSound 或 Cat 的 makeSound,这就是运行时多态。*/
class Animal {
public:virtual void makeSound() { cout << "Animal sound" << endl; }
};class Dog : public Animal {
public:void makeSound() override { cout << "Woof!" << endl; }
};class Cat : public Animal {
public:void makeSound() override { cout << "Meow!" << endl; }
};int main() {Animal* animal1 = new Dog();Animal* animal2 = new Cat();animal1->makeSound();  // 输出 "Woof!",调用 Dog 的 makeSound()animal2->makeSound();  // 输出 "Meow!",调用 Cat 的 makeSound()delete animal1;delete animal2;
}

多态的实现原理:
运行时多态依赖于虚函数表(vtable)。当类中定义虚函数时,编译器会为该类创建一个虚函数表,表中存储指向类中虚函数的地址。每个对象包含一个指向该虚表的指针(vptr)。在运行时,通过vptr查找实际调用的函数地址,从而实现动态多态。
多态的作用:
代码灵活:多态允许你通过基类指针或引用操作派生类对象,而不必关心派生类的具体类型,提供了更高的灵活性。
可扩展性:新功能可以通过继承并重写虚函数来实现,而无需修改现有代码,方便系统的扩展。
代码复用:基类可以提供通用的接口和功能,而具体的实现则由派生类完成,减少代码重复。
总结:
多态是对象在不同上下文中表现出不同行为的能力。
静态多态是在编译时决定的函数调用,而动态多态是在运行时根据对象类型动态决定的函数调用。
动态多态通过虚函数、继承和虚函数表实现,极大地提高了代码的灵活性与可扩展性。

2、什么是重载、重写(覆盖)、重定义(隐藏)?

总结:
重载(Overloading):同一作用域中,函数名相同,但参数不同。
重写(Overriding):派生类中重新实现与基类虚函数相同的函数,用于多态。
重定义(Hiding):派生类中定义了与基类同名但参数不同的函数,隐藏基类的同名函数。

  1. 重载(Overloading)
    重载是指在同一个类中,多个函数名称相同,但它们的参数列表不同(参数类型、数量或顺序不同),编译器根据调用时传递的参数类型和数量来决定调用哪个函数。
    特点:
    发生在同一个作用域。
    参数列表必须不同,返回类型可以相同也可以不同。
    可以重载普通函数、构造函数和运算符。
class Math {
public:int add(int a, int b) { return a + b; }double add(double a, double b) { return a + b; }int add(int a, int b, int c) { return a + b + c; }
};int main() {Math math;cout << math.add(2, 3) << endl;        // 调用 add(int, int)cout << math.add(2.5, 3.5) << endl;    // 调用 add(double, double)cout << math.add(1, 2, 3) << endl;     // 调用 add(int, int, int)
}
  1. 重写(Overriding,覆盖)
    重写是指在派生类中,重新定义与基类中的虚函数相同的函数,即函数名、参数列表、返回类型必须完全相同。重写主要用于实现运行时多态。重写的函数必须是虚函数,通过基类指针或引用调用时,动态决定调用派生类的实现。
    特点:
    发生在继承关系中。
    函数签名(函数名、参数列表和返回类型)必须与基类中的虚函数完全相同。
    重写的函数必须是虚函数,且派生类中的函数也默认是虚函数(使用override关键字可以明确表明函数是重写的)。
class Animal {
public:virtual void sound() { cout << "Animal sound" << endl; }
};class Dog : public Animal {
public:void sound() override { cout << "Woof!" << endl; }  // 重写基类的虚函数
};int main() {Animal* animal = new Dog();animal->sound();  // 输出 "Woof!",调用派生类 Dog 的 sound 函数delete animal;
}
  1. 重定义(Hiding,隐藏)
    重定义是指在派生类中,定义了与基类同名但参数列表不同的函数。由于在派生类中定义了同名函数,基类中的同名函数会被隐藏,调用时只能使用派生类的函数,而基类的函数即便参数列表不同也无法通过派生类对象访问。
    特点:
    发生在继承关系中。
    派生类的函数参数列表可以与基类不同,基类中的同名函数被隐藏。
    如果基类的函数想继续保留,可以通过using声明重新引入。
class Base {
public:void display(int a) { cout << "Base class display: " << a << endl; }
};class Derived : public Base {
public:void display(double a) { cout << "Derived class display: " << a << endl; }
};int main() {Derived obj;obj.display(5.5);  // 调用 Derived::display(double)// obj.display(5); // 编译错误,Base::display(int) 被隐藏
}

3、多态的实现原理?

见1题。

4、inline函数可以是虚函数吗?

可以、但是inline只是一个建议。当一个函数是虚函数以后,多态调用中,inline失效了。
**可以,inline只是一个建议。**当一个函数是虚函数时,编译器可能依然会将它内联,但前提是编译器可以确定具体的函数调用对象。通常情况下,如果通过具体对象调用虚函数(即编译器能够知道对象的静态类型),虚函数仍可能被内联。
然而,在多态调用(即通过基类指针或引用调用虚函数)中,由于编译器需要在运行时通过虚表动态决定调用哪个版本的函数,inline优化就无法生效。因为内联要求编译器在编译时知道要调用的具体函数,而多态性导致这一点无法确定,因此在这种情况下内联失效了。

5、static函数(静态成员)可以是虚函数吗?

不能,因为静态成员函数没有this指针,使用 类型::成员 函数的调用方式(类域指定的方式,如 Person::Func2())无法访问虚函数表,所以静态成员函数无法放进虚函数表。虚函数是为了实现多态,多态都是运行时去虚表找决议。static成员函数都是在编译时决议,他是virtual没有价值。
静态成员函数不能是虚函数的原因:
虚函数依赖对象实现:虚函数的多态性需要在运行时根据对象的实际类型来决定调用哪个函数,而静态成员函数不属于任何具体对象,因此无法通过虚表进行动态绑定。
没有 this 指针:虚函数通常需要 this 指针来访问对象的成员,但静态成员函数没有 this 指针,所以无法实现虚函数的特性。
结论:
静态成员函数不能是虚函数,因为虚函数依赖对象实现多态性,而静态成员函数与对象无关,不支持动态绑定。因此,static 和 virtual 是相互冲突的,无法在同一个函数上同时使用。

6、构造函数可以是多态吗?

不可以,virtual函数是为了实现多态,运行时去虚表找对应虚函数进行调用,对象中虚表指针都是构造函数初始化列表阶段才初始化的。
构造函数虚函数是没有意义的。
构造函数不能是多态的。构造函数在 C++ 中无法实现多态,主要原因如下:
构造函数不参与虚函数机制:
虚函数的多态性依赖于对象的类型在运行时动态绑定,而构造函数是在对象创建时调用的。在构造对象的过程中,虚表还没有被初始化或设置,因此无法实现多态行为。
构造函数的目的是初始化对象:构造函数的主要任务是初始化对象的成员变量和资源,它负责生成对象本身。多态依赖于已有的对象实例,但构造函数在创建对象的过程中,无法确定派生类的行为。
虚表初始化顺序:虚表(VTable)是在构造函数执行完毕后,派生类的构造函数才能设置。因此,在调用基类构造函数时,多态机制尚未建立,无法进行动态绑定。
总结:构造函数不能是多态的,因为多态依赖虚表和动态绑定,而虚表在构造函数调用期间尚未建立或完成初始化。

7、析构函数可以是虚函数吗?

可以。而且析构函数建议虚函数

8、拷贝构造和operator= 可以是虚函数吗?

拷贝构造不可以。拷贝构造也是构造函数,和构造函数一样,没有this指针,无法设置虚函数。
operator赋值 语法上可以(质疑),但是没有实际价值 。

8、对象访问普通函数快还是虚函数更快?

如果虚函数不构成多态(通过具体对象调用),编译器可以进行静态绑定或内联优化,调用开销与普通函数几乎一致。如果构成多态,普通函数更快。
如果虚函数构成多态(通过基类指针或引用调用),则需要进行虚表查找,存在动态绑定的开销。
普通函数调用:普通函数是静态绑定的,在编译时就已经确定调用哪个函数。调用过程是直接的,编译器会在生成代码时直接插入该函数的地址。调用速度更快,因为不需要额外的查找过程。
虚函数调用:虚函数是动态绑定的,依赖于对象的动态类型。在运行时通过虚表(VTable)来查找并调用正确的函数实现。虚函数调用涉及额外的步骤:首先,通过对象的虚表指针找到虚表,然后根据虚表中的函数指针找到具体的函数实现。这一过程增加了运行时开销。调用速度相对较慢,因为多了虚表查找的步骤。
总结:普通函数调用更快,因为它是静态绑定,编译时直接确定,不涉及任何额外查找。虚函数调用稍慢,因为需要通过虚表查找函数指针,存在运行时开销。

9、虚函数表是在什么阶段生成的,存在那的?

虚表在编译阶段生成,用于存储虚函数的指针。
虚表指针(vptr)在运行时存储于每个对象的内存中,用于动态绑定。
虚表本身是存储在全局内存区域,且每个类只有一个虚表。虚表(VTable)本身是存储在静态区域,通常是全局内存区域。虚函数表是编译器生成的全局结构,每个类有一个虚表,与对象无关,因此它不会随每个对象重复存储。
区域划分说明:
常量区:程序中不可修改的常量数据,如字符串字面量和 const 常量。
全局数据区(静态区):存储全局变量、静态变量和类的虚表。虚表属于这个区域。
栈区:用于存储局部变量、函数参数等。
堆区:用于动态分配的内存,如使用 new 分配的对象。
总结:虚表存储在静态区域(全局数据区),它在程序生命周期内存在。
虚表的内容(函数指针)会动态指向不同的函数实现,因此虚表不是存储在常量区,而是在静态内存区域的部分。

10、C++的菱形继承问题是什么?虚继承的原理是什么?

总结:
菱形继承问题:当一个类通过多个路径继承同一个基类时,会导致基类的多次拷贝,产生访问歧义,使用虚继承可以解决这个问题。
虚函数的原理:通过虚表和虚表指针实现动态多态,允许程序在运行时根据实际对象类型调用合适的函数版本。
菱形继承是指一个类通过多个继承路径从同一个基类继承,这种结构会导致一些问题,特别是关于基类成员的多次拷贝和访问歧义。
问题1:基类成员的多次拷贝
由于 B 和 C 都继承了 A,而 D 又从 B 和 C 继承,因此 D 类中会有两份 A 类的副本。这导致基类的成员函数和成员变量在 D 中存在两份。如果调用 D 对象的 func() 函数,编译器不知道该调用 B 中的 A::func() 还是 C 中的 A::func(),这会导致访问歧义。
解决方案:虚继承
为了解决这个问题,可以使用虚继承,即通过 virtual 关键字来指定基类 A 只保留一份副本。虚继承确保无论 A 被继承多少次,最终在派生类 D 中只会存在 一个 A 类的实例。使用虚继承后,D 类中只有一个 A 的实例,B 和 C 共享这个实例,从而避免了多次拷贝和访问歧义。
虚继承的原理:
虚继承的原理是为了避免在多重继承(尤其是菱形继承)时,基类的多次拷贝问题。它通过引入虚基表(VBTable)和虚基表指针(vbptr)来确保派生类中只有一个共享的基类实例。这种机制可以解决多路径继承时的重复继承问题,保证数据一致性。
虚继承通过让派生类共享一个共同的基类实例来解决这一问题。实现虚继承时,编译器会生成额外的数据结构和指针来确保基类在派生类中只有一个实例。
虚继承的实现步骤:
虚基表(VBTable):当一个类使用虚继承时,编译器会为其生成一个虚基表(VBTable)。这个表中存储了指向虚基类的地址偏移量,确保派生类在访问虚基类时能够找到唯一的基类实例。
虚基表指针(vbptr):每个虚继承的类中,都会有一个隐藏的虚基表指针(vbptr),指向虚基表。这相当于一个中间指针,用于帮助派生类找到基类的唯一实例。
偏移量访问虚基类:当派生类访问虚基类的成员时,编译器通过 vbptr 和 VBTable 确定虚基类在内存中的位置,确保派生类总是引用同一个基类实例。

虚函数的原理:
虚函数(virtual function)是用于实现动态多态的机制,允许程序根据运行时的对象类型(而非编译时的类型)调用适当的函数版本。虚函数的核心机制依赖于虚函数表(VTable)和虚表指针(vptr)。
虚函数的原理:
虚表(VTable):每个定义了虚函数的类都有一个虚表(VTable),虚表中存储该类的所有虚函数的函数指针。如果子类重写了基类的虚函数,子类的虚表中会包含指向子类实现的函数指针,而不是基类的版本。
虚表指针(vptr):每个对象都包含一个隐藏的指针,称为虚表指针(vptr),它指向该对象所属类的虚表。当对象创建时,构造函数负责初始化 vptr,指向对应类的虚表。
运行时多态:当通过基类指针或引用调用虚函数时,程序会通过对象的 vptr 查找虚表,然后从虚表中获取对应的函数指针并调用。这种机制允许在运行时根据实际的对象类型调用正确的函数版本,即实现动态多态。
虚函数的调用过程:
编译时:编译器无法确定虚函数的具体调用对象。
运行时:程序通过对象的 vptr 找到虚表,根据虚表中的函数指针调用具体的函数。

虚函数表与虚基表的区别

在这里插入图片描述
总结:
虚函数表用于动态多态的虚函数调用,通过基类指针调用派生类的函数。
虚基表用于虚继承中处理菱形继承问题,确保基类不会被多次拷贝,派生类只会继承一个基类实例。

11、什么是抽象类?抽象类的作用?

抽象类是包含至少一个纯虚函数的类,不能直接实例化对象,必须通过派生类重写其纯虚函数后才能实例化。抽象类通常作为接口使用,规定派生类必须实现某些功能。
抽象类的特点:
纯虚函数:抽象类中至少有一个纯虚函数,形式为 virtual 函数名() = 0;。
不能实例化:抽象类不能创建对象,必须通过派生类实现纯虚函数后才能实例化派生类对象。
派生类的实现:派生类继承抽象类时,必须实现所有的纯虚函数,否则该派生类也将成为抽象类。
抽象类的作用:
接口设计:抽象类可以定义一组规范,派生类必须按照这些规范去实现。这种方式提供了一种设计接口的机制。
多态性:抽象类常用于多态的实现,通过基类指针或引用调用派生类的具体实现,而不关心派生类的具体类型。
代码复用:抽象类可以为派生类提供公共接口,减少代码重复,让多个派生类共享基础的功能。
抽象类的作用总结:
通过定义接口,规范派生类的行为。
提供多态机制,使代码更加灵活和可扩展。
提供公共的功能和接口,减少代码冗余。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/54937.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Spring AI】Java实现类似langchain的第三方函数调用_原理与详细示例

Spring AI 介绍 &#xff1a;简化Java AI开发的统一接口解决方案 在过去&#xff0c;使用Java开发AI应用时面临的主要困境是没有统一且标准的封装库&#xff0c;导致开发者需要针对不同的AI服务提供商分别学习和对接各自的API&#xff0c;这增加了开发难度与迁移成本。而Sprin…

【数据结构】邻接表

一、概念 邻接表是一个顺序存储与链式存储相结合的数据结构&#xff0c;用于描述一个图中所有节点之间的关系。 若是一个稠密图&#xff0c;我们可以选择使用邻接矩阵&#xff1b;但当图较稀疏时&#xff0c;邻接矩阵就显得比较浪费空间了&#xff0c;此时我们就可以换成邻接…

机器人的应用 基于5G的变电站智慧管控系统

背景概述 一、电力行业面临的挑战与变革 随着全球工业化和信息化的快速发展&#xff0c;电力行业作为国民经济的基础性行业&#xff0c;其重要性日益凸显。然而&#xff0c;随着电力网络的不断扩展和复杂化&#xff0c;变电站和开关站作为电力传输与分配的关键节点&#xff0…

Excel中Ctrl+e的用法

重点&#xff1a;想要使用ctrle&#xff0c;前提是整合或拆分后的结果放置的单元格必须和被提取信息的单元格相邻&#xff0c;且被提取信息的单元格也必须相连。 下图为错误示例 这样则可以使用ctrle 1、信息整合 2、提取信息 3、添加符号 4、信息顺序调换 5、数字提取 crtle还…

HarmonyOS NEXT 应用开发实战(三、ArkUI页面底部导航TabBar的实现)

在开发HarmonyOS NEXT应用时&#xff0c;TabBar是用户界面设计中不可或缺的一部分。本文将通过代码示例&#xff0c;带领大家一同实现一个常用的TabBar&#xff0c;涵盖三个主要的内容页&#xff1a;首页、知乎日报和我的页面。以模仿知乎日报的项目为背景驱动&#xff0c;设定…

解决ubuntu 下 VS code 无法打开点击没反应问题

从Ubuntu 22.04 升级到ubuntu 24.04 后&#xff0c;发现Vsode无法打开&#xff0c;不论是点击图标&#xff0c;还是terminator里面运行code 可执行程序&#xff0c;均没有反应。debug如下: 提示权限不够。 解决方案&#xff1a; sudo sysctl -w kernel.apparmor_restrict_unp…

C语言题目练习2

前面我们知道了单链表的结构及其一些数据操作&#xff0c;今天我们来看看有关于单链表的题目~ 移除链表元素 移除链表元素&#xff1a; https://leetcode.cn/problems/remove-linked-list-elements/description/ 这个题目要求我们删除链表中是指定数据的结点&#xff0c;最终返…

C语言 | Leetcode C语言题解之第460题LFU缓存

题目&#xff1a; 题解&#xff1a; /* 数值链表的节点定义。 */ typedef struct ValueListNode_s {int key;int value;int counter;struct ValueListNode_s *prev;struct ValueListNode_s *next; } ValueListNode;/* 计数链表的节点定义。 其中&#xff0c;head是数值链表的头…

腾讯云Android 与 iOS 相关

移动端&#xff08;Android/iOS&#xff09;支持哪几种系统音量模式&#xff1f; 支持2种系统音量类型&#xff0c;即通话音量类型和媒体音量类型&#xff1a; 通话音量&#xff1a;手机专门为通话场景设计的音量类型&#xff0c;使用手机自带的回声抵消功能&#xff0c;音质…

谷歌浏览器 文件下载提示网络错误

情况描述&#xff1a; 谷歌版本&#xff1a;129.0.6668.90 (正式版本) &#xff08;64 位&#xff09; (cohort: Control)其他浏览器&#xff0c;比如火狐没有问题&#xff0c;但是谷歌会下载失败&#xff0c;故推断为谷歌浏览器导致的问题小文件比如1、2M会成功&#xff0c;大…

【LeetCode】动态规划—95. 不同的二叉搜索树 II(附完整Python/C++代码)

动态规划—95. 不同的二叉搜索树 II 题目描述前言基本思路1. 问题定义二叉搜索树的性质&#xff1a; 2. 理解问题和递推关系递归构造思想&#xff1a;状态定义&#xff1a;递推公式&#xff1a;终止条件&#xff1a; 3. 解决方法递归 动态规划方法&#xff1a;伪代码&#xff…

如何使用vscode的launch.json来debug调试

1、创建一个launch.json文件 选择Python Debugger&#xff0c;再选择Python文件&#xff0c;创建处理如下 默认有下面五个参数 "name": "Python Debugger: Current File","type": "debugpy","request": "launch"…

金九银十软件测试面试题(800道)

今年你的目标是拿下大厂offer&#xff1f;还是多少万年薪&#xff1f;其实这些都离不开日积月累的过程。 为此我特意整理出一份&#xff08;超详细笔记/面试题&#xff09;它几乎涵盖了所有的测试开发技术栈&#xff0c;非常珍贵&#xff0c;人手一份 肝完进大厂 妥妥的&#…

【LeetCode】动态规划—123. 买卖股票的最佳时机 III(附完整Python/C++代码)

动态规划—123. 买卖股票的最佳时机 III 题目描述前言基本思路1. 问题定义2. 理解问题和递推关系状态定义&#xff1a;状态转移公式&#xff1a;初始条件&#xff1a; 3. 解决方法动态规划方法伪代码&#xff1a; 4. 进一步优化5. 小总结 Python代码Python代码解释 C代码C代码解…

Python基础之List列表用法

1、创建列表 names ["张三","李四","王五","Mary"] 2、列表分片 names[1]&#xff1a;获取数组的第2个元素。 names[1:3]&#xff1a;获取数组的第2、第3个元素。包含左侧&#xff0c;不包含右侧。 names[:3]等同于names[0:3]&…

List子接口

1.特点&#xff1a;有序&#xff0c;有下标&#xff0c;元素可以重复 2.方法&#xff1a;包含Collection中的所有方法&#xff0c;还包括自己的独有的方法&#xff08;API中查找&#xff09; 还有ListIterator&#xff08;迭代器&#xff09;&#xff0c;功能更强大。 包含更多…

机器学习/数据分析--用通俗语言讲解时间序列自回归(AR)模型,并用其预测天气,拟合度98%+

时间序列在回归预测的领域的重要性&#xff0c;不言而喻&#xff0c;在数学建模中使用及其频繁&#xff0c;但是你真的了解ARIMA、AR、MA么&#xff1f;ACF图你会看么&#xff1f;&#xff1f; 时间序列数据如何构造&#xff1f;&#xff1f;&#xff1f;&#xff0c;我打过不少…

读书笔记 - 虚拟化技术 - 0 QEMU/KVM概述与历史

《QEMU/KVM源码解析与应用》 - 王强 概述 虚拟化简介 虚拟化思想 David Wheeler&#xff1a;计算机科学中任何问题都可以通过增加一个中间层来解决。 虚拟化思想存在与计算机科学的各个领域。 主要思想&#xff1a;通过分层将底层的复杂&#xff0c;难用的资源虚拟抽象为简…

Spring Cloud 3.x 集成eureka快速入门Demo

1.什么是eureka&#xff1f; Eureka 由 Netflix 开发&#xff0c;是一种基于REST&#xff08;Representational State Transfer&#xff09;的服务&#xff0c;用于定位服务&#xff08;服务注册与发现&#xff09;&#xff0c;以实现中间层服务的负载均衡和故障转移&#xff…

day01-Qt5入门

day01-Qt5入门 窗体应用 1.1 窗体基类说明 创建项目在details中编辑器提供了三个基类&#xff0c;分别是 QMainWindows、Qwidget、QDialog 1、 QMainWindow QMainWindow 类提供一个有菜单条、锚接窗口&#xff08;例如工具条&#xff09;和一个状态条的主应用 程序窗口。…