最近遇到挺多宏定义的代码,其实挺烦的,每次看复杂的宏定义看到一半就懵了,今天盘一盘它。本篇设计宏定义的原理、使用方法、使用技巧。
目录
一、宏定义原理
二、宏定义定义复杂功能函数
2.1 定义注册函数
三、宏定义实现条件编译
四、宏定义实现代码重用
4.1 代码重用
4.2 宏定义实现简单的类型转换
一、宏定义原理
C++的宏定义是一种预处理器指令,它可以用来在编译阶段进行简单的文本替换。当编译器遇到宏定义时,它将会把宏定义中的符号替换为其定义的文本内容,然后再继续编译程序。
具体来说,C++代码在编译之前首先被送到预处理器进行预处理,预处理器对代码进行扫描和处理,将所有的宏定义替换为宏定义中指定的内容,生成预处理后的代码,然后编译器再对预处理后的代码进行编译生成目标代码。
下面是一个简单的宏定义示例:
#define PI 3.1415926#define SQUARE(x) ((x) * (x))int main() {int a = 5;int b = SQUARE(a);return 0;
}
在这个示例中,编译器会把所有出现的“PI”符号替换为其定义的文本内容“3.1415926”。宏定义SQUARE(x)
将参数x
的平方作为返回值。当宏定义被调用时,编译器将把函数调用中的参数替换成宏定义中的文本内容,从而实现了计算参数平方的功能。
tips:
需要注意的是,函数宏并不是真正的函数,它没有函数调用的开销和参数检查等功能。另外,由于宏定义只是简单的文本替换,因此在使用函数宏时需要注意其可能带来的意外行为,比如参数求值次数不确定、符号重定义等问题。
这样的宏定义是你熟悉的,下面增加难度。
二、宏定义定义复杂功能函数
2.1 定义注册函数
第一个例子:
在C++中,可以使用宏定义来模拟注册函数。注册函数是指程序在运行时向一个注册表中添加一个函数指针,从而允许在需要时动态调用这个函数。以下是一个简单的示例:
#include <vector>
#define REGISTER_FUNCTION(FUNC_NAME) \static void FUNC_NAME##_register() __attribute__((constructor)); \static void FUNC_NAME##_register() { \function_registry.push_back(&FUNC_NAME); \} \void FUNC_NAME()std::vector<void (*)()> function_registry;REGISTER_FUNCTION(my_function) {// 函数实现
}
在上面的示例中,宏定义REGISTER_FUNCTION(FUNC_NAME)
定义了一个注册函数,该函数在程序运行时向全局的函数注册表中添加函数指针。宏定义中使用了一些技巧来实现函数的自动注册,具体如下:
FUNC_NAME##_register()
定义了一个静态函数,函数名为FUNC_NAME_register
,用于在程序启动时自动调用并向注册表中添加函数指针。这里使用了函数名连接符##
,将宏定义中的函数名和_register
连接起来,生成新的函数名。__attribute__((constructor))
是GNU C编译器提供的一种函数属性,表示该函数将在程序启动时自动调用。这里将函数FUNC_NAME##_register()
设置为该属性,以实现自动注册的功能。function_registry.push_back(&FUNC_NAME)
将函数指针&FUNC_NAME
添加到全局的函数注册表中。void FUNC_NAME()
定义了实际的函数实现,这里使用了空函数体,具体的函数实现可以根据需求进行修改。
第二个例子:
#include <iostream>
#include <string>
#include <unordered_map>using namespace std;// 定义一个宏,用于简化类的注册函数的定义
#define REGISTER_CLASS(class_name) \class class_name; \namespace { \struct Register_##class_name { \Register_##class_name() { \ClassFactory::getInstance().registerClass(#class_name, []() -> void* { return new class_name; }); \} \}; \static Register_##class_name s_register_##class_name; \} \// 类工厂,用于注册和创建类
class ClassFactory {
public:static ClassFactory& getInstance() {static ClassFactory instance;return instance;}void registerClass(const string& className, void* (*creator)()) {m_classRegistry[className] = creator;}void* createClass(const string& className) {auto it = m_classRegistry.find(className);if (it == m_classRegistry.end()) {return nullptr;}else {return it->second();}}private:unordered_map<string, void* (*)()> m_classRegistry;
};// 定义一个基类
class BaseClass {
public:virtual ~BaseClass() {}virtual void print() = 0;
};// 定义一个派生类1
class DerivedClass1 : public BaseClass {
public:void print() override {cout << "DerivedClass1" << endl;}
};// 定义一个派生类2
class DerivedClass2 : public BaseClass {
public:void print() override {cout << "DerivedClass2" << endl;}
};// 注册类
REGISTER_CLASS(DerivedClass1);
REGISTER_CLASS(DerivedClass2);// 主函数
int main() {auto obj1 = static_cast<BaseClass*>(ClassFactory::getInstance().createClass("DerivedClass1"));obj1->print();auto obj2 = static_cast<BaseClass*>(ClassFactory::getInstance().createClass("DerivedClass2"));obj2->print();return 0;
}
在上面的代码中,我们使用了#define
宏定义了一个宏REGISTER_CLASS
,用于简化类的注册函数的定义。这个宏定义的具体实现包括以下几个步骤:
-
定义一个类对象,这个类对象用于触发类的注册操作。
-
定义一个匿名命名空间,在这个命名空间中定义一个结构体
Register_##class_name
,这个结构体在构造函数中调用类工厂的registerClass
函数,将类的名称和一个创建类的函数关联起来。在结构体定义之后,我们创建一个静态结构体对象s_register_##class_name
,这个对象的唯一作用就是调用结构体的构造函数,从而触发类的注册操作。 -
最后,我们在类的定义后面使用
REGISTER_CLASS
宏来注册这个类。
使用宏定义可以让我们在类的定义中加入很少的代码,就可以将类注册到类工厂中。
三、宏定义实现条件编译
为实现代码的移植能力,我们可以使用宏定义设置编译条件。例如实现调试模式、跨平台支持等。这种宏定义通常在开发过程中使用较多。
#ifdef DEBUG// 调试模式代码
#endif#ifdef _WIN32// Windows 平台代码
#endif#ifdef __linux__// Linux 平台代码
#endif
另一个例子:
#include <iostream>
using namespace std;#define DEBUGint main() {#ifdef DEBUGcout << "Debug version" << endl;#elsecout << "Release version" << endl;#endifreturn 0;
}
我们使用了#define宏定义了一个名为DEBUG的宏。在主函数中,我们使用#ifdef指令和#endif指令将一段代码包裹起来,这段代码只有在DEBUG宏被定义的情况下才会被编译。在这个例子中,我们只是简单地输出一句话,但在实际的应用中,我们可以在条件编译中包含或者排除一些特定的代码,以实现特定的功能或者优化。
需要注意的是,条件编译是在预处理阶段完成的,而不是在编译阶段。在预处理阶段,预处理器会扫描源代码中的宏定义和条件编译指令,并根据它们生成一份新的代码,这份新的代码会成为编译器的输入。因此,在编译时,条件编译指令不会再起作用,已经被处理掉了。
我们不能将宏定义当成if else来使用,会出问题的。
四、宏定义实现代码重用
例如实现多次调用相同的代码片段、实现简单的类型转换等。这种宏定义通常使用较多。
#define MY_MACRO(code) do { \// 执行代码片段 code \// ... \
} while (0)#define CAST(type, ptr) reinterpret_cast<type>(ptr)
4.1 代码重用
#include <iostream>#define PRINT_MSG(msg) cout << "Message: " << msg << endlusing namespace std;int main() {PRINT_MSG("Hello World!");PRINT_MSG("Welcome to the world of macros!");return 0;
}
我们使用了#define
宏定义了一个名为PRINT_MSG
的宏。这个宏接受一个参数msg
,在控制台输出一个以"Message:"
开头的字符串和msg
参数。在main
函数中,我们两次调用这个宏,分别输出了"Hello World!"
和"Welcome to the world of macros!"
。
4.2 宏定义实现简单的类型转换
#include <iostream>#define FLOAT_TO_INT(x) (int)(x)using namespace std;int main() {float f = 3.14159;int i = FLOAT_TO_INT(f);cout << "The value of float f is " << f << endl;cout << "The value of int i is " << i << endl;return 0;
}
在上面的例子中,我们使用了#define
宏定义了一个名为FLOAT_TO_INT
的宏。这个宏接受一个浮点数类型的参数x
,将其强制转换为整数类型,并返回整数值。在main
函数中,我们定义了一个浮点数变量f
,并将其赋值为3.14159
。然后,我们调用FLOAT_TO_INT
宏,将f
转换为整数类型,并将转换后的值赋值给整数变量i
。最后,我们分别输出了f
和i
的值。