bilibili C++开发 一面
1. C++11 / 17 / 20 比较有意思的新特性
C++11 最重要的是把现代 C++ 的写法真正建立起来了,比如 auto、decltype、右值引用、移动语义、lambda、智能指针、线程库这些。lambda 很有意思,因为它把函数对象的使用门槛降得很低,很多回调、排序、自定义策略都能直接在调用点写掉。C++17 我会比较关注结构化绑定、if constexpr、optional、variant、string_view,这些东西很实用,既提升表达力,也减少不必要的拷贝。C++20 更有代表性的像 concept、ranges、coroutine。concept 让模板约束更清晰,ranges 让算法和容器组合更自然,coroutine 则对异步流程表达很友好。如果说“有趣”,我会优先聊 lambda、移动语义和 coroutine,因为这几个对代码风格和工程写法影响都很大。
代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {4, 2, 5, 1, 3};
sort(v.begin(), v.end(), [](int a, int b) {
return a < b;
});
for (auto x : v) {
cout << x << " ";
}
return 0;
}
2. auto 和 decltype 的区别
auto 更偏向“让编译器帮我推导这个变量的类型”,通常用于定义变量时根据初始化表达式来推导类型。decltype 更偏向“告诉我这个表达式的类型到底是什么”,它不一定要求真的创建对象,更适合在模板、返回值推导、类型萃取里用。两者都和类型推导有关,但使用场景不一样。auto 主要解决写起来太啰嗦的问题,decltype 更像是一个观察和提取类型的工具。如果在复杂泛型代码里,decltype 会比 auto 更精准,因为它保留表达式类型特征的能力更强。
代码:
#include <iostream>
using namespace std;
int main() {
int x = 10;
auto a = x; // int
decltype(x) b = 20; // int
decltype((x)) c = x; // int&
c = 30;
cout << x << endl; // 30
return 0;
}
3. const 的作用,const 修饰成员函数的作用,如果要修改该怎么办
const 的核心作用是表达“这个值或这个接口不应该修改对象状态”。修饰变量时表示只读语义;修饰指针时可以约束“指向内容不可改”或者“指针本身不可改”;修饰成员函数时,表示这个函数承诺不修改对象的逻辑状态,所以 this 会变成指向常量对象的指针。成员函数后面加 const 很重要,因为它不仅能增强接口约束,还能让常对象也能调用这个函数。如果确实有个别成员需要在 const 函数里修改,比如缓存命中次数、延迟初始化标记这类不影响对象对外逻辑语义的字段,可以用 mutable。
代码:
#include <iostream>
using namespace std;
class Demo {
public:
int get() const {
++cnt_;
return val_;
}
private:
int val_ = 10;
mutable int cnt_ = 0;
};
int main() {
const Demo d;
cout << d.get() << endl;
return 0;
}
4. static 的作用
static 在 C++ 里不是单一语义。修饰局部变量时,它改变的是存储期,变量不再随着函数退出销毁,而是整个程序运行期间都存在,但作用域还是局部;修饰全局变量或函数时,它改变的是链接属性,让符号只在当前编译单元内可见;修饰类成员变量时,它表示这个成员属于类本身,而不属于具体某个对象。所以理解 static 时最好把“生命周期”“作用域”“链接可见性”“归属关系”这几个概念分开看,不要混在一起。
5. C++ 多态的实现机制,虚表指针怎么理解
答案:运行时多态主要依赖继承、虚函数和基类指针或引用。底层上,编译器通常会为有虚函数的类生成一张虚函数表,对象里会有一个隐藏的虚表指针 vptr 指向这张表。调用虚函数时,不是编译期直接决定调用地址,而是运行时通过对象里的 vptr 找到对应函数入口。这也是为什么同一个基类接口,在不同派生类对象上会表现出不同实现。虚表一般在编译期生成,虚表指针则随着对象一起存在于对象内存布局中。
代码:
#include <iostream>
using namespace std;
class Base {
public:
virtual void print() { cout << "Base\n"; }
virtual ~Base() = default;
};
class Derived : public Base {
public:
void print() override { cout << "Derived\n"; }
};
int main() {
Base* p = new Derived();
p->print();
delete p;
return 0;
}
6. 可以用基类对象接收派生类对象来实现多态吗
答案:不能真正实现运行时多态。如果是“基类对象 = 派生类对象”,会发生对象切片。也就是说,派生类中超出基类的那部分会被截掉,只保留基类子对象部分。这样之后通过这个基类对象调用函数,本质上已经不是在操作完整的派生类对象了。真正的运行时多态需要基类指针或者基类引用去绑定派生类对象,这样动态类型信息才不会丢。所以面试里如果问这个点,关键就是答出“对象切片”。
7. 析构函数为什么通常要求写成虚析构,如果不是虚的为什么会出问题
答案:如果一个类会作为基类被多态使用,也就是可能通过基类指针指向派生类对象并释放,那么析构函数通常必
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.