C++面试题基础
最近我帮几个朋友准备C++面试,发现很多题目都是反复出现的经典问题。我整理了一份包含33道高频面试题的完整解析,希望能帮到正在准备面试的你。
接下来我将从这些题目中精选出最重要的几个,深入解析它们的原理和应用场景。
为什么这33道题如此重要
分析了多家知名互联网公司的面试题,发现这33道题的出现频率最高。它们覆盖了C++的核心概念:
基础概念:C和C++区别、static关键字作用
内存管理:智能指针、堆栈区别、内存泄漏
面向对象:虚函数、多态、继承机制
实用技巧: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有什么作用?
标准回答:
修饰全局变量:改变链接属性
修饰局部变量:延长生命周期,保持作用域
修饰类成员:属于类而非对象
2. 结合实际场景
问题:智能指针有什么用?
回答框架:
解决问题:内存泄漏、悬挂指针
实现原理:RAII、引用计数
实际应用:项目中如何使用
性能考虑:开销和权衡
3. 举一反三
例如,问到虚函数时,可以主动提到:
纯虚函数和抽象类
虚继承和菱形问题
虚函数表的内存布局
性能影响
常见错误和避免方法
我总结了几个最常见的错误: