宏与前向声明
引言
宏是很多语言(尤其 C/C++)绕不开的概念,而前向声明更是提高编程和编译效率的利器,所以两者对 C/C++ 学习来说是必须要掌握的内容。
宏
宏(Macro)是一个广泛应用于计算机科学、编程、办公软件及自动化领域的术语,其核心定义是通过预定义的指令或代码块,实现批量操作、自动化任务或扩展功能。
计算机科学里的宏是一种抽象(Abstraction),它根据一系列预定义的规则替换一定的文本模式。解释器或编译器在遇到宏时会自动进行这一模式替换。对于编译语言,宏展开在编译时发生,进行宏展开的工具常被称为宏展开器。
宏这一术语也常常被用于许多类似的环境中,它们是源自宏展开的概念,这包括键盘宏和宏语言。绝大多数情况下,“宏”这个词的使用暗示着将小命令或动作转化为一系列指令。
宏不仅在编程语言中被大量使用,在 Microsoft Office、VIM等文本编辑器中也偶尔会用到。
以下是 C/C++ 中对宏特性的概述:
预处理器在除预处理器指令之外的所有行中扩展宏,这些行使用 # 作为第一个非空格字符。 它在一些指令的各部分展开宏,这些指令在条件编译过程中没有跳过。 条件编译指令可以禁止编译源文件的各个部分。 它们会测试常数表达式或标识符,以确定要传递给编译器的文本块,以及预处理过程中要从源文件中移除的文本块。
#define 指令通常用于将有用标识符与常量、关键字和常用语句或表达式关联。 表示常数的标识符有时称为“符号常数”或“清单常数”。 表示语句或表达式的标识符称为“宏”。 在该预处理器文档中,仅使用术语“宏”。
在程序源文本或某些其他预处理器命令的自变量中识别宏的名称时,它将被视为对该宏的调用。 宏名称将替换为宏主体的副本。 如果宏接受自变量,则宏名称后面的实际自变量将替换为宏主体内的形式参数。 将宏调用替换为已处理的主体副本的过程称为宏调用的“扩展”。
实际上,有两种类型的宏。 “类似于对象”的宏不采用任何自变量。 “类似于函数”的宏可以定义为接受自变量,以便其外观和行为类似于函数调用。 由于宏不生成实际函数调用,因此你有时可以将函数调用替换为宏以使程序更快地运行。 (在 C++ 中,内联函数通常是首选方法。)但是,如果未定义和小心使用宏,宏会导致出现问题。 必须在带有自变量的宏定义中使用括号,以便在表达式中保持适当的优先级。 此外,宏无法正确处理具有副作用的表达式。 有关详细信息,请参阅 #define 指令中的 getrandom 示例。
一旦定义了宏,就无法在未先移除原始定义的情况下将其重定义为不同的值。 但是,您可以使用完全相同的定义来重定义宏。 因此,相同的定义可能会在一个程序中出现多次。
#undef 指令将移除宏的定义。 一旦移除了定义,就可以将宏重定义为不同的值。
前向声明
前向声明是一种告诉编译器某个类、结构或函数存在的声明,但并不提供它的具体定义。前向声明的目的是在不需要完整定义的情况下让编译器知道某个标识符的存在,从而减少编译依赖和加快编译速度。
为什么需要前向声明?
在 C++ 中,编译器需要在使用某个标识符之前知道它的定义。通过前向声明,可以告诉编译器某个类或结构的存在,而无需引入它的完整定义。这可以用来解决循环依赖问题,同时还可以提高编译效率。
前向声明原理
前向声明是告诉编译器“某个类或结构存在”,C++ 中编译器在编译阶段只需要知道:
- 某个标识符(如类、结构或枚举)的存在;
- 如果只涉及指针或引用的声明,编译器只需要知道这个类型的名字,而不需要知道其具体定义。
换句话说,前向声明是告诉编译器:“这个名字是合法的,稍后(链接时)会提供它的具体定义。”
C++ 编译流程
主要分为以下几个阶段:
预处理阶段
- 展开宏,包含头文件等操作。
编译阶段
- 将每个 .cpp 文件翻译为机器代码(生成目标文件);
- 在这一步,编译器只需要知道符号的类型和占用的空间(例如指针的大小),但不需要知道类型的具体实现。
链接阶段
- 将多个目标文件和库链接在一起,生成最终的可执行程序;
- 此时会检查类型的具体实现是否存在。
前向声明的作用在 编译阶段,它可以让编译器知道某些类型的存在,而完整定义可以在链接阶段解决。
占位符机制
- 在前向声明中,编译器会为前向声明的类型创建一个“占位符”;
- 这个占位符只包含类型的名字,表示“这个名字是合法的”。
由上可见,前向声明主要作用在编译和链接阶段:编译阶段告诉编译器该类型合法,需要为其创建一个“占位符”;链接阶段链接器检查是否存在具体定义,如果存在则将其引入,如果不存在会采取相应措施(报错并终止)。
但是即便编译期不需要类型的具体定义,当该类型用作定义一个变量或作为函数入参或返回值时只能定义为该类型的指针,原因是编译期编译器需要知道目标变量所占用空间大小,而指针类型对编译器而言是固定的(如,32位机上大小是四字节),也就不会出现编译错误。
前向声明总结
优点
- 减少头文件包含,降低代码耦合;
- 提高编译效率;
- 打破循环依赖。
缺点
- 只能用于指针、引用等,不能实例化对象或调用函数;
- 使用过度会大幅降低代码可读性,不利维护。
注:前向声明和具体定义之间涉及标识符(变量、结构、函数等)实现细节的使用都是非法的(表明只能使用指针的形式来引用前向声明)。
最后
宏和前向声明都是编程开发过程中有利的辅助工具,合理使用可以大幅提高开发效率,使代码更加简洁,提高代码的可重用性,但过度依赖也会造成难以维护,可读性降低等负面影响。掌握好二者之间的平衡对整个项目的开发周期及产品质量有着极大的积极作用。

浙公网安备 33010602011771号