理想 后端开发-C++ 一面
1. 在多态场景下,父类析构函数为什么通常要写成虚函数
答案:如果一个类会被当成基类使用,并且外部可能通过父类指针去释放子类对象,那父类析构函数就应该定义成虚函数。原因是删除对象时如果析构函数不是虚函数,那么 delete basePtr 只会调用父类析构,不会正确调用子类析构,子类里申请的资源就可能泄露。这本质上是为了保证析构链完整执行,也是运行时多态在销毁阶段的体现。如果一个类根本不打算被继承,也不会通过基类指针释放对象,那不一定非要虚析构,但只要它是“多态基类”,基本都应该加上。
代码:
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() {
cout << "Base dtor\n";
}
};
class Derived : public Base {
public:
~Derived() {
cout << "Derived dtor\n";
}
};
int main() {
Base* p = new Derived();
delete p;
return 0;
}
2. father* p1 = new father[3]; 应该怎么释放,为什么
答案:这种写法申请的是对象数组,释放时必须使用 delete[] p1;,不能写成 delete p1;。因为 new[] 和 delete[] 是成对出现的。数组形式的分配通常会额外记录元素个数,释放时需要逐个调用每个元素的析构函数。如果误用 delete p1,结果是未定义行为,轻则析构不完整,重则内存破坏。这类题本质上是在看你对对象构造、析构和内存模型是不是清楚。
代码:
#include <iostream>
using namespace std;
class Father {
public:
Father() { cout << "ctor\n"; }
~Father() { cout << "dtor\n"; }
};
int main() {
Father* p1 = new Father[3];
delete[] p1;
return 0;
}
3. 多线程里你一般怎么把任务交给线程执行
答案:如果只是简单起线程,可以直接用 std::thread 把函数或可调用对象丢进去执行。如果任务量比较大,通常不会每来一个任务就创建一个线程,而是做成线程池:主线程负责提交任务,任务先进队列,工作线程从队列里不断取任务执行。完整流程一般是任务封装、入队、条件变量唤醒、工作线程取任务、执行、异常处理、线程回收。实际项目里更看重的是线程之间怎么同步、共享数据怎么保护、线程是否可控退出,而不只是“会不会开线程”。
代码:
#include <iostream>
#include <thread>
using namespace std;
void work(int id) {
cout << "thread " << id << " running\n";
}
int main() {
thread t(work, 1);
t.join();
return 0;
}
4. C++ 里常见的设计模式你用过哪些,挑两个讲讲
答案:C++ 里常见的像单例、工厂、观察者、策略、适配器、责任链都比较常见。如果结合工程实践,单例一般用于配置中心、日志器、线程池这类全局唯一资源;工厂模式适合做对象创建解耦,比如根据协议类型、消息类型或者资源类型创建不同实例。观察者模式在事件系统、消息分发、UI 通知里用得很多;策略模式更适合把算法或规则抽出去,比如不同重试策略、不同路由策略、不同排序策略。面试官真正想听的通常不是你背了多少模式名字,而是你知不知道这些模式解决的是什么耦合问题。
代码:
#include <iostream>
#include <memory>
using namespace std;
class Product {
public:
virtual void run() = 0;
virtual ~Product() = default;
};
class AProduct : public Product {
public:
void run() override { cout << "AProduct\n"; }
};
class Factory {
public:
static unique_ptr<Product> create() {
return make_unique<AProduct>();
}
};
int main() {
auto p = Factory::create();
p->run();
return 0;
}
5. 详细讲讲你对栈 Stack 的理解
答案:如果说数据结构里的栈,它是先进后出,典型操作就是 push、pop、top。如果说程序运行时的栈内存,它主要用来保存函数调用现场,比如参数、返回地址、局部变量、寄存器上下文这些。函数调用一层层压栈,返回时再一层层出栈,所以递归太深会导致栈溢出。栈内存分配释放很快,因为基本只需要移动栈顶指针,但空间通常比较小,也不适合管理生命周期特别长的大对象。这题面试里经常会故意说“栈”,其实是看你能不能区分数据结构层面的栈和内存模型里的栈。
6. 面向对象的三大特性是什么,分别怎么理解
答案:三大特性是封装、继承、多态。封装是把数据和行为放在一起,并通过访问控制隐藏内部实现细节;继承是让子类复用父类能力,减少重复代码;多态是统一接口对应不同实现,同一调用在不同对象上表现不一样。真正写业务代码时,这三个特性通常不是孤立存在的。比如一个基类对外暴露统一接口,这是封装;多个子类复用和扩展它,这是继承;通过基类指针调用不同子类逻辑,这是多态。如果答得再深一点,可以补一句:封装解决的是边界问题,继承解决的是复用问题,多态解决的是扩展问题。
7. 多态的底层原理是什么
答案:运行时多态主要依赖虚函数表。带虚函数的类,编译器通常会为它生成一张虚表,对象里会有一个隐藏的虚表指针。当通过基类指针或引用调用虚函数时,编译器不会在编译期写死调用哪个函数,而是运行时根据对象里的虚表指针去查真实函数地址,这样才能做到“同一个接口,不同对象执行不同实现”。这也是为什么多态一般要满足三个条件:继承关系、父类函数为虚函数、通过父类指针或引用调用。再往深一点问,面试官还可能继续追构造函数里调虚函数、虚析构、纯虚函数和抽象类。
代码:
#include <iostream>
using namespace std;
class Base {
public:
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.