代码自动生成-宏带来的奇计淫巧
2011-02-25 11:25
176 查看
宏有一个很大的作用,就是自动为我们产生代码。如果说模板可以为我们产生各种类型的代码(类型替换),那么宏其实可以为我们在符号上产生新的代码(即符号替换、增加)。
1.宏可以像函数一样被定义。
2.如果宏需要参数,你可以不传,编译器会给你警告(宏参数不够),但是这会导致错误,如C++书籍中所描述的,编译器(预处理器)对宏的语法检查不够,所以更多的检查性工作得你自己来做。
3.#符号把一个符号直接转换为字符串,例如
str的内容 就是"test_string",也就说#会把其后的符号直接加上双引号。
##符号会连接两个符号,从而产生新的符号(语法层次),例如:
宏被展开后将成为: int INT_1;
4.变参宏,它使得你可以定义类似的宏:
__VA_ARGS__是系统预定义宏,被自动替换为参数列表
5.当一个宏自己调用自己时,
会发生什么?为了防止无限制递归展开,语法规定,当一个宏遇到自己时,就停止展开,也就是说,
当对TEST(1)进行展开时,展开过程中又发现了一个TEST,那么就将这个TEST当作一般的符号。TEST(1)最终被展开为:1+TEST(1)。
6.宏参数的prescan
当一个宏参数被放进宏体时,这个宏参数会首先被全部展开(有例外)。当展开后的宏参数被放进宏体时,预处理器对新展开的宏体进行第二次扫描,并继续展开,例如:
因为ADDPARAM(1)是作为PARAM的宏参数,所以先将ADDPARAM(1)展开为INT_1,然后再将INT_1放进PARAM。
例外情况是,如果PARAM宏里对宏参数使用了#或##,那么宏参数不会被展开:
//将被展开为"ADDPARAM(1)"。
使用这么一个规则,可以创建一个有趣的技术:打印出一个宏被展开后的样子,这样可以方便你分析代码:
TO_STRING首先会将x全部展开(如果x也是一个宏的话),然后再传给TO_STRING1转换为字符串,现在你可以这样:
const char *str = TO_STRING(PARAM(ADDPARAM(1))); 去一探PARAM展开后的样子。
7.一个很重要的补充:如果一个像函数的宏在使用时没有出现括号,那么预处理器只是将这个宏作为一般的符号处理(那就是不处理)
Function库里为了自动产生很多类似的代码,使用了一个宏:
该宏的最终目的是:通过类似于TTL_FUNC_BUILD_FUNCTOR_CALLER(1)的调用方式,自动产生很多functor_caller_base的模板:
那么核心部分在TTL_TPARAMS(n)这个宏,可以看出这个宏最终产生的是:
typename T1
typename T1, typename T2
typename T1, typename T2, typename T3
///...
分析过程,逐层分析,逐层展开,例如TTL_TPARAMS(1):
注意,TTL_TPARAM,TTL_TPARAM_END虽然也是两个宏,他们作为TTL_REPEAT宏的参数,按照prescan规则,似乎应该先将这个宏
展开在传给TTL_REPEAT。但是,如同我在前面重点提到的,这两个宏是function-like macro,使用时需要加括号,如果没加括号,则不当做宏处理。因此展开TTL_REPEAT时,应该为:
=> TTL_APPEND(TTL_REPEAT_, TTL_DEC(1))(TTL_PARAM, TTL_TPARAM_END, T)
TTL_APPEND(TTL_LAST_REPEAT_, 1)(TTL_TPARAM_END, T)
这个宏体看来很复杂,仔细分析下,可以分为两部分:
TTL_APPEND(TTL_REPEAT_, TTL_DEC(1)) (TTL_TPARAM_END, T)以及
TTL_APPEND(TTL_LAST_REPEAT_, 1)(TTL_TPARAM_END, T)
先分析第一部分:
根据先展开参数的原则,会先展开TTL_DEC(1)
=>TTL_APPEND(TTL_CNTDEC_, 1) => TTL_CNTDEC_1
=>0,也就是说,TTL_DEC(1)最终被展开为0,回到TTL_APPEND部分:
=>TTL_REPEAT_0(TTL_TPARAM, TTL_TPARAM_END, T)
TTL_REPEAT_0这个宏为空,那么,上面说的第一部分别忽略,现在只剩下第二部分:
TTL_APPEND(TTL_LAST_REPEAT_, 1) (TTL_TPARAM_END, T)
=>TTL_LAST_REPEAT_1(TTL_TPARAM_END, T) //TTL_APPEND将TTL_LAST_REPEAT_和1合并起来
=>TTL_TPARAM_END(1, T)
=>typename T1 展开完毕。
我们应该从那些宏里去获取作者关于宏的编程思想。很好使用宏看上去似乎是一些偏门的奇迹淫巧,但是他确实可以让我们编码更自动化。
http://www.docin.com/p-56213848.html
1.宏可以像函数一样被定义。
2.如果宏需要参数,你可以不传,编译器会给你警告(宏参数不够),但是这会导致错误,如C++书籍中所描述的,编译器(预处理器)对宏的语法检查不够,所以更多的检查性工作得你自己来做。
3.#符号把一个符号直接转换为字符串,例如
#define STRING(x) #x const char *str = STRING(test_string);
str的内容 就是"test_string",也就说#会把其后的符号直接加上双引号。
##符号会连接两个符号,从而产生新的符号(语法层次),例如:
#define SIGN(x) INT_##x int SIGN(1);
宏被展开后将成为: int INT_1;
4.变参宏,它使得你可以定义类似的宏:
#define LOG(format, ...) printf(format, __VA_ARGS__) LOG("%s %d", str, count);
__VA_ARGS__是系统预定义宏,被自动替换为参数列表
5.当一个宏自己调用自己时,
#define TEST(x) (x + TEST(x)) TEST(1);
会发生什么?为了防止无限制递归展开,语法规定,当一个宏遇到自己时,就停止展开,也就是说,
当对TEST(1)进行展开时,展开过程中又发现了一个TEST,那么就将这个TEST当作一般的符号。TEST(1)最终被展开为:1+TEST(1)。
6.宏参数的prescan
当一个宏参数被放进宏体时,这个宏参数会首先被全部展开(有例外)。当展开后的宏参数被放进宏体时,预处理器对新展开的宏体进行第二次扫描,并继续展开,例如:
#define PARAM(x) x #define ADDPARAM(x) INT_##x PARAM(ADDPARAM(1));
因为ADDPARAM(1)是作为PARAM的宏参数,所以先将ADDPARAM(1)展开为INT_1,然后再将INT_1放进PARAM。
例外情况是,如果PARAM宏里对宏参数使用了#或##,那么宏参数不会被展开:
#define PARAM(x) #x #define ADDPARAM(x) INT_##x PARAM(ADDPARAM(1));
//将被展开为"ADDPARAM(1)"。
使用这么一个规则,可以创建一个有趣的技术:打印出一个宏被展开后的样子,这样可以方便你分析代码:
#define TO_STRING(x) TO_STRING1(x) #define TO_STRING1(x) #x
TO_STRING首先会将x全部展开(如果x也是一个宏的话),然后再传给TO_STRING1转换为字符串,现在你可以这样:
const char *str = TO_STRING(PARAM(ADDPARAM(1))); 去一探PARAM展开后的样子。
7.一个很重要的补充:如果一个像函数的宏在使用时没有出现括号,那么预处理器只是将这个宏作为一般的符号处理(那就是不处理)
Function库里为了自动产生很多类似的代码,使用了一个宏:
#define TTL_FUNC_BUILD_FUNCTOR_CALLER(n) / template<typename R, TTL_TPARAMS(n)> / struct functor_caller_base##n/ ///....
该宏的最终目的是:通过类似于TTL_FUNC_BUILD_FUNCTOR_CALLER(1)的调用方式,自动产生很多functor_caller_base的模板:
template<typename R, typename T1> struct functor_caller_base1 template<typename R, typename T1, typename T2> struct functor_caller_base2 template<typename R, typename T1, typename T2> struct functor_caller_base3 ///...
那么核心部分在TTL_TPARAMS(n)这个宏,可以看出这个宏最终产生的是:
typename T1
typename T1, typename T2
typename T1, typename T2, typename T3
///...
分析过程,逐层分析,逐层展开,例如TTL_TPARAMS(1):
#define TTL_TPARAMS(n) TTL_TPARAMSX(n, T) =>TTL_TPARAMSX(1, T) #define TTL_TPARAMSX(n, t) TTL_REPEAT(n, TTL_TPARAM, TTL_TPARAM_END, t) =>TTL_REPEAT(1, TTL_TPARAM, TTL_TPARAM_END, T) #define TTL_TPARAM(n, t) typename t##n, #define TTL_TPARAM_END(n, t) typename t##n #define TTL_REPEAT(n, m, l, p) TTL_APPEND(TTL_REPEAT_, TTL_DEC(n))(m, l, p) TTL_APPEND(TTL_LAST_REPEAT_, n)(l, p)
注意,TTL_TPARAM,TTL_TPARAM_END虽然也是两个宏,他们作为TTL_REPEAT宏的参数,按照prescan规则,似乎应该先将这个宏
展开在传给TTL_REPEAT。但是,如同我在前面重点提到的,这两个宏是function-like macro,使用时需要加括号,如果没加括号,则不当做宏处理。因此展开TTL_REPEAT时,应该为:
=> TTL_APPEND(TTL_REPEAT_, TTL_DEC(1))(TTL_PARAM, TTL_TPARAM_END, T)
TTL_APPEND(TTL_LAST_REPEAT_, 1)(TTL_TPARAM_END, T)
这个宏体看来很复杂,仔细分析下,可以分为两部分:
TTL_APPEND(TTL_REPEAT_, TTL_DEC(1)) (TTL_TPARAM_END, T)以及
TTL_APPEND(TTL_LAST_REPEAT_, 1)(TTL_TPARAM_END, T)
先分析第一部分:
#define TTL_APPEND(x, y) TTL_APPEND1(x, y) //先展开x,y再将x,y连接起来 #define TTL_APPEND1(x, y) x##y #define TTL_DEC(n) TTL_APPEND(TTL_CNTDEC_, n)
根据先展开参数的原则,会先展开TTL_DEC(1)
=>TTL_APPEND(TTL_CNTDEC_, 1) => TTL_CNTDEC_1
#define TTL_CNTDEC_1 0 //注意,TTL_CNTDEC_不是宏,TTL_CNTDEC_1是一个宏
=>0,也就是说,TTL_DEC(1)最终被展开为0,回到TTL_APPEND部分:
=>TTL_REPEAT_0(TTL_TPARAM, TTL_TPARAM_END, T)
#define TTL_REPEAT_0(m, l, p)
TTL_REPEAT_0这个宏为空,那么,上面说的第一部分别忽略,现在只剩下第二部分:
TTL_APPEND(TTL_LAST_REPEAT_, 1) (TTL_TPARAM_END, T)
=>TTL_LAST_REPEAT_1(TTL_TPARAM_END, T) //TTL_APPEND将TTL_LAST_REPEAT_和1合并起来
#define TTL_LAST_REPEAT_1(m, p) m(1, p)
=>TTL_TPARAM_END(1, T)
#define TTL_TPARAM_END(n, t) typename t##n
=>typename T1 展开完毕。
我们应该从那些宏里去获取作者关于宏的编程思想。很好使用宏看上去似乎是一些偏门的奇迹淫巧,但是他确实可以让我们编码更自动化。
http://www.docin.com/p-56213848.html
相关文章推荐
- [转]代码自动生成-宏带来的奇技淫巧
- 代码自动生成-宏带来的奇技淫巧
- 代码自动生成-宏带来的奇技淫巧
- 代码自动生成-宏带来的奇技淫巧
- 代码自动生成-宏带来的技巧
- 代码自动生成-宏带来的技巧
- 代码自动生成-宏带来的奇技淫巧
- 代码自动生成-宏带来的奇技淫巧
- 代码自动生成-宏带来的奇技淫巧(转载)
- 代码自动生成-宏带来的奇技淫巧
- 代码自动生成-宏带来的奇技淫巧
- js代码自动生成
- (转)myeclipse自动生成注册码代码
- mybatis-generator 代码自动生成插件
- C# 通过T4自动生成代码
- JAVA 自动生成对应数据库表的JPA代码工具
- 设置 Xcode 自动生成代码片段
- 自动生成变色字体html代码
- mybatis中代码自动生成gengrator
- 最全Pycharm教程(16)——Pycharm编辑器功能之代码自动生成