【问题标题】:What is the purpose of the #define directive in C++?C++ 中#define 指令的目的是什么?
【发布时间】:2011-02-17 21:06:18
【问题描述】:

#define 指令的作用是什么?

【问题讨论】:

标签: c++ c-preprocessor


【解决方案1】:

#define 用于在 C 和 C++ 中创建宏。您可以在C preprocessor documentation 中阅读更多相关信息。快速的回答是它做了几件事:

  1. 简单的宏 - 基本上只是文本替换。编译时间常数就是一个很好的例子:

    #define SOME_CONSTANT 12
    

    只需将文本 SOME_CONSTANT 替换为 12 在代码中出现的任何位置。这种宏通常用于提供代码块的条件编译。例如,项目中的每个源文件都可能包含一个标头,其中包含项目的选项列表:

    #define OPTION_1
    #define OPTION_2
    #undef  OPTION_3
    

    然后项目中的代码块将使用匹配的 #ifdef/#endif# 块包装,以在完成的项目中启用和禁用这些选项。使用-D gcc 标志将提供类似的行为。然而,对于这种方法是否真的是为应用程序提供配置的好方法,存在着强烈的意见。

  2. 带参数的宏 - 允许您制作可以接受参数并操作它们的“类似函数”的宏。例如:

    #define SQUARE(x)  ((x) * (x))
    

    将返回参数的平方作为结果;小心潜在的操作顺序或副作用问题!下面的例子:

    int x = SQUARE(3);     // becomes int x = ((3) * (3));
    

    可以正常工作,但类似:

    int y = SQUARE(f());   // becomes int y = ((f()) * (f()));
    

    会打电话给f() 两次,甚至更糟:

    int z = SQUARE(x++);   // becomes int z = ((x++) * (x++));
    

    导致未定义的行为!

    使用一些工具,带参数的宏也可以是variadic,可以派上用场。

正如下面在 cmets 中提到的,过度使用宏或开发过于复杂或令人困惑的宏被许多人认为是不好的风格 - 一如既往,将代码的可读性、可维护性和可调试性置于“聪明”的技术技巧之上.

【讨论】:

  • +1,但应该添加一个示例,说明 SQUARE() 在某些情况下将如何扩展
  • 我会强调使用它们进行配置(#ifdef...#endif 中的代码部分)。在 C++ 中,1) 使用 const 可能更好,2) 使用模板。
  • @pkh:“大多数现代工具”不支持 C99。大多数编译器都支持 C99 的零碎部分,但该版本的语言从未真正流行起来,因此简单地说“它是 C99”并不意味着“它得到了很好的支持”
  • @pkh:可变参数宏不在标准 C++ 中,尽管它们在当前委员会的下一版本标准草案中(据我了解,可能会在明年发布)。更不用说 jalf 是对的。
  • 在我看来,这些是宏的常见误用(对于 C++)。这不是 C++ 中宏系统的设计目的(诚然,它实际上是为 C 设计的,以上是 C 的常见用途)。上述每个示例在 C++ 中都有一个更安全的替代方案。宏的真正用途是针对不同类型的架构进行条件编译。
【解决方案2】:

#define(相反,#undef)可用于设置编译器指令,然后可以使用#ifndef 或#ifdef 对其进行测试。这允许在源文件中定义自定义行为。它通常用于针对不同的环境进行编译或调试代码。

一个例子:

#define DEBUG



#ifdef DEBUG

//perform debug code

#endif

【讨论】:

    【解决方案3】:

    #define 最常见的用法(far)用于包含守卫:

    // header.hh
    #ifndef HEADER_HH_
    #define HEADER_HH_
    
    namespace pony {
    // ...
    }
    
    #endif
    

    #define 的另一个常见用途是创建一个配置文件,通常是一个 config.h 文件,其中我们根据各种状态和条件创建了 #define 宏。然后,在我们的代码中,我们使用#ifdef#elif defined() 等测试这些宏,以支持针对不同情况的不同编译。这不像 include-guard 习惯用法那样可靠,你需要在这里小心,因为如果分支错误,那么你可能会得到非常模糊的编译器错误,或者更糟糕的是运行时行为。

    一般来说,除了包含保护之外,您还需要仔细考虑(最好是两次)这个问题,看看是否可以使用编译器而不是预处理器来解决它。编译器比预处理器更聪明。不仅如此,编译器不可能迷惑预处理器,而预处理器绝对可以迷惑和误导编译器。

    【讨论】:

      【解决方案4】:

      #define 指令有两个常见用途。

      第一个是控制编译器的行为。为此,我们还需要#undef、#ifdef 和#ifndef。 (还有#endif ......)

      您可以通过这种方式制作“编译器逻辑”。一个常见的用途是激活或不激活代码的调试部分,如下所示:

      #ifdef DEBUG
      
      //debug code here
      
      #endif
      

      例如,您可以通过编写#define DEBUG 来编译调试代码

      这种逻辑东西的另一个用途是避免双重包含......

      例如,文件 A,#includes 文件 B 和 C。但文件 B 也包含 C。这可能会导致编译错误,因为“C”存在两次。

      解决办法是这样写的:

      #ifndef C_FILE_INCLUDED
      #define C_FILE_INCLUDED
      
      //the contents of header "c" go here.
      
      #endif
      

      #define 的另一个用途是生成宏。

      最简单的,由简单的替换组成,例如:

      #define PI 3.14159265
      
      float perimeter(float radius) {
          return radius*2*PI;
      }
      

      #define SHOW_ERROR_MESSAGE printf("An serious error happened");
      
      if ( 1 != 1 ) { SHOW_ERROR_MESSAGE }
      

      然后你也可以制作接受参数的宏,printf本身通常是一个宏,在头文件中用#define创建。

      但不应该这样做,原因有两个: 首先,speed os 宏,与使用 inline 相同,其次,我们有 c++ 模板,允许对变量类型的函数进行更多控制。所以,使用带参数的宏的唯一原因是制作奇怪的结构,以后会很难理解,比如元编程的东西......

      【讨论】:

      • 哇,我花了很长时间才输入我的帖子,现在看来我只是复制了之前两个人的帖子并将它们捣碎成一个:(
      【解决方案5】:

      在 C++ 中,#define 具有非常狭窄的特殊角色:

      • 标题保护,在其他答案中描述
      • 与标准库交互。例如,在包含 windows.h 之前#defining WINDOWS_LEAN_AND_MEAN 会关闭某些经常出现问题的宏,例如 MAX。
      • 涉及字符串化(即打印调试消息的宏)或标记粘贴的高级宏。

      您应该避免出于以下目的使用#define。原因很多;查看实例this FAQ entry

      • 编译时常量。请改用const
      • 简单的宏函数。请改用inline 函数和模板。

      【讨论】:

        【解决方案6】:

        CC++ #define 允许您创建预处理器宏。

        在正常的 CC++ 构建过程中,首先发生的是预处理器运行,预处理器在源文件中查找预处理器指令,如 #define#include,然后用它们执行简单的操作。

        #define 指令的情况下,预处理器会进行简单的基于文本的替换。

        例如,如果您有代码

        #define PI 3.14159f
        
        float circum = diameter*PI;
        

        预处理器会将其变成:

        float circum = diameter* 3.14159;
        

        只需将 PI 的实例替换为相应的文本即可。这只是 #define 语句的最简单形式,对于更高级的用途,请查看来自 MSDN 的 article

        【讨论】:

          【解决方案7】:

          inCorrectUseOfHashDefine()

          {

          #define 的作用是让那些继承你的代码的人迷惑不解,比如:

          foreverandever
          

          因为:

          #define foreverandever for(;;)
          

          }

          请使用常量而不是#define。

          它也用于设置编译器指令...

          【讨论】:

          • 因为这不是#define的角色,而是误用
          • @Rene OK - 我的意图更明确了
          • @Rene:你是认真地告诉我你错过了讽刺?
          • @Troubadour:我没有告诉任何人我错过了讽刺,但我认为这个答案对任何想了解#defines 的人没有帮助。在解释了#defines之后,可以指出如果不仔细使用它会带来的各种问题。
          【解决方案8】:

          关于#defines 的大部分内容已经被告知,但尚不清楚 C++ 是否有更好的替代品来满足它们的大部分用途:

          1. #define 定义数字常量可以很容易地替换为 const “变量”,作为 #define,它实际上并不存在于编译的可执行文件中。 AFAIK 它可以用于几乎所有可以使用#defined 数值常量的情况,包括数组边界。对我来说,主要优点是这些常量是明确类型的,所以不需要在宏中添加强制转换“只是为了确定”,并且是有范围的,所以它们可以保存在命名空间/类/函数中,而不会污染所有申请。

           

          const int max_array_size=50;
          int an_array[max_array_size];
          
          1. #define 创建宏:宏通常可以被模板替换;例如,可怕的 MAX 宏

           

          #define MAX(a,b)    ((a)<(b)?(b):(a))
          

          ,它有几个缺点(例如重复的参数评估,不可避免的内联扩展),可以用 max 函数代替

          template<typename T> T & max(T & a, T & b)
          {
              return a<b?b:a;
          }
          

          它可以是类型安全的(在这个版本中,两个参数被强制为同一类型),可以内联扩展也不能(这是编译器决定),只评估一次参数(当它被调用时) , 并且是作用域的。更详细的解释可以找到here

          不过,宏仍然必须用于包含守卫,以创建某种奇怪的语言扩展,这些扩展会扩展到更多代码行、括号不平衡等。

          【讨论】:

            猜你喜欢
            • 2012-07-25
            • 1970-01-01
            • 2012-09-25
            • 2015-06-17
            • 1970-01-01
            • 1970-01-01
            • 2021-12-30
            • 2011-11-12
            • 1970-01-01
            相关资源
            最近更新 更多