C++面试题基础:高频考题深度解析

深度解析33道C++经典面试题,涵盖static关键字、智能指针、虚函数等核心概念,结合大厂面试经验,助你应对技术面试。包含实战技巧、常见错误分析和面试策略,帮你系统掌握C++知识点,轻松应对技术挑战,成为面试强者,拿到心仪理想offer。

C++面试题基础

最近我帮几个朋友准备C++面试,发现很多题目都是反复出现的经典问题。我整理了一份包含33道高频面试题的完整解析,希望能帮到正在准备面试的你。

接下来我将从这些题目中精选出最重要的几个,深入解析它们的原理和应用场景。

为什么这33道题如此重要

分析了多家知名互联网公司的面试题,发现这33道题的出现频率最高。它们覆盖了C++的核心概念:

  1. 基础概念:C和C++区别、static关键字作用

  2. 内存管理:智能指针、堆栈区别、内存泄漏

  3. 面向对象:虚函数、多态、继承机制

  4. 实用技巧:const使用、类型转换、设计模式

Static关键字的三重身份

static是C++中最容易被误解的关键字之一。总结了它的三种完全不同的用法:

1. 全局作用域的链接属性

1
2
3
4
5
// file1.cpp
static int global_var = 100;  // 只在本文件内可见

// file2.cpp
static int global_var = 200;  // 这是不同的变量,无冲突

关键点:static修饰全局变量时,改变的是链接属性,从external变为internal。

2. 局部静态变量的生命周期

1
2
3
4
int get_unique_id() {
    static int counter = 0;  // 只初始化一次
    return counter++;       // 每次调用都递增
}

3. 类成员的静态特性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class MyClass {
public:
    static int count;        // 静态成员变量
    static void print() {    // 静态成员函数
        cout << "Count: " << count << endl;
        // error: cannot access 'this'
        // cout << this->member_var << endl;
    }
private:
    int member_var;
};

int MyClass::count = 0;  // 必须在类外初始化

观察到很多面试者容易混淆静态成员函数和普通成员函数的区别。关键在于静态成员函数没有this指针。

智能指针:现代C++的内存管理神器

内存管理是C++面试的重中之重。我总结了四种智能指针的核心差异:

unique_ptr:独占所有权

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <memory>

void unique_ptr_example() {
    auto ptr1 = make_unique<int>(42);
    // auto ptr2 = ptr1;  // 编译错误:不能复制

    auto ptr2 = move(ptr1);  // 转移所有权
    if (ptr1 == nullptr) {
        cout << "ptr1 is now empty" << endl;
    }
}

核心优势:编译时检查,运行时零开销, RAII机制自动管理内存。

shared_ptr:引用计数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void shared_ptr_example() {
    auto ptr1 = make_shared<int>(42);
    auto ptr2 = ptr1;  // 可以复制

    cout << "Use count: " << ptr1.use_count() << endl; // 输出2

    {
        auto ptr3 = ptr1;
        cout << "Use count: " << ptr1.use_count() << endl; // 输出3
    }

    cout << "Use count: " << ptr1.use_count() << endl; // 输出2
}

weak_ptr:解决循环引用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Node {
public:
    shared_ptr<Node> next;
    weak_ptr<Node> parent;  // 避免循环引用
    ~Node() { cout << "Node destroyed" << endl; }
};

void weak_ptr_example() {
    auto parent = make_shared<Node>();
    auto child = make_shared<Node>();

    parent->next = child;
    child->parent = parent;  // 使用weak_ptr避免循环引用
}

为什么废弃auto_ptr

查看过auto_ptr的源码,发现它的设计有严重缺陷:

1
2
auto_ptr<int> p1(new int(42));
auto_ptr<int> p2 = p1;  // p1变成NULL!这是非常危险的行为

虚函数与多态:C++的核心魅力

虚函数是C++面试的必考题。通过一个完整的例子来解析它的工作原理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Animal {
public:
    virtual void speak() {
        cout << "Animal speaks" << endl;
    }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() override {
        cout << "Woof!" << endl;
    }
};

void polymorphism_example() {
    Animal* animal = new Dog();
    animal->speak();  // 输出: Woof!
    delete animal;
}

虚函数表的工作机制

深入研究了虚函数的实现原理,每个包含虚函数的类都有一个虚函数表(vtable):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 编译器生成的伪代码
struct Animal_VTable {
    void (*speak)(Animal*);
    void (*destructor)(Animal*);
};

struct Animal {
    Animal_VTable* vtable;
};

void Animal_speak_impl(Animal* this) {
    cout << "Animal speaks" << endl;
}

Animal_VTable Animal_vtable = {
    &Animal_speak_impl,
    &Animal_destructor_impl
};

析构函数必须是虚函数

这是在实际项目中踩过的坑:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Base {
public:
    ~Base() { cout << "Base destructor" << endl; }
};

class Derived : public Base {
public:
    ~Derived() { cout << "Derived destructor" << endl; }
};

void destructor_problem() {
    Base* ptr = new Derived();
    delete ptr;  // 只调用Base的析构函数!
}

正确做法

1
2
3
4
class Base {
public:
    virtual ~Base() { cout << "Base destructor" << endl; }
};

内存管理:堆栈的区别与应用

经常被问到堆和栈的区别,这里用一个表格总结:

实际应用场景

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void memory_management_example() {
    int stack_var = 42;  // 栈上分配,函数结束时自动释放

    int* heap_var = new int(42);  // 堆上分配,需要手动释放
    delete heap_var;

    // 智能指针是最佳实践
    auto smart_ptr = make_unique<int>(42);
    // 自动释放,无需手动delete
}

const的正确使用姿势

const是C++中最重要的关键字之一,我总结了几种常见用法:

1. const变量

1
2
const int MAX_SIZE = 100;  // 编译时常量
const std::string str = "hello";  // 运行时常量

2. const成员函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class MyClass {
public:
    int getValue() const {  // 承诺不修改成员变量
        return value;
    }

    void setValue(int new_value) {  // 非const函数
        value = new_value;
    }

private:
    int value;
};

3. const参数

1
2
3
4
5
6
// 按值传递:const无意义
void func1(const int value);  // 等同于 void func1(int value)

// 按引用传递:const很重要
void func2(const std::string& str);  // 不允许修改str
void func3(std::string& str);        // 允许修改str

4. const返回值

1
2
3
const std::string getName() const {
    return name;  // 返回const对象,防止修改临时对象
}

类型转换:安全第一

C++提供了四种类型转换操作符,按安全性排序:

1. static_cast:编译时检查

1
2
3
4
5
double d = 3.14;
int i = static_cast<int>(d);  // 安全的显式转换

Base* base_ptr = new Derived();
Derived* derived_ptr = static_cast<Derived*>(base_ptr);  // 向下转换

2. dynamic_cast:运行时检查

1
2
3
4
5
6
7
Base* base_ptr = get_object();
Derived* derived_ptr = dynamic_cast<Derived*>(base_ptr);
if (derived_ptr) {
    // 转换成功
} else {
    // 转换失败
}

3. const_cast:移除const属性

1
2
const char* const_str = "hello";
char* modifiable_str = const_cast<char*>(const_str);

4. reinterpret_cast:最危险的转换

1
2
int* int_ptr = new int(42);
char* char_ptr = reinterpret_cast<char*>(int_ptr);

实战面试技巧

基于面试经验,我总结了几个回答技巧:

1. 先讲概念,再说细节

问题:static有什么作用?

标准回答

  1. 修饰全局变量:改变链接属性

  2. 修饰局部变量:延长生命周期,保持作用域

  3. 修饰类成员:属于类而非对象

2. 结合实际场景

问题:智能指针有什么用?

回答框架

  1. 解决问题:内存泄漏、悬挂指针

  2. 实现原理:RAII、引用计数

  3. 实际应用:项目中如何使用

  4. 性能考虑:开销和权衡

3. 举一反三

例如,问到虚函数时,可以主动提到:

  • 纯虚函数和抽象类

  • 虚继承和菱形问题

  • 虚函数表的内存布局

  • 性能影响

常见错误和避免方法

我总结了几个最常见的错误:

Licensed under CC BY-NC-SA 4.0
最后更新于 2026-01-11 00:00