引言
C++ 作为一门高效且功能强大的编程语言,在系统编程、游戏开发、高频交易和嵌入式系统等领域占据着核心地位。因此,掌握 C++ 是许多技术面试中的关键要求。本指南旨在帮助求职者系统地复习 C++ 的核心概念,从基础语法到高级特性,并提供实用的面试技巧,助你在激烈的竞争中脱颖而出。
第一部分:C++ 基础语法回顾
1.1 基本数据类型与变量
C++ 提供了多种基本数据类型,包括整型、浮点型、字符型和布尔型。理解它们的范围和占用内存大小是基础。
#include <iostream>
#include <climits> // 用于整型范围限制
int main() {
// 整型
short s = 32767; // 短整型,通常占2字节
int i = INT_MAX; // 整型,通常占4字节
long l = LONG_MAX; // 长整型,通常占4或8字节
// 浮点型
float f = 3.14f; // 单精度浮点数
double d = 3.1415926535; // 双精度浮点数
// 字符型
char c = 'A'; // 单个字符
// 布尔型
bool b = true; // true or false
std::cout << "Size of int: " << sizeof(i) << " bytes" << std::endl;
return 0;
}
面试要点:面试官可能会问 sizeof 操作符的作用,或者不同数据类型在不同平台上的差异(如 int 的大小)。
1.2 控制流语句
掌握 if-else、switch、for、while 和 do-while 等控制流语句是编写逻辑的基础。
// Switch 语句示例
void checkGrade(int score) {
switch (score / 10) {
case 10:
case 9:
std::cout << "A" << std::endl;
break;
case 8:
std::cout << "B" << std::endl;
break;
default:
std::cout << "C" << std::endl;
}
}
面试要点:注意 break 在 switch 中的作用,以及 switch 与 if-else if 链的适用场景(通常当分支较多且基于整型或枚举时,switch 效率更高)。
1.3 函数
函数是代码复用的基本单元。理解参数传递(值传递、引用传递、指针传递)至关重要。
// 值传递:不改变原值
void increment(int a) {
a++;
}
// 引用传递:改变原值
void incrementRef(int &a) {
a++;
}
// 指针传递:改变原值
void incrementPtr(int *a) {
(*a)++;
}
int main() {
int val = 5;
increment(val); // val 仍为 5
incrementRef(val); // val 变为 6
incrementPtr(&val); // val 变为 7
return 0;
}
面试要点:面试官常问“引用和指针的区别?”(引用必须初始化,不能改变指向;指针可以为空,可以改变指向)。
第二部分:面向对象编程 (OOP)
2.1 类与对象
C++ 是面向对象的语言,类是创建对象的蓝图。
class Rectangle {
private:
double width;
double height;
public:
// 构造函数
Rectangle(double w, double h) : width(w), height(h) {}
// 成员函数
double area() const { // const 成员函数保证不修改成员变量
return width * height;
}
// Getter 和 Setter
void setWidth(double w) { width = w; }
double getWidth() const { return width; }
};
2.2 封装、继承与多态
这是 OOP 的三大支柱,也是面试必考点。
- 封装:通过
private、protected、public控制访问权限。 - 继承:允许派生类继承基类的特性。
- 多态:允许不同类的对象对同一消息作出响应。在 C++ 中,多态通过虚函数实现。
// 基类
class Shape {
public:
virtual double area() const = 0; // 纯虚函数,定义抽象类
virtual ~Shape() {} // 虚析构函数,防止内存泄漏
};
// 派生类
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override { // override 关键字确保重写正确
return 3.14159 * radius * radius;
}
};
// 多态的使用
void printArea(const Shape& s) {
std::cout << "Area: " << s.area() << std::endl;
}
int main() {
Circle c(5.0);
printArea(c); // 输出圆的面积
return 0;
}
面试要点:
- 虚函数表 (vtable):解释编译器如何为包含虚函数的类维护一个虚函数表,以及对象如何通过虚指针 (vptr) 访问它。
- 纯虚函数:
virtual void func() = 0;使得类成为抽象类,不能实例化。 - 析构函数:为什么基类的析构函数通常应该是虚函数?(防止通过基类指针删除派生类对象时发生内存泄漏)。
第三部分:内存管理
C++ 的手动内存管理是其强大之处,也是难点和面试重点。
3.1 栈 (Stack) 与堆 (Heap)
- 栈:由编器自动分配和释放,存储局部变量、函数参数。速度快,但空间有限。
- 堆:由程序员手动分配 (
new/malloc) 和释放 (delete/free)。空间大,但管理不当容易造成内存泄漏或碎片。
3.2 智能指针 (Smart Pointers)
现代 C++ (C++11 及以后) 推荐使用智能指针来管理动态内存,遵循 RAII (Resource Acquisition Is Initialization) 原则。
std::unique_ptr:独占所有权,不可复制,只能移动。std::shared_ptr:共享所有权,通过引用计数管理。std::weak_ptr:解决shared_ptr的循环引用问题。
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "MyClass Constructed\n"; }
~MyClass() { std::cout << "MyClass Destructed\n"; }
void doSomething() { std::cout << "Doing something\n"; }
};
void smartPointerDemo() {
// unique_ptr
std::unique_ptr<MyClass> uPtr = std::make_unique<MyClass>();
uPtr->doSomething();
// uPtr2 = uPtr; // 错误:不能复制
std::unique_ptr<MyClass> uPtr2 = std::move(uPtr); // 可以移动,uPtr 变为空
// shared_ptr
std::shared_ptr<MyClass> sPtr1 = std::make_shared<MyClass>();
{
std::shared_ptr<MyClass> sPtr2 = sPtr1; // 引用计数 +1
std::cout << "Use count: " << sPtr1.use_count() << std::endl; // 输出 2
} // sPtr2 离开作用域,引用计数 -1
std::cout << "Use count: " << sPtr1.use_count() << std::endl; // 输出 1
} // sPtr1 离开作用域,引用计数为 0,对象析构
int main() {
smartPointerDemo();
return 0;
}
面试要点:
- 解释
new和malloc的区别(new是操作符,调用构造函数;malloc是库函数,只分配内存)。 - 什么是 RAII?为什么它很重要?
shared_ptr是线程安全的吗?(引用计数的修改是原子的,但指向的对象数据不一定安全)。
第四部分:标准模板库 (STL)
STL 是 C++ 的利器,熟练使用能极大提高编码效率。
4.1 容器 (Containers)
- 序列容器:
vector(动态数组),list(双向链表),deque(双端队列)。 - 关联容器:
set,map(基于红黑树,有序),unordered_set,unordered_map(基于哈希表,无序)。
面试题示例:std::vector 是如何动态扩容的?
- 通常当
vector需要添加元素且容量不足时,它会分配一块更大的内存(通常是当前容量的 1.5 倍或 2 倍),然后将旧元素拷贝或移动到新内存中,最后释放旧内存。
4.2 算法 (Algorithms)
<algorithm> 头文件提供了大量通用算法,如排序、查找、遍历等。
#include <vector>
#include <algorithm>
#include <iostream>
void algorithmDemo() {
std::vector<int> nums = {5, 2, 8, 1, 9};
// 排序
std::sort(nums.begin(), nums.end());
// nums 现在是 {1, 2, 5, 8, 9}
// 查找 (二分查找,需先排序)
bool found = std::binary_search(nums.begin(), nums.end(), 5); // true
// 查找特定元素位置
auto it = std::find(nums.begin(), nums.end(), 8);
if (it != nums.end()) {
std::cout << "Found 8 at index: " << std::distance(nums.begin(), it) << std::endl;
}
// 遍历 (C++11 Lambda)
std::for_each(nums.begin(), nums.end(), [](int n) {
std::cout << n << " ";
});
std::cout << std::endl;
}
第五部分:C++11/14/17/20 新特性
现代 C++ 特性在面试中越来越受重视,展示了你对技术演进的了解。
5.1 自动类型推导 (auto) 和基于范围的 for 循环
std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}};
// auto 自动推导类型
for (auto& pair : myMap) {
// 基于范围的 for 循环
std::cout << pair.first << ": " << pair.second << std::endl;
}
5.2 Lambda 表达式
匿名函数对象,极大简化回调函数和局部算法的编写。
// Lambda 语法: [捕获列表](参数列表) mutable 异常属性 -> 返回类型 { 函数体 }
auto add = [](int a, int b) {
return a + b;
};
std::cout << "Lambda result: " << add(3, 4) << std::endl;
// 捕获列表示例
int factor = 10;
auto multiply = [factor](int a) {
return a * factor; // 按值捕获 factor
};
5.3 右值引用与移动语义 (Move Semantics)
这是 C++11 引入的最重要的特性之一,用于优化性能,避免不必要的深拷贝。
#include <vector>
#include <iostream>
#include <utility> // for std::move
class BigData {
public:
BigData() { std::cout << "Default Constructor\n"; }
// 拷贝构造函数
BigData(const BigData& other) {
std::cout << "Copy Constructor (Expensive!)\n";
}
// 移动构造函数
BigData(BigData&& other) noexcept {
std::cout << "Move Constructor (Cheap!)\n";
}
};
BigData createData() {
BigData temp;
return temp; // 这里通常会触发 RVO (返回值优化) 或移动语义
}
int main() {
BigData d1 = createData(); // 通常直接构造,无拷贝
BigData d2 = d1; // 拷贝构造
BigData d3 = std::move(d1); // 移动构造,d1 不再可用
return 0;
}
面试要点:
- 什么是左值 (Lvalue) 和右值 (Rvalue)?
- 解释
std::move的作用(它只是将左值转换为右值引用,真正的移动发生在移动构造/赋值函数中)。 - 什么是完美转发 (Perfect Forwarding)?(使用
std::forward保持参数的值类别)。
第六部分:高级主题与面试策略
6.1 常见面试算法题
实现单例模式 (Singleton):
- 要点:构造函数私有化,静态实例,线程安全(双重检查锁定或 Meyer’s Singleton)。
class Singleton { public: static Singleton& getInstance() { static Singleton instance; // C++11 保证线程安全 return instance; } private: Singleton() {} Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; };智能指针实现:
- 考察对 RAII 和拷贝/移动语义的理解。
6.2 面试技巧
- 沟通:在写代码前,先与面试官确认需求,理清思路。可以先口述伪代码。
- 边界条件:写完代码后,主动检查边界情况(空指针、空字符串、负数、溢出等)。
- 复杂度分析:写完后,分析代码的时间和空间复杂度。
- 提问环节:准备一些有深度的问题问面试官,如团队使用的 C++ 标准版本、代码库规模、面临的挑战等。
结语
C++ 面试不仅考察语法记忆,更看重对底层原理的理解和工程实践能力。通过系统复习基础、深入理解内存管理和 STL,并熟练运用现代 C++ 特性,你将能自信地应对各种挑战。持续练习,保持好奇心,祝你面试顺利!
