引言
C++ 作为一门高效、灵活且功能强大的编程语言,在系统开发、游戏引擎、高频交易和嵌入式系统等领域占据核心地位。因此,C++ 面试通常考察候选人对语言底层机制的理解,而不仅仅是语法的堆砌。面试官往往通过经典题目来评估你对内存管理、多态机制、STL 容器以及现代 C++ 特性的掌握程度。
本文将深入解析 C++ 面试中最高频出现的经典题目,涵盖从基础概念到高级特性的各个方面。通过掌握这些核心考点,你将能够自信地应对技术面试中的各种挑战。
一、C++ 基础与内存管理
1.1 栈(Stack)与堆(Heap)的区别
核心考点
:这是面试中最基础也是最重要的问题,考察你对内存分配机制的理解。
详细解析
:
栈(Stack)
:由编译器自动分配和释放,存放函数的局部变量、参数、返回地址等。其操作方式类似于数据结构中的栈,后进先出(LIFO)。栈内存分配速度快,但容量有限。
堆(Heap)
:由程序员手动分配(如使用
new
或
malloc
)和释放(如使用
delete
或
free
)。堆内存容量大,但分配和管理速度较慢,且容易产生内存碎片。
代码示例
:
#include
void stackHeapExample() {
// 栈上分配:变量 a 在函数结束时自动销毁
int a = 10;
// 堆上分配:指针 p 指向堆内存,需要手动释放,否则内存泄漏
int* p = new int(20);
std::cout << "Stack variable: " << a << std::endl;
std::cout << "Heap variable: " << *p << std::endl;
// 必须手动释放堆内存
delete p;
p = nullptr; // 良好习惯:释放后置空指针
}
int main() {
stackHeapExample();
return 0;
}
1.2
new
/
delete
与
malloc
/
free
的区别
核心考点
:考察对 C 和 C++ 内存管理机制差异的理解,以及是否了解构造函数和析构函数的调用。
详细解析
:
属性
:
new
/
delete
是 C++ 的操作符,
malloc
/
free
是 C 语言的标准库函数。
调用构造/析构函数
:
new
在分配内存后会调用类的构造函数,
delete
会调用析构函数;而
malloc
/
free
只负责内存的分配和释放,不会调用构造/析构函数。
返回类型
:
new
返回指定类型的指针,无需强制类型转换;
malloc
返回
void*
,需要强制类型转换。
失败处理
:
new
分配失败会抛出
std::bad_alloc
异常(除非使用
nothrow
),而
malloc
分配失败返回
NULL
。
代码示例
:
class MyClass {
public:
MyClass() { std::cout << "Constructor called" << std::endl; }
~MyClass() { std::cout << "Destructor called" << std::endl; }
};
void compareTest() {
// 使用 new/delete
MyClass* p1 = new MyClass(); // 输出: Constructor called
delete p1; // 输出: Destructor called
// 使用 malloc/free
MyClass* p2 = (MyClass*)malloc(sizeof(MyClass)); // 无输出,不调用构造函数
free(p2); // 无输出,不调用析构函数
}
1.3 什么是内存泄漏?如何避免?
核心考点
:考察代码健壮性和资源管理意识。
详细解析
:
内存泄漏(Memory Leak)是指程序在运行过程中,由于疏忽或错误导致未能释放不再使用的动态分配的内存。长期运行的程序(如服务器)发生内存泄漏会导致内存耗尽,最终崩溃。
避免方法
:
良好的编程习惯
:
new
和
delete
成对出现。
RAII(Resource Acquisition Is Initialization)
:即“资源获取即初始化”,这是 C++ 的核心思想。利用对象生命周期管理资源,最典型的实现是智能指针。
使用智能指针
:
std::unique_ptr
、
std::shared_ptr
等,它们在析构时自动释放内存。
代码示例
:
#include
void smartPointerDemo() {
// 普通指针容易泄漏
int* raw = new int(100);
// 如果这里发生异常或忘记 delete,就会泄漏
// delete raw;
// 使用 unique_ptr,无需手动 delete
std::unique_ptr
// 函数结束时,up 自动析构,内存自动释放
}
二、面向对象核心特性
2.1 C++ 多态的实现原理
核心考点
:这是 C++ 面试的“压轴题”,考察对虚函数、虚函数表(vtable)和虚指针(vptr)的理解。
详细解析
:
C++ 的多态分为
编译时多态
(函数重载、模板)和
运行时多态
(虚函数)。面试通常指运行时多态。
实现机制
:当一个类中包含虚函数时,编译器会为该类生成一个
虚函数表(vtable)
,表中存放了指向虚函数的指针。每个该类的对象内部会包含一个
虚指针(vptr)
,指向该类的 vtable。
调用过程
:当通过基类指针调用虚函数时(如
basePtr->virtualFunc()
),程序会根据 vptr 找到 vtable,再在 vtable 中查找对应的函数地址进行调用,从而实现根据对象实际类型调用相应函数的多态行为。
代码示例
:
#include
class Base {
public:
virtual void show() { std::cout << "Base show" << std::endl; }
void display() { std::cout << "Base display" << std::endl; } // 非虚函数
};
class Derived : public Base {
public:
void show() override { std::cout << "Derived show" << std::endl; } // 覆盖
void display() { std::cout << "Derived display" << std::endl; } // 隐藏
};
void polymorphismDemo() {
Base* b = new Derived();
// 多态:运行时根据对象类型调用 Derived::show
b->show(); // 输出: Derived show
// 静态绑定:编译时根据指针类型调用 Base::display
b->display(); // 输出: Base display
delete b;
}
2.2 虚析构函数的作用
核心考点
:考察内存回收机制与多态的结合。
详细解析
:
如果基类的析构函数不是虚函数,当通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。这会导致派生类对象中的资源无法正确释放,造成内存泄漏。
解决方法
:将基类的析构函数声明为
virtual
。
代码示例
:
class BaseBad {
public:
~BaseBad() { std::cout << "Base destructor" << std::endl; }
};
class DerivedBad : public BaseBad {
public:
int* data;
DerivedBad() { data = new int[100]; }
~DerivedBad() {
delete[] data;
std::cout << "Derived destructor" << std::endl;
}
};
void virtualDestructorDemo() {
BaseBad* p = new DerivedBad();
delete p;
// 输出: Base destructor
// 问题:Derived destructor 未被调用,data 内存泄漏!
// 修正:将 BaseBad 的析构函数改为 virtual
}
2.3
public
、
protected
、
private
继承的区别
核心考点
:考察对继承机制下访问权限控制的理解。
详细解析
:
public 继承
:保持基类中成员的访问权限不变。
public
->
public
,
protected
->
protected
,
private
不可访问。这是最常用的继承方式,体现 “is-a” 关系。
protected 继承
:基类的
public
成员在派生类中变为
protected
,
protected
保持不变。
private 继承
:基类的
public
和
protected
成员在派生类中均变为
private
。
总结表
:
基类成员类型
Public 继承后
Protected 继承后
Private 继承后
public
public
protected
private
protected
protected
protected
private
private
不可访问
不可访问
不可访问
三、C++ 11/14/17 新特性
3.1 智能指针详解
核心考点
:现代 C++ 开发必须掌握的内容,替代裸指针管理资源。
详细解析
:
std::unique_ptr
:独占所有权的智能指针,同一时间只能有一个
unique_ptr
指向一个对象。禁止拷贝,只能移动(move)。
std::shared_ptr
:共享所有权的智能指针,通过引用计数管理资源。当最后一个
shared_ptr
被销毁时,对象被删除。注意解决
循环引用
问题。
std::weak_ptr
:配合
shared_ptr
使用,它不增加引用计数,只观察资源。用于解决
shared_ptr
的循环引用问题。
代码示例
:
#include
#include
void smartPtrDemo() {
// unique_ptr
std::unique_ptr
// std::unique_ptr
std::unique_ptr
// shared_ptr
std::shared_ptr
std::cout << "Ref count: " << s1.use_count() << std::endl; // 1
{
std::shared_ptr
std::cout << "Ref count: " << s1.use_count() << std::endl; // 2
} // s2 销毁,引用计数减 1
std::cout << "Ref count: " << s1.use_count() << std::endl; // 1
}
3.2
auto
关键字与类型推导
核心考点
:简化代码,提高可读性,但也需了解其推导规则。
详细解析
:
auto
让编译器在编译时期自动推导变量的类型。它主要用于处理复杂类型(如迭代器)或简化模板编程。
代码示例
:
#include
#include