C++ 面试模拟02
第一部分:基础知识
- 什么是拷贝构造函数和赋值运算符?它们之间有什么区别?
- 在 C++ 中,
const
关键字的作用是什么?有哪些常见用法? - C++ 中的内存管理机制是怎样的?如何避免内存泄漏?
- 虚函数(virtual function)的作用是什么?虚函数表(vtable)是如何工作的?
第二部分:面向对象编程
- 什么是多态性?C++ 中如何实现运行时多态?
- 请解释什么是继承以及继承的优缺点。
- C++ 中的“菱形继承”是什么?如何通过虚继承来解决它带来的问题?
- 什么是抽象类?抽象类与接口类有何不同?
第三部分:STL(标准模板库)
std::vector
和std::list
有什么区别?它们各自的优点和缺点是什么?- 什么是迭代器?有哪些类型的迭代器?
std::map
和std::unordered_map
的底层实现分别是什么?性能有何差异?- C++ 中的容器适配器有哪些?请举例说明。
第四部分:并发与多线程
- C++ 中如何创建线程?有哪些常见的线程管理方式?
- 什么是互斥锁(mutex)?如何避免死锁?
- 解释一下条件变量(condition variable)的作用,并给出一个简单的使用例子。
- 什么是线程局部存储(Thread Local Storage)?在 C++ 中如何使用?
第五部分:C++ 高级特性
- 什么是移动语义?C++11 中如何实现移动构造函数与移动赋值运算符?
- C++20 中的协程(coroutine)是什么?它们是如何工作的?
- 模板元编程(Template Metaprogramming)是什么?在 C++ 中有哪些实际应用?
- 什么是 SFINAE?在模板中如何利用 SFINAE 进行类型选择?
第六部分:现场编程
题目:
请你实现一个线程安全的计数器类 ThreadSafeCounter
,它支持以下功能:
increment()
:计数器加 1decrement()
:计数器减 1get()
:返回当前计数器的值
要求:
- 使用
std::mutex
保证线程安全。 - 实现拷贝构造和赋值运算符重载。
答案:
C++知识点/面试问题指南-CSDN博客
第一部分:基础知识
-
拷贝构造函数和赋值运算符的区别:
- 拷贝构造函数:当用一个对象初始化另一个新对象时调用,用于对象的创建。
- 赋值运算符:在已存在对象的基础上,将另一个对象的内容赋值给它。
- 区别:拷贝构造函数是在对象初始化时调用的,赋值运算符则在对象已经创建后进行赋值。
-
const
关键字的作用:- 修饰变量:表示该变量的值不能修改。
- 修饰指针:区分指针本身是否可变和指向的对象是否可变。
- 修饰成员函数:表示该成员函数不会修改类的成员变量。
-
内存管理:
- C++ 使用动态分配(
new
/delete
)、栈分配以及 RAII(资源获取即初始化)来管理内存。 - 避免内存泄漏的方法包括使用智能指针(如
std::shared_ptr
和std::unique_ptr
)。
- C++ 使用动态分配(
-
虚函数和 vtable:
- 虚函数:用于实现运行时多态,基类中标记为
virtual
的函数可以在派生类中被重写。 - 虚函数表(vtable):编译器为包含虚函数的类创建一个表,指向虚函数的地址,保证在运行时能够调用正确的函数。
- 虚函数:用于实现运行时多态,基类中标记为
第二部分:面向对象编程
-
多态性:
- C++ 中通过虚函数实现运行时多态。基类的虚函数可以在派生类中重写,通过基类指针或引用调用时,动态地选择合适的派生类实现。
-
继承:
- 优点:允许代码重用和扩展基类功能。
- 缺点:不当的使用可能导致强耦合,难以维护。
-
菱形继承和虚继承:
- 菱形继承:如果一个类从多个基类继承,而这些基类又继承自同一祖先类,会导致多个基类有相同的祖先成员。
- 虚继承:通过
virtual
继承,确保继承链中的祖先类只会有一份副本。
-
抽象类:
- 抽象类:至少包含一个纯虚函数(
= 0
的函数)。不能实例化,只能被继承。 - 接口类:所有成员函数都为纯虚函数。
- 抽象类:至少包含一个纯虚函数(
第三部分:STL
-
std::vector
和std::list
区别:std::vector
:基于动态数组,支持随机访问,插入/删除代价高(中间位置)。std::list
:基于双向链表,不支持随机访问,插入/删除较快。- 优缺点:
vector
适合频繁随机访问,list
适合频繁插入/删除。
-
迭代器的类型:
- 输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器。
-
std::map
vsstd::unordered_map
:std::map
:基于红黑树(有序),O(log n) 查找。std::unordered_map
:基于哈希表(无序),O(1) 平均查找,但最坏情况 O(n)。
-
容器适配器:
- 包括
std::stack
、std::queue
、std::priority_queue
,它们通过封装其他容器(如deque
、list
)实现特定的行为。
- 包括
第四部分:并发与多线程
-
线程的创建:
- 使用
std::thread
创建线程:
- 使用
std::thread t([] { std::cout << "Thread is running"; });
t.join(); // 等待线程结束
-
互斥锁(mutex)与死锁:
- 互斥锁:
std::mutex
用于保护共享资源避免数据竞争。通过std::lock_guard
或std::unique_lock
自动管理锁。 - 避免死锁:使用相同顺序加锁,或者使用
std::lock()
同时加锁多个资源。
- 互斥锁:
-
条件变量:
- 用于线程间同步,等待特定条件:
std::condition_variable cv;
std::mutex mtx;
bool ready = false;
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&ready] { return ready; });
线程局部存储:
- 使用
thread_local
关键字为每个线程定义独立的存储空间:
thread_local int x = 0; // 每个线程都有自己独立的 x 变量
第五部分:C++ 高级特性
-
移动语义:
- 移动语义通过 移动构造函数 和 移动赋值运算符 避免不必要的深拷贝,使用
std::move
来将对象转化为右值引用,从而实现资源的转移而不是复制。
- 移动语义通过 移动构造函数 和 移动赋值运算符 避免不必要的深拷贝,使用
-
C++20 协程:
- 协程通过
co_await
、co_yield
、co_return
实现协程的控制流,用来处理异步任务和生成器。 - 使用例:
- 协程通过
std::future<int> asyncAdd(int a, int b) {
co_return a + b;
}
-
模板元编程:
- 通过模板递归和模板特化实现编译期计算。常见应用包括类型萃取(type traits)和元函数。
-
SFINAE:
- 在模板中,"Substitution Failure Is Not An Error" 规则允许在匹配失败时尝试其他模板。常用于限制模板特化。
template<typename T>
std::enable_if_t<std::is_integral<T>::value, T> add(T a, T b) { return a + b; }
第六部分:现场编程
#include <iostream>
#include <mutex>
class ThreadSafeCounter {
private:
int counter;
std::mutex mtx;
public:
ThreadSafeCounter() : counter(0) {}
// 增加计数
void increment() {
std::lock_guard<std::mutex> lock(mtx);
++counter;
}
// 减少计数
void decrement() {
std::lock_guard<std::mutex> lock(mtx);
--counter;
}
// 获取当前计数
int get() const {
std::lock_guard<std::mutex> lock(mtx);
return counter;
}
// 拷贝构造函数
ThreadSafeCounter(const ThreadSafeCounter& other) {
std::lock_guard<std::mutex> lock(other.mtx);
counter = other.counter;
}
// 赋值运算符
ThreadSafeCounter& operator=(const ThreadSafeCounter& other) {
if (this != &other) {
std::lock_guard<std::mutex> lockThis(mtx);
std::lock_guard<std::mutex> lockOther(other.mtx);
counter = other.counter;
}
return *this;
}
};