小米汽车 软件工程师-C++ 一面
1、make_shared 了解过吗?
答案:了解。make_shared 本质上是用来创建 shared_ptr 的工厂函数。和直接 shared_ptr<T>(new T(...)) 相比,它通常只做一次内存分配,把对象本体和控制块放在一块连续内存里,所以性能更好,缓存局部性也更好。另外它异常安全性也更强,像复杂表达式里直接 new 可能在构造 shared_ptr 前抛异常,make_shared 可以避免这种问题。但它也不是所有场景都适合。比如对象特别大、你希望对象和控制块分开释放,或者需要自定义删除器,那就不一定用 make_shared。因为只要还有 weak_ptr 持有控制块,对象那块整体内存通常不能完全回收。
代码:
#include <iostream>
#include <memory>
using namespace std;
class Test {
public:
Test(int x) : x_(x) { cout << "ctor\n"; }
~Test() { cout << "dtor\n"; }
int x_;
};
int main() {
auto p = make_shared<Test>(42);
cout << p->x_ << endl;
return 0;
}
2、模板的优缺点
答案:模板最大的优点是代码复用和泛型编程能力强,可以把类型无关的逻辑抽象出来,像容器、算法、智能指针这些几乎都离不开模板。另外模板很多时候是在编译期展开的,类型检查更早,性能也比较好,不需要像传统面向对象那样依赖虚函数分发。缺点也很明显。一个是错误信息通常比较难看,尤其模板嵌套深的时候,编译报错会很长。另一个是代码膨胀,模板实例化过多会让二进制体积变大,编译时间也会变长。还有一点是模板接口设计要求比较高,不然很容易把简单逻辑写得很绕。
代码:
template <typename T>
T add(T a, T b) {
return a + b;
}
3、static 如何理解,除了内存地址,作用域如何理解?
答案:static 在 C++ 里不是一个单一含义,得放到具体语境里看。修饰局部变量时,表示这个变量生命周期延长到整个程序运行期间,但作用域还是局部作用域,只能在定义它的函数里访问。修饰全局变量或函数时,表示它的链接属性变成内部链接,只能在当前编译单元可见,相当于把可见范围限制在当前源文件。修饰类成员变量时,表示它属于类本身,不属于某个对象,所有对象共享一份。修饰类成员函数时,表示这个函数没有 this 指针,只能访问类的静态成员。所以可以把它理解成同时影响了生命周期、链接属性、归属关系这几个维度,不只是“放在静态区”这么简单。
代码:
#include <iostream>
using namespace std;
void func() {
static int cnt = 0;
++cnt;
cout << cnt << endl;
}
class A {
public:
static int x;
static void print() {
cout << x << endl;
}
};
int A::x = 10;
int main() {
func();
func();
A::print();
return 0;
}
4、了解过单例模式吗?
答案:单例模式就是保证一个类在进程里只有一个实例,并提供全局访问点。最常见的写法是局部静态变量版本,也叫 Meyers Singleton。从 C++11 开始,函数内局部静态对象的初始化是线程安全的,所以这种写法在大多数场景下已经够用了。单例的优点是方便统一管理全局资源,比如配置中心、日志器、线程池。缺点是容易让代码耦合变高,测试不方便,也容易被滥用成“全局变量升级版”。如果面试官继续追,通常会问线程安全、析构时机、跨模块初始化顺序这些问题。
代码:
#include <iostream>
using namespace std;
class Singleton {
public:
static Singleton& instance() {
static Singleton ins;
return ins;
}
void hello() {
cout << "hello singleton" << endl;
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
int main() {
Singleton::instance().hello();
return 0;
}
5、虚函数表指针的地址怎么理解?
答案:先区分两个概念,一个是虚表指针存的值,一个是虚表指针这个成员在对象里的位置。带虚函数的对象里通常会有一个隐藏成员,也就是虚表指针 vptr。它本身一般放在对象起始位置附近,但这不是语言标准强制规定,是主流编译器的常见实现。vptr 里存的是虚函数表 vtable 的地址,调用虚函数时会先通过对象拿到 vptr,再去表里找对应函数地址。如果问“虚函数表指针的地址”,严格讲可以有两层意思:一层是 vptr 自己在对象内存中的地址;一层是 vptr 保存的那个虚表地址。面试里只要把这两个概念分开说清楚,一般就够了。
代码:
#include <iostream>
using namespace std;
class Base {
public:
virtual void f() { cout << "Base::f\n"; }
int x = 1;
};
int main() {
Base b;
void** obj = reinterpret_cast<void**>(&b);
cout << "vptr stored value = " << obj[0] << endl; // 虚表地址
cout << "object address = " << &b << endl; // 对象起始地址
return 0;
}
6、多态如何理解,或者说有哪几种多态?
答案:多态本质上就是同一个接口在不同场景下表现出不同的行为。C++ 里通常分两大类。一种是编译时多态,也叫静态多态,比如函数重载、运算符重载、模板
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.
查看16道真题和解析
