海康威视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。

全部评论
感谢分享,吸欧气!
点赞 回复 分享
发布于 昨天 19:36 四川

相关推荐

评论
1
5
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务