精华内容
参与话题
问答
  • Scheduler在使用之前需要实例化。一般通过SchedulerFactory来创建一个实例。有些用户将factory的实例保存在JNDI中,但直接初始化,然后使用该实例也许更简单(见下面的示例)。 scheduler实例化后,可以启动(start)...

    Scheduler在使用之前需要实例化。一般通过SchedulerFactory来创建一个实例。有些用户将factory的实例保存在JNDI中,但直接初始化,然后使用该实例也许更简单(见下面的示例)。

    scheduler实例化后,可以启动(start)、暂停(stand-by)、停止(shutdown)。注意:scheduler被停止后,除非重新实例化,否则不能重新启动;只有当scheduler启动后,即使处于暂停状态也不行,trigger才会被触发(job才会被执行)。

    下面的代码片段,实例化并启动一个scheduler,调度执行一个job:

    SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
    
     Scheduler sched = schedFact.getScheduler();
    
     sched.start();
    
     // define the job and tie it to our HelloJob class
     JobDetail job = newJob(HelloJob.class)
         .withIdentity("myJob", "group1")
         .build();
    
     // Trigger the job to run now, and then every 40 seconds
     Trigger trigger = newTrigger()
         .withIdentity("myTrigger", "group1")
         .startNow()
         .withSchedule(simpleSchedule()
             .withIntervalInSeconds(40)
             .repeatForever())
         .build();
    
     // Tell quartz to schedule the job using our trigger
     sched.scheduleJob(job, trigger);
    

    你看到了,quartz的使用并不难。教程二会简要地介绍job和trigger,以及quartz的API,然后你会更好地理解上面的示例。

    展开全文
  • c语言宏定义详解

    千次阅读 多人点赞 2016-05-11 10:07:07
    一、#define的基本用法  #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率,但学生在学习时往往不能 理解该命令的本质,总是在此处产生一些...
    一、#define的基本用法

        #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率,但学生在学习时往往不能 理解该命令的本质,总是在此处产生一些困惑,在编程时误用该命令,使得程序的运行与预期的目的不一致,或者在读别人写的程序时,把运行结果理解错误,这对 C语言的学习很不利。

    1 #define命令剖析

    1.1   #define的概念

        #define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。
    该命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义。

    (1)简单的宏定义:
    1. #define <宏名>  <字符串>
    2. 例: #define PI 3.1415926
    (2) 带参数的宏定义
     
    1. #define <宏名> (<参数表>) <宏体>
    2. 例: #define A(x) x
        一个标识符被宏定义后,该标识符便是一个宏名。这时,在程序中出现的是宏名,在该程序被编译前,先将宏名用被定义的字符串替换,这称为宏替换,替换后才进行编译,宏替换是简单的替换

    1.2 宏替换发生的时机

        为了能够真正理解#define的作用,让我们来了解一下对C语言源程序的处理过程。当我们在一个集成的开发环境如Turbo C中将编写好的源程序进行编译时,实际经过了预处理、编译、汇编和连接几个过程。其中预处理器产生编译器的输出,它实现以下的功能:
    (1)文件包含
        可以把源程序中的#include 扩展为文件正文,即把包含的.h文件找到并展开到#include 所在处。
    (2)条件编译
        预处理器根据#if和#ifdef等编译命令及其后的条件,将源程序中的某部分包含进来或排除在外,通常把排除在外的语句转换成空行。
    (3)宏展开
        预处理器将源程序文件中出现的对宏的引用展开成相应的宏 定义,即本文所说的#define的功能,由预处理器来完成。
        经过预处理器处理的源程序与之前的源程序有所有不同,在这个阶段所进行的工作只是纯粹的替换与展开,没有任何计算功能,所以在学习#define命令时只要能真正理解这一点,这样才不会对此命令引起误解并误用。

    2 #define使用中的常见问题解析

    2.1 简单宏定义使用中出现的问题

        在简单宏定义的使用中,当替换文本所表示的字符串为一个表达式时,容易引起误解和误用。如下例:
       
    1. 例1 #define N 2+2
    2. void main()
    3. {
    4.    int a=N*N;
    5.    printf(%d”,a);
    6. }
        (1) 出现问题:
     
        在此程序中存在着宏定义命令,宏N代表的字符串是2+2,在程序中有对宏N的使用,一般同学在读该程序时,容易产生的问题是先求解N为2+2=4,然后在程序中计算a时使用乘法,即N*N=4*4=16,其实该题的结果为8,为什么结果有这么大的偏差?

        (2) 问题解析:
     
        如1节所述,宏展开是在预处理阶段完成的,这个阶段把替换文本只是看作一个字符串,并不会有任何的计算发生,在展开时是在宏N出现的地方 只是简单地使用串2+2来代替N,并不会增添任何的符号,所以对该程序展开后的结果是a=2+2*2+2,计算后=8,这就是宏替换的实质,如何写程序才能完成结果为16的运算呢?

        (3)解决办法:
     
    1. /*将宏定义写成如下形式*/
    2. #define N (2+2)
    3. /*这样就可替换成(2+2)*(2+2)=16*/

    2.2 带参数的宏定义出现的问题

        在带参数的宏定义的使用中,极易引起误解。例如我们需要做个宏替换能求任何数的平方,这就需要使用参数,以便在程序中用实际参数来替换宏定义中的参数。一般学生容易写成如下形式:
    1. #define area(x) x*x
    2. /*这在使用中是很容易出现问题的,看如下的程序*/

    3. void main()
    4. {
    5.     int y = area(2+2);
    6.     printf(%d”,y);
    7. }
        按理说给的参数是2+2,所得的结果应该为4*4=16,但是错了,因为该程序的实际结果为8,仍然是没能遵循纯粹的简单替换的规则,又是先计算再替换 了,在这道程序里,2+2即为area宏中的参数,应该由它来替换宏定义中的x,即替换成2+2*2+2=8了。那如果遵循(1)中的解决办法,把2+2 括起来,即把宏体中的x括起来,是否可以呢?#define area(x) (x)*(x),对于area(2+2),替换为(2+2)*(2+2)=16,可以解决,但是对于area(2+2)/area(2+2)又会怎么样呢,有的学生一看到这道题马上给出结果,因为分子分母一样,又错了,还是忘了遵循先替换再计算的规则了,这道题替换后会变为 (2+2)*(2+2)/(2+2)*(2+2)即4*4/4*4按照乘除运算规则,结果为16/4*4=4*4=16,那应该怎么呢?解决方法是在整个宏体上再加一个括号,即#define   area(x) ((x)*(x)),不要觉得这没必要,没有它,是不行的。
        要想能够真正使用好宏定义,那么在读别人的程序时,一定要记住先将程序中对宏的使用全部替换成它所代表的字符串,不要自作主张地添加任何其他符号,完全展开后再进行相应的计算,就不会写错运行结果。
        如果是自己编程使用宏替换,则在使用简单宏定义时,当字符串中不只一个符号时,加上括号表现出优先级,如果是带参数的宏定义,则要给宏体中的每个参数加上括号,并在整个宏体上再加一个括号。看到这里,不禁要问,用宏定义这么麻烦,这么容易出错,可不可以摒弃它, 那让我们来看一下在C语言中用宏定义的好处吧。
    如:
    1. #include <iostream.h>
    2. #define product(x)    x*x
    3. int main()
    4. {
    5.     int i=3;
    6.     int j,k;
    7.     j = product(i++);
    8.     cout<<"j="<<j<<endl;
    9.     cout<<"i="<<i<<endl;
    10.     k = product(++i);
    11.     cout<<"k="<<k<<endl;
    12.     cout<<"i="<<i<<endl;
    13.     return 0;
    14. }
    依次输出结果:
    j=9;i=5;k=49;i=7


    3 宏定义的优点

    (1)   方便程序的修改

        使用简单宏定义可用宏代替一个在程序中经常使用的常量,这样在将该常量改变时,不用对整个程序进行修改,只修改宏定义的字符串即可,而且当常量比较长时, 我们可以用较短的有意义的标识符来写程序,这样更方便一些。我们所说的常量改变不是在程序运行期间改变,而是在编程期间的修改,举一个大家比较熟悉的例子,圆周率π是在数学上常用的一个值,有时我们会用3.14来表示,有时也会用3.1415926等,这要看计算所需要的精度,如果我们编制的一个程序中 要多次使用它,那么需要确定一个数值,在本次运行中不改变,但也许后来发现程序所表现的精度有变化,需要改变它的值, 这就需要修改程序中所有的相关数值,这会给我们带来一定的不便,但如果使用宏定义,使用一个标识符来代替,则在修改时只修改宏定义即可,还可以减少输入 3.1415926这样长的数值多次的情况,我们可以如此定义 #define   pi   3.1415926,既减少了输入又便于修改,何乐而不为呢?

    (2) 提高程序的运行效率

        使用带参数的宏定义可完成函数调用的功能,又能减少系统开销,提高运行效率。正如C语言中所讲,函数的使用可以使程序更加模块化,便于组织,而且可重复利用,但在发生函数调用时,需要保留调用函数的现场,以便子 函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,如果子函数执行的操作比较多,这种转换时间开销可以忽 略,但如果子函数完成的功能比较少,甚至于只完成一点操作,如一个乘法语句的操作,则这部分转换开销就相对较大了,但使用带参数的宏定义就不会出现这个问 题,因为它是在预处理阶段即进行了宏展开,在执行时不需要转换,即在当地执行。宏定义可完成简单的操作,但复杂的操作还是要由函数调用来完成,而且宏定义所占用的目标代码空间相对较大。所以在使用时要依据具体情况来决定是否使用宏定义。

    4 结语

        本文对C语言中宏定义#define在使用时容易出现的问题进行了解析,并从C源程序处理过程的角度对#define的处理进行了分析,也对它的优点进行 了阐述。只要能够理解宏展开的规则,掌握使用宏定义时,是在预处理阶段对源程序进行替换,只是用对应的字符串替换程序中出现的宏名,这样就可在正确使用的 基础上充分享受使用宏定义带来的方便和效率了

    二、define中的三个特殊符号:#,##,#@

    1. #define Conn(x,y) x##y
    2. #define ToChar(x) #@x
    3. #define ToString(x) #x
    (1)x##y表示什么?表示x连接y,举例说:
    1. int n = Conn(123,456); /* 结果就是n=123456;*/
    2. char* str = Conn("asdf", "adf"); /*结果就是 str = "asdfadf";*/
    (2)再来看#@x,其实就是给x加上单引号,结果返回是一个const char。举例说:
    char a = ToChar(1);结果就是a='1';
    做个越界试验char a = ToChar(123);结果就错了;
    但是如果你的参数超过四个字符,编译器就给给你报错了!
    error C2015: too many characters in constant   :P
    (3)最后看看#x,估计你也明白了,他是给x加双引号
    char* str = ToString(123132);就成了str="123132";
    三、常用的一些宏定义

    1 防止一个头文件被重复包含 
    1. #ifndef BODYDEF_H 
    2. #define BODYDEF_H 
    3.  //头文件内容 

    4. #endif
     
    2 得到指定地址上的一个字节或字

    1. #define MEM_B( x ) ( *( (byte *) (x) ) ) 
    2. #define MEM_W( x ) ( *( (word *) (x) ) )
    用法如下:
    1. #include <iostream>
    2. #include <windows.h>

    3. #define MEM_B(x) (*((byte*)(x)))
    4. #define MEM_W(x) (*((WORD*)(x)))

    5. int main()
    6. {
    7.     int bTest = 0x123456;

    8.     byte m = MEM_B((&bTest));/*m=0x56*/
    9.     int n = MEM_W((&bTest));/*n=0x3456*/

    10.     return 0;
    11. }

    3 得到一个field在结构体(struct)中的偏移量

     
    1. #define OFFSETOF( type, field ) ( (size_t) &(( type *) 0)-> field )
        请参考文章:详解写宏定义:得到一个field在结构体(struct type)中的偏移量

    4 得到一个结构体中field所占用的字节数 
    1. #define FSIZ( type, field ) sizeof( ((type *) 0)->field )

    5 得到一个变量的地址(word宽度) 
    1. #define B_PTR( var ) ( (byte *) (void *) &(var) ) 
    2. #define W_PTR( var ) ( (word *) (void *) &(var) )
    6 将一个字母转换为大写

    1. #define UPCASE( c ) ( ((c) >= ''a'' && (c) <= ''z'') ? ((c) - 0x20) : (c) )
    7 判断字符是不是10进值的数字

    1. #define DECCHK( c ) ((c) >= ''0'' && (c) <= ''9'')
    8 判断字符是不是16进值的数字 
    1. #define HEXCHK( c ) ( ((c) >= ''0'' && (c) <= ''9'') ||((c) >= ''A'' && (c) <= ''F'') ||((c) >= ''a'' && (c) <= ''f'') )
    9 防止溢出的一个方法

    1. #define INC_SAT( val ) (val = ((val)+> (val)) ? (val)+: (val))
    10 返回数组元素的个数 
    1. #define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) )
    11 使用一些宏跟踪调试

    ANSI标准说明了五个预定义的宏名。它们是: 
    1. _LINE_ /*(两个下划线),对应%d*/
    2. _FILE_ /*对应%s*/
    3. _DATE_ /*对应%s*/
    4. _TIME_ /*对应%s*/

    宏定义中do{ }while(0)

       第一眼看到这样的宏时,觉得非常奇怪,为什么要用do……while(0)把宏定义的多条语句括起来?非常想知道这样定义宏的好处是什么,于是google、百度一下了。

        采用这种方式是为了防范在使用宏过程中出现错误,主要有如下几点:

      (1)空的宏定义避免warning:
      #define foo() do{}while(0)
      (2)存在一个独立的block,可以用来进行变量定义,进行比较复杂的实现。
      (3)如果出现在判断语句过后的宏,这样可以保证作为一个整体来是实现:
          #define foo(x) \
            action1(); \
            action2();
        在以下情况下:
        if(NULL == pPointer)
             foo();
        就会出现action1和action2不会同时被执行的情况,而这显然不是程序设计的目的。
      (4)以上的第3种情况用单独的{}也可以实现,但是为什么一定要一个do{}while(0)呢,看以下代码:
          #define switch(x,y) {int tmp; tmp="x";x=y;y=tmp;}
          if(x>y)
            switch(x,y);
          else       //error, parse error before else
          otheraction();
        在把宏引入代码中,会多出一个分号,从而会报错。这对这一点,可以将if和else语句用{}括起来,可以避免分号错误。
      使用do{….}while(0) 把它包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。同时因为绝大多数的编译器都能够识别do{…}while(0)这种无用的循环并进行优化,所以使用这种方法也不会导致程序的性能降低

    展开全文
  • 1、宏的功能介绍 在 C 语言中,可以采用命令 #define 来定义宏。该命令允许把一个名称指定成任何所需的文本,例如一个常量值或者一条语句。在定义了宏之后,无论宏名称出现在源代码的何处,预处理器都会把它用定义...

    1、宏的功能介绍

    • 在 C 语言中,可以采用命令 #define 来定义宏。该命令允许把一个名称指定成任何所需的文本,例如一个常量值或者一条语句。在定义了宏之后,无论宏名称出现在源代码的何处,预处理器都会把它用定义时指定的文本替换掉。
    • 关于宏的一个常见应用就是,用它定义数值常量的名称:
    #define         ARRAY_SIZE 100
    double   data[ARRAY_SIZE];
    • 这两行代码为值 100 定义了一个宏名称 ARRAY_SIZE,并且在数组 data 的定义中使用了该宏。惯例将宏名称每个字母采用大写,这有助于区分宏与一般的变量。上述简单的示例也展示了宏是怎样让 C 程序更有弹性的。
    • 在翻译的第三个步骤中,预处理器会分析源文件,把它们转换为预处理器记号和空白符。如果遇到的记号是宏名称,预处理器就会展开(expand)该宏;也就是说,会用定义的文本来取代宏名称。出现在字符串字面量中的宏名称不会被展开,因为整个字符串字面量算作一个预处理器记号。
    • 无法通过宏展开的方式创建预处理器命令。即使宏的展开结果会生成形式上有效的命令,但预处理器不会执行它。
    • 在宏定义时,可以有参数,也可以没有参数。

    2、没有参数的宏

    • 没有参数的宏定义,采用如下形式:
    #define 宏名称 替换文本
    • “替换文本”前面和后面的空格符不属于替换文本中的内容。替代文本本身也可以为空。下面是一些示例:
    #define TITLE "*** Examples of Macros Without Parameters ***"
    #define BUFFER_SIZE (4 * 512)
    #define RANDOM (-1.0 + 2.0*(double)rand() / RAND_MAX)
    • 标准函数 rand()返回一个伪随机整数,范围在 [0,RAND_MAX] 之间。rand()的原型和 RAND_MAX 宏都定义在标准库头文件 stdlib.h 中。
    • 下面的语句展示了上述宏的一种可能使用方式:
    #include <stdio.h>
    #include <stdlib.h>
    /* ... */
    // 显示标题
    puts( TITLE );
    // 将流fp设置成“fully buffered”模式,其具有一个缓冲区,
    // 缓冲区大小为BUFFER_SIZE个字节
    // 宏_IOFBF在stdio.h中定义为0
    static char myBuffer[BUFFER_SIZE];
    setvbuf( fp, myBuffer, _IOFBF, BUFFER_SIZE );
    // 用ARRAY_SIZE个[-10.0, +10.0]区间内的随机数值填充数组data
    for ( int i = 0; i < ARRAY_SIZE; ++i )
      data[i] = 10.0 * RANDOM;
    • 用替换文本取代宏,预处理器生成下面的语句:
    puts( "*** Examples of Macros Without Parameters ***" );
    static char myBuffer[(4 * 512)];
    setvbuf( fp, myBuffer, 0, (4 * 512) );
    for ( int i = 0; i < 100; ++i )
    data[i] = 10.0 * (-1.0 + 2.0*(double)rand() / 2147483647);
    • 在上例中,该实现版本中的 RAND_MAX 宏值是 2147483647。如果采用其他的编译器,RAND_MAX 的值可能会不一样。
    • 如果编写的宏中包含了一个有操作数的表达式,应该把表达式放在圆括号内,以避免使用该宏时受运算符优先级的影响,进而产生意料之外的结果。例如,RANDOM 宏最外侧的括号可以确保 10.0*RANDOM 表达式产生想要的结果。如果没有这个括号,宏展开后的表达式变成:
    10.0 * -1.0 + 2.0*(double)rand() / 2147483647
    • 这个表达式生成的随机数值范围在 [-10.0,-8.0] 之间。

    3、带参数的宏

    • 你可以定义具有形式参数(简称“形参”)的宏。当预处理器展开这类宏时,它先使用调用宏时指定的实际参数(简称“实参”)取代替换文本中对应的形参。带有形参的宏通常也称为类函数宏(function-like macro)。
    • 可以使用下面两种方式定义带有参数的宏:
    #define 宏名称( [形参列表] ) 替换文本
    #define 宏名称( [形参列表 ,] ... ) 替换文本
    • “形参列表”是用逗号隔开的多个标识符,它们都作为宏的形参。当使用这类宏时,实参列表中的实参数量必须与宏定义中的形参数量一样多(然而,C99 允许使用“空实参”,下面会进一步解释)。这里的省略号意味着一个或更多的额外形参。
    • 当定义一个宏时,必须确保宏名称与左括号之间没有空白符。如果在名称后面有任何空白,那么命令就会把宏作为没有参数的宏,且从左括号开始采用替换文本。
    • 常见的两个函数 getchar()和 putchar(),它们的宏定义在标准库头文件 stdio.h 中。它们的展开值会随着实现版本不同而有所不同,但不论何种版本,它们的定义总是类似于以下形式:
    #define getchar() getc(stdin)
    #define putchar(x) putc(x, stdout)
    • 当“调用”一个类函数宏时,预处理器会用调用时的实参取代替换文本中的形参。C99 允许在调用宏的时候,宏的实参列表可以为空。在这种情况下,对应的替换文本中的形参不会被取代;也就是说,替换文本会删除该形参。然而,并非所有的编译器都支持这种“空实参”的做法。
    • 如果调用时的实参也包含宏,在正常情况下会先对它进行展开,然后才把该实参取代替换文本中的形参。对于替换文本中的形参是 # 或 ## 运算符操作数的情况,处理方式会有所不同。下面是类函数宏及其展开结果的一些示例:
    #include <stdio.h>             // 包含putchar()的定义
    #define DELIMITER ':'
    #define SUB(a,b) (a-b)
    putchar( DELIMITER );
    putchar( str[i] );
    int var = SUB( ,10);
    • 如果 putchar(x)定义为 putc(x,stdout),预处理器会按如下方式展开最后三行代码:
    putc(':', stdout);
    putc(str[i], stdout);
    int var = (-10);
    • 如下例所示,替换文本中所有出现的形参,应该使用括号将其包围。这样可以确保无论实参是否是表达式,都能正确地被计算:
    #define DISTANCE( x, y ) ((x)>=(y) ? (x)-(y) : (y)-(x))
    d = DISTANCE( a, b+0.5 );
    • 该宏调用展开如下所示:
    d = ((a)>=(b+0.5) ? (a)-(b+0.5) : (b+0.5)-(a));
    • 如果 x 与 y 没有采用括号,那么扩展后将出现表达式 a-b+0.5,而不是表达式(a)-(b+0.5),这与期望的运算不同。
    • 3.1 可选参数

      • C99 标准允许定义有省略号的宏,省略号必须放在参数列表的后面,以表示可选参数。你可以用可选参数来调用这类宏。
      • 当调用有可选参数的宏时,预处理器会将所有可选参数连同分隔它们的逗号打包在一起作为一个参数。在替换文本中,标识符 VA_ARGS 对应一组前述打包的可选参数。标识符 VA_ARGS 只能用在宏定义时的替换文本中。
      • VA_ARGS 的行为和其他宏参数一样,唯一不同的是,它会被调用时所用的参数列表中剩下的所有参数取代,而不是仅仅被一个参数取代。下面是一个可选参数宏的示例:
      // 假设我们有一个已打开的日志文件,准备采用文件指针fp_log对其进行写入
      #define printLog(...) fprintf( fp_log, __VA_ARGS__ )
      // 使用宏printLog
      printLog( "%s: intVar = %d\n", __func__, intVar );
      • 预处理器把最后一行的宏调用替换成下面的一行代码:
      fprintf( fp_log, "%s: intVar = %d\n", __func__, intVar );
      • 预定义的标识符 func 可以在任一函数中使用,该标识符是表示当前函数名的字符串。因此,该示例中的宏调用会将当前函数名和变量 intVar 的内容写入日志文件。
    • 3.2 字符串化运算符

      • 一元运算符 # 常称为字符串化运算符(stringify operator 或 stringizing operator),因为它会把宏调用时的实参转换为字符串。# 的操作数必须是宏替换文本中的形参。当形参名称出现在替换文本中,并且具有前缀 # 字符时,预处理器会把与该形参对应的实参放到一对双引号中,形成一个字符串字面量。
      • 实参中的所有字符本身维持不变,但下面几种情况是例外:
        • (1) 在实参各记号之间如果存在有空白符序列,都会被替换成一个空格符。
        • (2) 实参中每个双引号(")的前面都会添加一个反斜线()。
        • (3) 实参中字符常量、字符串字面量中的每个反斜线前面,也会添加一个反斜线。但如果该反斜线本身就是通用字符名的一部分,则不会再在其前面添加反斜线。
      • 下面的示例展示了如何使用#运算符,使得宏在调用时的实参可以在替换文本中同时作为字符串和算术表达式:
      #define printDBL( exp ) printf( #exp " = %f ", exp )
      printDBL( 4 * atan(1.0));           // atan()在math.h中定义
      • 上面的最后一行代码是宏调用,展开形式如下所示:
      printf( "4 * atan(1.0)" " = %f ", 4 * atan(1.0));
      • 因为编译器会合并紧邻的字符串字面量,上述代码等效为:
      printf( "4 * atan(1.0) = %f ", 4 * atan(1.0));
      • 该语句会生成下列文字并在控制台输出:
      4 * atan(1.0) = 3.141593
      • 在下面的示例中,调用宏 showArgs 以演示 # 运算符如何修改宏实参中空白符、双引号,以及反斜线:
      #define showArgs(...) puts(#__VA_ARGS__)
      showArgs( one\n,       "2\n", three );
      • 预处理器使用下面的文本来替换该宏:
      puts("one\n, \"2\\n\", three");
      • 该语句生成下面的输出:
      one
      , "2\n", three
    • 3.3 记号粘贴运算符

      • 运算符是一个二元运算符,可以出现在所有宏的替换文本中。该运算符会把左、右操作数结合在一起,作为一个记号,因此,它常常被称为记号粘贴运算符(token-pasting operator)。如果结果文本中还包含有宏名称,则预处理器会继续进行宏替换。出现在 ## 运算符前后的空白符连同 ## 运算符本身一起被删除。
      • 通常,使用 ## 运算符时,至少有一个操作数是宏的形参。在这种情况下,实参值会先替换形参,然后等记号粘贴完成后,才进行宏展开。如下例所示:
      #define TEXT_A "Hello, world!"
      #define msg(x) puts( TEXT_ ## x )
      msg(A);
      • 无论标识符 A 是否定义为一个宏名称,预处理器会先将形参 x 替换成实参 A,然后进行记号粘贴。当这两个步骤做完后,结果如下:
      puts( TEXT_A );
      • 现在,因为 TEXT_A 是一个宏名称,后续的宏替换会生成下面的语句:
      puts( "Hello, world!" );
      • 如果宏的形参是 ## 运算符的操作数,并且在某次宏调用时,并没有为该形参准备对应的实参,那么预处理使用占位符(placeholder)表示该形参被空字符串取代。把一个占位符和任何记号进行记号粘贴操作的结果还是原来的记号。如果对两个占位符进行记号粘贴操作,则得到一个占位符。
      • 当所有的记号粘贴运算都做完后,预处理器会删除所有剩下的占位符。下面是一个示例,调用宏时传入空的实参:
      msg();
      • 这个调用会被展开为如下所示的代码:
      puts( TEXT_ );
      • 如果TEXT_不是一个字符串类型的标识符,编译器会生成一个错误信息。
      • 字符串化运算符和记号粘贴运算符并没有固定的运算次序。如果需要采取特定的运算次序,可以将一个宏分解为多个宏。

    4、在宏内使用宏

    • 在替换实参,以及执行完 # 和 ## 运算之后,预处理器会检查操作所得的替换文本,并展开其中包含的所有宏。但是,宏不可以递归地展开:如果预处理器在 A 宏的替换文本中又遇到了 A 宏的名称,或者从嵌套在 A 宏内的 B 宏内又遇到了 A 宏的名称,那么 A 宏的名称就会无法展开。
    • 类似地,即使展开一个宏生成有效的命令,这样的命令也无法执行。然而,预处理器可以处理在完全展开宏后出现 _Pragma 运算符的操作。
    • 下面的示例程序以表格形式输出函数值:
    // fn_tbl.c: 以表格形式输出一个函数的值。该程序使用了嵌套的宏
    // -------------------------------------------------------------
    #include <stdio.h>
    #include <math.h>                          // 函数cos()和exp()的原型
    #define PI              3.141593
    #define STEP    (PI/8)
    #define AMPLITUDE       1.0
    #define ATTENUATION     0.1                      // 声波传播的衰减指数
    #define DF(x)   exp(-ATTENUATION*(x))
    #define FUNC(x) (DF(x) * AMPLITUDE * cos(x)) // 震动衰减
    // 针对函数输出:
    #define STR(s) #s
    #define XSTR(s) STR(s)                   // 将宏s展开,然后字符串化
    int main()
    {
      double x = 0.0;
      printf( "\nFUNC(x) = %s\n", XSTR(FUNC(x)) );          // 输出该函数
      printf("\n %10s %25s\n", "x", STR(y = FUNC(x)) );             // 表格的标题
      printf("-----------------------------------------\n");
      for ( ; x < 2*PI + STEP/2; x += STEP )
        printf( "%15f %20f\n", x, FUNC(x) );
      return 0;
    }
    • 该示例输出下面的表格:
    FUNC(x) = (exp(-0.1*(x)) * 1.0 * cos(x))
              x                 y = FUNC(x)
    -----------------------------------------
                  0.000000          1.000000
                  0.392699          0.888302
    ...
              5.890487              0.512619
              6.283186              0.533488

    5、宏的作用域和重新定义

    • 你无法再次使用 #define 命令重新定义一个已经被定义为宏的标识符,除非重新定义所使用的替换文本与已经被定义的替换文本完全相同。如果该宏具有形参,重新定义的形参名称也必须与已定义形参名称的一样。
    • 如果想改变一个宏的内容,必须首先使用下面的命令取消现在的定义:
    #undef 宏名称
    • 执行上面的命令之后,标识符“宏名称”可以再次在新的宏定义中使用。如果上面指定的标识符并非一个已定义的宏名称,那么预处理器会忽略这个 #undef 命令。
    • 标准库中的多个函数名称也被定义成了宏。如果想直接调用这些函数,而不是调用同名称的宏,可以使用 #undef 命令取消对这些宏的定义。即使准备取消定义的宏是带有参数的,也不需要在 #undef 命令中指定参数列表。如下例所示:
    #include <ctype.h>
    #undef isdigit          // 移除任何使用该名称的宏定义
    /* ... */
    if ( isdigit(c) )               // 调用函数isdigit()
    /* ... */
    • 当某个宏首次遇到它的 #undef 命令时,它的作用域就会结束。如果没有关于该宏的 #undef 命令,那么它的作用域在该翻译单元结束时终止。

    转载于:https://www.cnblogs.com/CH520/p/10162766.html

    展开全文
  • c++中define用法

    2020-04-09 08:40:47
    c++中define用法 define在c++语言中用法比较多,这里对其进行整理。 1.无参宏定义 无参宏的宏名后不带参数。 其定义的一般形式为: ...#define 标识符 字符串 ...其中的“#”表示这是一条预处理命令。...

    c++中define用法

    define在c++语言中用法比较多,这里对其进行整理。

    1.无参宏定义

    无参宏的宏名后不带参数。
    其定义的一般形式为:

    #define  标识符  字符串
    1
    其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。
    例如:

    #define MAXNUM 99999

    这样MAXNUM就被简单的定义为99999。

    2.有参宏定义

    C++语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。
    对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。
    带参宏定义的一般形式为:

     #define  宏名(形参表)  字符串
    1
    在字符串中含有各个形参。在使用时调用带参宏调用的一般形式为:宏名(实参表);
    例如:

    #define add(x, y) (x + y)
    int main()
    {
        cout << "1 plus 1 is " << add(1, 1.5) << ".\n";
        //输出“1 plus 1 is 2.5.”
        system("pause");
        return(0);
    }

    这个“函数”定义了加法,但是该“函数”没有类型检查,有点类似模板,但没有模板安全,可以看做一个简单的模板。

    注意:该“函数”定义为(a + b),在这里加括号的原因是,宏定义只是在预处理阶段做了简单的替换,如果单纯的替换为a + b时,当你使用5 * add(2, 3)时,被替换为5 * 2 + 3,值为13,而非5 * (2 + 3),值为25。

    3.宏定义中的特殊操作符
    define 中的特殊操作符有#,##和… and __VA_ARGS__
    (1)#
    假如希望在字符串中包含宏参数,ANSI C允许这样作,在类函数宏的替换部分,#符号用作一个预处理运算符,它可以把语言符号转化程字符串。例如,如果x是一个宏参量,那么#x可以把参数名转化成相应的字符串。该过程称为字符串化。

    例如:

    #incldue <stdio.h>
    #define PSQR(x) printf("the square of" #x "is %d.\n",(x)*(x))
    int main(void)
    {
        int y =4;
        PSQR(y);
        //输出:the square of y is 16.
        PSQR(2+4);
        //输出:the square of 2+4 is 36.
        return 0;
    }

    (2)##

    ##运算符可以用于类函数宏的替换部分。另外,##还可以用于类对象宏的替换部分。这个运算符把两个语言符号组合成单个语言符号。
    例如:

    #include <stdio.h>
    #define XNAME(n) x##n
    #define PXN(n) printf("x"#n" = %d\n",x##n)
    int main(void)
    {
        int XNAME(1)=12;//int x1=12;
        PXN(1);//printf("x1 = %d\n", x1);
        //输出:x1=12
        return 0;
    }

    (3)可变参数宏 …和__VA_ARGS__

    __VA_ARGS__ 是一个可变参数的宏,很少人知道这个宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持(VC6.0的编译器不支持)。
    实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏__VA_ARGS__就可以被用在替换部分中,替换省略号所代表的字符串。

    例如:

    #define PR(...) printf(__VA_ARGS__)
    int main()
    {
        int wt=1,sp=2;
        PR("hello\n");
        //输出:hello
        PR("weight = %d, shipping = %d",wt,sp);
        //输出:weight = 1, shipping = 2
        return 0;
    }

    省略号只能代替最后面的宏参数。
    #define W(x,…,y)错误!
    但是支持#define W(x, …),此时传入的参数个数必须能够匹配。

    这里再介绍几个系统的宏:

    1) __VA_ARGS__ 是一个可变参数的宏,很少人知道这个宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持(VC6.0的编译器不支持)。宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的”,”去掉的作用,否则会编译出错, 你可以试试。
    2) __FILE__ 宏在预编译时会替换成当前的源文件名
    3) __LINE__宏在预编译时会替换成当前的行号
    4) __FUNCTION__宏在预编译时会替换成当前的函数名称

    4.宏定义中的多行定义

    非常经典。

    #define MACRO(arg1, arg2) do { /
    /* declarations */ /
    stmt1; /
    stmt2; /
    /* ... */ /
    } while(0) /* (no trailing ; ) */

    记得要在每一个换行的时候加上一个”/”

    5.宏定义中的条件编译

    在大规模的开发过程中,特别是跨平台和系统的软件里,define最重要的功能是条件编译。

    #ifdef WINDOWS
    ......
    (#else)
    ......
    #endif
    #ifdef LINUX
    ......
    (#else)
    ......
    #endif

    可以在编译的时候通过#define设置编译环境。

    6.如何取消宏

    //定义宏
    #define [MacroName] [MacroValue]
    //取消宏
    #undef [MacroName]

    7.防止重复包含头文件
    由于头文件包含可以嵌套,那么C文件就有可能包含多次同一个头文件,就可能出现重复定义的问题的。
    通过条件编译开关来避免重复包含(重复定义)
    例如:

    #ifndef __headerfileXXX__
    #define __headerfileXXX__

    文件内容

    #endif

    小结及说明

    1) 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。
    2) 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起置换。
    3) 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。只要函数定义在#undefine之后,则函数无法使用#define的内容。
    4) 宏名在源程序中若用引号括起来,则预处理程序不对其作宏代换。
    5) 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层代换。
    例如:

     #define PI 3.1415926
     #define S PI*y*y          /* PI是已定义的宏名*/

    6) 习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。
    7) 可用宏定义表示数据类型,使书写方便。
    应注意用宏定义表示数据类型和用typedef定义数据说明符的区别。
    宏定义只是简单的字符串代换,是在预处理完成的,而typedef是在编译时处理的,它不是作简单的代换,而是对类型说明符重新命名。被命名的标识符具有类型定义说明的功能。
    请看下面的例子:

    #define PIN1 int *
    typedef (int *) PIN2;
    1
    2
    从形式上看这两者相似, 但在实际使用中却不相同。
    下面用PIN1,PIN2说明变量时就可以看出它们的区别:
    PIN1 a,b;在宏代换后变成:
    int *a,b;
    表示a是指向整型的指针变量,而b是整型变量。
    然而:
    PIN2 a,b;
    表示a,b都是指向整型的指针变量。因为PIN2是一个类型说明符。由这个例子可见,宏定义虽然也可表示数据类型, 但毕竟是作字符代换。在使用时要分外小心,以避出错。
    8) 对“输出格式”作宏定义,可以减少书写麻烦。
    例如:

    #define P printf
    #define D "%d\n"
    #define F "%f\n"
    main(){
      int a=5, c=8, e=11;
      float b=3.8, d=9.7, f=21.08;
      P(D F,a,b);
      P(D F,c,d);
      P(D F,e,f);
    }

    9)带参宏定义中,宏名和形参表之间不能有空格出现。
    10)在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义。而宏调用中的实参有具体的值。要用它们去代换形参,因此必须作类型说明。这是与函数中的情况不同的。在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”。而在带参宏中,只是符号代换,不存在值传递的问题。
    11)在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。
     

    展开全文
  • 宏定义的几种用法

    2020-08-19 11:15:31
    今天小编发现宏定义可以修改,实际测试发现也能修改,通过查询网上资料总结如下: 1、用 #define 定义标识符的一般形式为: #define标识符常量//注意, 最后没有分号 2、第一种用法,宏定义固定值:#define PI=3.14...
  • 定义常量的几种方式

    2020-03-11 12:44:31
    第一种:使用关键字const声明的常量 #include <iostream> using namespace std; int main() { const int day = 365; cout << "一年有" << day << "天" << endl; system("pause...
  • Pycharm简单使用教程

    万次阅读 多人点赞 2018-03-02 11:19:51
    1、下载pycharm pycharm是一种Python IDE,能够帮助我们在编写代码时提高效率。 网上提供的有专业版和教育版之分。 专业版是收费的,功能更全面点。...教育版或社区版是阉割版本,但它是免费的。...
  • 线上发现有机器,在发生某块业务大量请求时,后面就没有日志了,查看线程状态,如图1,发现很多线程被阻塞了,查看代码发现,用到了scan,如图2,百度之后,发现该操作不会自动释放redis连接,导致redis连接被占满,...
  • STM32CubeIDE使用笔记(01):基础说明与开发流程

    万次阅读 多人点赞 2019-07-31 13:30:38
    文章目录
  • ctags使用详解

    万次阅读 2018-05-15 21:26:54
    ctags的功能:扫描指定的源文件,找出其中所包含的语法元素,并将找到的相关内容记录下来。 查看ctags支持的语言 ctags --list-languages 查看语言和扩展名的对应关系 ctags --list-maps ...[chuxi...
  • VSCode详细使用教程

    万次阅读 多人点赞 2019-09-05 03:16:53
          VSCode(Visual Studio Code)是由微软研发的一款免费、开源的跨平台文本(代码)编辑器,算是目前前端开发几乎完美的软件开发工具。 1.VSCode下载 ...2.VSCode汉化 ...
  • win32 010 使用masm32

    2014-05-08 16:51:45
    使用MASM07   让编程改变世界 Change the world by program   标号、变量和数据结构   当程序中要跳转到另一位置时,需要有一个标识来指示位置,这就是标号。 通过在目的地址的前面放上一个标号,可以...
  • MASM使用总结

    2010-01-30 21:54:00
    MASM(Macro Assembler)是由微软公司提供的汇编工具,虽然有些年头了,但是仍然存在于 vc.net这样比较新的工具中。有很多汇编教科书以这个为对象,讲述了如何用汇编去设计一个程序,作为计算机科学系学生的基础课...
  • MASM使用教程

    2015-05-25 11:02:56
    MASM汇编软件的使用的简单介绍。微机原理课程用到
  • 使用MASM-标号、变量和数据结构(1)

    千次阅读 2013-06-30 12:36:05
    使用变量是任何编程语言都要遇到的工作,Win32汇编也不例外,在MASM使用变量也有需要注意的几个问题,错误地使用变量定义或用错误的方法初始化变量会带来难以定位的错误。变量是计算机内存中已命名的存储位置,在...
  • 64位win7使用debug(Masm所有子程序…

    千次阅读 2013-05-31 12:26:18
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ...     ...精心整理,不装虚拟机,简单几步在Win7 64位系统下实现Debug汇编的方法。...使用32
  • MASM 6.1 高品質 PDF 格式使用手冊,書中插圖清晰可縮放。 全部四冊手冊都包含在內: Getting Started Environment and Tools Programmer's Guide Reference
  • MASMPlus 是一个使用MASM 编写的 MASM Integrate Develop Environment. 专业汇编程序员90%均使用 MASM,为它开发的编辑器非常多,其中最有名的是 RadASM, 而现在,真正能与 RadASM 同级的就只有 MASMPlus 了,理由是...
  • 需要 MASM 5.0 或者以上的汇编编译器 首先,是要编辑汇编源代码: 其实对于源码的编辑根本不需要向如下这么麻烦,直接拿个记事本就 OK 了 运行 cmd 命令 输入 Edit 命令从而弹出汇编源码编辑框
  • TITLE (.asm) ; This program; Last update: Include Irvine32.inc.data .codemain PROC exitmain ENDPEND main 转载于:https://blog.51cto.com/yybug/213361
  • 编译一个扩展文件名为ASM的汇编语言源程序 用汇编程序MASM汇编上述的汇编语言源程序,形成目标代码文件 用连接程序LINK或TLINK连接目标代码文件,形成可执行文件 运行可执行文件,观察执行结果,验证其正确性
  • 这里包括了dosbox的程序和masm工具,只能两步就可以直接使用了 dosbox下载链接 提取码:dosb 1、设置挂载的文件夹,后期写的汇编程序可以放到那个文件夹下来编译运行 (1)打开dosbox\DOSBox-0.74文件夹 (2)用记事本...
  • 使用masm5.0编译时提示incorrect ms-dos version怎么办? 是在asmedit.exe里点编译吗? 我也是这样噢!手动添加路径后问题依旧。 没办法,大家用cmd打开masm的目录,自己使用masm、link、debug等的命令去...
  • https://blog.csdn.net/WuchangI/article/details/79658730 https://blog.csdn.net/xyisv/article/details/69062382 //废话不多说,这两个博客是最好的win10 系统下安装dos开发环境最好的博客 ...
  • 3.3.5 变量的使用1. 以不同的类型访问变量这个话题有点像C语言中的数据类型强制转换,C语言中的类型转换指的是把一个变量的内容转换成另外一种类型,转换过程中,数据的内容已经发生了变化,如把浮点数转换成整数后...
  • 在VC++中使用MASM-项目属性设置

    千次阅读 2010-04-29 21:34:00
    你或许会想知道更多在Visual C++中关于汇编语言的设置。假设示例程序已经被打开,选择项目菜单下的Project属性。展开配置属性=>Microsoft Macro Assembler,下面是你将会看到的: 点击General,可以看到Include ...
  • 使用MASM-高级语法(2)

    2012-06-03 21:39:45
    使用MASM-高级语法(2) 可以看到,MASM编译器对这些条件分支伪指令优化得相当好,看到这些反汇编后的指令,惟一的感觉是好像又回到了DOS汇编时代分支指令堆中,从这里可以发现,这些伪指令把汇编...
  • 最近刚学习win32汇编程序,用的工具是MASM32,MASM32安装包不会对注册表进行写操作,安装后,它会在桌面创建qeditor.exe的快捷方式,qeditor是MASM32的IDE环境,我们要做的事就是对这个IDE进行设置。如何设置咱们...
  • 中英文对照—masm编译时错误性息提示   方便一下喜欢汇编的人,里面有一些错误,望牛人帮忙改正。 ml.exe错误性息 FATAL 严重错误 cannot open file 不能打开文件 I/O error closing file I/O错误 正在...
  • ;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&...lt

空空如也

1 2 3 4 5 ... 20
收藏数 18,628,100
精华内容 7,451,240
关键字:

使用