宏
- 来源: https://www.freebasic.net/wiki/wikka.php?wakka=ProPgMacros
- 最后更新: 2022-09-02
宏是命名的代码片段,每次在程序中遇到其名称时都会将其替换。
宏提供了一种扩展语言和创建可重用代码的强大方式。
使用宏的原因之一是性能。
它们是消除过程调用开销的一种方式,因为它们总是内联展开的。
在 FreeBASIC 中没有替代方案,因为它不支持内联过程。
宏定义
预处理器在文本替换机制中可以使用提供给要替换标识符的参数。
这些参数然后在替换文本中不做修改地替换。
替换文本称为宏。
定义宏的语法如下(参见'预处理器命令'):
- 单行宏:
#define identifier([parameters]) body
parameters 将 define 转变为类函数宏,允许将文本参数传递给宏。
identifier 后面应该紧跟左括号((),中间不得有任何空白,否则编译器会将其视为 body 的一部分。
- 多行宏:
#macro identifier([parameters])
body
#endmacro
#macro 是 #define 的多行版本。
# stringize 运算符可用于将宏参数转换为字符串字面量,## concatenate 运算符可用于将标记合并在一起。
(参见'预处理器运算符')
定义和宏是_有作用域的_(只在定义它们的作用域内可见)。
另一方面,命名空间对定义和宏的可见性没有任何影响。
宏机制允许实现通用过程的等效功能,适用于所有类型。
但是,必须注意,传递给宏的参数在宏定义中每次使用时都会被求值。这可能导致性能问题,甚至更严重的是,导致不希望的副作用。
应始终在宏的参数周围加上括号:
确实,这些参数可以是复合表达式,必须在宏中使用之前完整计算。
括号强制进行此计算。
如果不设置,运算符优先级规则可能会在宏本身中产生逻辑错误。
类似地,返回值的宏也应该用括号括起来,以便在另一个表达式中使用之前强制完整求值。
错误和正确宏的示例:
start GeSHi
#define MUL1(x, y) x * y '' incorect macro (parameters must be enclosed in parentheses)
#define MUL2(x, y) ( x ) * ( y ) '' incorrect macro (and returned result must also be in parentheses)
#define MUL3(x, y) ( ( x ) * ( y ) ) '' correct macro
Print MUL1(5-3, 1+2)^2 '' 6 (incorrect result)
Print MUL2(5-3, 1+2)^2 '' 18 (incorrect result)
Print MUL3(5-3, 1+2)^2 '' 36 (correct result)
Sleepend GeSHi
因此,括号确保了宏的一致行为(括号可以添加到宏定义中,但它们是绝对必要的)。
宏调试
使用宏可能非常不安全,它们隐藏了很多很难发现的陷阱。
过程提供类型检查和作用域,但宏只是替换传递的参数。
宏的另一个缺点是程序的大小。原因是,预处理器会在程序的编译过程之前将程序中的所有宏替换为其真实定义。
仅查看源代码文件,找出问题的唯一方法是查看宏的定义并尝试理解发生了什么。
使用宏时最常见的错误是不匹配的左括号(导致编译时错误)。
另一个是忘记在宏定义中的参数(或返回结果,如果存在)周围加括号。由于运算符优先级,这可能会导致一些相当麻烦的副作用(导致编译时错误或运行时错误)。
当编译器在宏内部(展开后)检测到错误时,它提供一条简陋的错误消息,其中只包含:
宏调用所在的行号,
错误类型,
调用文本(宏的调用)。
当错误不明显(报告的错误类型模糊)时,(仅凭调用行号)目前唯一的解决方案是迭代执行以下 5 个步骤,直到成功修正:
Do
1. 对源文件调用 fbc,但使用 '-pp' 编译选项(fbc 命令只输出预处理后的输入文件,不进行编译),
2. 获取预处理后的文件,
3. 直接从此预处理后的文件编辑和编译,
4. 分析错误,理解它,修正它,然后再次编译修改后的预处理文件,
5. 将等效的修正推迟到原始源文件中相关宏体中。
Loop
短代码中的错误示例:
- 源文件(*.bas):
- 预处理后的文件(*.pp.bas):
- 源代码(*.bas)中宏修正示例:
注意:另一个解决方案可能是编译器提供更详细的错误消息,涉及宏的调用(改进错误消息的建议已在功能请求中提交)。
可变参数宏
在 #macro 或 #define 声明中最后一个参数后面使用省略号 "..." (3个点) 允许创建可变参数宏:
#macro identifier([parameters,] variadic_parameter...)
body
#endmacro
或:
#define identifier([parameters,] variadic_parameter...) body
因此,可以通过 variadic_parameter 传递任意数量的参数,在宏体中使用时就像普通宏参数一样。
在宏展开过程中,宏替换列表中每次出现的 variadic_parameter 都会被传递的参数替换。variadic_parameter 将展开为传递给它的完整参数列表(包括逗号),也可以完全为空。
没有直接提供递归访问变量参数列表中各个参数的方法。
要区分通过 variadic_parameter 传递的不同参数,可以先使用 # stringize 运算符将 variadic_parameter 转换为字符串字面量,然后在此字符串字面量(#variadic_parameter)中通过定位分隔符(逗号)来区分每个传递的参数。
示例
单行和多行宏的示例:
start GeSHi
#define MIN(x, y) IIf( ( x ) < ( y ), x, y ) '' maximum function-like macro
#define MAX(x, y) IIf( ( x ) > ( y ), x, y ) '' minimum function-like macro
#define MUL(x, y) ( ( x ) * ( y ) ) '' multiply function-like macro
#macro REV_STR(str_dest, str_src) '' reverse-string sub-like macro
For i As Integer = Len(str_src) To 1 Step -1
str_dest &= Mid(str_src, i, 1)
Next I
#endmacro
Print MIN(5 - 3, 1 + 2) '' 2
Print MAX(5 - 3, 1 + 2) '' 3
Print MUL(5 - 3, 1 + 2)^2 '' 36
Dim As String s
REV_STR(s, "CISABeerF") '' FreeBASIC
Print s
Sleepend GeSHi
可变参数宏的示例:
start GeSHi
' macro with a variadic parameter which can contain several sub-parameters:
' To distinguish between the different arguments passed by variadic_parameter,
' you can first convert variadic_parameter to a string using the Operator # (Preprocessor Stringize),
' then differentiate in this string (#variadic_parameter) each passed argument by locating the separators (usually a comma).
#macro average(result, arg...)
Scope
Dim As String s = #arg
If s <> "" Then
result = 0
Dim As Integer n
Do
Dim As Integer k = InStr(1, s, ",")
If k = 0 Then
result += Val(s)
result /= n + 1
Exit Do
End If
result += Val(Left(s, k - 1))
n += 1
s = Mid(s, k + 1)
Loop
End If
End Scope
#endmacro
Dim As Double result
average(result, 1, 2, 3, 4, 5, 6)
Print result
' Output : 3.5end GeSHi
另请参阅
返回 目录