米哈游 客户端开发-C++ 一面
1. C++ 里的多态是什么?编译期多态和运行时多态有什么区别?
多态可以理解为“同一个接口,不同实现”。 编译期多态在编译阶段确定调用目标,常见形式是函数重载、模板;运行时多态在程序运行时根据对象真实类型决定调用目标,依赖虚函数机制。
#include <iostream>
using namespace std;
struct Base {
virtual void foo() { cout << "Base\n"; }
};
struct Derived : Base {
void foo() override { cout << "Derived\n"; }
};
void func(int) { cout << "int\n"; }
void func(double) { cout << "double\n"; }
template <typename T>
void tfunc(T) { cout << "template\n"; }
int main() {
Base* p = new Derived();
p->foo(); // 运行时多态
func(1); // 编译期多态(重载)
tfunc(3.14); // 编译期多态(模板)
delete p;
}
如果你也学C++ , 可能你需要一份完整的C++ 面试题: C++ 常考面试题总结
2. 重写和重载有什么区别?
重写发生在继承关系中,子类重写父类的虚函数,函数签名要匹配。 重载发生在同一作用域,函数名相同但参数列表不同。
struct Base {
virtual void f(int) {}
};
struct Derived : Base {
void f(int) override {} // 重写
void f(double) {} // 重载
};
3. 什么是虚函数?纯虚函数和虚函数有什么区别?
虚函数用于支持运行时多态。 纯虚函数写成 =0,用于定义接口;含纯虚函数的类是抽象类,不能直接实例化。
struct Shape {
virtual double area() = 0;
virtual ~Shape() = default;
};
struct Circle : Shape {
double r;
explicit Circle(double rr) : r(rr) {}
double area() override { return 3.14159 * r * r; }
};
4. 说说 C++ 的 RAII
RAII 的核心是:资源由对象管理。 对象创建时获取资源,对象销毁时自动释放资源。常用于锁、文件句柄、堆内存管理,能有效减少泄漏问题。
#include <mutex>
std::mutex mtx;
void safeFunc() {
std::lock_guard<std::mutex> lk(mtx); // 离开作用域自动解锁
}
5. unique_ptr、shared_ptr、weak_ptr 区别是什么?
unique_ptr 独占所有权,不能拷贝,可以移动。 shared_ptr 共享所有权,使用引用计数。 weak_ptr 不参与引用计数,常用于解决循环引用。
#include <memory>
using namespace std;
struct Node {
shared_ptr<Node> next;
weak_ptr<Node> prev;
};
6. new/delete 和 malloc/free 的区别?
new/delete 是 C++ 运算符,会调用构造和析构。 malloc/free 是 C 库函数,只做内存分配和释放,不负责对象生命周期。
7. C++ 的 move 语义是什么?
move 语义通过右值引用转移资源所有权,减少不必要的深拷贝,提高性能。 常用 std::move 把左值显式转换为右值引用。
#include <vector>
#include <string>
using namespace std;
int main() {
string s = "hello";
vector<string> v;
v.push_back(std::move(s));
}
8. C++ 内存分区有哪些?
常见有:代码区、全局/静态区、常量区、栈区、堆区。 栈由系统自动管理;堆通常由程序动态申请和释放。
9. 什么是哈希表?
哈希表通过哈希函数把键映射到桶位置,实现平均 O(1) 的查找、插入和删除。 冲突处理常用链地址法或开放定址法,装载因子高时会扩容。
10. 哈希表中键值能不能是一个对象?要满足什么条件?
可以。通常需要: 1) 可比较相等; 2) 可计算哈希值; 3) 作为 key 使用期间,影响哈希/相等判断的字段不要被修改。
#include <unordered_map>
#include <string>
using namespace std;
struct User {
int id;
string name;
bool operator==(const User& o) const {
return id == o.id && name == o.name;
}
};
struct UserHash {
size_t operator()(const User& u) const {
return hash<int>()(u.id) ^ (hash<string>()(u.name) << 1);
}
};
int main() {
unordered_map<User, int, UserHash> mp;
mp[{1, "tom"}] = 95;
}
11. 线程安全一般怎么保证?
常见手段有:互斥锁、读写锁、原子变量、线程隔离、消息队列。 核心目标是避免数据竞争,并兼顾性能和可维护性。
12. 进程和线程有什么区别?
进程是资源分配单位,彼此地址空间独立;线程是调度单位,同一进程内线程共享地址空间。 线程更轻量,但并发编程复杂度更高。
13. 死锁的条件是什么?如何避免?
死锁四个必要条件:互斥、占有并等待、不可剥夺、循环等待。 常见避免方式:统一加锁顺序、减少持锁时间、try_lock + 失败回退、超时机制。
14. TCP 三次握手和四次挥手分别是什么?
三次握手用于建立连接:SYN -> SYN+ACK -> ACK。 四次挥手用于断开连接:FIN -> ACK -> FIN -> ACK,因为双方关闭发送能力是分开的。
15. TCP 为什么可靠?
靠这些机制保证可靠性:序列号、确认应答、超时重传、滑动窗口、流量控制、拥塞控制、校验和。
16. select、poll、epoll 有什么区别?
select:fd 数量受限,位图扫描,开销较大。 poll:没有固定 fd 上限,但仍是线性遍历。 epoll:事件驱动,活跃 fd 通知,适合高并发。
17. socket、TCP、HTTP 的关系是什么?
socket 是网络编程接口,TCP 是传输层协议,HTTP 是应用层协议。 常见情况下,HTTP 基于 TCP,通过 socket API 完成连接和数据收发。
18. 输出包含重复数字的数组的全排列
先排序,再回溯。 每一层递归中,如果当前元素和前一个元素相同,且前一个元素在当前路径未使用,就跳过当前元素,避免重复排列。
#include <bits/stdc++.h>
using namespace std;
void dfs(vector<int>& nums, vector<int>& used, vector<int>& path, vector<vector<int>>& res) {
if ((int)path.size() == (int)nums.size()) {
res.push_back(path);
return;
}
for (int i = 0; i < (int)nums.size(); ++i) {
if (used[i]) continue;
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue;
used[i] = 1;
path.push_back(nums[i]);
dfs(nums, used, path, res);
path.pop_back();
used[i] = 0;
}
}
int main() {
vector<int> nums = {1, 1, 2};
sort(nums.begin(), nums.end());
vector<int> used(nums.size(), 0), path;
vector<vector<int>> res;
dfs(nums, used, path, res);
for (auto& p : res) {
for (int x : p) cout << x << " ";
cout << "\n";
}
}
19. 走迷宫最短路径怎么做?
把迷宫看成无权图,使用 BFS。 从起点按层扩展上下左右,第一次到达终点时的步数就是最短路径长度。
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<string> g = {
"S..#",
".#..",
"..#E"
};
int n = g.size(), m = g[0].size();
queue<pair<int,int>> q;
vector<vector<int>> dist(n, vector<int>(m, -1));
int ex = -1, ey = -1;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (g[i][j] == 'S') {
q.push({i, j});
dist[i][j] = 0;
}
if (g[i][j] == 'E') {
ex = i; ey = j;
}
}
}
int dir[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
while (!q.empty()) {
auto [x, y] = q.front(); q.pop();
for (auto &d : dir) {
int nx = x + d[0], ny = y + d[1];
if (nx < 0 || ny < 0 || nx >= n || ny >= m) continue;
if (g[nx][ny] == '#') continue;
if (dist[nx][ny] != -1) continue;
dist[nx][ny] = dist[x][y] + 1;
q.push({nx, ny});
}
}
cout << dist[ex][ey] << "\n"; // 不可达时为 -1
}
查看11道真题和解析