网易 后端开发-C++ 一面
1. 自我介绍
2. 智能指针了解什么,unique_ptr、shared_ptr、weak_ptr 分别适合什么场景
答案:unique_ptr 表示独占所有权,一个资源同一时刻只能有一个拥有者,适合所有权非常明确的场景,开销也最小。shared_ptr 表示共享所有权,底层通过控制块和引用计数管理对象生命周期,适合多个对象共同持有同一资源的情况。weak_ptr 不增加强引用计数,主要用于观察 shared_ptr 管理的对象,典型作用是打破循环引用。真正写工程时,智能指针不是“哪里都能上”,本质还是要先把对象所有权设计清楚。shared_ptr 用多了,很多时候反而说明对象边界没设计好。
代码:
#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> p1 = make_unique<int>(10);
shared_ptr<int> p2 = make_shared<int>(20);
weak_ptr<int> p3 = p2;
cout << *p1 << endl;
cout << *p2 << endl;
cout << p2.use_count() << endl;
if (auto sp = p3.lock()) {
cout << *sp << endl;
}
return 0;
}
3. shared_ptr 的底层实现是怎样的,为什么它比裸指针重
答案:shared_ptr 一般由两部分组成,一部分是对象指针,一部分是控制块。控制块里通常保存强引用计数、弱引用计数、删除器、分配器等信息。每次拷贝 shared_ptr,强引用计数加一;析构或重置时减一;当强引用计数归零时,对象本体释放;当强弱计数都归零时,控制块本身释放。它比裸指针重,主要重在控制块的额外内存、计数维护成本,以及多线程场景下原子操作带来的同步开销。所以如果对象只有唯一拥有者,就没必要硬上 shared_ptr。
4. 虚函数的原理是什么,为什么能通过父类指针调用到子类实现
答案:虚函数实现运行时多态,核心依赖虚函数表。通常带虚函数的类,编译器会给这个类生成一张虚表,对象里会有一个隐藏的虚表指针。通过父类指针调用虚函数时,编译器不会在编译期写死目标函数,而是运行时根据对象真实类型的虚表去查函数地址,所以同样的接口调用到的是具体子类实现。这也是为什么多态一般要求三个条件:继承、父类函数是虚函数、通过父类指针或引用调用。如果继续深挖,通常还会问虚析构、纯虚函数、构造析构期间调用虚函数这些问题。
代码:
#include <iostream>
using namespace std;
class Base {
public:
virtual void func() { cout << "Base\n"; }
virtual ~Base() = default;
};
class Derived : public Base {
public:
void func() override { cout << "Derived\n"; }
};
int main() {
Base* p = new Derived();
p->func();
delete p;
return 0;
}
5. 空类里通常有什么,为什么 sizeof(空类) 不等于 0
答案:一个空类从语义上看什么都没写,但编译器通常仍然会给它分配至少 1 字节大小。原因不是空类真的需要存东西,而是为了保证这个类创建出的不同对象在内存中有不同地址。如果大小是 0,那么同类型多个对象可能地址完全一样,这会破坏对象模型和很多语言规则。另外一旦类里出现虚函数、虚继承或者静态成员以外的成员,类对象布局就会进一步发生变化。静态成员不算进对象大小,因为它不属于某个具体对象。
代码:
#include <iostream>
using namespace std;
class Empty {};
int main() {
cout << sizeof(Empty) << endl; // 一般为 1
return 0;
}
6. C++ 内存分区一般怎么理解
答案:常见可以从代码区、全局/静态区、栈区、堆区、常量区这几个角度理解。代码区放可执行指令;全局/静态区放全局变量和静态变量;常量区一般放字符串字面量和只读常量;栈区由编译器自动管理,主要放局部变量和函数调用信息;堆区由程序员或分配器动态管理。面试里如果问内存分区,很多时候不只是让你背名字,而是想看你是否理解不同区域的生命周期、访问方式以及常见问题,比如栈溢出、悬空指针、内存泄漏、越界写等。
7. 左值、右值、右值引用和移动语义怎么理解
答案:左值通常可以理解成“有名字、可以取地址、生命周期相对稳定”的对象,右值更偏向临时对象或者即将被销毁的值。右值引用 T&& 的引入,是为了区分“这个对象的资源可以被转移”。移动语义的核心不是简单拷贝值,而是把内部资源所有权从旧对象转到新对象,避免深拷贝开销。像 vector 扩容、函数返回大对象、容器插入临时对象这些场景,移动语义都很重要。但要注意,具名的右值引用变量本身是左值,想继续以右值方式传递,通常要用 std::move。
代码:
#include <iostream>
#include <vector>
using namespace std;
class Buffer {
public:
Buffer(size_t n = 0) : size_(n), data_(n ? new int[n] : nullptr) {}
~Buffer() { delete[] data_; }
Buffer(const Buffer& other) : size_(other.size_), data_(new int[other.size_]) {
copy(other.data_, other.data_ + other.size_, data_);
cout << "copy\n";
}
Buffer(Buffer&& other) noexcept : size_(other.size_), data_(other.data_) {
other.size_ = 0;
other.data_ = nullptr;
cout << "move\n";
}
private:
size_t size_;
int* data_;
};
int main() {
vector<Buffer> v;
v.push_back(Buffer(100));
return 0;
}
8. 容器了解哪些,vector、list、deque、unordered_map 分别有什么特点
答案:vector 底层连续内存,随机访问快,cache 友好,绝大多数读多改少场景都很适合。list是双向链表,已知位置插入删除快,但节点离散,内存额外开销大,遍历性能往
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.

查看4道真题和解析