C语言中Macro的终极用法

本文介绍了C语言中宏的多种实用技巧,包括头文件包含守护、文件内外的常量定义、简写、代码和静态数据分离以及数据填充模板等。通过这些技巧,可以提高代码的可读性和可维护性。
  转载请注明出处,谢谢。 www.donevii.comwww.cpplite.com 

   

    那么,接下来我就先来介绍一下常用的宏技巧吧(听起来有点老套,不过请耐心看下去,会有收获的)。

    1. 头文件包含守护
        出现在某.h或.hpp .hxx中,
        #ifndef __HTML_PAGE_BUILDER_H__
        #define __HTML_PAGE_BUILDER_H__
                  // 一大堆声明出现在这里
        #endif  // #ifndef __HTML_PAGE_BUILDER_H__ (加这样的注释是一种好习惯呕~)

      

    2. 文件域内/外的常量定义
        为什么说“文件域内/外”呢?因为宏(Macro)根据它定义所在单元(文件)不同,将拥有不同的有效域,或叫不同的外部视野。
        a) 定义在.c/.cpp/.cxx源文件内
            在.c类似的源文件编译单元内,宏只能被该单元可见。也就是说在该单元被第一次预编译时,会先找出单元所属所有的宏,然后在该单元可见,在该单元编译结束时,所有这些宏又全部消失掉。
            从而,我们可以做个实验,以VC的COMPILER为例:
            // my_a.c
            #define __AVAIL_ONLY_IN_MY_A_C__
            // my_b.c
            #if defined(__AVAIL_ONLY_IN_MY_A_C__)
            #error Error will never occurred
            #endif // #if defined(__AVAIL_ONLY_IN_MY_A_C__)

            不管以怎样的编译顺序,在Compile my_b.c时都不会发生错误。

            那这时候您想问,“请问宏的名字能不能在单元内被复用?”,我可以回答说,“能,但有限制”,如下面的用法:
           // my_a.c
           #define SOME_FACTOR   (1.0)
               // …
               {
                    // …
                    some_var = calc_with( SOME_FACTOR );
               }
           #undef SOME_FACTOR
           #define SOME_FACTOR   (2.0)
               // …
               {
                    // …
                    some_var = calc_with( SOME_FACTOR );
               }
           #undef SOME_FACTOR

           明白了吧,当您的代码结足够好,逻辑足够清楚时,也可以尝试一下这种用法。

           宏定义是没有位置限制的,也可以尝试一下下面的BT用法:
           void some_func( void )
           {
               #if defined(SOME_FACTOR)
               #error OH MY GOD, it make the codes so confused!
               #endif // #if defined(SOME_FACTOR)
               #define SOME_FACTOR  (1.0)
               // …
               calc_with( SOME_FACTOR );
               // …
               some_var = calc2_with( SOME_FACTOR );
               some_var = calc3_with( SOME_FACTOR );
               // …
               #undef SOME_FACTOR
           }

           这样做的优点就是,在IDE非常弱智的话可以在任意代码处看到宏的值是什么,修改起来比较方便。
           如果这些宏通常只要你在自己的模块中调试时所用,并且不想改动任何其他人的头文件时,也是不错的办法。

        b) 定义在.h/.hxx/.hpp头文件内
           这个就不多说了,被全局引用的宏定义。通常是一些Feature开关的设定。比如:
           // xxx_global_features.h
           #define __PC_SIDE_DEBUG__
           #if defined(__ON_HARDWARE__)
           #define __PC_SIDE_DEBUG__
           #endif // #if defined(__ON_HARDWARE__)

    3. 简写
        所以要简写……
        // xxx.h
        #define XXX_CONTRACT_FLAG_TAG_MUST_BE_PRESENT   (0×0000 << 0)
        #define XXX_CONTRACT_FLAG_TAG_MUST_BE_PAIRED   (0×0000 << 1)
        // xxx.c
        #include “xxx.h”
        #define __CF_PRST  XXX_CONTRACT_FLAG_TAG_MUST_BE_PRESENT
        #define __CF_PAIR  XXX_CONTRACT_FLAG_TAG_MUST_BE_PAIRED
        {
             // …
             xxx_flag = __CF_PRST|__CF_PAIR;
        }

        如果这招从来没用过……别跟我讲你是用C语言写程序的……。

    3. 代码和静态数据分离
        先声明,我以确在别人的源代码中见过这种用法,不过在我看到之前我本人就已经在用了…… 不能说是我发明的,但的确是英雄所见。
        举个例子,比如我们有一个算法库,里面有定义很多很多很多数组,在一个特殊的OS A(Nucleus)上出于加速及优化方法考虑,一定要定义为静态数组。但同时,由于我要移植到另一个特别OS B上(如Symbian),不允许这样使用。因此,我们要把所有代码里所有的静态数组都找出来,再在栈上定义它们。
       这样的话,我们将有两份或多份代码来同时维护,如:
       // my_algo.c for OS A
       static uint32_t __some_global_datas[] = {
            0, 1, 2, 3, 4, 5 ….
            0, 1, 2, 3, 4, 5 ….
            ….
       };
       // my_algo.c for OS B
       {
           uint32_t __some_global_datas[] = {
                0, 1, 2, 3, 4, 5 ….
                0, 1, 2, 3, 4, 5 ….
                ….
           };
       }

        有一个办法可以只通过一个宏,将两份代码合并到一起,而且不会使代码冗长,最可能不是最好的办法。
        1) 先定义一个OS Feature的头文件:
        // which_os.h
        //#define __OS_A__
        #define __OS_B__

        2) 再定义一个专门用来存数组数据的头文件:
        // array_datas.h
        #if defined(__MY_ALGO_C__)
                0, 1, 2, 3, 4, 5 ….
                0, 1, 2, 3, 4, 5 ….
                ….
        #endif // #if defined(__MY_ALGO_C__)

        是的你没有看错,的确在头文件里是这样写的,而且没有任何的包括守护。

        3) 再来看看新的my_algo.c:
        #define __MY_ALGO_C__
        #include “which_os.h”
        #if defined(__OS_A__)
        static uint32_t __some_global_datas[] = {
              #include “array_datas.h”
       };
       #endif // #if defined(__OS_A__)
       // ….
        {
              #if defined(__OS_B__)
              uint32_t __some_global_datas[] = {
                   #include “array_datas.h”
              };
              #endif // #if defined(__OS_B__)
       }

       想想有多份代码的话,有一天老板告诉你说,“我们要真对那些数据的内容做一次修改,然后要对每份代码进行同步。”,如果你真的不知道这种Hack技巧,OH MY GOD,我想我真的要疯掉了。
       如果你跟我一样,在代码中的确需要这样的功能,有这样的特殊需求,那么可以试一下。
       适应不同的OS的同时,代码整洁不了,不是吗?

    4. 数据填充模板
       看过之上几招之后,是不是感觉很用法怪异,作者很BT?那我再来告诉你招巨BT,但不太实用的Hack技巧吧。
       打个比方,我有n个固定的UI Widget的样式数据,像下面这样写在头文件如:
       // x,   y,   z,  w,     h, ext
            0,   0,  0,  200, 20,  0×11, // Icon bar
            0,   200, 0, 200, 20, 0×22, // Soft bar
            ….

       而我要用到这些数据的结构体一共有2个,被定义如下:
       struct __struct_UI_widget_coord {
              uint32_t x;
              uint32_t y;
       };
       struct __struct_UI_widget_ext {
              uint8_t ext;
       };
       static struct __struct_UI_widget_coord  __global_UI_coords[TOTAL_UI_WIDGETS];
       static struct __struct_UI_UI_widget_ext __global_UI_exts[TOTAL_UI_WIDGETS];

       怎样将我的UI_widget_detail.h中的数据分别填进两个数据中?看看下面的方法:
       1) 修改UI_widget_detail.h为如下的格式:
       // UI_widget_detail.h
       // x,   y,   z,  w,     h, ext
            __FILL_DATA( 0,   0,  0,  200, 20,  0×11 ),// Icon bar
            __FILL_DATA( 0,   200, 0, 200, 20, 0×22 ),// Soft bar
            ….
       
       2) 填充数组数据
       #undef __FILL_DATA
       #define __FILL_DATA( x, y, z, w, h, ext )   { x, y }
       static struct __struct_UI_widget_coord  __global_UI_coords[TOTAL_UI_WIDGETS] = {
            #include “UI_widget_detail.h”
       };
       #undef __FILL_DATA
       #define __FILL_DATA( x, y, z, w, h, ext )   { ext }
       static struct __struct_UI_UI_widget_ext __global_UI_exts[TOTAL_UI_WIDGETS] = {
            #include “UI_widget_detail.h”
       };

    5. 代码复用模板
       我想你一定认为我疯了,不过这些大多不为人知或不常用的技巧的的确确,我一直在用。
       不敢说如火纯青,大概也让我少敲击了几下键盘。来看看代码复用模板是怎么一回事?
       还是以举个例子:
             比如我有一个LIST数据结构,让我感觉烦的是,每次做迭代操作都要写一大堆代码,而且都是一样的。
             像下面这个样子:
              {
                    lnode_ptr_t curr;
                    lnode_ptr_t next;
                    // …
                    curr = some_first_node;
                    while ( NULL != curr ) {
                        next = curr->_next;
                        operate1( curr );
                        operate2( curr );
                        curr = next;
                   }
                   // …
             }

             如果这样的代码在几百个.c中到处被用到,而且只有中间操作过程不一样,会不会感觉很烦呀?
            
             那我来给你个提示,怎么复用这些框架代码:
             #define FOREACH_NODE_FORWARD( __first, __curr, __op ) /
                          do { /
                                lnode_ptr_t curr__ = (__first);  /
                                while ( NULL != curr__ ) /
                                { /
                                      (__curr) = curr__; /
                                      curr__ = curr__->_next; /
                                      __op /
                                } /
                          } while ( 0 );

              使用方式如下:
              {
                    lnode_ptr_t curr_ptr = NULL;
                   // …
                   FOREACH_NODE_FORWARD
                   (
                         some_first_node,
                         curr_ptr,
                         { 
                             operate1( curr );
                             operate2( curr );
                         }
                   );
                   // …
              }

              看懂了吗?我们真的用宏把这些框架代码给复用了,而且使代码很整洁美观。我们并没有增加一个变量就能使结构如此的完美。

       好了,就先说这些多,还有其他更多的技巧请关注我和Dengwei的BLOG。晚安。

<think>嗯,用户想知道C语言中宏定义(macro)和define关键字的区别以及它们的应用场景。首先,我需要确认用户的问题是否存在理解上的偏差。因为在C语言中,#define是预处理指令,用来定义宏的,所以严格来说,宏(macro)和#define并不是两个独立的概念,而是宏由#define来定义。可能用户误以为它们是不同的东西,或者想了解宏与其他类似机制的区别,比如typedef或inline函数。 接下来,我需要参考提供的引用内容。引用1提到#define机制允许参数替换到文本中,称为宏。引用2和3则详细说明了宏的一些规则,比如不能递归,以及预处理器的处理方式。这说明用户可能对宏的基本概念和#define的使用有疑问,需要明确两者之间的关系。 首先,要澄清术语。在C语言中,宏是通过#define指令定义的,所以#define是定义宏的关键字,而宏本身是一种替换机制。用户可能混淆了术语,认为macro和define是不同的东西。因此,回答中需要明确说明这一点,即#define用于创建宏,而宏本身是预处理器进行的文本替换。 接下来,区分宏的不同类型。比如,不带参数的宏和带参数的宏。例如,#define PI 3.14是不带参数的,而#define MAX(a,b) ((a)>(b)?(a):(b))是带参数的宏。这里需要说明不同的用法场景,以及使用它们时的注意事项,比如括号的使用来避免优先级错误。 然后,需要考虑用户可能关心的宏的优缺点。比如,宏在预处理阶段展开,不进行类型检查,可能导致错误,但执行效率高。而inline函数虽然类似,但属于编译器处理,有类型检查。这可能也是用户想了解的区别点。 另外,用户可能想知道宏的常见应用场景,比如定义常量、条件编译、代码简化等。同时,需要提到宏的潜在问题,比如调试困难、副作用(如多次求值导致的错误),以及如何避免这些问题。 还需要根据引用内容中的信息,比如宏不能递归,字符串常量中的宏不会被替换,预处理器的处理方式等,来补充回答,确保准确引用资料。例如,引用2提到宏参数中可以包含其他#define定义的符号,但不能递归,字符串常量中的内容不会被搜索替换,这需要解释清楚。 最后,生成相关问题时要围绕宏的使用、优缺点、替代方案、调试技巧等,确保问题能够帮助用户进一步理解主题。</think>### C语言中宏定义(macro)与`#define`关键字的区别及用法场景 在C语言中,**`#define`是预处理指令**,用于定义宏(macro)。因此,**宏(macro)是`#define`的产物**,而非独立的关键字。两者的核心关系可总结为: - **`#define`**:定义宏的语法工具。 - **宏(macro)**:通过`#define`生成的代码替换规则。 --- #### 一、宏的分类与`#define`的作用 1. **无参宏(对象式宏)** 用于定义常量或代码片段,例如: ```c #define PI 3.1415926 // 常量替换 #define LOG "Debug Mode" // 字符串替换 ``` **特点**:直接替换文本,不涉及参数。 2. **带参宏(函数式宏)** 类似函数的替换规则,例如: ```c #define MAX(a, b) ((a) > (b) ? (a) : (b)) // 参数替换为表达式 ``` **特点**:参数会被直接替换到代码中,需注意运算符优先级(需加括号)[^2]。 --- #### 二、宏与`#define`的核心区别 | **特性** | **`#define`** | **宏(macro)** | |----------------|---------------|-----------------------| | **本质** | 预处理指令 | 代码替换规则 | | **作用阶段** | 预处理阶段 | 预处理阶段生效 | | **参数支持** | 支持参数 | 通过`#define`定义参数 | | **调试可见性** | 不可见 | 展开后代码不可读 | --- #### 三、宏的典型应用场景 1. **常量定义** 替代魔法数字,提高可读性: ```c #define BUFFER_SIZE 1024 ``` 2. **条件编译** 配合`#ifdef`、`#ifndef`实现平台适配或调试模式: ```c #define DEBUG_MODE // 开启调试日志 ``` 3. **代码简化** 通过宏封装重复代码片段: ```c #define SWAP(a, b) do { int temp = a; a = b; b = temp; } while(0) ``` 4. **类型无关操作** 实现泛型函数(如取最大值): ```c #define MAX(a, b) ((a) > (b) ? (a) : (b)) ``` --- #### 四、宏的局限性及注意事项 1. **无类型检查** 宏替换不验证参数类型,可能导致隐式错误,例如: ```c MAX("abc", 10); // 编译通过但逻辑错误 ``` 2. **副作用问题** 参数可能被多次求值,例如: ```c #define SQUARE(x) ((x) * (x)) SQUARE(i++); // 实际展开为 (i++) * (i++) ``` 3. **调试困难** 宏展开后的代码在编译错误中难以定位[^3]。 --- #### 五、替代方案与最佳实践 1. **优先使用`const`或`enum`定义常量** 提高类型安全性: ```c const int buffer_size = 1024; enum { MAX_LEN = 100 }; ``` 2. **复杂逻辑用`inline`函数替代** 避免副作用,增强可读性: ```c static inline int max(int a, int b) { return a > b ? a : b; } ``` 3. **使用`#pragma once`替代`#ifndef`头文件保护** 简化代码并避免宏命名冲突。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值