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 << "Dat