C++ 面试题解
概述
C++面试涵盖语言基础、高级特性、算法数据结构、系统设计等多个方面。本章精选常见C++面试题目,提供详细解答和代码示例,帮助准备技术面试。
📚 语言基础题
指针和引用
cpp
// Q1: 指针和引用的区别
/*
面试官:请解释指针和引用的区别
答案要点:
1. 初始化:引用必须在声明时初始化,指针可以后初始化
2. 重新赋值:引用不能重新指向其他对象,指针可以
3. 空值:引用不能为空,指针可以为nullptr
4. 内存:引用不占用额外内存,指针占用内存
5. 运算:指针支持算术运算,引用不支持
*/
void demonstratePointerVsReference() {
int a = 10, b = 20;
// 引用必须初始化
int& ref = a; // 正确
// int& ref2; // 错误:引用必须初始化
// 指针可以不初始化
int* ptr; // 正确但危险
ptr = &a; // 后续赋值
// 引用不能重新指向
// ref = b; // 这是赋值,不是重新指向
// 指针可以重新指向
ptr = &b; // 正确
// 引用不能为空
// int& nullRef = nullptr; // 错误
// 指针可以为空
int* nullPtr = nullptr; // 正确
// 指针算术
int arr[] = {1, 2, 3};
int* p = arr;
p++; // 指向下一个元素
// ref++; // 错误:引用不支持算术运算
}
// Q2: 野指针、悬挂指针、内存泄漏
void demonstratePointerProblems() {
// 野指针:未初始化的指针
int* wildPtr; // 野指针,指向未知位置
// *wildPtr = 10; // 危险:可能导致程序崩溃
// 悬挂指针:指向已释放内存的指针
int* danglingPtr = new int(42);
delete danglingPtr;
// *danglingPtr = 10; // 危险:悬挂指针
danglingPtr = nullptr; // 应该置空
// 内存泄漏:分配的内存未释放
for (int i = 0; i < 1000; ++i) {
int* leak = new int(i);
// 忘记delete leak; // 内存泄漏
}
// 正确做法:使用智能指针
std::unique_ptr<int> smartPtr = std::make_unique<int>(42);
// 自动释放,无需手动delete
}类和对象
cpp
// Q3: 构造函数和析构函数的调用顺序
class Base {
public:
Base() { std::cout << "Base构造" << std::endl; }
virtual ~Base() { std::cout << "Base析构" << std::endl; }
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived构造" << std::endl; }
~Derived() override { std::cout << "Derived析构" << std::endl; }
};
void demonstrateConstructorOrder() {
std::cout << "创建对象:" << std::endl;
Derived obj; // 输出:Base构造 -> Derived构造
std::cout << "销毁对象:" << std::endl;
// 自动析构:Derived析构 -> Base析构
}
// Q4: 深拷贝vs浅拷贝
class ShallowCopy {
private:
int* data_;
public:
ShallowCopy(int value) : data_(new int(value)) {}
// 默认拷贝构造函数(浅拷贝)
// ShallowCopy(const ShallowCopy& other) : data_(other.data_) {}
~ShallowCopy() { delete data_; } // 可能导致双重删除
};
class DeepCopy {
private:
int* data_;
public:
DeepCopy(int value) : data_(new int(value)) {}
// 深拷贝构造函数
DeepCopy(const DeepCopy& other) : data_(new int(*other.data_)) {}
// 深拷贝赋值运算符
DeepCopy& operator=(const DeepCopy& other) {
if (this != &other) {
delete data_;
data_ = new int(*other.data_);
}
return *this;
}
~DeepCopy() { delete data_; }
int getValue() const { return *data_; }
};
// Q5: 虚函数和纯虚函数
class Shape {
public:
// 虚函数:有默认实现,可被重写
virtual double area() const {
return 0.0;
}
// 纯虚函数:没有实现,必须被重写
virtual void draw() const = 0;
// 虚析构函数:确保派生类析构函数被调用
virtual ~Shape() = default;
};
class Circle : public Shape {
private:
double radius_;
public:
Circle(double radius) : radius_(radius) {}
double area() const override {
return 3.14159 * radius_ * radius_;
}
void draw() const override {
std::cout << "Drawing circle" << std::endl;
}
};
// Q6: 多态实现原理
void demonstratePolymorphism() {
std::unique_ptr<Shape> shape = std::make_unique<Circle>(5.0);
// 运行时多态:通过虚函数表实现
std::cout << "Area: " << shape->area() << std::endl; // 调用Circle::area()
shape->draw(); // 调用Circle::draw()
// 虚函数表解释:
// 1. 每个有虚函数的类都有一个虚函数表
// 2. 对象的前几个字节存储指向虚函数表的指针
// 3. 调用虚函数时通过虚函数表查找实际函数地址
}🔧 高级特性题
模板编程
cpp
// Q7: 函数模板vs类模板
template<typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
template<typename T>
class Stack {
private:
std::vector<T> data_;
public:
void push(const T& item) { data_.push_back(item); }
T pop() {
if (data_.empty()) {
throw std::runtime_error("Stack is empty");
}
T item = data_.back();
data_.pop_back();
return item;
}
bool empty() const { return data_.empty(); }
size_t size() const { return data_.size(); }
};
// Q8: 模板特化
template<typename T>
class TypeInfo {
public:
static std::string getName() { return "unknown"; }
};
// 全特化
template<>
class TypeInfo<int> {
public:
static std::string getName() { return "integer"; }
};
template<>
class TypeInfo<std::string> {
public:
static std::string getName() { return "string"; }
};
// 偏特化(只对类模板有效)
template<typename T>
class TypeInfo<T*> {
public:
static std::string getName() { return "pointer to " + TypeInfo<T>::getName(); }
};
void demonstrateTemplateSpecialization() {
std::cout << TypeInfo<int>::getName() << std::endl; // integer
std::cout << TypeInfo<double>::getName() << std::endl; // unknown
std::cout << TypeInfo<int*>::getName() << std::endl; // pointer to integer
}
// Q9: SFINAE (Substitution Failure Is Not An Error)
template<typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
isEven(T value) {
return value % 2 == 0;
}
template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
isEven(T value) {
return false; // 浮点数没有偶数概念
}
// C++17 if constexpr版本
template<typename T>
bool isEvenModern(T value) {
if constexpr (std::is_integral_v<T>) {
return value % 2 == 0;
} else {
return false;
}
}内存管理
cpp
// Q10: 智能指针的区别和使用场景
void demonstrateSmartPointers() {
// unique_ptr:独占所有权
std::unique_ptr<int> uniquePtr = std::make_unique<int>(42);
// std::unique_ptr<int> copy = uniquePtr; // 错误:不能拷贝
std::unique_ptr<int> moved = std::move(uniquePtr); // 可以移动
// shared_ptr:共享所有权
std::shared_ptr<int> sharedPtr1 = std::make_shared<int>(100);
std::shared_ptr<int> sharedPtr2 = sharedPtr1; // 可以拷贝
std::cout << "引用计数: " << sharedPtr1.use_count() << std::endl; // 2
// weak_ptr:避免循环引用
std::weak_ptr<int> weakPtr = sharedPtr1;
if (auto locked = weakPtr.lock()) { // 尝试获取shared_ptr
std::cout << "对象仍然存在: " << *locked << std::endl;
}
}
// Q11: 循环引用问题
class Parent;
class Child;
class Parent {
public:
std::shared_ptr<Child> child;
~Parent() { std::cout << "Parent destroyed" << std::endl; }
};
class Child {
public:
std::weak_ptr<Parent> parent; // 使用weak_ptr避免循环引用
~Child() { std::cout << "Child destroyed" << std::endl; }
};
void demonstrateCircularReference() {
auto parent = std::make_shared<Parent>();
auto child = std::make_shared<Child>();
parent->child = child;
child->parent = parent; // weak_ptr不增加引用计数
// 当函数结束时,parent和child都会被正确销毁
}
// Q12: 内存对齐
struct UnalignedStruct {
char a; // 1 byte
int b; // 4 bytes
char c; // 1 byte
// 实际大小可能是12字节(而不是6字节)由于内存对齐
};
struct AlignedStruct {
int b; // 4 bytes
char a; // 1 byte
char c; // 1 byte
// 实际大小可能是8字节
};
void demonstrateAlignment() {
std::cout << "UnalignedStruct size: " << sizeof(UnalignedStruct) << std::endl;
std::cout << "AlignedStruct size: " << sizeof(AlignedStruct) << std::endl;
// 使用alignof查看对齐要求
std::cout << "int alignment: " << alignof(int) << std::endl;
std::cout << "char alignment: " << alignof(char) << std::endl;
}🧮 算法和数据结构题
经典算法实现
cpp
// Q13: 实现快速排序
template<typename Iterator>
void quickSort(Iterator begin, Iterator end) {
if (std::distance(begin, end) <= 1) {
return;
}
auto pivot = std::prev(end); // 选择最后一个元素作为基准
auto middle = std::partition(begin, pivot, [pivot](const auto& elem) {
return elem < *pivot;
});
std::iter_swap(middle, pivot);
quickSort(begin, middle);
quickSort(std::next(middle), end);
}
// Q14: 实现LRU缓存
template<typename Key, typename Value>
class LRUCache {
private:
using ListIter = typename std::list<std::pair<Key, Value>>::iterator;
size_t capacity_;
std::list<std::pair<Key, Value>> cache_list_;
std::unordered_map<Key, ListIter> cache_map_;
public:
explicit LRUCache(size_t capacity) : capacity_(capacity) {}
Value get(const Key& key) {
auto it = cache_map_.find(key);
if (it == cache_map_.end()) {
throw std::runtime_error("Key not found");
}
// 移动到前面(最近使用)
cache_list_.splice(cache_list_.begin(), cache_list_, it->second);
return it->second->second;
}
void put(const Key& key, const Value& value) {
auto it = cache_map_.find(key);
if (it != cache_map_.end()) {
// 更新现有键值
it->second->second = value;
cache_list_.splice(cache_list_.begin(), cache_list_, it->second);
} else {
// 添加新键值
if (cache_list_.size() >= capacity_) {
// 删除最旧的元素
auto last = cache_list_.back();
cache_map_.erase(last.first);
cache_list_.pop_back();
}
cache_list_.emplace_front(key, value);
cache_map_[key] = cache_list_.begin();
}
}
};
// Q15: 实现线程安全的单例模式
class ThreadSafeSingleton {
private:
static std::unique_ptr<ThreadSafeSingleton> instance_;
static std::mutex mutex_;
ThreadSafeSingleton() = default;
public:
static ThreadSafeSingleton* getInstance() {
std::lock_guard<std::mutex> lock(mutex_);
if (instance_ == nullptr) {
instance_ = std::unique_ptr<ThreadSafeSingleton>(new ThreadSafeSingleton());
}
return instance_.get();
}
// 更简单的C++11版本(推荐)
static ThreadSafeSingleton& getInstanceStatic() {
static ThreadSafeSingleton instance; // C++11保证线程安全
return instance;
}
ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
};
std::unique_ptr<ThreadSafeSingleton> ThreadSafeSingleton::instance_ = nullptr;
std::mutex ThreadSafeSingleton::mutex_;复杂数据结构
cpp
// Q16: 实现红黑树或AVL树的基本操作
enum class Color { RED, BLACK };
template<typename T>
struct RBTreeNode {
T data;
Color color;
std::shared_ptr<RBTreeNode> left;
std::shared_ptr<RBTreeNode> right;
std::weak_ptr<RBTreeNode> parent;
RBTreeNode(const T& value) : data(value), color(Color::RED) {}
};
template<typename T>
class RedBlackTree {
private:
using NodePtr = std::shared_ptr<RBTreeNode<T>>;
NodePtr root_;
void rotateLeft(NodePtr node) {
auto right = node->right;
node->right = right->left;
if (right->left) {
right->left->parent = node;
}
right->parent = node->parent;
if (auto parent = node->parent.lock()) {
if (node == parent->left) {
parent->left = right;
} else {
parent->right = right;
}
} else {
root_ = right;
}
right->left = node;
node->parent = right;
}
void fixInsert(NodePtr node) {
while (node != root_ && node->parent.lock()->color == Color::RED) {
auto parent = node->parent.lock();
auto grandparent = parent->parent.lock();
if (parent == grandparent->left) {
auto uncle = grandparent->right;
if (uncle && uncle->color == Color::RED) {
parent->color = Color::BLACK;
uncle->color = Color::BLACK;
grandparent->color = Color::RED;
node = grandparent;
} else {
if (node == parent->right) {
node = parent;
rotateLeft(node);
parent = node->parent.lock();
grandparent = parent->parent.lock();
}
parent->color = Color::BLACK;
grandparent->color = Color::RED;
rotateRight(grandparent);
}
} else {
// 对称情况
// ...类似实现
}
}
root_->color = Color::BLACK;
}
public:
void insert(const T& value) {
auto newNode = std::make_shared<RBTreeNode<T>>(value);
if (!root_) {
root_ = newNode;
root_->color = Color::BLACK;
return;
}
// BST插入逻辑
NodePtr current = root_;
NodePtr parent = nullptr;
while (current) {
parent = current;
if (value < current->data) {
current = current->left;
} else {
current = current->right;
}
}
newNode->parent = parent;
if (value < parent->data) {
parent->left = newNode;
} else {
parent->right = newNode;
}
fixInsert(newNode);
}
};🧪 系统设计题
设计模式应用
cpp
// Q17: 设计一个线程池
class ThreadPool {
private:
std::vector<std::thread> workers_;
std::queue<std::function<void()>> tasks_;
std::mutex mutex_;
std::condition_variable condition_;
bool stop_;
public:
explicit ThreadPool(size_t num_threads) : stop_(false) {
for (size_t i = 0; i < num_threads; ++i) {
workers_.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mutex_);
condition_.wait(lock, [this] { return stop_ || !tasks_.empty(); });
if (stop_ && tasks_.empty()) {
return;
}
task = std::move(tasks_.front());
tasks_.pop();
}
task();
}
});
}
}
template<typename F, typename... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type> {
using ReturnType = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<ReturnType()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<ReturnType> result = task->get_future();
{
std::unique_lock<std::mutex> lock(mutex_);
if (stop_) {
throw std::runtime_error("enqueue on stopped ThreadPool");
}
tasks_.emplace([task] { (*task)(); });
}
condition_.notify_one();
return result;
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(mutex_);
stop_ = true;
}
condition_.notify_all();
for (std::thread& worker : workers_) {
worker.join();
}
}
};
// Q18: 观察者模式实现
template<typename EventType>
class Observer {
public:
virtual ~Observer() = default;
virtual void onEvent(const EventType& event) = 0;
};
template<typename EventType>
class Subject {
private:
std::vector<std::weak_ptr<Observer<EventType>>> observers_;
public:
void subscribe(std::shared_ptr<Observer<EventType>> observer) {
observers_.push_back(observer);
}
void unsubscribe(std::shared_ptr<Observer<EventType>> observer) {
observers_.erase(
std::remove_if(observers_.begin(), observers_.end(),
[observer](const std::weak_ptr<Observer<EventType>>& weak_obs) {
return weak_obs.lock() == observer;
}),
observers_.end());
}
void notify(const EventType& event) {
// 清理失效的观察者
observers_.erase(
std::remove_if(observers_.begin(), observers_.end(),
[](const std::weak_ptr<Observer<EventType>>& weak_obs) {
return weak_obs.expired();
}),
observers_.end());
// 通知所有观察者
for (auto& weak_obs : observers_) {
if (auto obs = weak_obs.lock()) {
obs->onEvent(event);
}
}
}
};总结
面试准备策略
- 基础扎实: 指针、引用、类、继承、多态
- 现代特性: 智能指针、lambda、移动语义
- 算法功底: 排序、搜索、动态规划
- 系统设计: 设计模式、并发编程
- 实际经验: 项目经历、问题解决
高频考点
| 类别 | 重点内容 | 难度 |
|---|---|---|
| 语言基础 | 指针引用、类设计、虚函数 | 中等 |
| 内存管理 | 智能指针、RAII、内存泄漏 | 中等 |
| 模板编程 | 模板特化、SFINAE | 困难 |
| 并发编程 | 线程安全、锁机制 | 困难 |
| 算法实现 | 排序、树、图算法 | 中等 |
回答技巧
- 结构清晰: 先答要点,再举例说明
- 代码演示: 用代码证明理解深度
- 对比分析: 比较不同方案优劣
- 实际应用: 结合项目经验说明
- 扩展思考: 主动讨论相关问题
面试建议
- 准备充分: 复习基础概念和高级特性
- 练习编码: 手写常见算法和数据结构
- 模拟面试: 找同事或朋友练习
- 项目准备: 深入理解自己的项目
- 持续学习: 关注C++标准发展
C++面试不仅考察语言知识,更重要的是解决问题的能力和工程实践经验。扎实的基础加上丰富的实践是成功的关键。