编译
Table 1 预处理行为
| 序号 | 步骤名称 | 工作内容 |
|---|---|---|
| 1 | 头文件展开 | 将所有#include引入的头文件递归嵌入到源文件中 |
| 2 | 宏文本替换 | 代码中所有宏标识符替换为定义的文本内容 |
| 3 | 去除注释 | 删除代码里全部单行//、多行/* */注释 |
| 4 | 条件编译展开 | 解析#ifdef/else/endif,剔除不生效的代码分支 |
Table 1 Preprocessing
| Serial Number | Step Name | Work Details |
|---|---|---|
| 1 | Header file expansion | Recursively expand all included header files into the source file |
| 2 | Macro text replacement | Replace all macro names with corresponding literal text |
| 3 | Comment elimination | Delete all single-line and multi-line comments in code |
| 4 | Conditional compilation expansion | Parse #ifdef/#else/#endif and discard unreachable code branches |
Table 2 编译链接核心工作
| 操作项 | 详细说明 |
|---|---|
| 数据段合并 | 合并所有目标文件的全局变量、静态变量、常量字符串到统一段区 |
| 地址回填(重定位) | 解析未定义符号,为代码引用填充正确内存地址 |
| 库引入 | 加载静态库/动态库,完成跨文件外部符号解析 |
Table 2 Compile & Link Core Work
| Operation Item | Specific Description |
|---|---|
| Data segment merging | Merge global variables, static variables, string constants from all object files into unified segments |
| Address backfilling (relocation) | Resolve undefined symbol addresses and fill correct memory addresses for references |
| Library introduction | Load static/dynamic libraries, resolve external cross-file symbols |
局部数组0初始化
局部数组 char str20; 存储随机垃圾值,不会自动全部置零
char str20 = {};//置零,C++98
char str20{};//置零,C++11 引入
全局 / 静态数组 char str[20];(定义在函数外部)无需手动赋值,会自动全部置零。
-
English: Local variables are allocated on the stack, and stack memory is not cleared automatically by the compiler.
局部变量分配在栈内存,栈内存不会由编译器自动清零。
-
English: If you write
char str[20] = {0};, only the first element is set to 0, and all remaining elements are zero-initialized implicitly.如果写
char str[20] = {0};,仅第一个元素显式设为 0,剩余所有元素会被隐式初始化为 0。
静态成员变量
类内声明,类外定义
为什么不用构造函数初始化化静态成员变量?
因为静态成员变量可以直接用类名访问,不需要依赖对象,这时候不经过构造函数,没有采用构造函数内的初始化条件
sizeof(空类)=1
每个对象都应该在内存上有独一无二的地址,
不要重载&&和||
逻辑与和逻辑或, 无法模拟其短路特性
operator&&和operator||的操作数是对象而不是表达式,需要先得到/计算出具体的对象才能进行,因此无法短路
singleton中从指针拿到单例对象的引用
singleton模式的类class Printer 的唯一 static Printer * printer;
用引用方式取得 Printer & ref_printer = * printer; 这就从指针拿到了对象
可以使用对象来调用重载函数,例如打印函数的成员函数operator() 可以直接使用
operator= 自赋值判断
== this==&person==
若为自赋值,则可能会导致误删自己的内存,这是无法恢复的
要记得return *this
值初始化 (value‑initialization) new int10 ();
基础数据类型T
new (T)10 (); +()可以初始化为0,这是C++98的标准写
(与类的默认构造一致,基础数据类型也可以这样)
cpp
new int[10]; // 默认初始化
new int[10](); // 值初始化
new int[10]{ }; // C++11以后,列表初始化
- 对自定义类类型
cpp
struct A { int x; };
A* arr = new A[10](); 中 ()触发值初始化:会调用A()默认构造函数逐个构造对象。
不带括号:A* arr = new A[10];同样调用默认构造;类类型默认初始化与值初始化行为大多一致;差异主要体现在内置基础类型。
模拟实现vector不要全部初始化
vector使用push_back()插入对象,插入一个初始化一个即可,没有必要在分配内存时对所有元素进行零初始化,这样做会带来大量无用开销。
std::vector 的 reserve() 只会分配原始的、未初始化的内存块(raw memory) ,不会构造任何对象,不会做零初始化 ;等到push_back/emplace_back的时候,再用定位 new在空闲内存上逐个构造对象。
- 如果 reserve 分配内存的时候,就把 capacity 全部的空间全部初始化 / 置零,当 capacity 很大、实际 size 很小的时候,会产生大量完全没有必要的构造、清零开销。
- vector 底层需要拿到原始、未初始化的内存,只负责内存的申请,对象构造推迟到 push_back。
该结论针对reserve;如果是vector(n)、resize(n),业务语义要求存在 n 个有效对象,则必须完成对应对象构造。
重载\[\]的operator\[\]一定要进行下标index的判断
继承与类型转换
向上转型(基类指针/引用接收派生类对象);protected/private继承阻断隐式向上转换,不再满足
is‑a语义。
三种继承访问控制 & 向上隐式转换规则 表
| 继承方式 | 语法示例 | 派生类内部基类成员可见性 (基类public→派生类中) | 派生类内部基类成员可见性 (基类protected→派生类中) | 派生类内部基类成员可见性 (基类private→派生类中) | 派生类对象 → 基类指针/引用 隐式向上转型 | 解读/注释 |
|---|---|---|---|---|---|---|
public公有继承 |
class Derived : public Base |
public | protected | 不可见 | ✅支持隐式向上转型 | 满足经典is‑a 语义;派生类对象就是一种基类对象;外部可以直接 Base* p = &d;;是多态的前提条件。 |
protected保护继承 |
class Derived : protected Base |
protected | protected | 不可见 | ❌禁止隐式向上转型 可强制static_cast,语义不是is‑a | 基类public全部降级为protected;仅派生类、派生类的子类内部可以访问基类成员;外部不能隐式转基类指针;表达"实现复用",不是is‑a关系。 |
private私有继承 |
class Derived : private Base |
private | private | 不可见 | ❌禁止隐式向上转型 可强制static_cast,语义不是is‑a | 基类全部成员降级为派生类private;仅本派生类内部可用,派生类的子类也无法访问基类成员;纯粹为复用基类实现,完全没有is‑a。 |
- 向上转型隐式转换的本质 :C++语法规则:只有
public继承才代表派生类是一种基类,编译器自动允许隐转换;protected/private继承代表"复用基类代码",不是is‑a,编译器直接禁止隐式转换。static_cast<Base*>(&derived)强制转换语法上可以编译通过,属于绕过继承访问权限,但不代表is‑a,业务上不要这么写;private/protected继承设计本意就不是用来做向上转型。
继承与多态
三种继承与多态支持情况 表
| 继承方式 | 是否支持多态(虚函数) | 推荐程度 | 语义关系 | 关键说明、注意事项 | 典型使用场景 |
|---|---|---|---|---|---|
public公有继承 |
✅支持多态 | ⭐强烈推荐 | is‑a(是一个) | 只有public继承,虚函数才能发挥多态效果;允许隐式向上转型;面向对象接口继承。 | 接口抽象、虚函数多态、基类指针管理派生类对象。 |
protected保护继承 |
⚠️语法上虚函数仍存在,但不能外部多态调用 | ⚠️不推荐 | has‑a(复用实现) | 外部无法隐式向上转型,无法通过基类指针触发多态;仅派生类及其子类内部可以使用基类功能。 | 极少使用;需要让派生类及其子类复用基类实现,对外屏蔽基类接口。 |
private私有继承 |
❌不支持对外多态 | ❌不推荐 | has‑a(复用实现) | 虚函数仅在派生类内部可见,外部无法通过基类指针调用多态行为;等价于组合的一种替代方案。 | 复用基类代码实现,但完全对外隐藏基类所有接口;优先使用**组合(包含对象成员)**替代私有继承。 |
定位new(placement new)
cpp
char buf[100];
T* p = new (buf) T(10);
// Only construct object on buf, no memory allocation
定位new(placement new),C++98 标准就已经存在。
- English: Placement new does NOT allocate any memory; it only invokes the constructor of T on the memory address you provide (here the address stored in
finish).
中文:定位new不分配任何内存,它只在你给定的内存地址(此处是finish保存的地址)上调用T的构造函数。
arr decays to int*
cpp
int arr[10] = {1,2,3};
int (*arrx2)[10] = arr;//err
int (*arrx2)[10] = &arr;//correct form
用数组指针强转,可以将退化的数组指针 重新指向整个数组