精华内容
下载资源
问答
  • 宏定义有无参数宏定义和带参数宏定义两种。 无参数的宏定义的一般形式为 # define 标识符 字符序列 其中# define之后的标识符称为宏定义名(简称宏名),要求宏名与字符序列之间用空格符分隔。这种宏定义要求编译...

    宏定义有无参数宏定义和带参数宏定义两种。

    无参数的宏定义的一般形式为

    # define 标识符 字符序列
    

    其中# define之后的标识符称为宏定义名(简称宏名),要求宏名与字符序列之间用空格符分隔。这种宏定义要求编译预处理程序将源程序中随后所有的定名的出现(注释与字符串常量中的除外)均用字符序列替换之。前面经常使用的定义符号常量是宏定义的最简单应用。如有:

           # define TRUE 1
           # define FALSE 0
    

    则在定义它们的源程序文件中,凡定义之后出现的单词TRUE将用1替代之;出现单词FALSE将用0替代之。
    在宏定义的#之前可以有若干个空格、制表符,但不允许有其它字符。宏定义在源程序中单独另起一行,换行符是宏定义的结束标志。如果一个宏定义太长,一行不 够时,可采用续行的方法。续行是在键人回车符之前先键入符号"/"。注意回车要紧接在符号"/"之后,中间不能插入其它符号。
    宏定义的有效范围称为宏定义名的辖域,辖域从宏定义的定义结束处开始到其所在的源程序文件末尾。宏定义名的辖域不受分程序结构的影响。可以用预处理命令#undef终止宏定义名的辖域。
    在新的宏定义中,可以使用前面已定义的宏名。例如,

             # define R 2.5
             # define PI 3.1415926
             # define Circle 2*PI*R
             # define Area PI* R * R
    

    程序中的Circle被展开为23.1415926 2.5, Area被展开为3.14159262.52.5。
    如有必要,宏名可被重复定义。被重复定义后,宏名原先的意义被新意义所代替。
    通常,无参数的宏定义多用于定义常量。程序中统一用宏名表示常量值,便于程序前后统一,不易出错,也便于修改,能提高程序的可读性和可移植性。特别是给数组元素个数一个宏定义,并用宏名定义数组元素个数能部分弥补数组元素个数固定的不足。
    注意:预处理程序在处理宏定义时,只作字符序列的替换工作,不作任何语法的检查。如果宏定义不当,错误要到预处理之后的编译阶段才能发现。
    宏定义以换行结束,不需要分号等符号作分隔符。如有以下定定义:

       # define PI 3.1415926;
    

    原希望用PI求圆的周长的语句

       c=2*PI*r;
    

    经宏展开后,变成

      c=2*3.1415926*r;
    

    这就不能达到希望的要求。
    带参数宏定义进一步扩充了无参数宏定义的能力,在字符序列替换同时还能进行参数替换。带参数定定义的一般形式为

    # define 标识符(参数表)字符序列
    

    其中参数表中的参数之间用逗号分隔,字符序列中应包含参数表中的参数。在定义带参数的宏时,宏名标识符与左圆括号之间不允许有空白符,应紧接在一起,否则变成了无参数的宏定义。如有宏定义:

    # define MAX(A,B) ((A) > (B)?(A):(B))
    

    则代码 y= MAX( p+q, u+v)将被替换成 y=((p+q) >(u+v)?(p+q):(u+v)。
    程序中的宏调用是这样被替换展开的,分别用宏调用中的实在参数字符序列(如p+q和u+V) 替换宏定义字符序列中对应所有出现的形式参数(如用p+q替代所有形式参数A,用u+V替代所有形式参数B),而宏定义字符序列中的不是形式参数的其它字 符则保留。这样形成的字符序列,即为宏调用的展开替换结果。宏调用提供的实在参数个数必须与宏定义中的形式参数个数相同。
    注意:宏调用与函数调用的区别。

    函数调用宏调用
    程序运行时执行编译预处理阶段进行
    对实参有类型要求实参与形参没有类型的概念,只有字符序列的对应关系
    返回一个值C代码
    实参表达式分别独立求值在前,执行函数体在后实在参数字符序列替换形式参数,替换后,实在参数字符序列就与相邻的字符自然连 接,实在参数的独立性就不一定依旧存在。
    1. 函数调用在程序运行时实行,而宏展开是在编译的预处理阶段进行;
    2. 函数调用占用程序运行时间,宏调用只占编译时间
    3. 函数调用 对实参有类型要求,而宏调用实参与宏定义形式参数之间没有类型的概念只有字符序列的对应关系
    4. 函数调用可返回一个值,宏调用获得希望的C代码
    5. 函数调用时,实参表达式分别独立求值在前,执行函数体在后。宏调用是实在参数字符序列替换形式参数。替换后,实在参数字符序列就与相邻的字符自然连 接,实在参数的独立性就不一定依旧存在。如下面的宏定义:
      # define SQR(x) x*x
    

    希望实现表达式的平方计算。对于宏调用
    P=SQR(y)
    能得到希望的宏展开p= yy。但对于宏调用q=SQR(u+v)得到的宏展开是q=u+Vu+V。显然,后者的展开结果不是程序设计者所希望的。进一步,为了保证宏调用的独立性,作为算式的宏定义也应加括
    。如 SQR宏定义改写成:

      # define SQR(x) (x)*(x)
    

    才是正确的宏定义。(转载的原文出错了,我这才是正确的!!!)

    使用一些宏跟踪调试

    A N S I标准说明了五个预定义的宏名。它们是:

    _ L I N E _ (两个下划线),对应%d

    _ F I L E _ 对应%s

    _ D A T E _ 对应%s

    _ T I M E _ 对应%s

    _ S T D C _

    如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序

    也许还提供其它预定义的宏名。

    _ L I N E F I L E _宏指令在有关# l i n e的部分中已讨论,这里讨论其余的宏名。

    _ D AT E _宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。

    源代码翻译到目标代码的时间作为串包含在_ T I M E _中。串形式为时:分:秒。

    如果实现是标准的,则宏_ S T D C _含有十进制常量1。如果它含有任何其它数,则实现是

    非标准的。

    可以定义宏,例如:

    当定义了_DEBUG,输出数据信息和所在文件所在行

    #ifdef _DEBUG

    #define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%s”,date,LINE,FILE)

    #else

      #define DEBUGMSG(msg,date)
    

    #endif

    20,宏定义防止使用是错误

    用小括号包含。

    例如:#define ADD(a,b) (a+b)

    用do{}while(0)语句包含多语句防止错误

    例如:#difne DO(a,b) a+b;/

                   a++;
    

    应用时:if(….)

          DO(a,b); //产生错误
    
        else
    

    解决方法: #difne DO(a,b) do{a+b;/

                   a++;}while(0)
    

    宏中"#“和”##"的用法
    一、一般用法
    我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起(这里说的是在预处理是对源文件的操作).
    用法:

    #include<cstdio> 
    #include<climits> 
    using namespace std;
    
    #define STR(s)     #s 
    #define CONS(a,b) int(a##e##b)
    
    int main() 
    { 
    printf(STR(vck));           // 输出字符串"vck" 
    printf("%d/n", CONS(2,3)); // 2e3 输出:2000 
    return 0; 
    }
    

    二、当宏参数是另一个宏的时候

    需要注意的是凡宏定义里有用’’#’‘或’’##’'的地方宏参数是不会再展开.

    1, 非’’#’‘和’’##’'的情况
    #define TOW (2)
    #define MUL(a,b) (a*b)

    printf("%d*%d=%d/n", TOW, TOW, MUL(TOW,TOW));
    这行的宏会被展开为:
    printf("%d*%d=%d/n", (2), (2), ((2)*(2)));
    MUL里的参数TOW会被展开为(2).

    2, 当有’’#’‘或’’##’'的时候
    #define A (2)
    #define STR(s) #s
    #define CONS(a,b) int(a##e##b)

    printf(“int max: %s/n”, STR(INT_MAX)); // INT_MAX #include
    这行会被展开为:
    printf(“int max: %s/n”, “INT_MAX”);

    printf("%s/n", CONS(A, A)); // compile error
    这一行则是:
    printf("%s/n", int(AeA));

    INT_MAX和A都不会再被展开, 然而解决这个问题的方法很简单. 加多一层中间转换宏.
    加这层宏的用意是把所有宏的参数在中间层里全部展开, 那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数.

    #define A (2)
    #define _STR(s) #s
    #define STR(s) _STR(s) // 转换宏
    #define _CONS(a,b) int(a##e##b)
    #define CONS(a,b) _CONS(a,b) // 转换宏

    printf(“int max: %s/n”, STR(INT_MAX)); // INT_MAX,int型的最大值,为一个变量 #include
    输出为: int max: 0x7fffffff
    STR(INT_MAX) --> _STR(0x7fffffff) 然后再转换成字符串;

    printf("%d/n", CONS(A, A));
    输出为:200
    CONS(A, A) --> _CONS((2), (2)) --> int((2)e(2))

    三、’’#’‘和’’##’'的一些应用特例

    1、合并匿名变量名
    #define ___ANONYMOUS1(type, var, line) type var##line
    #define __ANONYMOUS0(type, line) ___ANONYMOUS1(type, _anonymous, line)
    #define ANONYMOUS(type) __ANONYMOUS0(type, LINE)
    例:ANONYMOUS(static int); 即: static int _anonymous70; 70表示该行行号;
    第一层:ANONYMOUS(static int); --> __ANONYMOUS0(static int, LINE);
    第二层: --> ___ANONYMOUS1(static int, _anonymous, 70);
    第三层: --> static int _anonymous70;
    即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;

    2、填充结构
    #define FILL(a) {a, #a}

    enum IDD{OPEN, CLOSE};
    typedef struct MSG{
    IDD id;
    const char * msg;
    }MSG;

    MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
    相当于:
    MSG _msg[] = {{OPEN, “OPEN”},
    {CLOSE, “CLOSE”}};

    3、记录文件名
    #define _GET_FILE_NAME(f) #f
    #define GET_FILE_NAME(f) _GET_FILE_NAME(f)
    static char FILE_NAME[] = GET_FILE_NAME(FILE);

    4、得到一个数值类型所对应的字符串缓冲大小
    #define _TYPE_BUF_SIZE(type) sizeof #type
    #define TYPE_BUF_SIZE(type) _TYPE_BUF_SIZE(type)
    char buf[TYPE_BUF_SIZE(INT_MAX)];
    –> char buf[_TYPE_BUF_SIZE(0x7fffffff)];
    –> char buf[sizeof “0x7fffffff”];
    这里相当于:

    char buf[11]

    本文转载自http://hi.baidu.com/ufo008ahw/blog/item/5e943d4f5f49513caec3abd1.html)


    本文来自 叮当小菜 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/swee111/article/details/73275977?utm_source=copy

    更新


    //如果没有定义 HEADTEST_H_,继续往下
    //否则,就退出
    #ifndef  _HEADTEST_H_
    //定义 _HEADTEST_H_
    #define _HEADTEST_H_
    
    //宏定义,预编译阶段,直接替换
    
    #endif
    
    
    
    展开全文
  • 常见的宏定义两种,不带参数的宏定义和带参数的宏定义。 2、无参宏定义 无参数宏定义的格式为: #define 标识符 替换列表 替换列表可以是数值常量、字符常量、字符串常量等,故可以把宏定义理解为...

    1、宏定义说明

    • 宏定义是比较常用的预处理指令,即使用“标识符”来表示“替换列表”中的内容。标识符称为宏名,在预处理过程中,预处理器会把源程序中所有宏名,替换成宏定义中替换列表中的内容。
    • 常见的宏定义有两种,不带参数的宏定义和带参数的宏定义。

    2、无参宏定义

    • 无参数宏定义的格式为:
      #define 标识符 替换列表
    • 替换列表可以是数值常量、字符常量、字符串常量等,故可以把宏定义理解为使用标识符表示一常量,或称符号常量。
    • 说明:
    • 1) # 可以不在行首,但只允许它前面有空格符。例如:
    #define PI 3.1416 //正确,该行#前允许有空格
    int a;#define N 5 //错误,该行#前不允许有空格外的其他字符
    • 2) 标识符和替换列表之间不能加赋值号 =,替换列表后不能加分号
    #define N =5 //虽语法正确,但预处理器会把N替换成=5
    int a[N]; //错误,因为宏替换之后为 int a[=5];
    • 宏定义不是语句,是预处理指令,故结尾不加分号。如果不小心添加了分号,虽然有时该宏定义没问题,但在宏替换时,可能导致 C 语法错误,或得不到预期结果。例如:
    #define N 5; //虽语法正确,但会把N替换成5;
    int a[N]; //语法错误,宏替换后,为int a[5;];错误
    • 3) 由于宏定义仅是做简单的文本替换,故替换列表中如有表达式,必须把该表达式用括号括起来,否则可能会出现逻辑上的“错误”。例如:
    #define N 3+2
    int r = N * N;
    • 宏替换后为:
    int r=3+2*3+2; //r=11
    • 如果采用如下形式的宏定义:
    #define N (3+2)
    int r=N*N;
    • 则宏替换后,为:
    int r=(3+2)*(3+2); //r=25
    • 4) 当替换列表一行写不下时,可以使用反斜线\作为续行符延续到下一行。例如:
    #define USA "The United \
    States of \
    America"
    • 该宏定义中替换列表为字符串常量,如果该串较长,或为了使替换列表的结构更清晰,可使用续行符  把该串分若干行来写,除最后一行外,每行行尾都必须加续行符 。
    • 如果调用 printf 函数,以串的形式输出该符号常量,即:
    printf("%s\n",USA);
    • 则输出结果为:The United States of America
    • 注意:续行符后直接按回车键换行,不能含有包括空格在内的任何字符,否则是错误的宏定义形式。

    3、带参宏定义

    • 带参数的宏定义格式为:
      #define 标识符(参数1,参数2,...,参数n) 替换列表
    • 例如,求两个参数中最大值的带参宏定义为:
    #define MAX(a,b) ((a)>(b)?(a) : (b))
    • 当有如下语句时:
    int c=MAX(5,3);
    • 预处理器会将带参数的宏替换成如下形式:
    int c=((5)>(3)?(5) : (3));
    • 故计算结果c=5。
    • 删除宏定义的格式为:
      #undef 标识符
    • 说明:
      • 1) 标识符与参数表的左括号之间不能有空格,否则预处理器会把该宏理解为普通的无参宏定义,故以下是错误的带参宏定义形式。
      #define MAX (a,b) ( (a) > (b) ? (a) : (b) ) //错误的带参宏定义格式
      • 2) 宏替换列表中每个参数及整个替换列表,都必须用一对小括号 () 括起来,否则可能会出现歧义。
    • 【例 1】以下程序试图定义求两个参数乘积的宏定义,欲使用该宏求 3 与 6 的乘积,分析该程序能否实现预期功能,如果不能,请给出修改方案。
    #include <stdio.h>
    #define MUL(a,b) (a*b)
    int main (void)
    {
        int c;
        c=MUL(3,5+1);
        printf("c=%d\n",c);
        return 0;
    }
    • 分析:
      • 1) 由于该宏定义中的替换列表中的参数没有加括号,故宏调用时,如果参数是个表达式,可能会出现歧义,得不到预期结果。
      • 本例中宏调用 c=MUL(3,5+1); 会替换成 c=(3*5+1)=16;,与预期功能不符。
      • 2) 虽然把宏调用时的参数 5+1 括起来,可达到题目要求的效果,但这属于治标不治本。为统一编程规范,把替换列表中的每个参数均加括号,整个替换列表也加括号。
      • 同时,为达到标本兼治,在宏定义时,除单一值参数外,应显式加括号。
      • 修改代码为:
      #include <stdio.h>
      #define MUL(a,b) ((a)*(b))//修改处1
      int main (void)
      {
          int c;
          c=MUL(3,(5+1);//修改处2
          printf("c=%d\n",c);
          return 0;
      }
    • 带参宏定义 VS 函教调用
      • 接下来将从调用发生时间、参数类型检查、参数是否需要空间、运行速度等几个主要方面进行对比分析带参宏定义与函数调用的差异。
      • 调用发生的时间
      • 在源程序进行编译之前,即预处理阶段进行宏替换;而函数调用则发生在程序运行期间。
    • 参数类型检查
      • 函数参数类型检查严格。程序在编译阶段,需要检查实参与形参个数是否相等及类型是否匹配或兼容,若参数个数不相同或类型不兼容,则会编译不通过。
      • 在预处理阶段,对带参宏调用中的参数不做检查。即宏定义时不需要指定参数类型,既可以认为这是宏的优点,即适用于多种数据类型,又可以认为这是宏的一个缺点,即类型不安全。故在宏调用时,需要程序设计者自行确保宏调用参数的类型正确。
    • 参数是否需要空间
      • 函数调用时,需要为形参分配空间,并把实参的值复制一份赋给形参分配的空间中。而宏替换,仅是简单的文本替换,且替换完就把宏名对应标识符删除掉,即不需要分配空间。
    • 执行速度
      • 函数在编译阶段需要检查参数个数是否相同、类型等是否匹配等多个语法,而宏替换仅 是简单文本替换,不做任何语法或逻辑检查。
      • 函数在运行阶段参数需入栈和出栈操作,速度相对较慢。
    • 代码长度
      • 由于宏替换是文本替换,即如果需替换的文本较长,则替换后会影响代码长度;而函数不会影响代码长度。
      • 故使用较频繁且代码量较小的功能,一般采用宏定义的形式,比采用函数形式更合适。前面章节频繁使用的 getchar(),准确地说,是宏而非函数。
    • 为了使该宏调用像函数调用,故把该宏设计成了带参数的宏定义:
      #define getchar() getc(stdin)
    • 故调用该宏时,需要加括号,即传空参数:getchar()。

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

    展开全文
  • 宏定义

    2017-09-07 09:46:09
    宏定义 偏移地址 头文件 变参数宏定义

    1、重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植,假如平台不一样,字节不一样,就仅仅改变typedef定义即可。

    typedef  unsigned char      boolean;     /* Boolean value type. */
    typedef  unsigned long int   uint32;      /* Unsigned 32 bit value */
    typedef  unsigned short     uint16;      /* Unsigned 16 bit value */
    typedef  unsigned char      uint8;       /* Unsigned 8  bit value */
    typedef  signed long int     int32;       /* Signed 32 bit value */
    typedef  signed short       int16;       /* Signed 16 bit value */
    typedef  signed char        int8;        /* Signed 8  bit value */

    2、防止头文件被重复包含

    #ifndef __AD2S1210_H__
    #define __AD2S1210_H__
    #endif

    3、已知一个结构体成员的地址,求结构体首地址

    #define offsetof(TYPE, MEMBER) ((unsigned int) &((TYPE *)0)->MEMBER)
    //通过0地址得到结构体中成员地址的偏移量也就是相对于0这个地址的数值。
    
    #define container_of(ptr, type, member) ({          \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})
    
    //1、offsetof是偏移的数值就是距离首地址有多少字节。
    //指针类型加减数值相当于当前指针加减(指针所指类型字节*数值)。
    //为了不影响ptr的类型,所以通过缓存__mptr来处理。 
    
    
    #define offsetof1(TYPE, MEMBER) ((unsigned long) &((TYPE *)0)->MEMBER)
    #define container_of(ptr, type, member) ({          \
         const typeof( ((type *)0)->member ) *__mptr = (ptr);   \
         (type *)( (char *)__mptr - offsetof1(type,member) );})
    
     void fun(int a , int b)
     {
         printf("test");
     }
     int main(void)
     {
         struct str{
             int a;
             char b;
             int c;
             int d;
             void (*fun)(int ,int);
         }str;
         str.a = 12;
         str.b = 24;
         str.c = 24;
         str.d = 24;
         str.fun = fun;
         printf("0x%x\n" , &str);
         printf("0x%x\n" , container_of(&str.fun , struct str , fun));
         exit(0);
     }
    0xa089aa10
    0xa089aa10
    按 <RETURN> 来关闭窗口...
    //搞定
    /*
    编译出现错误error: cast from 'void (**)(int, int)' to 'unsigned int' loses precision [-fpermissive]
          printf("0x%x\n" , container_of(&str.fun , struct str , fun));                                                            
    编译的系统为linux64位,其指针类型和long型大小相等(8字节)而与int型4B所以出现了精度丢失,系统报错。将unsigned int 改成unsigned long即可
    */
    

    4、宏定义中do{ }while(0)

    如果要使用宏定义来定义多条语句时,采用do { … } while (0) 的形式是一种较好的方法。空的宏定义避免warning;存在一个独立的block中,可以用来进行变量定义作用域是块,因此可以实现比较复杂的功能;如果出现在判断语句过后的宏,这样可以保证作为一个整体来是实现。

    5、宏定义中#和##的用法

    宏定义默认遇到换行符才终止,但是可以通过\续行。

    • #的功能是将其后面的宏参数进行字符串化操作(Stringizing),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号,然后利用输出函数时候可以直接输出字符。
    #define WARNING(EXP)        \
            do{ if (EXP)        \
                    fprintf(stderr, "Warning: " #EXP "\n"); } \
            while(0)
    
    WARNING(divider == 0);//由于do{}while(0)的效果,后面可以加分号,类似函数调用。
    //等效于:
    do {
            if (divider == 0)
                fprintf(stderr, "Warning" "divider == 0" "\n");
    } while(0);
    • ##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定是宏的变量。比如你要做一个菜单项命令名和函数指针组成的结构体的数组,并且希望在函数名和菜单项命令名之间有直观的、名字上的关系。
    #define COMMAND(NAME) { NAME, NAME##_command }
    struct command
    {
    char * name;
    void (*function) (void);
    };
    struct command commands[] = {
    COMMAND(quit),
    COMMAND(help),
    };//定义结构体数组并且通过宏定义初始化,前面应该定义了对应的quit_command()函数才可以。
    #define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d
    LINK_MULTIPLE(name,company,position,salary);
    //等效于name_company_position_salary;
    //UBOOT里面运用#和##定义菜单宏。
    struct cmd_tbl_s {
        char        *name;      /* Command Name         */
        int     maxargs;    /* maximum number of arguments  */
        int     repeatable; /* autorepeat allowed?      */
                        /* Implementation function  */
        int     (*cmd)(struct cmd_tbl_s *, int, int, char *[]);//function pointer
        char        *usage;     /* Usage message    (short) */
        char        *help;      /* Help  message    (long)  */
    
    };
    //这是结构体,里面存放菜单需要的函数指针和字符指针说明用法。
    //为了达到直观的效果,利用宏定义进行初始化。
    #define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))
    //链接时候放在对应的段,后期直接到指定地方寻址,快速。
    typedef struct cmd_tbl_s    cmd_tbl_t;
    #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
    cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage} 
    //定义一个变量,并进行初始化。运用了格式字符串和连接符。
    
    U_BOOT_CMD(
        menu,   3,  0,  do_menu,
        "menu - display a menu, to select the items to do something\n",
        " - display a menu, to select the items to do something"
    );//效果很实用,达到方便快捷的效果。

    6、变参数宏:…和__VA_ARGS__

    这里写图片描述

    7、Side Effect是指宏在展开的时候对其参数可能进行多次Evaluation(也就是取值)

    #define min(X,Y) ((X) > (Y) ? (Y) : (X))
    int c = min(a,func(b));
    
    //这时进行宏替换的时候func()函数就被调用了两次。很有可能出现问题,为了解决这个潜在的问题,改写宏定义min(X,Y):
    
    #define min(X,Y) ({ \
          typeof (X) x_ = (X); \
          typeof (Y) y_ = (Y); \
          (x_ < y_) ? x_ : y_; }) //typeof将X类型返回用来定义x_暂存(X)。
    //({...})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部Scope),然后利用变量进行计算,最后一句是宏定义结果的返回。前面container_of宏定义用到了这种功能。
    展开全文
  • 常见的宏定义两种,不带参数的宏定义和带参数的宏定义。 无参宏定义 无参数宏定义的格式为: #define 标识符 替换列表 替换列表可以是数值常量、字符常量、字符串常量等,故可以把宏定义理解为使用标识符表示一...

    宏定义是比较常用的预处理指令,即使用“标识符”来表示“替换列表”中的内容。标识符称为宏名,在预处理过程中,预处理器会把源程序中所有宏名,替换成宏定义中替换列表中的内容。

    常见的宏定义有两种,不带参数的宏定义和带参数的宏定义。
    无参宏定义
    无参数宏定义的格式为:
    #define 标识符 替换列表

    替换列表可以是数值常量、字符常量、字符串常量等,故可以把宏定义理解为使用标识符表示一常量,或称符号常量。

    说明:

    1. 可以不在行首,但只允许它前面有空格符。例如:

    #define PI 3.1416 //正确,该行#前允许有空格
    int a;#define N 5 //错误,该行#前不允许有空格外的其他字符

    1. 标识符和替换列表之间不能加赋值号 =,替换列表后不能加分号
      #define N =5 //虽语法正确,但预处理器会把N替换成=5
      int a[N]; //错误,因为宏替换之后为 int a[=5];
      宏定义不是语句,是预处理指令,故结尾不加分号。如果不小心添加了分号,虽然有时该宏定义没问题,但在宏替换时,可能导致 C 语法错误,或得不到预期结果。例如:
      #define N 5; //虽语法正确,但会把N替换成5;
      int a[N]; //语法错误,宏替换后,为int a[5;];错误

    2. 由于宏定义仅是做简单的文本替换,故替换列表中如有表达式,必须把该表达式用括号括起来,否则可能会出现逻辑上的“错误”。例如:
      #define N 3+2
      int r=NN;
      宏替换后为:
      int r=3+2
      3+2; //r=11
      如果采用如下形式的宏定义:
      #define N (3+2)
      int r=NN;
      则宏替换后,为:
      int r=(3+2)
      (3+2); //r=25

    3. 当替换列表一行写不下时,可以使用反斜线\作为续行符延续到下一行。例如:
      #define USA “The United
      States of
      America”
      该宏定义中替换列表为字符串常量,如果该串较长,或为了使替换列表的结构更清晰,可使用续行符 \ 把该串分若干行来写,除最后一行外,每行行尾都必须加续行符 \。

    如果调用 printf 函数,以串的形式输出该符号常量,即:
    printf("%s\n",USA);
    则输出结果为:The United States of America

    注意:续行符后直接按回车键换行,不能含有包括空格在内的任何字符,否则是错误的宏定义形式。
    带参宏定义
    带参数的宏定义格式为:
    #define 标识符(参数1,参数2,…,参数n) 替换列表

    例如,求两个参数中最大值的带参宏定义为:
    #define MAX(a,b) ((a)>(b)?(a) : (b))
    当有如下语句时:
    int c=MAX(5,3);
    预处理器会将带参数的宏替换成如下形式:
    int c=((5)>(3)?(5) : (3));
    故计算结果c=5。

    删除宏定义的格式为:
    #undef 标识符

    说明:

    1. 标识符与参数表的左括号之间不能有空格,否则预处理器会把该宏理解为普通的无参宏定义,故以下是错误的带参宏定义形式。
      #define MAX (a,b) ( (a) > (b) ? (a) : (b) ) //错误的带参宏定义格式
    2. 宏替换列表中每个参数及整个替换列表,都必须用一对小括号 () 括起来,否则可能会出现歧义。

    【例 1】以下程序试图定义求两个参数乘积的宏定义,欲使用该宏求 3 与 6 的乘积,分析该程序能否实现预期功能,如果不能,请给出修改方案。
    #include <stdio.h>
    #define MUL(a,b) (a*b)
    int main (void)
    {
    int c;
    c=MUL(3,5+1);
    printf(“c=%d\n”,c);
    return 0;
    }
    分析:

    1. 由于该宏定义中的替换列表中的参数没有加括号,故宏调用时,如果参数是个表达式,可能会出现歧义,得不到预期结果。

    本例中宏调用 c=MUL(3,5+1); 会替换成 c=(3*5+1)=16;,与预期功能不符。

    1. 虽然把宏调用时的参数 5+1 括起来,可达到题目要求的效果,但这属于治标不治本。为统一编程规范,把替换列表中的每个参数均加括号,整个替换列表也加括号。

    同时,为达到标本兼治,在宏定义时,除单一值参数外,应显式加括号。

    修改代码为:
    #include <stdio.h>
    #define MUL(a,b) ((a)*(b))//修改处1
    int main (void)
    {
    int c;
    c=MUL(3,(5+1);//修改处2
    printf(“c=%d\n”,c);
    return 0;
    }
    带参宏定义 VS 函教调用
    接下来将从调用发生时间、参数类型检查、参数是否需要空间、运行速度等几个主要方面进行对比分析带参宏定义与函数调用的差异。

    调用发生的时间
    在源程序进行编译之前,即预处理阶段进行宏替换;而函数调用则发生在程序运行期间。

    参数类型检查
    函数参数类型检查严格。程序在编译阶段,需要检查实参与形参个数是否相等及类型是否匹配或兼容,若参数个数不相同或类型不兼容,则会编译不通过。

    在预处理阶段,对带参宏调用中的参数不做检查。即宏定义时不需要指定参数类型,既可以认为这是宏的优点,即适用于多种数据类型,又可以认为这是宏的一个缺点,即类型不安全。故在宏调用时,需要程序设计者自行确保宏调用参数的类型正确。

    参数是否需要空间
    函数调用时,需要为形参分配空间,并把实参的值复制一份赋给形参分配的空间中。而宏替换,仅是简单的文本替换,且替换完就把宏名对应标识符删除掉,即不需要分配空间。

    执行速度
    函数在编译阶段需要检查参数个数是否相同、类型等是否匹配等多个语法,而宏替换仅 是简单文本替换,不做任何语法或逻辑检查。

    函数在运行阶段参数需入栈和出栈操作,速度相对较慢。

    代码长度
    由于宏替换是文本替换,即如果需替换的文本较长,则替换后会影响代码长度;而函数不会影响代码长度。

    故使用较频繁且代码量较小的功能,一般采用宏定义的形式,比采用函数形式更合适。前面章节频繁使用的 getchar(),准确地说,是宏而非函数。

    为了使该宏调用像函数调用,故把该宏设计成了带参数的宏定义:

    #define getchar() getc(stdin)
    故调用该宏时,需要加括号,即传空参数:getchar()。

    展开全文
  • 宏定义详解

    2019-09-26 15:50:05
    宏定义有无参数宏定义和带参数宏定义两种。 无参数的宏定义的一般形式为 # define 标识符 字符序列其中# define之后的标识符称为宏定义名(简称宏名),要求宏名与字符序列之间用空格符分隔。这种宏定义要求编译预处理...
  • 宏定义的几用法

    2020-08-19 11:15:31
    2、第一用法,宏定义固定值:#define PI=3.14 //定义PI固定为3.14程序中不可修改; 2、第二用法,宏定义一个带参数值:#define DATA(n) if(n) printf("真") else printf("假") //定义数据传入带参数,通过传参...
  • C语言宏定义

    2019-07-17 15:55:04
    #define 是C语言中的宏定义命令,在一定程度上提高程序的运行...该命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义。 1) 简单的宏定义:用指定的的标识符(宏名)代表一串字符。 #define  <宏名&...
  • C语言的头文件和宏定义详解

    万次阅读 多人点赞 2018-01-24 21:00:44
    之前对C语言的头文件和宏定义抱着一知半解的态度,现理清思路并以文字的形式整理出来,以供不时之需 头文件 头文件,顾名思义就是定义在C语言文件头部的那一坨东西 #include 这就是一个标准输入输出的...
  • 常见的宏定义两种,不带参数的宏定义和带参数的宏定义。 无参宏定义 无参数宏定义的格式为: #define 标识符 替换列表 替换列表可以是数值常量、字符常量、字符串常量等,故可以把宏定义理解为使用标识符表示一常...
  • 今天小编就为大家分享一篇C语言#define拼接宏定义实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
  • C语言宏定义宏定义函数

    千次阅读 2016-03-13 19:55:59
    要写好C语言,漂亮的宏定义是非常重要的。宏定义可以帮助我们防止出错,提高代码的可移植性和可读性等。... 我们来看一个例子,比较个数或者表达式大小,首先我们把它写成宏定义:  #define MAX
  • C++宏定义详解

    2013-12-26 19:20:19
    宏定义有无参数宏定义和带参数宏定义两种。 无参数的宏定义的一般形式为  # define 标识符字符序列 其中# define之后的标识符称为宏定义名(简称宏名),要求宏名与字符序列之间用空格符分隔。这种宏定义要求编译...
  • 尽管函数式宏定义和普通函数相比有很多缺点,但只要小心使用还是会显著提高代码的执行效率,毕竟省去了分配和释放栈帧、传参、传返回值等一系列工作,因此那些简短并且被频繁调用的函数经常用函数式宏定义来代替实现...
  • 宏定义

    2009-05-05 13:16:00
    宏定义 在C语言源程序中允许用一个标识符来表示一个字符串, 称为“宏”。被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换, 这称为“宏代换”或...
  • C语言的宏定义

    2016-06-15 11:24:18
    c语言宏定义问题 宏定义不是可以实现和函数相同功能 那为什么还要分函数和宏定义两种功能?  分享| 2015-11-26 11:30妖紳 | 浏览 93 次 来自:手机知道  C语言函数 c语言宏定义问题 宏定义不是可以...
  • 使用#define指令进行宏定义 宏定义的... #define 指令的形式是: #define 宏名 宏定义内容  凡是在程序中遇到“宏名”预编译处理程序就用这个“宏定义内容”原封不动的替换宏名,进行编译。C语言中宏定义可分为无
  • 今天难得静下心来,先总结下有关“交换个变量的宏定义实现”的三种方式,相信没思考过的朋友们第一次遇到这种问题一定是需要一点时间去思考的,哪怕有个结果但也不一定是你今天看到了这三,其实这都是日常的积累...
  • dll导出导入宏定义使用总结,主要讲了两种宏定义的使用方法

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 172,098
精华内容 68,839
关键字:

宏定义的两种形式