static和friend
面向对象: 封装、继承、多态(虚函数表原理必看,面试必问)。
手写一个简单的 String 类的构造函数、析构函数和拷贝构造函数。
include // 为了使用 strlen, strcpy
include
class String {
private:
char* data;
public:
// 1. 普通构造函数
String(const char* str = nullptr) {
if (str == nullptr) {
data = new char[1]; // 分配一个字节存 '\0'
*data = '\0';
} else {
int length = std::strlen(str);
data = new char[length + 1]; // +1 为了存结束符
std::strcpy(data, str);
}
}
// 2. 析构函数
~String() {
if (data != nullptr) {
delete[] data; // 注意:对应 new char[],必须使用 delete[]
data = nullptr;
}
}
// 3. 拷贝构造函数 (深拷贝的核心)
String(const String& other) {
// 分配新内存
int length = std::strlen(other.data);
data = new char[length + 1];
// 复制内容
std::strcpy(data, other.data);
}
// 加分项:赋值运算符重载 (虽未问到,但通常是一套的)
String& operator=(const String& other) {
if (this == &other) return *this; // 自赋值检测
delete[] data; // 释放原有内存
int length = std::strlen(other.data);
data = new char[length + 1];
std::strcpy(data, other.data);
return *this;
}
};
Q1. 指针和引用有什么区别?(至少说出 3 点)
你的答案:
初始化要求: 引用在定义时必须初始化,且一旦绑定不可改变;指针可以不初始化(虽不推荐),且可以随时改变指向的对象。
空值 (Nullability): 指针可以是 nullptr (空指针);引用通常必须指向一个有效的对象,不存在“空引用”。
本质与内存: 从汇编层面看,引用通常是指针的语法糖(底层也是存地址),但在 C++ 语法层面,指针是一个持有地址的变量(占用内存),而引用只是对象的别名(不额外占用内存,sizeof 引用得到的是被引用对象的大小)。
多级性: 存在多级指针 (如 int**),但不存在多级引用 (没有 int&& 这种指向引用的引用,注:右值引用是另一回事)。
Q2. new/delete 和 malloc/free 的核心区别是什么?
你的答案: 为了清晰对比,我们可以使用下表:

Q3. 什么是内存泄漏?如何检测?
你的答案:
定义: 程序在堆 (Heap) 上分配了内存,但在不再使用后没有释放,导致这块内存无法被再次使用。随着程序运行,可用内存逐渐减少,最终可能导致程序崩溃 (OOM)。
如何检测:
静态分析工具: 如 Cppcheck, SonarQube。
动态运行时工具:
Linux: Valgrind (最常用,命令 valgrind --leak-check=full ./app)。
Google Sanitizers: 编译时加上 -fsanitize=address (AddressSanitizer)。
Windows: Visual Studio 自带的 CRT 调试库 (_CrtDumpMemoryLeaks)。
Q4. const int *p 和 int * const p 的区别?
你的答案: 记忆口诀:“左定值,右定向” (以 * 为界)。
const int *p (指针指向的内容是常量):
解读: *p 是 const。
效果: 你不能通过 p 修改它指向的数据 (即 *p = 10 ❌),但你可以修改 p 指向别的地址 (即 p = &b ✅)。
int * const p (指针本身是常量):
解读: p 是 const。
效果: 你不能修改 p 的指向 (即 p = &b ❌),但你可以修改它指向的数据 (即 *p = 10 ✅)。
Q5. 为什么父类的析构函数必须是虚函数 (virtual)?
你的答案: 为了防止内存泄漏。 当使用父类指针指向子类对象,并对该指针执行 delete 操作时:
如果是虚析构: 运行时会通过虚函数表找到并调用子类的析构函数,然后再自动调用父类的析构函数,从而正确释放子类申请的资源。
如果不是虚析构: 编译器只会静态绑定调用父类的析构函数。子类的析构函数不会被执行,子类中特有的资源(如 new 出来的成员)将无法释放,导致内存泄漏。
Q6. 什么是虚函数表 (vtable)?它存在内存的哪里?
你的答案:
什么是 vtable: 编译器为每个包含虚函数的类(注意是类,不是对象)生成的一张表,表中存储了该类所有虚函数的入口地址。每个该类的对象内部都有一个隐藏指针 (vptr) 指向这张表,用于实现多态(动态绑定)。
存在哪里: 标准没有强制规定,但通常编译器将其存放在只读数据段 (.rdata 或 .rodata) 或 代码段 (.text) 中,因为虚函数表在程序运行时是固定不变的。
Q7. 请简述 C++ 中的深拷贝与浅拷贝。
你的答案:
浅拷贝 (Shallow Copy): 仅复制对象中的值。如果对象包含指针,浅拷贝只复制指针的地址,导致两个对象指向同一块内存。后果是当其中一个对象析构释放内存后,另一个对象就变成了悬空指针,再次析构会引发 Double Free 崩溃。
深拷贝 (Deep Copy): 在拷贝时,不仅复制值,还为指针成员重新分配独立的内存空间,并将原数据复制过去。两个对象互不干扰。
浙公网安备 33010602011771号