小黑盒 软件研发-C++ 一面

1、C++ 和 Java 的类有什么区别,底层实现上有什么不同

答案:

C++ 的类更接近“对象布局”这个概念。类的成员变量、虚函数表指针、继承带来的基类子对象,最后都会直接体现在对象内存布局里。对象可以建在栈上,也可以建在堆上,生命周期既可以由语言自动管理,也可以由程序员手动控制。编译器在编译期就会把很多信息确定下来,所以 C++ 更强调零开销抽象和静态分发能力。

Java 的类更像“运行时托管对象”的模型。对象基本都分配在堆上,由 GC 管理生命周期;方法调度、类型信息、反射能力,都更依赖 JVM 的运行时系统。Java 对象头里通常会放运行时元信息,比如类型、锁状态、GC 相关信息。它的类模型和执行模型跟虚拟机强绑定,所以语言层看起来统一,但底层依赖的是 JVM 提供的对象管理和方法调度机制。

简单说,C++ 的类更偏编译期对象模型,Java 的类更偏运行时对象模型。C++ 给程序员更多控制权,也因此更容易写出高性能代码,但同时内存、析构、对象语义都得自己兜住;Java 更强调托管和统一抽象,执行灵活性更高,但很多成本转移给了运行时。

2、C++ 栈上对象、堆上对象怎么构建,生命周期有什么区别

栈上对象通常是直接定义出来的,比如局部变量。进入作用域时构造,离开作用域时自动析构,它的生命周期由作用域控制。优点是开销小、释放自动、异常安全更自然。

堆上对象通常通过 new、工厂函数或者智能指针来构建。它不依赖当前作用域结束而销毁,什么时候释放取决于谁持有它、谁负责销毁它。灵活性更强,但如果所有权设计不清楚,就容易出现泄漏、悬空指针、重复释放这些问题。

这题面试官可能还会追问“构造和初始化有什么差别”。如果对象在栈上,编译器通常直接在那块栈内存上完成构造;如果在堆上,先申请堆内存,再在那块内存上调用构造函数完成初始化。

代码:

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

class A {
public:
    A() { cout << "A ctor" << endl; }
    ~A() { cout << "A dtor" << endl; }
};

int main() {
    A a;  // 栈上对象

    A* p = new A();  // 堆上对象
    delete p;

    auto sp = make_shared<A>(); // 堆上对象,交给智能指针管理
    return 0;
}

3、智能指针的区别

C++ 里常见的智能指针主要是 unique_ptrshared_ptrweak_ptr

unique_ptr 是独占所有权,一个资源只能被一个智能指针拥有,不能拷贝,只能移动。它最轻量,额外开销最小,适合所有权清晰的场景。shared_ptr 是共享所有权,内部靠引用计数来管理对象生命周期,多个地方都能持有同一个对象,但会多出控制块和原子计数的开销。weak_ptr 不拥有对象,不增加引用计数,它只是观察者,用来打破 shared_ptr 的循环引用,或者在不延长生命周期的前提下访问对象。

真实工程里,一般不是“哪个高级用哪个”,而是优先考虑所有权模型。能独占就用 unique_ptr,真的需要共享再用 shared_ptr,需要旁路观察时再加 weak_ptr

代码:

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

class Node {
public:
    ~Node() {
        cout << "Node destroyed" << endl;
    }
};

int main() {
    unique_ptr<Node> p1 = make_unique<Node>();

    shared_ptr<Node> p2 = make_shared<Node>();
    shared_ptr<Node> p3 = p2;
    cout << p2.use_count() << endl;

    weak_ptr<Node> wp = p2;
    if (auto sp = wp.lock()) {
        cout << "lock success" << endl;
    }
    return 0;
}

4、unordered_map 的底层结构,为什么它不一定总比 map

unordered_map 底层通常是哈希表。它先对 key 做哈希计算,再根据桶数组大小映射到某个桶上。桶里如果出现哈希冲突,就需要进一步处理,常见做法是链地址法,也就是一个桶里挂一条链或者类似链式结构。

它平均查找复杂度通常是 O(1),但这个平均前提依赖两个条件:一是哈希函数分布合理,二是负载因子不要太高。如果冲突严重,查找复杂度就会退化;如果 rehash 频繁,也会带来明显抖动。

所以不能简单说 unordered_map 一定比 map 快。map 虽然是 O(log n),但它有序、性能稳定、不会因为哈希冲突突然恶化;unordered_map 则更依赖分布和装载情况。实际开发里要结合数据规模、key 分布、是否需要有序遍历一起选。

5、哈希表的实现方式,开散列和闭散列分别是什么,各自优劣

答案:哈希表核心问题不是“怎么哈希”,而是“冲突怎么解决”。

开散列也叫链地址法。每个桶对应一个链表或者链式结构,多个哈希到同一位置的元素挂在同一个桶后面。它的优点是实现直观、删除方便、装载因子可以大于 1;缺点是节点分散,缓存局部性差,指针开销也更大。

闭散列也叫开放定址法。发生冲突后不挂链,而是在表里继续找别的位置放元素,常见探查方式有线性探查、二次探查和双重哈希。它的优点是数据更紧凑,缓存友好;缺点是删除麻烦,需要墓碑标记或者特殊处理,而且负载因子高时性能下降明显。

从工程角度看,开散列更稳一些,闭散列更吃装载率和探查策略。如果很关注 cache locality,开放定址类实现会很有吸引力;但如果需要频繁删除,链地址法通常更省心。

6、现代高性能哈希表为什么很多会偏向开放定址,而不是传统拉链法

传统教科书喜欢讲链地址法,因为概念清楚,也方便分析。但现代高性能哈希表很多会偏向开放定址,核心原因是 CPU 访存模型变了。今天性能瓶颈很多时候不是算得慢,而是内存访问慢。链地址法节点分散在堆上,查找一个 key 可能要跟着指针跳好几次,cache miss 很严重。开放定址把元素放在连续内存区域里,探查时局部性更好,往往更符合现代 CPU 的缓存特性。

当然它也不是没有代价。删除复杂、负载因子高时退化明显、实现细节更绕,这些都是真的。所以很多现代库并不是“纯

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

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

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

全部评论

相关推荐

给我面没招了,感觉自己好菜、面试很难,还是要多多练习1.为什么在&nbsp;RAG&nbsp;流程中引入父子索引(Parent-Document&nbsp;Retrieval)?2.为什么引入&nbsp;BM25?向量检索和&nbsp;BM25&nbsp;的融合比例是怎样的?3.检索融合的具体流程是什么?召回后有没有做&nbsp;Rerank?4.Rerank&nbsp;后返回几个块(Chunk)?有没有针对这个返回数量做过验证?5.Rerank&nbsp;后的&nbsp;TopK&nbsp;截断是怎么做的?为什么是这个值?有没有其他截断方案?6.讲一下上下文工程(Context&nbsp;Engineering),Agent&nbsp;的记忆(Memory)是怎么做的?7.请详细讲解分布式令牌桶限流的实现。8.漏桶算法(Leaky&nbsp;Bucket)的原理讲一下。9。滑动窗口算法限流讲一下。如果用代码实现,滑动窗口的结构体会包含哪些字段?10.滑动窗口和令牌桶相比有什么缺点?11.在&nbsp;Redis&nbsp;中,你会用什么数据结构来实现滑动窗口限流?12.LRU&nbsp;缓存的实现原理讲一下。13.布隆过滤器(Bloom&nbsp;Filter)的底层原理和适用场景讲一下。14.MySQL&nbsp;索引会在哪些情况下失效?15.在使用&nbsp;LIKE&nbsp;进行模糊查询时,索引什么情况下会失效?16.MySQL&nbsp;的事务隔离级别有哪些?如何保证一致性?17.详细说说&nbsp;MVCC&nbsp;的实现,ReadView&nbsp;的生成时机是怎样的?18.在不同的隔离级别下,一个事务分别会创造几个&nbsp;ReadView?19.MySQL&nbsp;都有哪些锁?它们的作用分别是什么?20.为什么选择&nbsp;Python&nbsp;和&nbsp;Go&nbsp;作为技术栈?在处理并发时有什么区别?21.手撕代码:实现反转链表
被普调的95后很想居...:主播真是传奇耐面王
查看21道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

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