zoukankan      html  css  js  c++  java
  • 用宏展开代码

       “宏”这个玩意儿可能会触动很多人抵触的情绪,我也一样:很讨厌它。通常我不会用它进行计算,只有在合适的时候(比如能让我少打一些字,或者能增强代码的可读)才会请出它来。好了,言归正转,现在我要将一个代码片段有规律地重复N次,更具体点,就是在定义一个模板的时候,参数列表会很长,但是这些参数的名字是很有规律地:依次为typename T1,typename T2,....typename TN:

    template<typename T1,typename T2,...,typename TN>
    class ManyTemplateParms
    {};
    
    

    在C++还没有支持模板的List参数(《C++ template》第13章)之前,我们可以利用宏来减少我们的工作量。

      想一下吧,如果问题变了,现在的问题是要你写一个函数打印出上面的模板参数列表,你会怎么干?拿到这个需求时,我写下了两个函数:

    • printMetaExpr: 接收一个参数i,打印出 typename Ti,
    •    print:接收一个参数N,作为需要打印的参数个数,然后在一个for循环中调用printMetaExprN次,如下:
    #include <iostream>
    void printMetaExpr(int i)
    {
    	std::cout<<"typename T"<<i<<",";
    }
    
    void print(int n)
    {
    	for (int i = 0;i < n;++i)
    	{
    		printMetaExpr(i);
    	}
    } 

         宏其实也和stream差不多,只不过宏不是把这些东西输出到控制台或者文件中,而是输出给编译器看的,本质上没什么区别,所以我们可以这样来设计我们的宏:

    •   定义宏DEFINE_TEMPLATE_PARAM(N),来展开typename TN,这个比较简单:#define DEFINE_TEMPLATE_PARAM(N) typename T##N
    •   定义一个可以调用DEFINE_TEMPLATE_PARAM的宏CALL_MACRO(N,callee,sep):这个宏会调用callee N次,并且每一次调用后都会加上一个分隔sep.

         难点就在每二步了,如何来循环调用宏呢?搔头了吧,其实也不是十分地难,还是拿上面打印到控制台的程序来说吧,如果你将print写了成递归的形式,一切就真相大白啦:

    void print(int n)
    {
    if (1 == n)
    {
    printMetaExpr(
    1);
    }
    else
    {
    print(n
    - 1);
    printMetaExpr(n);
    }
    }

         按照这个思路,如果说我们要支持最多可以调用5次,我们就可以写出下面的递归宏啦:

    #define CALL_MACRO0(callee)      
    #define CALL_MACRO1(callee)   calee(1)   
    #define CALL_MACRO2(callee)   CALL_MACRO1(callee) , callee(2)
    #define CALL_MACRO3(callee)   CALL_MACRO2(callee) , callee(3)   
    #define CALL_MACRO4(callee)   CALL_MACRO3(callee) , callee(4)  
    #define CALL_MACRO5(callee)   CALL_MACRO4(callee) , callee(5)  
    

         最后用下面的宏封装一下上面的宏

        #define CALL_MACRO(N,callee) CALL_MACRO##N(callee)

         为了验证上述宏的正确性,我向大家隆重介绍一个可以调试宏的宏。

    #define PRINT_MACRO_( x,...) #x #__VA_ARGS__
    #define PRINT_MACRO( x ) PRINT_MACRO_( x )

        好了,现在可以写一个简单地程序来测试一下成果啦:

    代码
    #define DEFINE_TEMPLATE_PARAM(N) typename T##N
    #define CALL_MACRO0(callee)
    #define CALL_MACRO1(callee) callee(1)
    #define CALL_MACRO2(callee) CALL_MACRO1(callee) , callee(2)
    #define CALL_MACRO3(callee) CALL_MACRO2(callee) , callee(3)
    #define CALL_MACRO4(callee) CALL_MACRO3(callee) , callee(4)
    #define CALL_MACRO5(callee) CALL_MACRO4(callee) , callee(5)

    #define CALL_MACRO(N,callee) CALL_MACRO##N(callee)

    #define PRINT_MACRO_( x,...) #x #__VA_ARGS__
    #define PRINT_MACRO( x ) PRINT_MACRO_( x )

    int main(int argc, char* argv[])
    {
    std::cout
    <<PRINT_MACRO(CALL_MACRO(5,DEFINE_TEMPLATE_PARAM))<<std::endl;
    return 0;
    }

    运行后输出:

       下面给出我使用宏的一点心得:

       宏的性质:

    •    #可以将传入的参数变为字符串,PRINT_MACRO使用了这个思想,##用以连接宏参数和其他的字符,例如如果有宏

     

    #defineMEMBER(name)   m_##name;
    
         调用 MEMBER(Num)  你会得到 m_Num;

    •     宏不能实现真正意义上的递归调用,例如:
    #define Add(n)  Add(n – 1) + n
    

         调用Add(3),你将得到:Add(3 – 1) + 3,这恐怕不是你想要的。为了实现宏的递归调用必须像上面定义CALL_MACRO一样一步步地递归。

    •   我将宏分成了两类:

               (1)值宏,例如: #define __COMMA__ ,

               (2)函数宏,例如CALL_MACRO。函数宏又分为两种

                          I. 可调用宏,也就是可以作为其他宏的参数被调用,例如DEFINE_TEMPLATE_PARAM

                          II.不可调用宏,由于#和##的存在。例如: CALL_MACRO

    •  定义一个宏时要注意的是:

                (1). 只将宏用在展开代码的时候。其他的功能用inline函数和模板代替吧。

                (2). 如果你在写一个可调用宏,对于传入的函数宏,要让它可以展开,值宏只能在最下层展开.比如逗号,为了让它只在最底层展开,你可以这样来定义它:              

    #define _COMMA_(x) ,

         .传参时,传入_COMMA_,在最底层调用时,用_COMMA_(1)

  • 相关阅读:
    HDU 4472 Count DP题
    HDU 1878 欧拉回路 图论
    CSUST 1503 ZZ买衣服
    HDU 2085 核反应堆
    HDU 1029 Ignatius and the Princess IV
    UVa 11462 Age Sort
    UVa 11384
    UVa 11210
    LA 3401
    解决学一会儿累了的问题
  • 原文地址:https://www.cnblogs.com/li_shugan/p/1903884.html
Copyright © 2011-2022 走看看