海康威视C++软件开发 二面总结
1. 手写一个线程安全的单例模式(要求使用C++11特性)
答案:
class Singleton {
private:
Singleton() {}
~Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& getInstance() {
static Singleton instance; // C++11保证线程安全
return instance;
}
void doSomething() {
// 业务逻辑
}
};
// 使用
Singleton::getInstance().doSomething();
解释:
- C++11保证局部静态变量初始化的线程安全性
- 使用delete禁止拷贝和赋值
- 懒汉式,第一次调用时才创建
- 无需手动管理锁,性能最优
2. 实现一个生产者-消费者模型(使用条件变量)
答案:
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
template<typename T>
class BlockingQueue {
private:
std::queue<T> queue_;
std::mutex mutex_;
std::condition_variable cond_producer_;
std::condition_variable cond_consumer_;
size_t capacity_;
public:
BlockingQueue(size_t capacity) : capacity_(capacity) {}
void push(const T& item) {
std::unique_lock<std::mutex> lock(mutex_);
// 队列满时等待
cond_producer_.wait(lock, [this]() {
return queue_.size() < capacity_;
});
queue_.push(item);
cond_consumer_.notify_one(); // 通知消费者
}
T pop() {
std::unique_lock<std::mutex> lock(mutex_);
// 队列空时等待
cond_consumer_.wait(lock, [this]() {
return !queue_.empty();
});
T item = queue_.front();
queue_.pop();
cond_producer_.notify_one(); // 通知生产者
return item;
}
};
// 使用示例
BlockingQueue<int> queue(10);
void producer() {
for (int i = 0; i < 100; ++i) {
queue.push(i);
std::cout << "Produced: " << i << std::endl;
}
}
void consumer() {
for (int i = 0; i < 100; ++i) {
int item = queue.pop();
std::cout << "Consumed: " << item << std::endl;
}
}
3. 说说你对内存池的理解?如何设计一个简单的内存池?
答案:内存池的优势:
- 减少频繁malloc/free的开销
- 减少内存碎片
- 提高内存分配效率
- 可以预分配内存
简单实现:
class MemoryPool {
private:
struct Block {
Block* next;
};
Block* freeList_;
size_t blockSize_;
size_t blockCount_;
char* pool_;
public:
MemoryPool(size_t blockSize, size_t blockCount)
: blockSize_(blockSize), blockCount_(blockCount) {
pool_ = new char[blockSize * blockCount];
freeList_ = reinterpret_cast<Block*>(pool_);
// 构建空闲链表
Block* current = freeList_;
for (size_t i = 0; i < blockCount - 1; ++i) {
current->next = reinterpret_cast<Block*>(
pool_ + (i + 1) * blockSize
);
current = current->next;
}
current->next = nullptr;
}
~MemoryPool() {
delete[] pool_;
}
void* allocate() {
if (!freeList_) return nullptr;
Block* block = freeList_;
freeList_ = freeList_->next;
return block;
}
void deallocate(void* ptr) {
Block* block = static_cast<Block*>(ptr);
block->next = freeList_;
freeList_ = block;
}
};
4. 如何实现一个智能指针?请手写shared_ptr的简化版本
答案:
template<typename T>
class SharedPtr {
private:
T* ptr_;
size_t* refCount_;
void release() {
if (refCount_ && --(*refCount_) == 0) {
delete ptr_;
delete refCount_;
}
}
public:
// 构造函数
explicit SharedPtr(T* ptr = nullptr)
: ptr_(ptr), refCount_(ptr ? new size_t(1) : nullptr) {}
// 拷贝构造
SharedPtr(const SharedPtr& other)
: ptr_(other.ptr_), refCount_(other.refCount_) {
if (refCount_) {
++(*refCount_);
}
}
// 移动构造
SharedPtr(SharedPtr&& other) noexcept
: ptr_(other.ptr_), refCount_(other.refCount_) {
other.ptr_ = nullptr;
other.refCount_ = nullptr;
}
// 拷贝赋值
SharedPtr& operator=(const SharedPtr& other) {
if (this != &other) {
release();
ptr_ = other.ptr_;
refCount_ = other.refCount_;
if (refCount_) {
++(*refCount_);
}
}
return *this;
}
// 移动赋值
SharedPtr& operator=(SharedPtr&& other) noexcept {
if (this != &other) {
release();
ptr_ = other.ptr_;
refCount_ = other.refCount_;
other.ptr_ = nullptr;
other.refCount_ = nullptr;
}
return *this;
}
// 析构函数
~SharedPtr() {
release();
}
// 操作符重载
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
T* get() const { return ptr_; }
size_t use_count() const { return refCount_ ? *refCount_ : 0; }
explicit operator bool() const { return ptr_ != nullptr; }
};
5. 说说你对C++对象模型的理解?虚继承的内存布局是怎样的?
答案:C++对象模型:
- 非静态成员变量存储在对象内部
- 静态成员变量存储在全局数据区
- 成员函数存储在代码段
- 虚函数通过虚函数表实现
虚继承的内存布局:
class Base {
int base_data;
};
class Derived1 : virtual public Base {
int d1_data;
};
class Derived2 : virtual public Base {
int d2_data;
};
class Final : public Derived1, public Derived2 {
int final_data;
};
Final对象的内存布局:
[vbptr_D1][d1_data][vbptr_D2][d2_data][final_data][base_data]
- vbptr:虚基类指针,指向虚基类表
- 虚基类Base只有一份实例,位于对象末尾
- 通过虚基类表找到Base的偏移量
虚继承的作用:
- 解决菱形继承问题
- 确保虚基类只有一个实例
6. 手写一个LRU缓存(要求O(1)时间复杂度)
答案:
#include <unordered_map>
#include <list>
class LRUCache {
private:
int capacity_;
std::list<std::pair<int, int>> cache_; // key-value对
std::unordered_map<int, std::list<std::pair<int, int>>::iterator> map_;
public:
LRUCache(int capacity) : capacity_(capacity) {}
int get(int key) {
auto it = map_.find(key);
if (it == map_.end()) {
return -1; // 未找到
}
// 移动到链表头部(最近使用)
cache_.splice(cache_.begin(), cache_, it->second);
return it->second->second;
}
void put(int key, int value) {
auto it = map_.find(key);
if (it != map_.end()) {
// 已存在,更新值并移到头部
it->second->second = value;
cache_.splice(cache_.begin(), cache_, it->second);
return;
}
// 新插入
if (cache_.size() >= capacity_) {
// 删除最久未使用的(链表尾部)
int old_key = cache_.back().first;
cache_.pop_back();
map_.erase(old_key);
}
// 插入到头部
cache_.emplace_front(key, value);
map_[key] = cache_.begin();
}
};
// 使用示例
LRUCache cache(2);
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 移除 key 2
cache.get(2); // 返回 -1 (未找到)
7. 如何实现一个线程池?说说你的设计思路
答案:
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>
class ThreadPool {
private:
std::vector<std::thread> workers_;
std::queue<std::function<void()>> tasks_;
std::mutex mutex_;
std::condition_variable condition_;
bool stop_;
public:
ThreadPool(size_t threads) : stop_(false) {
for (size_t i = 0; i < 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()) {
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
C++八股文全集 文章被收录于专栏
本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。