C++八股文(基础知识篇2)
1. 什么是内联函数(inline)?
- 定义:用inline关键字修饰的函数,建议编译器在调用处展开函数体,而不是进行常规的函数调用
- 优点:消除函数调用开销(压栈、跳转、返回),提高执行效率,特别适合小型、频繁调用的函数
- 缺点:代码膨胀,如果函数体较大或调用次数多,会增加可执行文件大小
- 注意事项:inline只是建议,编译器可能忽略;递归函数、虚函数通常不会内联;类内定义的成员函数默认是inline
- 使用场景:getter/setter、简单的数学运算、循环内的小函数
2. 如何定义和使用常量表达式(constexpr)?
- 定义:C++11引入,表示值可以在编译期计算的常量表达式
- constexpr变量:
constexpr int SIZE = 100;必须用常量表达式初始化,可用于数组大小等需要编译期常量的地方 - constexpr函数:
constexpr int square(int x) { return x * x; }如果参数是常量,编译期求值;否则运行期执行 - 与const区别:const只保证不可修改,constexpr保证编译期可求值。constexpr隐含const
- C++14增强:constexpr函数可以包含多条语句、局部变量、循环等
- 使用场景:性能优化、模板元编程、编译期计算
3. 类型转换(static_cast、dynamic_cast、const_cast、reinterpret_cast)有什么区别?
- static_cast:编译期检查的类型转换,用于基本类型转换、类层次结构中的上行转换(派生类→基类)、显式构造函数调用。不能去除const,不做运行期检查
- dynamic_cast:运行期类型检查,用于类层次结构中的安全下行转换(基类→派生类)。要求基类有虚函数,转换失败返回nullptr(指针)或抛异常(引用)
- const_cast:唯一能去除或添加const/volatile属性的转换。常用于调用非const函数但不想修改对象时
- reinterpret_cast:最危险的转换,重新解释内存位模式,用于指针与整数、不同类型指针间转换。不做任何检查,完全依赖程序员
- 使用原则:优先用static_cast,需要运行期检查用dynamic_cast,避免使用reinterpret_cast
4. typedef 和 using 有何区别?
- 基本功能:都用于类型别名。
typedef int* IntPtr;和using IntPtr = int*;等价 - 语法清晰度:using语法更直观,特别是函数指针:
using FuncPtr = void(*)(int);vstypedef void(*FuncPtr)(int); - 模板别名:using支持模板别名(C++11),typedef不支持。
template<typename T> using Vec = std::vector<T>; - 作用域:两者作用域规则相同
- 推荐:现代C++推荐using,语法更清晰统一
5. explicit 关键字有何作用?
- 作用:防止单参数构造函数的隐式类型转换
- 问题场景:
class String { String(int size); };可能导致String s = 10;这种意外的隐式转换 - 解决方案:
explicit String(int size);禁止隐式转换,必须显式调用String s(10); - 适用范围:单参数构造函数、转换运算符(C++11)
- 最佳实践:除非明确需要隐式转换(如智能指针转bool),否则单参数构造函数都应该加explicit
- 好处:避免意外的类型转换,提高代码安全性和可读性
6. mutable 关键字是什么?
- 作用:允许const成员函数修改被mutable修饰的成员变量
- 使用场景:缓存、计数器、互斥锁等逻辑上不改变对象状态但需要修改的成员
- 示例:
mutable int accessCount;在const函数中可以accessCount++; - 设计理念:区分逻辑常量性(对外接口不变)和物理常量性(内存不变)
- 典型应用:延迟计算、线程同步(
mutable std::mutex mtx;)、调试信息 - 注意:不要滥用,只用于确实不影响对象逻辑状态的成员
7. 什么是命名空间(namespace),如何使用它?
- 定义:将全局作用域划分为不同的命名区域,避免命名冲突
- 声明:
namespace MyLib { class MyClass {}; } - 使用方式:①完全限定
MyLib::MyClass obj;②using声明using MyLib::MyClass;③using指令using namespace MyLib;(不推荐) - 嵌套命名空间:
namespace A::B::C {}(C++17简化语法) - 匿名命名空间:
namespace {}替代static,实现文件内部链接 - std命名空间:标准库都在std中,避免污染全局空间
- 最佳实践:头文件避免using指令,实现文件可以局部使用
8. 模板(Template)是什么?
- 定义:泛型编程的基础,允许编写与类型无关的代码,编译期生成具体类型的代码
- 函数模板:
template<typename T> T max(T a, T b) { return a > b ? a : b; } - 类模板:
template<typename T> class Vector { T* data; }; - 特化:为特定类型提供特殊实现。全特化和偏特化
- 模板参数:类型参数、非类型参数(整数、指针等)、模板模板参数
- 优点:代码复用、类型安全、性能优化(编译期展开)
- 缺点:编译时间长、错误信息复杂、代码膨胀
- STL基础:容器、算法、迭代器都基于模板实现
9. 什么是编译时和运行时多态?
- 编译时多态(静态多态):通过函数重载、模板实现,编译期确定调用哪个函数。性能高,无运行时开销,但灵活性较低
- 运行时多态(动态多态):通过虚函数和继承实现,运行期通过虚函数表(vtable)确定调用。灵活,支持动态绑定,但有虚函数调用开销
- 实现机制:编译时多态靠名称修饰和模板实例化;运行时多态靠虚函数指针和动态绑定
- 使用场景:编译时多态用于性能敏感、类型已知的场景;运行时多态用于需要动态扩展、插件系统等
- 现代趋势:C++20的concepts增强了编译时多态的表达能力
10. inline 函数和宏(macro)有什么不同?
- 类型安全:inline函数有类型检查,宏是文本替换,没有类型检查
- 调试:inline函数可以调试、设断点,宏展开后难以调试
- 作用域:inline函数遵循作用域规则,宏无视作用域
- 副作用:宏可能导致参数多次求值
#define MAX(a,b) ((a)>(b)?(a):(b))如果a是i++会执行两次;inline函数参数只求值一次 - 返回值:inline函数有明确的返回类型,宏没有
- 命名空间:inline函数可以在命名空间中,宏不能
- 性能:两者都能消除函数调用开销,但inline更可控
- 结论:现代C++应该用inline函数或constexpr替代宏,宏只用于条件编译
C++八股文全集 文章被收录于专栏
本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。
