多态常见面试问题

news2024/11/25 14:34:14

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.coloradmin.cn/o/2208708.html

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

相关文章

DDoS攻击快速增长,如何在抗ddos防护中获得主动?

当下DDoS攻击规模不断突破上限。前段时间&#xff0c;中国首款3A《黑神话&#xff1a;悟空》也在一夜之内遭受到28万次攻击DDoS攻击&#xff0c;严重影响到全球玩家的游戏体验。Gcore发布的数据也显示了 DDoS攻击令人担忧的趋势&#xff0c;尤其是峰值攻击已增加到了令人震惊的…

腾讯IM SDK:TUIKit发送多张图片

一、问题描述 在使用腾讯IM DEMO&#xff08;https://github.com/TencentCloud/chat-uikit-vue.git&#xff09;时发现其只支持发送一张图片&#xff1a; 二、解决方案 // src\TUIKit\components\TUIChat\message-input-toolbar\image-upload\index.vue<inputref"inp…

【GaussDB】产品简介

产品定位 GaussDB 200是一款具备分析及混合负载能力的分布式数据库&#xff0c;支持x86和Kunpeng硬件架构&#xff0c;支持行存储与列存储&#xff0c;提供PB(Petabyte)级数据分析能力、多模分析能力和实时处理能力&#xff0c;用于数据仓库、数据集市、实时分析、实时决策和混…

transformers和bert实现微博情感分类模型提升

关于深度实战社区 我们是一个深度学习领域的独立工作室。团队成员有&#xff1a;中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等&#xff0c;曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝&#xff0c;拥有2篇国家级人工智能发明专利。 社区特色…

关于Linux下C++程序内存dump的分析和工具

前言 程序崩溃令人很崩溃&#xff0c;特别是让人找不到原因的崩溃&#xff0c;但是合适的工具可以帮助人很快的定位到问题&#xff0c;在AI基础能力ASR服务开发时&#xff0c;找到了一种比较实用和简单的内存崩溃的dump分析工具breakpad&#xff0c; 可以帮助在Linux下C开发程…

Skyeye 云智能制造 v3.14.8 发布,ERP 商城 + AI

Skyeye 云智能制造&#xff0c;采用 Springboot winUI 的低代码平台、移动端采用 UNI-APP。包含 30 多个应用模块、50 多种电子流程&#xff0c;CRM、PM、ERP、MES、ADM、EHR、笔记、知识库、项目、门店、商城、财务、多班次考勤、薪资、招聘、云售后、论坛、公告、问卷、报表…

(JAVA)2-3树思想与红黑树的实现与基本原理

1. 平衡树 ​ 学习过二叉查找树&#xff0c;发现它的查询效率比单纯的链表和数组的查询效率要高很多。 ​ 大部分情况下确实是这样的&#xff0c;但不幸的是&#xff0c;在最坏情况下&#xff0c;二叉查找树的性能还是很糟糕。 ​ 例如我们一次往二叉树中插入9,8,7,6,5,4,3,…

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

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

出海电商新怎样用海外云手机引流?

随着互联网行业的迅猛发展&#xff0c;出海电商、海外社交媒体营销以及游戏产业等领域对技术工具的需求不断增加。在这种趋势下&#xff0c;海外云手机作为一种新型解决方案&#xff0c;正在受到广泛关注。 特别是在出海电商中&#xff0c;平台如亚马逊、速卖通、eBay等通过结合…

Mysql(八) --- 视图

文章目录 前言1.什么是视图&#xff1f;2.创建视图3. 使用视图4. 修改数据4.1.注意事项 5. 删除视图6.视图的优点 前言 前面我们学习了索引&#xff0c;这次我们来学习视图 1.什么是视图&#xff1f; 视图是一个虚拟的表&#xff0c;它是基于一个或多个基本表或其他视图的查询…

8款宝藏手机app,适配安卓和苹果手机

好用的手机APP太多&#xff0c;差点挑花了眼&#xff01;今天来分享4款苹果手机和4款安卓手机上的宝藏软件&#xff0c;看看你喜欢哪一款~ IOS系统APP 1.搜图神器 一款拥有海量图片资源的图片搜索神器&#xff0c;它聚合海内外知名搜索引擎&#xff0c;想要图片直接搜索就行…

用java来编写web界面

一、ssm框架整体目录架构 二、编写后端代码 1、编写实体层代码 实体层代码就是你的对象 entity package com.cv.entity;public class Apple {private Integer id;private String name;private Integer quantity;private Integer price;private Integer categoryId;public…

【JavaScript】LeetCode:61-65

文章目录 61 课程表62 实现Trie&#xff08;前缀树&#xff09;63 全排列64 子集65 电话号码的字母组合 61 课程表 Map BFS拓扑排序&#xff1a;将有向无环图转为线性顺序。遍历prerequisites&#xff1a;1. 数组记录每个节点的入度&#xff0c;2. 哈希表记录依赖关系。n 6&a…

Vulnhub靶场案例渗透[7]- DC7

文章目录 1. 靶场搭建2. 信息收集2.1 确定靶机ip2.2 服务信息收集2.3 社工信息收集 3. 提权 1. 靶场搭建 靶场源地址 检验下载文件的检验码&#xff0c;对比没问题使用vmware打开 # windwos 命令 Get-FileHash <filePath> -Algorithm MD5 # linux md5sum filepath2. 信…

视频汇聚平台EasyCVR支持云端录像丨监控存储丨录像回看丨录像计划丨录像配置

EasyCVR视频汇聚融合平台&#xff0c;是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。平台以其强大的视频处理、汇聚与融合能力&#xff0c;在构建视频监控系统中展现出了独特的优势。 EasyCVR视频汇聚平台可接入传统监控行业中高清网络摄像机的RTSP…

提升实验室效率的秘籍

有组织、高效的实验室而言&#xff0c;业务“人、机、料、法、环、测”的多维度发展至关重要&#xff0c;为了提高实验室管理效率和质量&#xff0c;许多实验室开始采用LIMS&#xff08;实验室信息管理系统&#xff09;软件来辅助管理。LIMS软件能够帮助实验室实现信息化、自动…

leetcode 3217 从链表中移除在数组中的结点

1.题目要求: 给你一个整数数组 nums 和一个链表的头节点 head。从链表中移除所有存在于 nums 中的节点后&#xff0c;返回修改后的链表的头节点。 示例 1&#xff1a; 输入&#xff1a; nums [1,2,3], head [1,2,3,4,5] 输出&#xff1a; [4,5] 解释&#xff1a; 移除数值…

Java中的枚举

1.1 认识枚举 枚举是一种特殊的类&#xff0c;它的格式是&#xff1a; public enum 枚举类名{枚举项1,枚举项2,枚举项3; } 其实枚举项就表示枚举类的对象&#xff0c;只是这些对象在定义枚举类时就预先写好了&#xff0c;以后就只能用这几个固定的对象。 定义一个枚举类&am…

使用VS2015编写C语言程序

前面我们给出了一段完整的C语言代码&#xff0c;就是在显示器上输出“C语言中文网”&#xff0c;如下所示&#xff1a; #include <stdio.h>int main(){puts("C语言中文网");return 0;}本节我们就来看看如何通过 VS2015 来运行这段代码。 1) 创建项目&#xf…

QD1-P8 HTML 格式化标签(font、pre、b、strong、i、u、del、s、sub、sup)

本节学习&#xff1a;HTML 格式化标签。 本节视频 www.bilibili.com/video/BV1n64y1U7oj?p8 ‍ 一、font 标签 用途&#xff1a;定义文本的字体大小、颜色和 face&#xff08;字体类型&#xff09;。 示例 <!DOCTYPE html> <html><head><meta cha…