多态常见面试问题

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.rhkb.cn/news/448237.html

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

相关文章

Java项目:160 基于springboot物流管理系统(PPT+论文+说明文档)

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 物流管理系统有管理员和用户两个角色。 ​ 管理员功能有个人中心&#xff0c;用户管理&#xff0c;车辆信息管理&#xff0c;公告信息管理&#xff…

windows以zip方式安装mysql8

1.下载安装包 mysql官网&#xff0c;下载后放到D:\Program Files\mysql-8.0.40-winx64 2.安装 cmd进入D:\Program Files\mysql-8.0.40-winx64\bin &#xff08;1&#xff09;在路径下新建my.ini配置文件 [mysql] # 设置mysql客户端默认字符集 default-character-setutf8 …

测网速小程序,纯前端

搜索&#xff1a;证寸照制作 源码介绍: 测网速小程序源码&#xff0c;是一款纯前端无需服务器的测网速小程序&#xff0c;依赖百度开发者中心js接口&#xff0c;真正的永久使用的小工具源码&#xff0c;很实用&#xff0c;可以单独运行&#xff0c;测网速很流畅~ 合法域名: ht…

OPC Router快速打通设备层与influxDB数据通讯

随着时代演化&#xff0c;数据量呈几何倍数增加的情况下出现了时序数据库。时序数据库是基于时间进行存储的数据库&#xff0c;每一条数据中都有一个时间戳&#xff0c;这种数据库特别适合存储那些随着时间变化的数据&#xff0c;通过一些工具处理后&#xff0c;能够分析出数据…

智绘城市地图:使用百度地图 API 实现智能定位

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

SQL优化 TABLE ACCESS BY INDEX ROWID 索引回表

问题&#xff1a; SQL语句运行时间主要消耗在TABLE ACCESS BY INDEX ROWID 索引回表扫描上&#xff0c;怎么优化&#xff1f; 模拟测试&#xff1a; 1.创建测试表 SYSdb11g> create table t1015 as select * from dba_objects;Table created.SYSdb11g> create index …

WPF -- LiveCharts的使用和源码

LiveCharts 是一个开源的 .NET 图表库&#xff0c;特别适用于 WPF、WinForms 和其他 .NET 平台。它提供了丰富的图表类型和功能&#xff0c;使开发者能够轻松地在应用程序中创建动态和交互式图表。下面我将使用WPF平台创建一个测试实例。 一、LiveCharts的安装和使用 1.安装N…

ESP-01S WIFI模块指南

ESP-01S模块&#xff0c;接5V才能正常工作&#xff0c;接3.3V很有可能不会正常工作 ESP-01S模块&#xff0c;TXD和RXD和CH340交叉接入 ESP-01s出厂波特率正常是115200, 注意&#xff1a;AT指令&#xff0c;控制类都要加回车&#xff0c;数据传输时不加回车 上电后&#xff0…

IDEA如何查看所有的断点(Breakpoints)并关闭

前言 我们在使用IDEA开发Java应用时&#xff0c;基本上都需要进行打断点的操作&#xff0c;这方便我们排查BUG&#xff0c;也方便我们查看设计的是否正确。 不过有时候&#xff0c;我们不希望进入断点&#xff0c;这时候除了点击断点关闭外&#xff0c;有没有更快速的方便关闭…

仓储管理系统原型图移动端(WMS),出入库管理、库存盘点、库存调拨等(Axure原型、Axure实战项目)

仓储管理系统原型图移动端 Warehouse Management System Prototype 仓储管理系统原型图移动端是一个以图形化方式展示系统移动端界面和功能的原型设计图。原型图展示和说明系统移动端的功能和界面布局&#xff0c;为相关利益方提供一个直观的视觉化展示&#xff0c;帮助他们更…

修改Linux的IP地址

方法一&#xff08;特点&#xff1a;命令执行后&#xff0c;IP立即修改&#xff0c;但重启后会恢复原来的IP地址&#xff09; 1.含义&#xff1a; inet ip地址 netmask 子网掩码 broadcast 广播地址 inet 192.168.44.129 netmask 255.255.255.0 broadcast 192.168.1.255 …

VScode写Java项目的教程

VScode写Java项目的教程 1.首先必选先安装Java解释器2.安装插件Java Extension Pack3.创建项目创建项目结构选择项目类型 4.测试结果源码内容 今天用一台老式笔记本写代码&#xff0c;IDEA跑不动就准备用VScode突然间就蒙了&#xff0c;怎么创建项目啊&#xff1f;于是就有了这…

基于微信小程序的社区二手交易系统的详细设计和实现(源码+lw+部署文档+讲解等)

项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…

Elasticsearch文本分析器

1. 前言 Elasticsearch数据搜索和关系型数据库的SQL查询最显著的区别就是&#xff1a;除了精准匹配和模糊查询&#xff0c;Elasticsearch还具备全文检索的能力&#xff0c;而全文检索的核心是文本分析。 文本分析会将长文本内容进行字符过滤和细粒度的分词&#xff0c;先将长…

IP地址查询在线小工具

IP属地查询工具 https://www.ipshudi.com/

嵌入式开发学习日记——认识指针及和数组函数的联系(c语言)

一、指针的定义 一般格式&#xff1a; 数据类型 * 指针变量名 [初始地址值]; 数据类型是指针所指向的地址处的数据类型&#xff0c;如 int、char、float 等。 符号 * 用于通知系统&#xff0c;这里定义的是一个指针变量&#xff0c;通常跟在类型关键字的后面&#xff0c;表示…

【C++笔记】string类深度解剖及其模拟实现

【C笔记】string类深度解剖及其模拟实现 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】string类深度解剖及其模拟实现前言一.string类说明1.1为什么实现string类1.2string类了解 二.string类的常用接口说明2.1str…

Nature Communications 英国伦敦大学等提出仿生自适应多平面触觉系统,实现机械与振动双重感知结合

触觉&#xff0c;作为人类感知外界并与之互动的重要方式&#xff0c;赋予了人类以辨识物体多重特性&#xff08;诸如纹理、硬度、可塑性及重量&#xff09;与捕捉微妙线索&#xff08;例如感知心跳或精准定位动脉振动&#xff09;的能力。这一感官机制不仅深化了人类对周围世界…

爬虫post请求

爬虫post请求 这一篇文章, 我们开始学习爬虫当中的post请求。 在讲post请求之前, 我们先来回顾一下get请求。 get请求: get请求的参数有三个, get(url, headers, params), 其中, params是可写可不写的。headers是请求头, 这个请求头在开发者工具里面的网络, 找到相应的请求, …

merlion的dashboard打开方法

安装好merlion包后&#xff0c;在anaconda prompt中进行如下图操作&#xff1a; 先进入创建好的虚拟环境&#xff1a;conda activate merlion再执行命令&#xff1a;python -m merlion.dashboard在浏览器中手动打开图中的地址&#xff1a; http://127.0.0.1:8050 打开后的界面…