C++中#define指令的目的是什么?

6
#define指令的作用是什么?

1
请查看以下文章中的示例代码,以了解如何使用宏:http://stackoverflow.com/questions/2777774/a-out-termniated-garbage-output-due-to-smashing-of-stack-how-to-remove-this - Nathan Ernst
1
@Nathan:让我们去真正的SO源头找找糟糕的宏定义:https://dev59.com/CnRB5IYBdhLWcg3wWGAH。 - David Thornley
Touche,@David,我只是去了我记忆中最近的一个。你的链接确实包含了许多可怕的东西。 - Nathan Ernst
8个回答

15

#define 用于在 C 和 C++ 中创建宏。您可以在C预处理器文档中了解更多信息。快速回答是,它可以做以下几件事情:

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

  2. #define SOME_CONSTANT 12
    

    这个宏会在你的代码中所有出现 SOME_CONSTANT 的地方都替换成 12。这种宏通常用于提供代码块的条件编译。例如,一个项目中每个源文件都包含一个选项列表的头文件:

    #define OPTION_1
    #define OPTION_2
    #undef  OPTION_3
    

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

  3. 带参数的宏 - 允许您创建可以接收参数并操纵它们的“函数式”宏。例如:

  4. #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++));
    

    结果会导致未定义的行为!

    使用某些工具,带有参数的宏也可以是可变参数的,这可能很方便。

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


1
+1,但应该添加一些SQUARE()在几种情况下将如何展开的示例。 - greyfade
5
我建议强调在配置中使用它们(代码段中的 #ifdef... #endif)。在 C++ 中,最好使用常量来实现 1),并使用模板来实现 2)。 - AshleysBrain
1
@pkh:“大多数现代工具”并不支持C99。大多数编译器支持C99的某些部分,但该语言版本从未真正流行起来,因此仅仅说“这是C99”并不意味着“它得到了良好的支持”。 - jalf
1
@pkh:可变参数宏不是标准C++的一部分,尽管它们在下一个版本的标准(据我了解,可能明年发布)的当前委员会草案中有所提及。更不用说jalf是正确的了。 - David Thornley
1
这些是我认为的宏的常见误用(C++)。在C++中,宏系统不是为此设计的(尽管实际上它是为C设计的,上述是C的常见好处)。以上每个示例在C++中都有更安全的替代方案。宏的真正用途是针对不同体系结构进行条件编译。 - Martin York
显示剩余5条评论

7

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

一个示例:

#define DEBUG



#ifdef DEBUG

//perform debug code

#endif

2

#define最常用(远超其他用途)的是用于包含保护:

// header.hh
#ifndef HEADER_HH_
#define HEADER_HH_

namespace pony {
// ...
}

#endif

另一个常见的使用#define的方法是创建配置文件,通常是config.h文件,在其中根据各种状态和条件#define宏。然后,在我们的代码中,我们使用#ifdef#elif defined()等测试这些宏,以支持不同情况下的不同编译。这不如include-guard惯用语法坚固,您需要小心,因为如果分支错误,则可能会出现非常晦涩的编译器错误,或者更糟糕的是运行时行为。
一般来说,除了包含保护之外,您需要仔细思考(最好思考两次),看看是否可以使用编译器而不是预处理器来解决问题。编译器比预处理器聪明得多。不仅如此,编译器不可能混淆预处理器,而预处理器肯定会混淆并误导编译器。

1

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

第一个用途是控制编译器的行为。为了做到这一点,我们还需要使用#undef、#ifdef和#ifndef。(以及#endif...)

你可以通过这种方式来制定“编译器逻辑”。一个常见的用法是激活或不激活代码中的调试部分,就像这样:

#ifdef DEBUG

//debug code here

#endif

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

这个逻辑的另一个用途是避免重复包含...

例如,文件 A 包含文件 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创建。

但是这样做有两个原因:首先,宏的速度与使用内联相同,其次,我们有c++模板,允许更多地控制带有变量类型的函数。因此,使用带有参数的宏的唯一原因是创建奇怪的结构,这些结构以后会很难理解,例如元编程的东西...


哇,我打这篇文章用了好久,现在看起来好像只是把之前两个人发的帖子复制粘贴到一起 :( - speeder

1

C++中,#define有非常狭窄、专门的作用:

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

以下情况应避免使用#define。原因很多,请参见this FAQ entry

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

0

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

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

对于#define指令,预处理器执行基于文本的简单替换。

例如,如果您有以下代码:

#define PI 3.14159f

float circum = diameter*PI;

预处理器将把它转换为:

float circum = diameter* 3.14159;

通过简单地将PI的实例替换为相应的文本即可完成。这只是一个#define语句的最简形式,更高级的用法请查看MSDN的文章


0

inCorrectUseOfHashDefine()

{

#define 的作用是让继承你代码的人感到困惑,因为会出现一些突然的语句。

foreverandever

由于:

#define foreverandever for(;;)

}

请优先使用常量而不是 #define。
这也适用于设置编译器指令...

因为这不是 #define 的作用,而是其误用。 - René Nyffenegger
@Rene OK - 我已经更明确地表达了我的意图。 - Robben_Ford_Fan_boy
@Rene:你是认真告诉我你没看出那是讽刺吗? - Troubadour
@Troubadour:我不会告诉任何人我错过了讽刺的意味,但我认为这个答案并没有帮助任何想要理解#define的人。在解释#define之后,如果没有经过深思熟虑地使用,指出各种问题是可以的。 - René Nyffenegger

0

#defines 的大部分内容已经被讲述过了,但是并不清楚的是 C++ 有更好的替代品来满足它们的大多数用途:

  1. 用 #define 定义数字常量可以很容易地被 const "变量" 替换,这个变量和 #define 一样,在编译后的可执行文件中并不存在。据我所知,它可以在几乎所有你可以使用 #define 数字常量的情况下使用,包括数组边界。对我来说,主要的优势在于这些常量是明确类型的,因此没有必要在宏中添加转换,只是为了保险起见,并且是有作用域的,因此它们可以被保留在命名空间/类/函数中,而不会污染整个应用程序。

 

const int max_array_size=50;
int an_array[max_array_size];

使用#define定义宏:通常可以通过模板替换宏;例如,可怕的MAX宏。

 

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

标签,存在一些缺点(例如重复参数评估,不可避免的内联扩展),可以被最大函数所替代。

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

这个函数可以保证类型安全(在这个版本中,两个参数强制为相同的类型),可以内联扩展或不扩展(由编译器决定),在调用时只评估一次参数,并且有范围。可以在这里找到更详细的解释。

然而,宏仍然必须用于包含保护,以创建某种奇怪的语言扩展,它们扩展为更多代码行,具有无法平衡的括号等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接