该集只是简单介绍一下

  • 用预处理器对某些操作进行==类似宏处理实现自动化 ==

  • 编译C++程序时,首先要进行预处理器处理

    • 以#开头的都叫做预处理器指令
    • 会被求值,然后处理好。再传递给编译器进行实际编译等操作—文本编辑阶段(可以控制实际输入到编译器的代码)
  • 工作阶段:宏是在 预处理(Preprocessing) 阶段处理的。这意味着在编译器真正看到你的代码并进行语法分析之前,宏就已经被处理完毕了。

  • 核心功能:宏本质上是 查找与替换(Find and Replace)。预处理器会扫描你的源代码,找到宏的名称,并将其替换为你定义的代码块或值。

  • 预处理器(Preprocessor)先处理,模板(Templates)后处理。

    • 预处理器处理完后,编译器再进行模板实例化
  • 建议:没有必要频繁的使用高级特性

  • 宏是“翻译单元”私有的

    • 在 C++ 编译过程中,每个 .cpp 文件都是独立编译的,这被称为一个翻译单元(Translation Unit)。
    • 如果你想在多个 .cpp 文件中使用同一个宏,你应该把它放在一个头文件(.h)中,然后在需要的 .cpp 文件里#include它。
#include <iostream>
#include <string>
int main()
{
	//#define在改行代码之后,所以这行编译不过去
	//WAIT;

	#define WAIT std::cin.get()
	//等待从控制台读取一行 
	WAIT;
}

代码被预处理器处理后编译器看不出和普通代码有任何区别,我们用宏仅仅是改变了源代码的生成方式

例子1#

#include <iostream>
#include <string>

//等待从控制台读取一行 
//如果改行末尾带了;分号,也会被替换
#define WAIT std::cin.get()
#define OPEN_CURLY { 
#define SAY_HELLO std::cout << "Hello1" << std::endl

int main()
OPEN_CURLY

	SAY_HELLO;
	WAIT;
}

例子2#

#include <iostream>
#include <string>

#define LOG(x) std::cout << x << std::endl

int main()
{

	LOG("Hello");
	std::cin.get();
}

分别处理调试和正式发布#

Debug模式下预处理定义了 LY_DEBUG;

RELEASE模式下预处理定义了 LY_RELEASE;

先看预览

RELEASE模式下没有打印任何东西

修正#

#include <iostream> 

//#define LY_DEBUG 0
#if LY_DEBUG == 1 //建议使用值判断
//#ifdef LY_DEBUG
#define LOG(x) std::cout << x << std::endl
#elif defined(LY_RELEASE)
#define LOG(x)
#endif
int main()
{

	LOG("Hello");
	std::cin.get();
}

因为通常是这样:#define LY_DEBUG 或者 #define LY_DEBUG 1 ,如果不需要的时候就可以直接#define LY_DEBUG 0而不是注释掉

修改后还要更改刚才的配置

一些其他用法#

使用预处理去除了代码中的一些内容

多行#

#include <iostream>
#include <string>

# define MAIN int main() \
{\
\
	std::cin.get();\
}

MAIN 
  • 换行前添加反斜杠
  • 确保反斜杠之后没有空格,否则就是在转义空格了,会报错

最后的替换系统内的new#

#include <iostream>
#include <string>

// --- 1. 自定义的内存分配函数 ---
// size 会由 new 运算符自动传入,file 和 line 由宏传入
void* LoggedAllocation(size_t size, const char* file, int line) {
    std::cout << "[Memory Log] 正在分配 " << size << " 字节 | 位置: "
        << file << " | 行号: " << line << std::endl;

    return malloc(size); // 实际调用底层的分配
}

// --- 2. 宏魔法 ---
// __FILE__ 和 __LINE__ 是预处理器的内置宏
// 当你在代码中写 MALLOC(50) 时,它们会被替换为当前文件的路径和当前行号
#define MALLOC(size) LoggedAllocation(size, __FILE__, __LINE__)

// --- 3. 进阶:劫持 new 关键字 ---
// 注意:这种重载 new 的方式通常配合宏来覆盖全局行为
void* operator new(size_t size, const char* file, int line) {
    std::cout << "[New Trace] 分配大小: " << size << " 字节" << std::endl;
    std::cout << "            文件路径: " << file << std::endl;
    std::cout << "            代码行号: " << line << std::endl;
    return malloc(size);
}

// 必须定义匹配的 delete(虽然宏不直接处理 delete 的行号,但语法上需要对应)
void operator delete(void* memory) noexcept {
    free(memory);
}

//这个_DEBUG是visual studio默认添加的定义,目前不知道是在哪,不过可以通过项目配置
//的UndefinePreprocessorDefinitions中添加 _DEBUG 去除定义
// 魔法核心:在 Debug 模式下开启替换
#ifdef _DEBUG
    // 将代码中所有的 "new" 替换为 "new(__FILE__, __LINE__)"
    // 这样编译器会寻找我们上面定义的那个带 3 个参数的 operator new
#define new new(__FILE__, __LINE__)
#endif

class Entity {
public:
    Entity() { std::cout << "Entity 构造成功!" << std::endl; }
};

int main() {
    // 场景 A:使用自定义宏
    void* buffer = MALLOC(100);

    std::cout << "-----------------------------------" << std::endl;

    // 场景 B:由于劫持了 new,这行代码在预处理后会变成:
    // Entity* e = new("C:\\User\\main.cpp", 51) Entity();
    Entity* e = new Entity();

    delete e;
    free(buffer);

    std::cin.get();
}

当你写下 new Entity() 时,它被称为 new 表达式 (new expression)。编译器并不会把它简单地看作一个函数调用,而是会将其拆解为两个完全不同的步骤执行:

第一步:分配原始内存 (Allocation)

编译器首先会调用一个名为 operator new 的函数。

  • 它的任务: 只负责“找地皮”。它根据对象的大小,在堆(Heap)上申请足够字节的原始内存。
  • 返回值: 它返回一个 void* 指针,指向一块未经初始化的、纯粹的“毛坯房”内存。

关联: 这就是你在 Cherno 视频中看到的,我们可以通过宏劫持并重写的部分。

第二步:初始化对象 (Initialization) —— 关键点

一旦第一步成功拿到地址,编译器会立即、自动在那个返回的内存地址上调用 构造函数 (Constructor)。

  • 它的任务: 负责“搞装修”。它把原始字节变成一个真正的对象,设置成员变量的初始值。
  • 强制性: 这一步是由 C++ 编译器根据 new 表达式 的语法规则强行插入的代码,你无法通过简单的宏替换来跳过这一步(除非你完全不用 new 这个词)。

例子

// 1. 模拟第一步:分配内存(拿到 void*,此时没有调用构造函数)
void* memory = operator new(sizeof(Entity)); 

// 2. 模拟第二步:手动在已有内存上构造(这叫 Placement New)
Entity* e = new(memory) Entity();