东软集团 C++开发 二面

1. C++ 中虚函数的作用是什么,虚函数表是怎么工作的

答案:虚函数的核心作用是支持运行时多态。当父类指针或引用指向子类对象时,调用虚函数可以在运行时决定具体执行哪个版本,而不是在编译期静态绑定。

底层上,编译器通常会为包含虚函数的类生成一张虚函数表,表里保存对应虚函数地址;对象内部会有一个隐藏的虚表指针,指向当前对象实际类型对应的虚表。调用虚函数时,大致过程是:

  • 先通过对象拿到虚表指针
  • 再从虚表中找到对应函数地址
  • 最终调用实际类型的实现

如果析构函数涉及多态删除,基类析构函数一般也要声明为虚函数,否则通过基类指针删除派生类对象会出现未定义行为。

代码:

#include <iostream>
using namespace std;

class Base {
public:
    virtual void run() {
        cout << "Base::run" << endl;
    }

    virtual ~Base() = default;
};

class Derived : public Base {
public:
    void run() override {
        cout << "Derived::run" << endl;
    }
};

int main() {
    Base* p = new Derived();
    p->run();
    delete p;
    return 0;
}

2. newmalloc 的区别是什么

答案:newmalloc 都可以申请内存,但它们不属于同一套机制。

malloc 是 C 语言库函数,做的事情主要是:

  • 按字节申请一段原始内存
  • 返回 void*
  • 不会调用构造函数
  • 释放时使用 free

new 是 C++ 运算符,除了分配内存外,还会:

  • 根据类型返回对应指针
  • 调用构造函数完成对象初始化
  • 失败时默认抛出异常
  • 释放时使用 delete
  • new[] / delete[] 成对使用

实际开发里,如果是在 C++ 里管理对象,通常优先使用 new,更进一步则优先使用智能指针和 RAII,尽量减少手动管理内存。

代码:

#include <iostream>
#include <cstdlib>
using namespace std;

class Test {
public:
    Test() { cout << "constructor" << endl; }
    ~Test() { cout << "destructor" << endl; }
};

int main() {
    Test* p1 = new Test();
    delete p1;

    Test* p2 = (Test*)malloc(sizeof(Test));
    free(p2);

    return 0;
}

3. 引用和指针的区别是什么

答案:引用可以理解为变量的别名,指针本质上是一个保存地址的变量。它们都能间接操作对象,但语义和使用方式不同。

主要区别有:

  • 引用定义时必须初始化,指针可以先不初始化
  • 引用一旦绑定对象后不能再改绑,指针可以修改指向
  • 引用使用时更像原变量,不需要显式解引用
  • 指针可以为空,引用通常不能为空
  • 指针更适合表达“可能没有对象”,引用更适合表达“必须有对象”

参数传递时:

  • 值传递会拷贝对象
  • 指针传递和引用传递都可以修改外部对象
  • 如果只读且避免拷贝,常用 const T&

代码:

#include <iostream>
using namespace std;

void change1(int x) {
    x = 100;
}

void change2(int* x) {
    if (x) *x = 200;
}

void change3(int& x) {
    x = 300;
}

int main() {
    int a = 10;
    change1(a);
    cout << a << endl;

    change2(&a);
    cout << a << endl;

    change3(a);
    cout << a << endl;
    return 0;
}

4. vectorlist 的区别是什么,实际开发中怎么选

答案:vector 底层是连续内存,list 底层是双向链表。它们最本质的差异是内存布局不同,这会直接影响访问性能和操作特性。

vector 的特点:

  • 连续内存,支持随机访问
  • 遍历性能好,cache 友好
  • 尾部插入效率高
  • 扩容时可能触发整体搬迁
  • 中间位置插入删除代价较高

list 的特点:

  • 节点离散分布,不支持随机访问
  • 插入删除方便
  • 每个节点额外维护前后指针
  • 内存开销更大
  • 遍历局部性差

工程里 vector 使用得通常更多,因为:

  • 连续内存对现代 CPU 更友好
  • 遍历效率通常明显更高
  • 很多业务场景读多写少

5. mapunordered_map 的区别是什么

答案:map 底层通常是红黑树,unordered_map 底层通常是哈希表。它们都可以保存 key-value,但在有序性、复杂度和使用场景上差异较大。

map 的特点:

  • 按 key 有序
  • 查找、插入、删除复杂度一般是 O(log n)
  • 支持范围查询、上下界查找
  • 迭代输出有序

unordered_map 的特点:

  • 无序
  • 平均查找、插入、删除是 O(1)
  • 冲突严重时可能退化
  • 不适合需要有序遍历和范围操作的场景

如果需求是:

  • 需要顺序、范围查询:优先 map
  • 更关注单点查找性能:优先 unordered_map

代码:

#include <iostream>
#include <map>
#include <unordered_map>
using namespace std;

int main() {
    map<int, int> mp1;
    unordered_map<int, int> mp2;

    mp1[3] = 30;
    mp1[1] = 10;
    mp1[2] = 20;

    mp2[3] = 30;
    mp2[1] = 10;
    mp2[2] = 20;

    for (auto& [k, v] : mp1) {
        cout << k << " " << v << endl;
    }

    return 0;
}

6. 模板和模板特化的作用是什么

答案:模板的作用是把类型参数化,让一套代码可以适配多种类型。这是 C++ 泛型编程的基础,常见有函数模板和类模板。

模板特化是在通用模板基础上,为某些特定类型提供专门实现。这样可以做到:

  • 针对特殊类型优化行为
  • 对某些类型做定制逻辑
  • 在通用性和特殊性之间做平衡

模板特化常见分为:

  • 全特化
  • 偏特化

代码:

#include <iostream>
using namespace std;

template <typename T>
class Printer {
public:
    void print() {
        cout << "generic" << endl;
    }
};

template <>
class Printer<int> {
public:
    void print() {
        cout << "int specialization" << endl;
    }
};

int main() {
    Printer<double> p1;
    p1.print();

    Printer<int> p2;
    p2.print();
    return 0;
}

7. 什么是右值引用,移动语义解决了什么问题

答案:右值引用是 C++11 引入的特性,形式是 T&&。它主要是为了支持移动语义和完美转发。

移动语义解决的问题是:很多对象在临时对象、返回值传递、容器扩容等场景下,本来只需要“转移资源所有权”,但如果按拷贝语义处理,就会产生额外的深拷贝开销。

典型收益有:

  • 减少不必要的对象拷贝
  • 提升容器操作效率
  • 提高返回对象时的性能

一个类如果自己管理资源,比如堆内存、文件句柄、socket 等,通常要考虑:

  • 拷贝构造
  • 拷贝赋值
  • 移动构造
  • 移动赋值
  • 析构函数

代码:

#include <iostream>
#include <utility>
using namespace std;

class Buffer {
public:
    Buffer(size_t n) : size_(n), data_(new int[n]) {
        cout << "construct" << endl;
    }

    ~Buffer() {
        delete[] data_;
    }

    Buffer(const Buffer& other) : size_(other.size_), data_(new int[other.size_]) {
        cout << "copy construct" << endl

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

C++ 常考面试题总结 文章被收录于专栏

本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.

全部评论

相关推荐

评论
1
5
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务