引言

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-elseswitchforwhiledo-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;
    }
}

面试要点:注意 breakswitch 中的作用,以及 switchif-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 的三大支柱,也是面试必考点。

  • 封装:通过 privateprotectedpublic 控制访问权限。
  • 继承:允许派生类继承基类的特性。
  • 多态:允许不同类的对象对同一消息作出响应。在 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;
}

面试要点

  1. 虚函数表 (vtable):解释编译器如何为包含虚函数的类维护一个虚函数表,以及对象如何通过虚指针 (vptr) 访问它。
  2. 纯虚函数virtual void func() = 0; 使得类成为抽象类,不能实例化。
  3. 析构函数:为什么基类的析构函数通常应该是虚函数?(防止通过基类指针删除派生类对象时发生内存泄漏)。

第三部分:内存管理

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;
}

面试要点

  • 解释 newmalloc 的区别(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 常见面试算法题

  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;
    };
    
  2. 智能指针实现

    • 考察对 RAII 和拷贝/移动语义的理解。

6.2 面试技巧

  1. 沟通:在写代码前,先与面试官确认需求,理清思路。可以先口述伪代码。
  2. 边界条件:写完代码后,主动检查边界情况(空指针、空字符串、负数、溢出等)。
  3. 复杂度分析:写完后,分析代码的时间和空间复杂度。
  4. 提问环节:准备一些有深度的问题问面试官,如团队使用的 C++ 标准版本、代码库规模、面临的挑战等。

结语

C++ 面试不仅考察语法记忆,更看重对底层原理的理解和工程实践能力。通过系统复习基础、深入理解内存管理和 STL,并熟练运用现代 C++ 特性,你将能自信地应对各种挑战。持续练习,保持好奇心,祝你面试顺利!