虎牙C++ 一面
1、自我介绍
介绍了一下我自己啥情况,之前在哪个公司实习过,主要参与 C++ 后端相关开发工作。 实习期间我做过消息处理/服务端模块开发,参与了功能迭代、问题排查和一部分性能优化工作。 技术栈方面,我主要掌握 C/C++,熟悉 STL、Linux 开发环境、网络编程(TCP/IP)、多线程与并发编程,了解常用的数据结构与算法,也用过 MySQL、Redis 这类常见组件。 整体上我偏工程实践,比较关注代码质量、稳定性和性能,想在 C++ 后端方向继续深入。
2、单例模式和工厂模式的作用
单例模式:保证一个类全局只有一个实例,适合日志、配置中心、线程池这种“全局只要一个”的对象。 工厂模式:把对象创建过程封装起来,调用方不用关心具体创建哪个类,便于解耦和扩展。
#include <iostream>
#include <memory>
using namespace std;
class Product {
public:
virtual ~Product() = default;
virtual void run() = 0;
};
class ProductA : public Product {
public:
void run() override { cout << "ProductA running\n"; }
};
class ProductB : public Product {
public:
void run() override { cout << "ProductB running\n"; }
};
class Factory {
public:
static unique_ptr<Product> create(int type) {
if (type == 1) return make_unique<ProductA>();
return make_unique<ProductB>();
}
};
3、懒汉模式的实例化细节(C++98 和 C++11)
懒汉模式是第一次使用时才创建实例。 C++98 中局部静态变量初始化不是线程安全的,需要手动加锁。 C++11 开始函数内局部静态变量初始化是线程安全的,推荐这种写法。
C++98 写法(加锁版):
#include <pthread.h>
class Singleton98 {
private:
Singleton98() {}
Singleton98(const Singleton98&);
Singleton98& operator=(const Singleton98&);
static Singleton98* instance_;
static pthread_mutex_t mutex_;
public:
static Singleton98* getInstance() {
pthread_mutex_lock(&mutex_);
if (instance_ == 0) {
instance_ = new Singleton98();
}
pthread_mutex_unlock(&mutex_);
return instance_;
}
};
Singleton98* Singleton98::instance_ = 0;
pthread_mutex_t Singleton98::mutex_ = PTHREAD_MUTEX_INITIALIZER;
C++11 推荐写法:
class Singleton11 {
public:
static Singleton11& getInstance() {
static Singleton11 instance; // C++11 线程安全初始化
return instance;
}
Singleton11(const Singleton11&) = delete;
Singleton11& operator=(const Singleton11&) = delete;
private:
Singleton11() = default;
};
4、用过哪些锁
常用:std::mutex、std::recursive_mutex、std::timed_mutex、std::shared_mutex(C++17)。 常搭配:std::lock_guard、std::unique_lock(RAII 管理锁)。 工程里一般优先 mutex + lock_guard,读多写少场景再用 shared_mutex。
#include <iostream>
#include <thread>
#include <mutex>
#include <shared_mutex>
using namespace std;
int counter = 0;
mutex mtx;
void add() {
lock_guard<mutex> lk(mtx);
++counter;
}
shared_mutex rw_mtx;
int data_val = 0;
void reader() {
shared_lock<shared_mutex> lk(rw_mtx);
cout << "read: " << data_val << "\n";
}
void writer() {
unique_lock<shared_mutex> lk(rw_mtx);
++data_val;
}
5、多态是如何实现的
C++ 运行时多态通过虚函数实现。 基类函数加 virtual,子类重写后,通过基类指针或引用调用时会发生动态绑定。 底层依赖虚函数表(vtable)和虚表指针(vptr)。
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() = default;
virtual void speak() { cout << "Base\n"; }
};
class Derive : public Base {
public:
void speak() override { cout << "Derive\n"; }
};
int main() {
Base* p = new Derive();
p->speak(); // 输出 Derive
delete p;
return 0;
}
6、虚函数表和虚表指针是在堆上还是栈上
虚函数表(vtable)通常在只读数据段。 虚表指针(vptr)属于对象本身,跟对象存储位置一致:对象在栈上,vptr 在栈对象里;对象在堆上,vptr 在堆对象里。
7、哈希表的理解
哈希表通过哈希函数把 key 映射到数组下标,查找/插入/删除平均时间复杂度 O(1)。 关键点是哈希均匀性、冲突处理(拉链法或开放寻址)、负载因子和扩容。 最坏情况下会退化到 O(n)。C++ 常用 unordered_map。
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<string, int> mp;
mp["alice"] = 95;
mp["bob"] = 88;
if (mp.find("alice") != mp.end()) {
cout << mp["alice"] << "\n";
}
mp.erase("bob");
return 0;
}
8、项目介绍
我在实习里做的是一个直播间消息处理相关的小模块,主要是把上游来的事件做清洗、分发和落库,目标是让链路更稳定、延迟更低。 我主要负责这个模块的开发和维护,做了三件比较关键的事:把串行处理改成线程池并发消费;拆分高频锁、缩小临界区;补齐监控和日志,方便排查抖动。 技术上主要用 C++、多线程、STL 容器和内部 RPC/存储接口。 最终效果是高峰期处理更稳,超时率明显下降,P99 延迟也比之前低了一截。
9、项目中遇到的困难
当时高峰期会出现偶发卡顿,平均延迟还行,但 P99 会突然拉高。 一开始怀疑网络,后来结合监控和火焰图定位到本地瓶颈:共享 map 锁竞争激烈,同时同步日志写入拖慢了主流程。 后面做了几步优化:热点读写分离、减少大锁持有时间、非关键日志异步化,再加上限流和队列长度告警。 优化后抖动明显减少,P99 更稳定,线上报警次数也下降了。这个问题也让我后面做并发模块时会优先关注锁粒度和慢路径。
10、反问
我问了几个问题: ① 我这次回答整体怎么样,哪些地方还不够到位,后续我可以重点改进。 ② 如果有机会进组,前 1-3 个月对新人的预期是什么,会不会有师兄带、代码评审和学习路线。 ③ 组里平时做性能优化最常用哪些工具、排查流程一般怎么走。
本专栏系统梳理C++面试高频考点,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力。
查看1道真题和解析