强者的自我修养免安装绿色中文版
4.29G · 2025-11-02
以下是更详细的 C++ 面试题集,涵盖核心考点,每个题目均包含代码示例、深入解析及易错点提示,适合面试冲刺复习:
const 关键字的深层用法(含代码示例)题目:const 在不同场景下的作用,如何区分 const int*、int* const 和 const int* const?答案:
const 修饰变量:变量只读,编译期检查,内存不可修改(强制修改会触发未定义行为)。
const 修饰指针:
const int* p:指针指向的内容不可改(*p = 2 错误),指针本身可改(p = &a 合法)。int* const p:指针本身不可改(p = &a 错误),指向的内容可改(*p = 2 合法)。const int* const p:指针本身和指向的内容均不可改。const 修饰类成员函数:函数体内不能修改非 mutable 成员变量,也不能调用非 const 成员函数。
代码示例:
cpp
运行
class A {
public:
int x;
mutable int y; // 可被 const 成员函数修改
void func() const {
// x = 10; 错误:const 函数不能修改非 mutable 成员
y = 20; // 正确:mutable 成员可修改
}
};
int main() {
const int a = 5;
// a = 10; 错误:const 变量不可修改
int b = 10;
const int* p1 = &b; // 指向内容不可改
// *p1 = 15; 错误
p1 = &a; // 正确:指针本身可改
int* const p2 = &b; // 指针本身不可改
*p2 = 15; // 正确:指向内容可改
// p2 = &a; 错误
return 0;
}
易错点:
const 在 * 左边则指向内容不可改,在右边则指针本身不可改)。const 成员函数中试图修改非 mutable 成员,或调用非 const 成员函数。static 在类中的完整用法题目:static 修饰类成员变量和成员函数的特点,如何初始化?答案:
静态成员变量:
static。类名::变量名 或对象访问(对象访问本质还是类共享)。静态成员函数:
this 指针,只能访问静态成员(变量 / 函数),不能访问非静态成员。类名::函数名 直接调用,无需实例化对象。代码示例:
cpp
运行
class Counter {
private:
static int count; // 类内声明
public:
Counter() { count++; }
static int getCount() { // 静态成员函数
return count;
}
};
int Counter::count = 0; // 类外初始化(必须!)
int main() {
Counter c1, c2;
cout << Counter::getCount() << endl; // 输出 2(通过类名调用)
cout << c1.getCount() << endl; // 输出 2(通过对象调用)
return 0;
}
易错点:
undefined reference to Counter::count)。题目:C++ 多态如何实现?虚函数表(vtable)的作用是什么?答案:多态通过虚函数和动态绑定实现,核心是虚函数表(vtable):
代码示例:
cpp
运行
class Base {
public:
virtual void func() { cout << "Base::func()" << endl; } // 虚函数
};
class Derived : public Base {
public:
void func() override { cout << "Derived::func()" << endl; } // 重写
};
int main() {
Base* p = new Derived(); // 父类指针指向子类对象
p->func(); // 输出 Derived::func()(动态绑定)
delete p;
return 0;
}
易错点:
题目:析构函数不设为虚函数会导致什么问题?举例说明。答案:若父类析构函数非虚函数,当用父类指针指向子类对象并 delete 时,只会调用父类析构函数,子类资源无法释放,导致内存泄漏。设为虚函数可保证析构函数动态绑定,先调用子类析构,再调用父类析构。
代码示例(错误情况) :
cpp
运行
class Base {
public:
~Base() { cout << "Base 析构" << endl; } // 非虚析构
};
class Derived : public Base {
private:
int* data;
public:
Derived() { data = new int[10]; }
~Derived() {
delete[] data;
cout << "Derived 析构" << endl; // 不会被调用!
}
};
int main() {
Base* p = new Derived();
delete p; // 仅输出 "Base 析构",Derived 的 data 内存泄漏
return 0;
}
正确做法:将父类析构函数声明为虚函数:
cpp
运行
class Base {
public:
virtual ~Base() { cout << "Base 析构" << endl; } // 虚析构
};
// 子类析构函数自动为虚函数(无需显式加 virtual)
// delete p 时会先调用 Derived 析构,再调用 Base 析构,无内存泄漏
易错点:
new/delete 与 malloc/free 的本质区别题目:从底层实现、功能、安全性等方面对比 new 和 malloc。答案:
| 维度 | new/delete | malloc/free |
|---|---|---|
| 性质 | C++ 运算符 | C 库函数 |
| 类型检查 | 有(返回对应类型指针) | 无(返回 void*,需手动转换) |
| 构造 / 析构 | 自动调用构造 / 析构函数 | 仅分配 / 释放内存,不调用 |
| 内存不足 | 抛出 bad_alloc 异常 | 返回 NULL |
| 重载 | 可重载 operator new | 不可重载 |
代码示例:
cpp
运行
class A {
public:
A() { cout << "A 构造" << endl; }
~A() { cout << "A 析构" << endl; }
};
int main() {
// new/delete:自动调用构造/析构
A* p1 = new A(); // 输出 "A 构造"
delete p1; // 输出 "A 析构"
// malloc/free:不调用构造/析构
A* p2 = (A*)malloc(sizeof(A)); // 无输出(未构造)
free(p2); // 无输出(未析构),对象处于未初始化状态
return 0;
}
易错点:
new 和 free、malloc 和 delete(例如 free(new A()) 会导致析构函数不被调用,内存泄漏)。delete 而非 delete[](delete[] 会调用每个元素的析构函数,delete 仅调用第一个,导致泄漏)。题目:shared_ptr 循环引用会导致什么问题?如何用 weak_ptr 解决?答案:
shared_ptr 指向对方,导致引用计数永远不为 0,对象无法释放,内存泄漏。weak_ptr(弱引用,不增加引用计数)。代码示例(循环引用问题) :
cpp
运行
#include <memory>
class B; // 前置声明
class A {
public:
shared_ptr<B> b_ptr;
~A() { cout << "A 析构" << endl; }
};
class B {
public:
shared_ptr<A> a_ptr; // B 持有 A 的 shared_ptr
~B() { cout << "B 析构" << endl; }
};
int main() {
shared_ptr<A> a(new A());
shared_ptr<B> b(new B());
a->b_ptr = b; // A 持有 B 的 shared_ptr
b->a_ptr = a; // 循环引用!
// 离开作用域时,a 和 b 的引用计数均为 1(未减到 0),析构函数不调用
return 0;
}
解决代码(用 weak_ptr) :
cpp
运行
class B {
public:
weak_ptr<A> a_ptr; // 改为弱引用,不增加计数
~B() { cout << "B 析构" << endl; }
};
// 此时 a 和 b 离开作用域时,引用计数减为 0,析构函数正常调用
易错点:
shared_ptr 而忽略循环引用风险(需根据 ownership 合理选择智能指针)。weak_ptr 直接访问对象(需先通过 lock() 转换为 shared_ptr,检查对象是否存活)。vector 的扩容机制与迭代器失效题目:vector 扩容时发生了什么?哪些操作会导致迭代器失效?答案:
扩容机制:vector 是动态数组,内存连续。当插入元素导致容量不足时,会:
迭代器失效场景:
erase 操作(删除位置后的迭代器失效);push_back/insert 可能导致扩容,间接使迭代器失效。代码示例(迭代器失效) :
cpp
运行
#include <vector>
int main() {
vector<int> v = {1, 2, 3};
auto it = v.begin();
v.push_back(4); // 可能触发扩容,it 失效
// *it = 10; 未定义行为(可能崩溃)
// erase 导致迭代器失效
it = v.erase(v.begin()); // erase 返回下一个有效迭代器
// 正确做法:用返回值更新迭代器
return 0;
}
易错点:
erase 后未更新迭代器(正确用法:it = v.erase(it))。c++11,主要新增了类型自动推导,智能指针,移动语义与右值引用, 匿名函数,for循环内迭代简化。
题目:什么是移动语义?std::move 的作用是什么?答案:
&&):绑定到右值(临时对象、字面量),是移动语义的基础。std::move:将左值强制转换为右值引用,使对象可被移动(本身不移动资源,仅改变值类别)。代码示例:
cpp
运行
class String {
private:
char* data;
public:
// 构造函数
String(const char* s) {
data = new char[strlen(s) + 1];
strcpy(data, s);
}
// 移动构造函数(窃取右值资源)
String(String&& other) noexcept : data(other.data) {
other.data = nullptr; // 原对象资源置空,避免析构时重复释放
}
~String() { delete[] data; }
};
int main() {
String s1("hello");
String s2 = std::move(s1); // 调用移动构造,s1 资源被窃取(s1.data 变为 nullptr)
return 0;
}
易错点:
std::move 会立即移动资源(实际仅允许移动,是否移动取决于是否有移动构造函数)。以上题目覆盖了 C++ 面试的核心考点,重点关注: