`

C++宏的使用(二)

    博客分类:
  • C++
 
阅读更多

基础部分

 

1. __VA_ARGS__: 用来替换任意参数部分, 相当于c语言中的va_list;

例:

#define OUT(...)    printf(__VA_ARGS__)

2. 宏从内向外展开(例外情况见4)

原因是, 当一个红的参数也是宏的时候, 它会尝试先展开它的参数;

例:

max(max(1, 3), 2) => max(3, 2) => 3

3. #和##. 前者用来将一个文本转化为语言内字符串, 后者用来连接两个文本

例:

#define TO_STRING(s)   #s

#define CAT(a, b)          a#b

TO_STRING(a)                => "a"

int CAT(a, b);                 => int ab;

4. 当一个宏对它的某个参数进行#或者##时, 这个参数使用点并不被替换为展开后的文本(这里的展开意思是, 将参数中的其他宏展开). 这句话隐含的意思是, 如果这个参数会多次使用, 只要不是#或者##, 都会被替换为展开后文本;

例:

#define TEST(a)           a; a + 1; a##a; #a

#define MAX(a, b)        ((a) > (b) ? (a) : (b))

TEST(MAX(1, 1))           => ((1) > (1) ? (1) : (1)); ((1) > (1) ? (1) : (1)) + 1; MAX(1, 1)MAX(1, 1); "MAX(1, 1)"

5. 对#和##做特殊处理; 由于一般宏在展开时, 它的参数都已经被展开, 而对参数应用了#和##的宏, 这里的参数中的宏却不展开, 所以有必要对它们进行特殊处理, 以符合一般的需要

工具:

#define TO_STRING(s)       TO_STRING_D(s)

#define TO_STRING_D(s)  #s

#define CONN(a, b)           CONN_D(a, b)

#define CONN_D(a, b)       a##b

原理: 利用一般宏会展开它参数的特性, TO_STRING和CONN先将参数完全展开, 然后再交给实际调用#和##的宏其中, TO_STRING非常有用, 除了服务一般编程外, 在使用宏的预处理期编程尤为重要, 是预处理期调试的重要打印手段

6. 带参数宏在无参数时被视为文本, 不展开

例:

#define MAX(a, b)                    ((a) > (b) ? (a) : (b))

TO_STRING(MAX(1, 2))             => ((1) > (2) ? (1) : (2))

TO_STRING(MAX(MAX, MAX))    => ((MAX) > (MAX) ? (MAX) : (MAX))

7. 宏不支持递归

运行时函数递归的方法是, 改变参数然后进行下步调用, 通过对不同参数的测试来判断是否到达终点应该停止递归.

元编程递归是通过特化、偏特化或者重载, 通过令终点历程不进行进一步调用的方式, 来终止递归.

表面上看, 由于宏并不支持结构控制(相对于函数递归的if和元编程的If模板), 所以宏不支持递归, 一旦预处理器发现一个宏在展开的过程中第二次出现, 则停止这个宏的展开.

关于这一点的更详细说明见后文.

 

预处理期编程

 

1. 数

 

宏没有实质上的变量, 宏的数据来源是用户编码; 而各种意义的常数中, 可用的只有宏和整形文字常量(用cout << TO_STRING(...); 来打印预处理期展开情况):

例:

#define INT_5                5

const int INT_7               = 7;

TO_STRING(INT_5)         => 5

TO_STRING(6)                => 6

TO_STRING(INT_7)         => INT_7

可见, 由于预处理期早于编译器, c++常量无效; 唯一的数据源只剩下宏和硬编码整数. 由于一个提供一定功能的宏只有一个文字数据源, 而又没有宏变量的说法, 所以, 要想在宏内部实现变量的效果, 就只有采用特殊手段:

#define INC_0   1

#define INC_1   2

#define INC_2   3

#define INC_3   4

#define DEC_0   0

#define DEC_1   0

#define DEC_2   1

#define DEC_3   2

TO_STRING(INC(INC(INC(0)))) => 3

TO_STRING(DEC(DEC(5)))      => 3

这样, 就实现了将文字数据源转化为范围受限整数的效果; 由于受宏嵌套深度和使用范围影响, 这些定义不需要太多, 一般不超过256左右就合适(宏自动生成的代码,比如模板类型参数, 32个以内就够用了)

2. BOOL值

由于BOOL值是非常有用的类型, 是结构控制中条件转移必须的元素, 所以这里需要一种手段将上述有限范围的整数转化为0和1

#define BOOL_0               0

#define BOOL_1               1

#define BOOL_2               1

#define BOOL_3               1

#define TO_BOOL(i)         CONN(BOOL_, i)

TO_STRING(TO_BOOL(3)) => 1

TO_STRING(TO_BOOL(0)) => 0

3. if-else

利用上面的BOOL值

 

#define IF_THEN_ELSE(condition, then, else)     CONN(CONDITION_, TO_BOOL(condition))(then, else)

#define CONDITION_1(a, b)                               a

#define CONDITION_0(a, b)                               b

4. for

由于宏不允许递归, 所以试图使for循环调用自身是不可能的, 这里将采用其他做法

调用FOR采用下面这种格式

 

FOR(数据集, 条件宏, 增量宏)

(1) 数据集是这样的格式(data1, data2, data3, ...), 含括号

(2) 条件宏, 接受两个参数-当前迭代轮数和数据集, 当条件宏判断为假, 表示不再迭代

(3) 增量宏, 增量宏同样接受迭代轮数和数据集, 它改变数据集后作为下次迭代的数据源

 

最终FOR返回的是一个数据集, 元素数和源数据相同, 用户提取其中相应的元素

#define GC_3(a, b, c)  

#define FOR1(data, toConntinue, inc) IF_THEN_ELSE(toConntinue(2, data), FOR2, data GC_3)(inc(2, data), toConntinue, inc)

#define FOR2(data, toConntinue, inc) IF_THEN_ELSE(toConntinue(3, data), FOR3, data GC_3)(inc(3, data), toConntinue, inc)

#define FOR3(data, toConntinue, inc) IF_THEN_ELSE(toConntinue(4, data), FOR4, data GC_3)(inc(4, data), toConntinue, inc)

其中传入的轮数是当前轮 + 1, 原因最后阐述. 这里暂时无法演示FOR的使用, 看下节

5. 加法

#define DIG(n)                                         n

#define TUPLE_SEL(sz, idx, tpl)               DIG(CONN(CONN(TUPLE_SEL_, sz), CONN(_, idx)) tpl)

#define TUPLE_SEL_2_0(p0, p1)             p0

TO_STRING(TUPLE_SEL(2, 0, (5, 10)))      => 5

TUPLE_SEL的作用是从数据集tpl中, 选出第idx个元素, sz表示数据集的尺寸

 

下面使用FOR来实现加法

#define ADD(x, y)                         TUPLE_SEL(2, 0, FOR1((x, y), ADD_NO_END_I,ADD_INC_I))

#define ADD_NO_END_I(i, xy)      TUPLE_SEL(2, 1, xy)

#define ADD_INC_I(i, xy)              ADD_INC(TUPLE_SEL(2, 0, xy), TUPLE_SEL(2,1, xy))

#define ADD_INC(x, y)                 (INC(x), DEC(y))

也就是说, 对于数据集(x, y), 每次将x加1, 并将y减1, 然后将结果传给下轮迭代, 当y减到0时, 从结果数据集(x + y, 0)中取出第一个元素就是加法的结果

 

6. 其他运算

减法和加法类似, 而乘法就是多次加法, 除法是多次减法

#define SUB(x, y)                       TUPLE_SEL(2, 0, FOR1((x, y), SUB_NO_END_I,SUB_INC_I))

#define SUB_N(x, y, n)               TUPLE_SEL(2, 0, CONN(FOR, n)((x, y),SUB_NO_END_I, SUB_INC_I))

#define SUB_NO_END_I             ADD_NO_END_I

#define SUB_INC_I(i, xy)            SUB_INC(TUPLE_SEL(2, 0, xy), TUPLE_SEL(2,1, xy))

#define SUB_INC(x, y)                (DEC(x), DEC(y))

#define MUL(x, y)                        TUPLE_SEL(3, 0, FOR1((0, x, y), MUL_NO_END_I, MUL_INC_I))

#define MUL_NO_END_I(i, sxy)   TUPLE_SEL(3, 2, sxy)

#define MUL_INC_I(i, sxy)           MUL_INC(i, TUPLE_SEL(3, 0, sxy), TUPLE_SEL(3, 1, sxy), TUPLE_SEL(3, 2, sxy))

#define MUL_INC(i, s, x, y)          (ADD_N(s, x, i), x, DEC(y))

#define DIV(x, y)                         TUPLE_SEL(3, 0, FOR1((0, x, y), DIV_NO_END_I, DIV_INC_I))

#define DIV_NO_END_I(i, cxy)    TUPLE_SEL(3, 1, cxy)

#define DIV_INC_I(i, cxy)            DIV_INC(i, TUPLE_SEL(3, 0, cxy), TUPLE_SEL(3, 1, cxy), TUPLE_SEL(3, 2, cxy))

#define DIV_INC(i, c, x, y)           (INC(c), SUB_N(x, y, i), y)

这里还有一些其他运算:

#define MOD(x, y)                       MOD_(SUB(MUL(y, DIV(x, y)), x), y)

#define MOD_(x, y)                     IF_THEN_ELSE(x, SUB(y, x), 0)

#define EQUAL(x, y)                     BITNOT(NOT_EQUAL(x, y))

#define NOT_EQUAL(x, y)            IF_THEN_ELSE(SUB(x, y), 1, SUB(y, x))

 

#define LESS(x, y)                       SUB(y, x)

#define GREATER(x, y)                SUB(x, y)

#define AND(x, y)                        BITAND(TO_BOOL(x), TO_BOOL(y))

#define OR(x, y)                          BITOR(TO_BOOL(x), TO_BOOL(y))

#define NOT(x)                            BITNOT(TO_BOOL(x))

#define BITAND(x, y)                  CONN(CONN(BITAND_, x), CONN(_, y))

#define BITOR(x, y)                    CONN(CONN(BITOR_, x), CONN(_, y))

#define BITXOR(x, y)                  CONN(CONN(BITXOR_, x), CONN(_, y))

#define BITNOT(x)                      CONN(BITNOT_, x)

7. 关于宏递归和嵌套循环

前面强调过, 宏不支持递归, 因为预处理器一发现出现过的宏就会停止展开. 那上面的乘法要怎么做? 乘法是用加法来实现的, 不可避免的就需要嵌套FOR; 一种做法是再准备另一份FOR2_0, FOR2_1..., 用于嵌套中第二曾, 结果就是FOR(; ; )FOR2(; ; ){}. 由于完全是两组宏, 自然也就避免了重复出现导致预处理器拒绝展开的问题.再讨论乘法的实现之前先看点其他的:

#define A(a)    B(a)

#define B(a)    A(a)C(a)

#define C(a)    a

TO_STRING(A(1))的结果? 是A(1)1. 因为预处理器在展开B时发现A已经出现过, 所以拒绝再展开

TO_STRING(CONN(0, CONN(CONN(1, 2), CONN(3, 4))))结果? 结果是01234, 完全展开了

这就需要追究所谓"出现过的宏", 精确定义; 其实上面两个例子已经在一定程度上演示了怎样的宏是出现过的宏. A(1)的例子中, 发生了一次根展开, 根由A变成了B: A(1) -> B(1). 假如B的展开中, 又出现A, 那么就会有递归的嫌疑, 所以B中的A调用将不会展开. 而CONN中, 完全没有根展开, 所以整个表达式肯定是用户编码, 没有递归可能, 因此预处理器进行了常规展开. 所以, "出现过的宏", 精确定义, 不是指编码重复, 而是指对于宏A, 在A所在的宏调用嵌套层中, 其父层次及本层次已经展开的宏中, 并没有包含A. 比如(为了比较和演示, 并不是预处理器实际展开顺序):

#define A(a)                                      A1(B(C(a), D(a)))

#define C(a)                                     C1(a)

#define D(a)                                     C1(a)

#define C1(a)                                   C(a)D(a)

A(#)                                                  => A展开成A1(...), 发生根替换, A不再可用

A1( B( C (#), D (#) ) )                        => C展开成C1, 对于本分支C1的展开, A和C不可用

A1( B( C1(#), D (#) ) )                       => D展开成C1, 对于本分支C1的展开, A和D不可用

A1( B( C1(#), C1(#) ) )                      => 把两个C1都展开, 左分支A, C, C1不可用, 右分支A, D, C1不可用

A1( B( C(#)D(#), C(#)D(#) ) )            => 左分支, 由于A和C不可用, 所以只有D展开, 进而A, C, C1, D都不可用

A1( B( C(#)C1(#), C(#)D(#) ) )          => 右分支的C展开, 进而A, D, C1, C都不可用

A1( B( C(#)C1(#), C1(#)D(#) ) )        => 左右分支都不可再展开, A1, B又未定义, 所以宏展开终点

TO_STRING(A(#))                               => A1(B(C(#)C1(#), C1(#)D(#)))

讲了这么多, 其实就是为了精确定义哪些宏进入了黑名单(这个黑链表应该是随着预处理器处理深度, 不停调整长度的).

 

再来说乘法的两层迭代实现方法:

对于MUL(x, y), 定义data => (s, x, y); 所以:

 

while (y != 0)

{

    s += x;

    --y;

}

 

由于宏运算只有++和--, 将上面换个写法:

 

for (s = 0; y != 0; --y)

{

    for (int i = 0; i < x; ++i)

    {

        ++s;

    }

}

 

最终: data => (xy, x, 0)

前面提到的矛盾就在于, 外层有个FOR, 而内层也有个FOR, 必须要使用某种手段, 令两层FOR并不冲突. 再来看FOR的迭代过程:

 

for1( data1, test, inc )           

 

=>  // 展开for1, for1已经不可用

if (test(..., data1))

{

    for2 (inc(..., data), test, inc);

}

else

{

    end;

}

 

如果把上面的表达式看作乘法的最外层循环的话, 那么内层的循环就在inc(..., data)中; 这个inc(..., data), 就是将一个x累加到s中, 也就是一个加法, 也就包含有另一个for循环. 上面已经指出了, 在for(n)展开的时候, for1, for2, ...for(n)都不可用,那么能够由于inc(..., data)中的, 只有for(n + 1), 所以inc(..., data) = inc(n + 1, data), 而inc的实现, 就是一个利用for(n + 1)开始进行的加法; 最终MUL(x, y)展开:

for1

    for2 for3... for(x + 1)                 <= 其中for(x + 1)就是执行++s

for2

    for3 for4... for(x + 2)

for3

    for4 for5... for(x + 3)

...

for(y)

    for(y + 1) for(y + 2)... for(x + y)

这样就实现了利用一组for实现两层迭代

下面是乘法的完整代码:

#define ADD_N(x, y, n)                      TUPLE_SEL(2, 0, CONN(FOR, n)((x, y), ADD_NO_END_I, ADD_INC_I))

#define MUL(x, y)                              TUPLE_SEL(3, 0, FOR1((0, x, y), MUL_NO_END_I, MUL_INC_I))

#define MUL_NO_END_I(i, sxy)         TUPLE_SEL(3, 2, sxy)

#define MUL_INC_I(i, sxy)                 MUL_INC(i, TUPLE_SEL(3, 0, sxy), TUPLE_SEL(3, 1, sxy), TUPLE_SEL(3, 2, sxy))

#define MUL_INC(i, s, x, y)                (ADD_N(s, x, i), x, DEC(y))

8. 最后

宏编程最主要的作用还是代码生成, 看过上面的描述后实现一个

LOOP(n, fItem, delimit)

(n = 3, fItem(n) = typename Tn, delimit = , ) =>

typename T1, typename T2, typename T3

的功能宏, 已经很容易了

恩, 由于boost里面已经有preprocessor库了, 它的稳定性、功能、可移植性都比自己写的要高出很多; 所以, 这篇宏运用的文字, 主要也就是让我这样的普通c++程序员有个底, 到底boost那些神秘强大的宏都在怎样做成代码的, 最终使用的, 还是boost.

这篇本来是作为自己宏编程笔记的, 毕竟被人们冠以奇技淫巧的宏编程和元编程是为库作者准备的, 我也就看看, 隔天就忘了... 从Kevin Lynx的代码自动生成-宏递归思想中学习宏生成代码, 从boost中学习宏的图灵完备性...

下面是这篇文章提到的代码

http://download.csdn.net/source/1842009

分享到:
评论

相关推荐

    Visual C++源代码 192 如何使用自动化运行Excel宏

    Visual C++源代码 192 如何使用自动化运行Excel宏Visual C++源代码 192 如何使用自动化运行Excel宏Visual C++源代码 192 如何使用自动化运行Excel宏Visual C++源代码 192 如何使用自动化运行Excel宏Visual C++源代码...

    c++宏的使用总结.pdf

    ### C++宏的使用总结 #### 一、概述 本文档详细介绍了C++中的宏使用技巧,涵盖了条件包含、条件编译、定义常量、可变参数宏以及宏的组合等多个方面。通过这些实例,可以帮助读者更好地理解和运用宏,从而提高编程...

    C++宏定义说明(详解)

    ### C++宏定义详解 #### 一、宏定义概述 宏定义是C++语言中预处理器的一种特性,它允许开发者创建简单的文本替换规则。通过宏定义,开发者可以在编译前阶段将特定的文本模式(宏名)替换为另一段文本(宏定义的...

    c++宏编程技巧代码

    一些c++宏技巧; 用于批量产生代码; 学习用; 优先推荐boost的preprocessor; 原帖: http://blog.csdn.net/CDScan/archive/2009/11/24/4863057.aspx

    Visual C++源代码 188 如何使用自动化运行Word宏

    Visual C++源代码 188 如何使用自动化运行Word宏Visual C++源代码 188 如何使用自动化运行Word宏Visual C++源代码 188 如何使用自动化运行Word宏Visual C++源代码 188 如何使用自动化运行Word宏Visual C++源代码 188...

    C/C++ 宏详解(详解)

    C/C++ 宏是编程语言中的预处理器指令,它们允许程序员在编译...理解宏的工作原理以及如何正确使用它们是C/C++编程中不可或缺的一部分。在适当的时候利用宏的优点,同时避免其潜在的问题,是成为高效C/C++程序员的关键。

    c/c++宏定义 宏定义的入门教材 基础

    ### C/C++宏定义基础知识详解 #### 一、引言 在C/C++编程语言中,宏定义是一种非常实用的功能,它可以用来简化代码编写过程,提高编程效率,并且有助于代码的可读性和维护性。本文将详细介绍C/C++中的宏定义基础...

    c++语言 工具 宏替换工具

    在不支持或不希望使用C++模板的情况下,宏可以作为一种替代方案来实现代码的泛型化。 标题中提到的"宏替换工具"可能是指一个专门用于帮助用户管理和展开自定义宏的外部应用程序,比如`marcoexp.exe`,这个程序可能...

    C++宏定义的计算器

    新手入门专用,其实只要看我写的代码,就能快速掌握C++了,本人自己写的,代码简单,通俗易懂,老幼皆宜,除此之外更多源码全都由本人原创完成,适合新手,程序员开发着研究。

    c和c++宏的使用总结

    因此,理解宏的工作原理和限制,并谨慎使用,是成为一名熟练的 C/C++ 程序员的重要一环。同时,随着 C++11 及更高版本引入的常量表达式(constexpr)等功能,一些以前依赖宏的情况现在可以通过更安全、类型安全的...

    C++中各种颜色宏定义

    平常在开发的时候会用到各种颜色RGB值定义,很多时候需要到绘图里查看各颜色的RGB值。该头文件把各种颜色进行了宏定义,使用时只要包含该头文件即可。 不仅能够加快开发速度,还能使颜色值更加明了,方便阅读代码。

    Visual C++ MFC 中常用宏的含义

    在Visual C++的MFC(Microsoft Foundation Classes)框架中,宏是经常被使用的工具,用于简化编程、增强可读性和实现特定功能。本篇文章将详细解释标题和描述中提到的一些常用宏及其作用。 1. `AND_CATCH` 和 `...

    c++中的宏、内联函数和宏的比较

    ### C++中的宏、内联函数和宏的比较 在C++编程中,宏和内联函数是提高代码效率和可读性的两种常见方法。它们各自有着不同的应用场景和特点,了解这些差异对于编写高质量的C++代码至关重要。 #### 宏(Macro) 宏...

    c++宏、函数期末重点.doc

    在 C++ 中,宏定义是使用 #define 指令来定义的。宏定义可以分为两种,一种是不带参数的宏定义,另一种是带参数的宏定义。不带参数的宏定义是用一个宏名来代表一个字符串,而带参数的宏定义是用一个宏名来代表一个带...

    c++宏的使用详解

    宏替换是C/C++系列语言的技术特色,C/C++语言提供了强大的宏替换功能,源代码在进入编译器之前,要先经过一个称为“预处理器”的模块,这个模块将宏根据编译参数和实际编码进行展开,展开后的代码才正式进入编译器,...

    C++宏返回数组大小

    配套本人QQ空间的日志:C++模板的奇技淫巧。

    c与c++头文件兼容宏定义

    ### c与c++头文件兼容宏定义 #### 概述 在软件开发过程中,经常会遇到C和C++代码需要互相调用的情况。由于C和C++编译方式的不同,这通常会导致链接错误或其他编译问题。为了确保这两种语言能够顺利地进行函数调用...

    C++宏参考【入门级】

    根据给定文件的信息,我们可以总结出以下关于C++宏的相关知识点: ### 宏的基本概念 在C++中,宏是一种预处理指令,主要用于文本替换。它由预处理器处理,并在编译之前完成替换操作。宏可以用于定义常量、创建简短...

    c++ 宏函数定义 例子 vs2012

    总的来说,理解C++的宏定义及其使用方式对于任何C++开发者来说都是必要的,但同时也应该意识到它们的潜在风险,并尽可能地使用更安全的语言特性。在VS2012这样的现代IDE中,你可以方便地调试和测试宏,以确保它们按...

    c++进阶自定义宏的介绍

    首先,__cplusplus是cpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码,也就是说,上面的代码的含义是:如果这是一段cpp的代码,那么加入extern "C"{和}处理其中的代码。  要明白为何使用extern "C",...

Global site tag (gtag.js) - Google Analytics