米哈游 客户端开发-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
}

20. 如果游戏英雄底层机制调整,测试怎么设计?

全部评论

相关推荐

评论
点赞
1
分享

创作者周榜

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