快手 客户端开发-C++ 一面
1、自我介绍
2、智能指针介绍
答案:C++ 里最常见的智能指针是 unique_ptr、shared_ptr 和 weak_ptr。
unique_ptr 表示独占所有权,一个资源同一时刻只能有一个拥有者,不能拷贝,只能移动。这个最轻量,也最推荐优先使用。shared_ptr 表示共享所有权,多个指针可以共同管理一块资源,内部通过引用计数决定什么时候释放。它用起来方便,但会有额外控制块开销。weak_ptr 本身不拥有资源,它主要是配合 shared_ptr 使用,解决循环引用问题,或者做一个“只观察不接管”的弱引用。
实际开发里,一般是能用 unique_ptr 就不用 shared_ptr,因为共享所有权本身就意味着管理更复杂。只有当对象确实需要被多个地方共同持有时,才会考虑 shared_ptr。而只要出现双向引用,就要警惕是不是该引入 weak_ptr 了。
代码:
#include <iostream>
#include <memory>
using namespace std;
class Test {
public:
~Test() {
cout << "Test destroyed" << endl;
}
};
int main() {
unique_ptr<Test> p1 = make_unique<Test>();
shared_ptr<Test> p2 = make_shared<Test>();
shared_ptr<Test> p3 = p2;
cout << p2.use_count() << endl; // 2
weak_ptr<Test> wp = p2;
if (auto sp = wp.lock()) {
cout << "object still alive" << endl;
}
return 0;
}
3、虚函数和纯虚函数介绍
答案:虚函数的核心作用是支持运行时多态。也就是说,虽然你手里拿的是基类指针或者基类引用,但真正调用哪个函数版本,要看对象的实际类型。
纯虚函数是在虚函数后面写上 = 0,它更像是在基类里定义了一份接口规范,告诉派生类:这个函数你必须实现。只要一个类里有纯虚函数,这个类就是抽象类,不能直接实例化。
在工程里,虚函数更多用于“同一类行为,不同实现方式”的场景;纯虚函数更常见于接口设计、模块解耦、插件式结构这种地方。
另外还有一个面试里很常追问的点:如果一个类要被当成基类使用,而且你会通过基类指针去释放派生类对象,那基类析构函数一定要写成虚函数,否则析构链不完整,容易出问题。
代码:
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() {
cout << "Base::show" << endl;
}
virtual void run() = 0;
virtual ~Base() = default;
};
class Derived : public Base {
public:
void show() override {
cout << "Derived::show" << endl;
}
void run() override {
cout << "Derived::run" << endl;
}
};
int main() {
Base* p = new Derived();
p->show();
p->run();
delete p;
return 0;
}
4、Qt 的事件循环机制是怎么工作的
答案:
Qt 的很多能力,本质上都建立在事件循环之上。程序启动后,主线程通常会进入一个事件循环,不断从事件队列里取出事件并分发处理。像鼠标点击、键盘输入、窗口重绘、定时器触发、网络回调、信号槽中的排队调用,最后很多都会落到这个事件循环里统一调度。
这也是为什么客户端里经常会提到“主线程不能做耗时操作”。因为主线程一旦被阻塞,事件循环就停住了,界面刷新、输入响应、重绘消息都会卡住,用户看到的就是界面卡死、无响应。
如果放到 Qt 开发场景里理解,可以这么记:事件循环负责调度,消息队列负责缓存,主线程负责消费。只要这条链路被阻塞,UI 就会出问题。
5、RTTI 介绍,dynamic_cast 和 typeid 平时怎么用
答案:RTTI 就是运行时类型识别,英文是 Run-Time Type Information。它解决的是这样一个问题:当你手里拿着一个基类指针时,能不能在运行时知道它真正指向的是哪个派生类对象。
最常见的两个工具就是 dynamic_cast 和 typeid。
dynamic_cast 常用于安全向下转型。比如一个基类指针,想转成某个派生类指针,就可以用它。如果转换失败,指针类型会返回 nullptr。typeid 更偏向获取对象的实际类型信息,适合做类型判断或者调试观察。
不过 RTTI 不是拿来乱用的。一个代码里如果到处都在 dynamic_cast,往往说明抽象设计本身有问题,很多时候更合理的做法是直接通过虚函数把行为下沉到派生类,而不是在外面不断判断类型。
代码:
#include <iostream>
#include <typeinfo>
using namespace std;
class Base {
public:
virtual ~Base() = default;
};
class Derived : public Base {
public:
void hello() {
cout << "Derived hello" << endl;
}
};
int main() {
Base* p = new Derived();
if (Derived* d = dynamic_cast<Derived*>(p)) {
d->hello();
}
cout << typeid(*p).name() << endl;
delete p;
return 0;
}
6、动静态库介绍
答案:静态库和动态库的区别,核心在于链接时机和运行时加载方式不同。
静态库是在链接阶段把需要的代码合进最终可执行文件里,所以发布时通常不依赖外部库文件,部署比较省事,但可执行文件会更大。动态库是在运行时加载,多个程序可以共享同一份动态库,升级起来更灵活,但部署时要处理好版本兼容、路径、依赖这些问题。
客户端开发里这两个都很常见。静态库适合做一些稳定的小型基
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.
