网易 软件开发-C++ 一面

1. 日常是怎么使用 AI 的

答案:我日常使用 AI 更多是把它当成辅助工具,而不是直接替我完成开发。比如看不熟悉的库时,我会让它先帮我梳理接口使用方式;写代码前,也会让它给一个大致思路或伪代码,然后我自己再结合项目约束重写。如果是 C++ 代码,我会特别注意生命周期、边界条件、线程安全、异常路径这些问题,因为 AI 生成的代码经常在这些地方比较粗糙。所以我更倾向于用 AI 提高检索和理解效率,但最终代码还是要自己 review、编译、测试和压测。

2. 你认为 AI 目前有什么问题

答案:AI 最大的问题是它经常给出“看起来很合理”的答案,但不一定真的正确。尤其是 C++、操作系统、网络编程这类问题,如果涉及内存模型、并发、系统调用语义,它可能会忽略很多细节。另外 AI 对上下文依赖比较强,如果需求描述不清楚,它可能会补全出一个错误假设,然后沿着错误假设继续生成。还有一个问题是它不理解真实线上环境,比如性能瓶颈、历史包袱、团队规范、异常数据和兼容性,这些都需要开发者自己判断。

3. 你会如何解决 AI 存在的问题

答案:我一般会把 AI 输出当成候选方案,而不是最终结论。如果它给的是概念,我会去查官方文档、标准库说明或者源码;如果它给的是代码,我会先跑编译,再补单元测试和边界测试。对于并发和内存相关代码,我会额外关注是否有数据竞争、悬空引用、重复释放、异常路径资源泄漏。如果是项目代码,我不会直接复制进去,而是根据项目已有风格、错误处理方式、日志规范重新整理一遍。

4. 你会信任 AI 写的代码吗

答案:不会无条件信任。AI 写的代码可以作为参考,尤其是一些样例代码或者模板代码,但不能直接默认它是正确的。比如它可能忘记处理空指针、边界条件、线程安全,也可能用了不符合当前项目编译标准的语法。我会先看它的实现思路是否合理,再看复杂度和资源释放是否正确,最后通过编译、测试和 review 来验证。

5. 你现在写代码是完全让 AI 写,还是自己也会写

答案:主要还是自己写。AI 可以帮我加快一些重复性工作,比如生成测试样例、整理接口说明、提示某个 API 的用法,但核心逻辑、架构拆分、异常处理和性能优化还是要自己来。特别是 C++ 里对象生命周期、智能指针所有权、锁粒度、网络连接关闭这些地方,不自己写和调试,很难真正理解。我觉得 AI 更适合辅助开发,而不是替代开发者的判断。

6. 你会如何验证 AI 写的代码是否正确

答案:我会先从静态层面看代码是否符合需求,比如输入输出、边界条件、异常路径、资源释放和线程安全有没有问题。然后会编译运行,补一些正常用例、边界用例和异常用例。如果是性能敏感代码,还要做压测或 benchmark,不能只看功能跑通。如果是并发代码,我会特别关注锁保护范围、对象生命周期和是否存在 ABA、死锁、数据竞争等问题,必要时配合 ASan、TSan、Valgrind 这类工具检查。

7. 介绍一下你的项目

8. 面向对象的三大特性

答案:面向对象三大特性是封装、继承和多态。封装是把数据和操作数据的方法放到一起,对外暴露稳定接口,隐藏内部实现。继承是子类复用和扩展父类能力,但继承关系不能滥用,否则类层次会变复杂。多态一般指通过基类指针或引用调用虚函数,运行时根据对象真实类型执行不同逻辑。C++ 里多态通常依赖虚函数表实现。

代码:

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

class Event {
public:
    virtual void handle() = 0;
    virtual ~Event() = default;
};

class LoginEvent : public Event {
public:
    void handle() override {
        cout << "handle login event\n";
    }
};

class BattleEvent : public Event {
public:
    void handle() override {
        cout << "handle battle event\n";
    }
};

int main() {
    unique_ptr<Event> e = make_unique<BattleEvent>();
    e->handle();
    return 0;
}

9. 项目里是否用到了继承

答案:有用到,但不会为了用继承而继承。在实时对战日志采集与风控分析平台里,我把不同类型的事件处理器抽象成一个基类,比如登录事件、战斗事件、异常操作事件都实现同一个 process 接口。这样事件分发模块只依赖抽象接口,不需要关心具体事件怎么处理。后面新增一种事件类型时,只要新增一个处理器并注册进去即可。但我也会控制继承层次,通常一到两层就够了。如果只是复用代码,不一定非要继承,也可以用组合。

代码:

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

struct LogEvent {
    int type;
    string payload;
};

class EventHandler {
public:
    virtual void process(const LogEvent& event) = 0;
    virtual ~EventHandler() = default;
};

class RiskHandler : public EventHandler {
public:
    void process(const LogEvent& event) override {
        // 风控规则检测
    }
};

class Dispatcher {
private:
    unordered_map<int, unique_ptr<EventHandler>> handlers;

public:
    void addHandler(int type, unique_ptr<EventHandler> h) {
        handlers[type] = std::move(h);
    }

    void dispatch(const LogEvent& event) {
        auto it = handlers.find(event.type);
        if (it != handlers.end()) {
            it->second->process(event);
        }
    }
};

10. 如何解决内存泄漏,C++11 之后智能指针怎么用

答案:解决内存泄漏首先要减少手动 new/delete,优先用 RAII、容器和智能指针管理资源。C++11 之后常用 unique_ptrshared_ptrweak_ptr。如果资源只有一个明确拥有者,就用 unique_ptr;如果对象需要被多个异步回调或模块共同持有,才考虑 shared_ptr;如果只是观察对象生命周期,或者要打破循环引用,就用 weak_ptr。项目里像日志文件句柄、buffer、处理器对象更适合 unique_ptr;连接对象、异步任务上下文可能会用 shared_ptr;定时器回调里一般用 weak_ptr 防止对象已经析构还被访问。

代码:

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

class Buffer {
public:
    vector<char> data;
};

class Connection {
private:
    unique_ptr<Buffer> inputBuffer;

public:
    Connection() : inputBuffer(make_unique<Buffer>()) {}
};

11. weak_ptr 解决什么问题,什么情况下 lock() 能成功

答案:weak_ptr 主要解决两个问题:循环引用和异步场景下的生命周期判断。它不增加对象的强引用计数,所以不会延长对象生命周期。访问对象时需要调用 lock(),如果对象还活着,也就是控制块里的强引用计数大于 0,lock() 会返回一个有效的 shared_ptr;如果对象已经释放,返回空。在项目里,定时器、延迟任务、跨线程回调都比较适合用 weak_ptr,避免回调执行时对象已经销毁。

代码:

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

class Session {
public:
    void check() {
        cout << "session alive\n";
    }
};

void onTimer(weak_ptr<Session> ws) {
    if (auto s = ws.lock()) {
        s->check();
    } else {
        // 对象已经释放,直接忽略
    }
}

12. 无锁队列如何实现,项目里为什么会用

答案:无锁队列一般依赖原子操作和 CAS。如果是单生产者单消费者场景,可以用环形数组加两个原子下标实现,生产者只修改 tail,消费者只修改 head,复杂度比多生产者多消费者低很多。项目里日志接入线程和分析线程之间可以用 SPSC 队列减少锁竞争,比如一个 IO 线程把解析后的日志事件投递给固定的分析线程。不过无锁队列不是万能的,如果是多生产者多消费者,内存序、ABA、回收问题会复杂很多,工程里要谨慎。

代码:

#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;
    }

   

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

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

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

全部评论

相关推荐

评论
1
1
分享

创作者周榜

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