网易 软件开发-C++ 实习 二面+HR面

1. 自我介绍,介绍一下项目背景和主要负责的功能

2. C++ 对象内存布局中,虚函数、多继承和虚继承分别会带来什么变化

答案:普通类对象一般按照成员声明顺序布局,中间可能因为对齐产生 padding。如果类里有虚函数,主流编译器通常会给对象加一个虚表指针,虚表里存放虚函数地址。对象大小会因此增加一个指针大小,虚函数调用也会多一次间接寻址。

多继承时,一个派生类对象内部会包含多个基类子对象。如果多个基类都有虚函数,对象里可能会有多个虚表指针。派生类指针转成不同基类指针时,地址可能发生偏移调整。虚继承主要解决菱形继承中公共基类重复的问题,但它会引入额外的虚基类偏移信息,对象布局和访问成本都会更复杂。

面试里如果让判断对象大小,不能只把成员大小简单相加,还要考虑 vptr、对齐、多继承基类子对象和虚继承额外结构。

代码:

#include <iostream>
using namespace std;

struct A {
    virtual void fa() {}
    int a;
};

struct B {
    virtual void fb() {}
    int b;
};

struct C : public A, public B {
    int c;
};

int main() {
    C obj;
    cout << "sizeof(A): " << sizeof(A) << endl;
    cout << "sizeof(B): " << sizeof(B) << endl;
    cout << "sizeof(C): " << sizeof(C) << endl;

    A* pa = &obj;
    B* pb = &obj;

    cout << "C*: " << &obj << endl;
    cout << "A*: " << pa << endl;
    cout << "B*: " << pb << endl;
}

3. 无锁队列如何实现,无锁和有锁的核心区别是什么,C++ 内存序有什么作用

答案:无锁队列通常依赖原子变量和 CAS,而不是 mutex。它的目标不是完全没有竞争,而是避免线程因为锁阻塞在内核态。有锁队列逻辑简单,临界区由 mutex 保护,正确性更容易保证;无锁队列要自己处理并发读写、内存可见性、ABA、队列满和队列空这些问题。

如果是单生产者单消费者,可以用环形数组加两个原子下标实现,生产者只更新 tail,消费者只更新 head。这里内存序很关键:生产者写入数据后,用 release 发布 tail;消费者 acquire 读取 tail,保证看到 tail 更新时,也能看到对应槽位的数据。如果是多生产者或多消费者,复杂度会明显增加,通常需要 CAS 抢槽位,甚至给每个槽位加序号。

代码:

#include <atomic>
#include <array>
using namespace std;

template <typename T, size_t N>
class SPSCQueue {
private:
    array<T, N> buf_;
    atomic<size_t> head_{0};
    atomic<size_t> tail_{0};

public:
    bool push(const T& val) {
        size_t t = tail_.load(memory_order_relaxed);
        size_t next = (t + 1) % N;

        if (next == head_.load(memory_order_acquire)) {
            return false;
        }

        buf_[t] = val;
        tail_.store(next, memory_order_release);
        return true;
    }

    bool pop(T& val) {
        size_t h = head_.load(memory_order_relaxed);

        if (h == tail_.load(memory_order_acquire)) {
            return false;
        }

        val = buf_[h];
        head_.store((h + 1) % N, memory_order_release);
        return true;
    }
};

4. 内存池怎么设计,项目里为什么要用内存池

答案:内存池的核心是提前申请一大块内存,然后按照固定大小或不同规格切分成小块,后续对象申请和释放都在池内完成,减少频繁 malloc/free 的系统开销和内存碎片。在多人协同白板实时状态同步服务里,操作事件对象非常多,比如一次拖拽可能产生很多增量操作。如果每条操作都直接走堆分配,高峰期会产生大量小对象分配,影响延迟稳定性,所以可以对固定大小的事件对象使用对象池。

设计时要注意几个点:池内对象的构造析构不能省略;多线程访问要做分片或线程本地缓存,避免一个全局锁成为瓶颈;释放对象时要防止重复释放;如果对象大小差异很大,可以按 size class 分多个池。内存池不是为了省所有内存,而是为了降低分配抖动和提升局部性。

代码:

#include <vector>
#include <memory>
using namespace std;

template <typename T>
class ObjectPool {
private:
    vector<T*> freeList_;
    vector<unique_ptr<T>> storage_;

public:
    template <typename... Args>
    T* create(Args&&... args) {
        if (!freeList_.empty()) {
            T* p = freeList_.back();
            freeList_.pop_back();
            new (p) T(forward<Args>(args)...);
            return p;
        }

        storage_.push_back(make_unique<T>(forward<Args>(args)...));
        return storage_.back().get();
    }

    void destroy(T* p) {
        if (!p) return;
        p->~T();
        freeList_.push_back(p);
    }
};

5. LRU 缓存怎么实现,多线程版本怎么做

答案:LRU 的核心是“最近使用的放前面,最久未使用的放后面”。常见实现是 list + unordered_maplist 维护访问顺序,头部是最新访问,尾部是最久未使用;unordered_map 负责根据 key 快速找到链表节点。get 时把节点移动到头部,put 时如果超过容量,就删除尾部节点。

多线程版本最简单的做法是给整个 LRU 加一把 mutex,正确但并发度一般。更高并发的做法是分片 LRU:按 key hash 到不同 shard,每个 shard 有自己的锁、map 和 list。这样不同 key 大概率落到不同分片,减少锁竞争。如果缓存读多写少,还可以考虑读写锁,但 LRU 的 get 本身也会修改访问顺序,所以读操作并不完全是只读。

代码:

#include <list>
#include <unordered_map>
#include <mutex>
using namespace std;

class LRUCache {
private:
    int cap_;
    list<pair<int, int>> lst_;
    unordered_map<int, list<pair<int, int>>::iterator> mp_;
    mutex mtx_;

public:
    explicit LRUCache(int cap) : cap_(cap) {}

    int get(int key) {
        lock_guard<mutex> lk(mtx_);

        auto it = mp_.find(key);
        if (it == mp_.end()) return -1;

        lst_.splice(lst_.begin(), lst_, it->second);
        return it->second->second;
    }

    void put(int key, int val) {
        lock_guard<mutex> lk(mtx_);

        auto it = mp_.find(key);
        if (it != mp_.end()) {
            it->second->second = val;
            lst_.splice(lst_.begin(), lst_, it->second);
            return;
        }

        if ((int)lst_.size() == cap_) {
            auto old = lst_.back();
            mp_.erase(old.first);
            lst_.pop_back();
        }

        lst_.push_front({key, val});
        mp_[key] = lst_.begin();
    }
};

6. 项目中如果房间状态很多,如何设计状态快照和增量日志

答案:多人协同白板里不能每次用户重连都从头回放所有操作,否则房间越活跃,恢复成本越高。比较合理的做法是“快照 + 增量日志”。服务端定期把房间完整状态生成一个快照,比如所有图形对象、层级关系、文本内容和版本号。快照之后的操作按递增序列号记录成增量日志。客户端重连时带上自己最后确认的版本,服务端根据版本选择直接补增量,或者下发最近快照再补后续日志。

难点在于快照生成不能阻塞正常操作。可以使用 copy-on-write 或者在房间线程内切出一致性视图,再交给后台线程压缩落盘。另外每条增量操作要具备幂等性,客户端重复收到同一个操作时不能重复应用。

代码:

#include <vector>
#include <string>
#include <unordered_map>
using namespace std;

struct Operation {
    uint64_t seq;
    string object

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

C++ 常考面试题总结 文章被收录于专栏

本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.

全部评论
虚继承那题答菱形继承能过吗
点赞 回复 分享
发布于 05-13 17:12 北京

相关推荐

头像
05-07 17:16
门头沟学院 C++
27考研备考计划表(数一+英一+408)核心配置-公共课:数学一、英语一-专业课:408计算机统考(数据结构+计组+操作系统+计算机网络)-时间节点:当前—2026.12.26初试一、阶段划分+每日任务第一阶段:现在—2026.6月底【零基础筑基|重中之重数一+408】数学一(每日4h,优先级最高)1.高数:一轮过完,极限/微分/积分/级数/微分方程/空间几何2.线代:全书一轮,吃透定义性质3.概率论:基础概念、分布、期望方差4.要求:全会基础题型,公式烂熟,不碰难题英语一(每日1.5h)1.考研大纲词全程不间断,每日80词2.长难句语法吃透,拆分句法3.不做真题,只打底408(每日3h)顺序:数据结构→计组→操作系统→计网1.通读教材1遍,看懂概念、逻辑、结构图2.不背诵,只搭建全书框架3.理解算法逻辑、硬件基础第二阶段:2026.7—8月【强化攻坚|黄金暑假】数学一(4.5h)1.高数/线代/概率论全部强化2.刷全书题型,整理错题3.攻克数一难点:级数、曲面积分、多元微分、概率论大数定律英语一(1.5h)1.开始05—18真题阅读,精翻精析2.只攻阅读,积累真题生词408(3.5h)1.四门二轮精读2.数据结构写代码+手写算法3.梳理四门交叉考点(IO、缓存、进程线程)4.开始做选择题第三阶段:2026.9—10月【真题刷题+报名】数学一(4h)1.数一真题全套模考,按年份计时2.错题二刷,攻克薄弱板块3.开始错题归类,固定解题模板英语一(2h)1.阅读二刷,完形+新题型入手2.10月整理英一大小作文模板政治(1h&nbsp;新开)只刷选择题,不碰大题408(3.5h)1.408统考真题全套刷2.名词解释、简答题开始背诵3.四门高频考点浓缩第四阶段:2026.11月【冲刺拔高】1.数一:真题三刷+模拟卷,全真计时2.英一:整套模考,作文通篇默写3.政治:狂刷选择,背诵核心考点4.408:全书狂背,真题三刷,算法默写第五阶段:12月1日—考前【押题保温+全真模考】1.数一:只刷错题+基础保温,不做新难题2.英一:作文全套背诵定型3.政治:背押题大题4.408:全书通篇默写、背高频简答、算法手撕二、每日固定作息-4.5h&nbsp;数学一-3.5h&nbsp;408-1.5h&nbsp;英语一-1h&nbsp;政治(9月启动)三、408&amp;amp;数一核心避坑1.数一:重计算,三重难点(级数、线面积分、概率)提前啃2.408:先理解再背诵,数据结构算法必须手写3.英一全程只刷真题,不做模拟4.政治9月再开始,不提前浪费时间
点赞 评论 收藏
分享
评论
1
1
分享

创作者周榜

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