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.析构函数可以抛出异常吗?为什么不能抛出异常?⭐

异常点之后的代码不会执行:当析构函数抛出异常时,异常将导致程序流程跳转到异常处理代码,导致异常点之后的代码不会被执行。这可能会导致对象销毁过程中的必要动作无法执行,例如释放资源,从而引发资源泄漏等问题。

还有以下几点原因:

  1. 安全性:抛出异常可能导致资源泄漏或不一致的状态。
  2. 可追踪性:异常的发生会增加代码的复杂性和调试的困难。
  3. 可移植性:不同编译器可能对析构函数中的异常支持不同。

为了解决这些问题,一种常见的做法是在析构函数中尽量避免抛出异常,而是使用try-catch块捕获和处理可能发生的异常。通过在try块中执行资源清理操作,并在catch块中进行适当的异常处理,可以确保对象的销毁过程能够正常进行,同时提供更好的程序安全性和可追踪性。

如果析构函数抛出异常,并且在异常点之后的程序不会执行,造成了资源泄漏等问题,可以考虑以下解决方法:

  1. 使用智能指针:使用C++中的智能指针(如std::unique_ptr、std::shared_ptr)来管理资源,可以自动处理资源的释放,避免手动管理资源导致的错误和异常。智能指针的析构函数会自动释放资源,即使在析构函数中抛出异常,也可以保证资源的正常释放。
  2. 分离资源管理:将资源的释放操作从析构函数中分离出来,使用独立的函数或类来管理资源的释放。在析构函数中调用这些资源管理函数,如果资源释放过程中发生异常,可以通过合适的方式处理异常,避免资源泄漏。
  3. 做好异常处理:在析构函数中合理地使用异常处理机制,例如使用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类的拷贝构造函数被调用来初始化obj2obj3对象。obj2通过使用已存在的obj1对象进行初始化,而obj3通过使用赋值运算符进行初始化。

4.mutable和volatile关键字功能对比?

mutable关键字

  • mutable关键字用于修饰类的成员变量,在常量成员函数中允许被修改。
  • 默认情况下,常量成员函数不允许修改类的成员变量,而使用mutable关键字可以解除这个限制,允许在常量成员函数中修改被mutable修饰的成员变量。
  • mutable关键字适用于一些内部状态需要在常量成员函数中更新的情况,例如缓存结果或需要记录操作次数等。

volatile关键字

  • volatile关键字用于修饰变量,在多线程、硬件IO和中断处理等场景中,告诉编译器该变量的值是可能发生变化的,需要特殊对待。
  • 为了告诉编译器不要对该变量进行优化,以防止对变量读取和写入的优化可能导致错误的行为。
  • volatile适用于需要和外部环境进行交互的变量,比如硬件寄存器的状态、多线

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

c++/嵌入式面经专栏 文章被收录于专栏

本人2022年毕业于山东大学,目前就职国内某芯片厂。打算把之前校招时做的笔记通过专栏发出来,本专栏适合于C/C++、嵌入式方向就业的同学,本篇面经总结数千篇面经的知识集合,实时更新全网最新的嵌入式/C++最新内容,囊括了C语言、C++、操作系统、计算机网络、嵌入式、算法与数据结构、数据库等一系列知识点,在我看来这些是求职者在面试中必须掌握的知识点。最后呢祝各位能找到自己合适的工作。

全部评论
创建对象数组用的不是构造函数吗?,为什么是拷贝构造函数
点赞
送花
回复
分享
发布于 2023-10-31 09:58 重庆
C++中,全局变量和静态变量的初始化行为有所不同。全局变量和静态变量在C++中的初始化推迟至它们"首次用到"时才进行,这是C++标准规定的行为。是这样子吗?感觉怪怪的。
点赞
送花
回复
分享
发布于 2023-11-29 19:06 广东
秋招专场
校招火热招聘中
官网直投

相关推荐

3 27 评论
分享
牛客网
牛客企业服务