C++面试高频(三)
1.静态绑定和动态绑定的介绍⭐
静态类型:对象在声明时使用的类型,在编译期就已经确定
动态类型:指针变量或引用变量所指向对象的类型,在运行期才能确定
静态绑定:绑定的是静态类型,对象的函数和属性依赖于绑定的静态类型,发生在编译期
动态绑定:绑定的是动态类型,对象的函数和属性依赖于绑定的动态类型,发生在运行期
而非虚函数一般都是静态绑定,虚函数则是动态绑定。
以下是一个简单的示例代码:
#include<iostream> class Base { public: virtual void display() { std::cout << "Base class display function" << std::endl; } }; class Derived : public Base { public: void display() override { std::cout << "Derived class display function" << std::endl; } }; int main() { Base baseObj; Derived derivedObj; Base* ptr = nullptr; ptr = &baseObj; ptr->display(); // 静态绑定,输出 "Base class display function" ptr = &derivedObj; ptr->display(); // 动态绑定,输出 "Derived class display function" return 0; }
代码附录解释:
定义了一个基类Base
和派生类Derived
。这两个类都有一个名为display
的函数,其中派生类重写了基类的display
函数。在main
函数中,我们创建了一个基类指针ptr
,并分别将其指向基类对象和派生类对象。
在静态绑定的情况下,当我们通过指针ptr
调用display
函数时,由于ptr
的静态类型是基类指针,所以编译器会根据基类的函数定义来解析它,并调用基类的display
函数。因此,输出结果为"Base class display function"。
然而,在动态绑定的情况下,当我们通过指针ptr
调用display
函数时,由于ptr
的动态类型是派生类对象,所以在运行时会根据对象的实际类型来解析函数调用,并调用派生类的display
函数。因此,输出结果为"Derived class display function"。
这个示例展示了动态绑定对于虚函数的重要性。通过使用虚函数,我们可以实现在运行时根据对象的动态类型来调用适当的函数,而不仅仅局限于对象的静态类型。这种动态绑定的特性可以增加程序的灵活性和可扩展性。
总结一下静态绑定和动态绑定的区别:
静态绑定发生在编译期,动态绑定发生在运行期
对象的动态类型可以更改,但是静态类型无法更改
要想实现动态,必须使用动态绑定
在继承体系中只有虚函数使用的是动态绑定,其他的全部是静态绑定;
2.析构函数可以抛出异常吗?为什么不能抛出异常?⭐
异常点之后的代码不会执行:当析构函数抛出异常时,异常将导致程序流程跳转到异常处理代码,导致异常点之后的代码不会被执行。这可能会导致对象销毁过程中的必要动作无法执行,例如释放资源,从而引发资源泄漏等问题。
还有以下几点原因:
- 安全性:抛出异常可能导致资源泄漏或不一致的状态。
- 可追踪性:异常的发生会增加代码的复杂性和调试的困难。
- 可移植性:不同编译器可能对析构函数中的异常支持不同。
为了解决这些问题,一种常见的做法是在析构函数中尽量避免抛出异常,而是使用try-catch块捕获和处理可能发生的异常。通过在try块中执行资源清理操作,并在catch块中进行适当的异常处理,可以确保对象的销毁过程能够正常进行,同时提供更好的程序安全性和可追踪性。
如果析构函数抛出异常,并且在异常点之后的程序不会执行,造成了资源泄漏等问题,可以考虑以下解决方法:
- 使用智能指针:使用C++中的智能指针(如std::unique_ptr、std::shared_ptr)来管理资源,可以自动处理资源的释放,避免手动管理资源导致的错误和异常。智能指针的析构函数会自动释放资源,即使在析构函数中抛出异常,也可以保证资源的正常释放。
- 分离资源管理:将资源的释放操作从析构函数中分离出来,使用独立的函数或类来管理资源的释放。在析构函数中调用这些资源管理函数,如果资源释放过程中发生异常,可以通过合适的方式处理异常,避免资源泄漏。
- 做好异常处理:在析构函数中合理地使用异常处理机制,例如使用try-catch块捕获异常,并在catch块中适当地处理异常。这样可以保证即使在析构过程中发生异常,也不会导致程序崩溃或其他严重问题。
3.什么情况下会调用拷贝构造函数?⭐
拷贝构造函数是类中特殊的构造函数,用于创建一个新的对象并将其初始化为同一类的另一个对象的副本。它通常用于在以下情况下进行对象的复制:
1.对象通过值传递或返回时:当对象作为函数参数按值传递或作为函数返回类型时,会触发拷贝构造函数的调用。这是因为在这些情况下,需要创建一个新的对象副本来传递给函数或作为返回值。
代码示例:
#include <iostream> class MyClass { public: MyClass(int value) : data(value) { std::cout << "Constructor called: " << data << std::endl; } MyClass(const MyClass& other) : data(other.data) { std::cout << "Copy constructor called: " << data << std::endl; } int getData() const { return data; } private: int data; }; void doSomething(MyClass obj) { std::cout << "Data inside doSomething: " << obj.getData() << std::endl; } MyClass createObject(int value) { return MyClass(value); } int main() { MyClass obj1(10); doSomething(obj1); MyClass obj2 = createObject(20); std::cout << "Data inside main: " << obj2.getData() << std::endl; return 0; } 输出: Constructor called: 10 Copy constructor called: 10 Data inside doSomething: 10 Constructor called: 20 Copy constructor called: 20 Data inside main: 20
代码解释:代码中,MyClass类的拷贝构造函数被调用来创建函数doSomething的参数对象和createObject的返回值对象。简化后的代码保留了拷贝构造函数的调用,并且输出了复制过程中的信息。
2.使用一个对象初始化另一个对象:当使用一个对象来初始化另一个对象时,会调用拷贝构造函数。这包括在创建一个新对象并用已存在的对象初始化它时,或者通过赋值运算符进行对象的初始化。
#include <iostream> class MyClass { public: MyClass(int value) : data(value) { std::cout << "Constructor called: " << data << std::endl; } MyClass(const MyClass& other) : data(other.data) { std::cout << "Copy constructor called: " << data << std::endl; } int getData() const { return data; } private: int data; }; int main() { MyClass obj1(10); // 调用构造函数进行初始化 MyClass obj2(obj1); // 使用obj1对象初始化obj2对象 std::cout << "Data in obj2: " << obj2.getData() << std::endl; MyClass obj3 = obj1; // 使用赋值运算符进行对象的初始化 std::cout << "Data in obj3: " << obj3.getData() << std::endl; return 0; } 输出: Constructor called: 10 Copy constructor called: 10 Data in obj2: 10 Copy constructor called: 10 Data in obj3: 10
代码解释:代码中,MyClass
类的拷贝构造函数被调用来初始化obj2
和obj3
对象。obj2
通过使用已存在的obj1
对象进行初始化,而obj3
通过使用赋值运算符进行初始化。
4.mutable和volatile关键字功能对比?
mutable关键字:
- mutable关键字用于修饰类的成员变量,在常量成员函数中允许被修改。
- 默认情况下,常量成员函数不允许修改类的成员变量,而使用mutable关键字可以解除这个限制,允许在常量成员函数中修改被mutable修饰的成员变量。
- mutable关键字适用于一些内部状态需要在常量成员函数中更新的情况,例如缓存结果或需要记录操作次数等。
volatile关键字:
- volatile关键字用于修饰变量,在多线程、硬件IO和中断处理等场景中,告诉编译器该变量的值是可能发生变化的,需要特殊对待。
- 为了告诉编译器不要对该变量进行优化,以防止对变量读取和写入的优化可能导致错误的行为。
- volatile适用于需要和外部环境进行交互的变量,比如硬件寄存器的状态、多线
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
BG双9,目前在某外企。打算把之前校招时做的笔记通过专栏发出来,本专栏适合于C/C++、嵌入式方向就业的同学,本篇面经总结数千篇面经的知识集合,实时更新全网最新的嵌入式/C++最新内容,囊括了C语言、C++、操作系统、计算机网络、嵌入式、算法与数据结构、数据库等一系列知识点,在我看来这些是求职者在面试中必须掌握的知识点。最后呢祝各位能找到自己合适的工作。。