C/C++ 宏详解

news/2024/7/7 20:13:19 标签: functor, string, preprocessor, 编译器, function, build

C/ C++  详解
  • 众多C++书籍都忠告我们C语言是万恶之首,但事情总不如我们想象的那么坏
  •  

    众多C++书籍都忠告我们C语言是万恶之首,但事情总不如我们想象的那么坏,就如同goto一样。
    一个很大的作用,就是自动为我们产生代码。如果说模板可以为我们产生各种型别的代码(型别替换),
    那么其实可以为我们在符号上产生新的代码(即符号替换、增加)。

    关于的一些语法问题,可以在google上找到。相信我,你对于的了解绝对没你想象的那么多。如果你
    还不知道#和##,也不知道prescan,那么你肯定对的了解不够。

    我稍微讲解下的一些语法问题(说语法问题似乎不妥,macro只与preprocessor有关,跟语义分析又无关):

    1. 可以像函数一样被定义,例如:
       #define min(x,y) (x    但是在实际使用时,只有当写上min(),必须加括号,min才会被作为展开,否则不做任何处理。
      
    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. 一个很重要的补充:就像我在第一点说的那样,如果一个像函数的在使用时没有出现括号,那么预处理器只是
       将这个作为一般的符号处理(那就是不处理)。


    我们来见识一下是如何帮助我们自动产生代码的。如我所说,是在符号层次产生代码。我在分析Boost.Function
    模块时,因为它使用了大量的(嵌套,再嵌套),导致我压根没看明白代码。后来发现了一个小型的模板库ttl,说的
    是开发一些小型组件去取代部分Boost(这是一个好理由,因为Boost确实太大)。同样,这个库也包含了一个function库。
    这里的function也就是我之前提到的functor。ttl.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 struct functor_caller_base1
    template struct functor_caller_base2
    template struct functor_caller_base3
    ///...
    那么,核心部分在于TTL_TPARAMS(n)这个,可以看出这个最终产生的是:
    typename T1
    typename T1, typename T2
    typename T1, typename T2, typename T3
    ///...
    我们不妨分析TTL_TPARAMS(n)的整个过程。分析主要把握我以上提到的一些要点即可。以下过程我建议你翻着ttl的代码,
    相关代码文件:function.hpp, macro_params.hpp, macro_repeat.hpp, macro_misc.hpp, macro_counter.hpp。

    so, here we go

    分析过程,逐层分析,逐层展开,例如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_TPARAM,TTL_TPARAM_END,T) TTL_APPEND( TTL_LAST_REPEAT_,1)(
    TTL_TPARAM_END,T)
    这个体看起来很复杂,仔细分析下,可以分为两部分:
    TTL_APPEND( TTL_REPEAT_, TTL_DEC(1))(TTL_TPARAM,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.niftyadmin.cn/n/1790565.html

相关文章

vue2的简单时间选择组件

github: https://github.com/longfei59418888/vui &#xff08;记得给一个 start,以后有一起讨论&#xff0c;各种好组件&#xff09; demo : http://60.205.209.185/#/time转载于:https://www.cnblogs.com/jiebba/p/7273247.html

前端数据结构(1)之栈及其应用

基础 栈是一种特殊的线性表&#xff0c;它可以用数组或者链表来实现。只能在栈顶操作数据&#xff0c;具有先进后出的特点。   假如有一个序列1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5。将它按顺序加入栈中&#xff0c;它的结构如下图&#xff1a;现实生活中&…

Sphinx安装笔记

// 下载所需文件wget http://www.sphinxsearch.com/downloads/sphinx-0.9.8-rc2.tar.gzwget http://www.coreseek.com/uploads/sources/sphinx-0.98rc2.zhcn-support.patchwget http://www.coreseek.com/uploads/sources/fix-crash-in-excerpts.patch#wget http://www.coreseek…

明天https://wenku.baidu.com/view/4c44ce953169a4517723a3df.html 综合素质

https://wenku.baidu.com/view/4c44ce953169a4517723a3df.html 清明节既属于节日又属于节气&#xff0c;隐忍之气不属于古代无为观念&#xff0c;古代观念无为可以是无所作为&#xff0c;但是圣人说的无为并不是无所作为&#xff0c;考的多的仪器是浑天仪&#xff0c;张衡所制&…

String,StringBuffer,StringBuilder的区别与共同点

1. String与StringBuffer,StringBuilder的区别&#xff1a; String为什么不可变:String存储数据是有final修饰的字符数组private final char value[];StringBuffer,StringBuilder的字符数组没有final修饰char[] value;所以说String存放的数据在字符串常量池中&#xff0c;当原本…

Java基础语法学习9——运算符优先级及润年案例

润年案例 package test;import java.util.Scanner;public class runnian { public static void main(String[] args) { // TODO Auto-generated method stub System.out.print("请输入年份&#xff1a;"); Scanner s new Scanner(System.in…

[译]Facebook是怎么做MySQL备份的?

原文意译:http://www.facebook.com/note.php?note_id10150098033318920转载请注明下,谢谢.. Facebook的用户每天创造大量的数据&#xff0c;为了确保数据可靠的存储&#xff0c;我们每天进行数据备份.我们通过将原来的逻辑备份改成定制化的物理备份,显著地提升了备份的速度(不…

阿里云如何提交工单?——区别于阿里云【知道】

很多人在阿里云上面提交工单&#xff0c;往往提交的工单就进入了“阿里云【知道】”平台 提醒&#xff1a;工单平台是阿里云官方技术人员回复&#xff0c;阿里云【知道】平台是第三方人员的回复 小编这里就教大家如何快速提交工单。 首先打开工单网址&#xff1a;https://selfs…