【问题标题】:What is the difference between a macro and a const in C++?C ++中的宏和常量有什么区别?
【发布时间】:2011-09-17 15:17:45
【问题描述】:

我在一次技术面试中被问到这个问题:

const 和 C++ 中的宏有什么区别?

我的回答是宏是一个预处理器指令,如果使用宏可能很难调试应用程序,因为它在编译之前被常量表达式替换,而 const 可以有一个类型标识符并且是易于调试。

谁能指出任何其他区别,哪个应该是首选?

编辑:

来自 IBM 的 C++ 文档:

以下是#defineconst 类型限定符之间的一些区别:

  1. #define 指令可用于为数字、字符或字符串常量创建名称,而可以声明任何类型的 const 对象。

  2. const 对象受变量范围规则的约束,而使用#define 创建的常量则不受约束。与const 对象不同,宏的值不会出现在编译器使用的中间源代码中,因为它们是内联扩展的。内联扩展使宏值对调试器不可用。

  3. 宏可用于常量表达式,例如绑定数组,而const 对象不能。 (我想我们肯定需要使用宏来定义array_size

  4. 编译器不对宏进行类型检查,包括宏参数。

【问题讨论】:

  • 规则“宏可以用于常量表达式,例如绑定数组,而常量对象不能。”仅适用于 C,不适用于 C++。

标签: c++ macros constants


【解决方案1】:

宏和常量并不是一回事,有时每个都适合具体情况,而您的回答只是触及了差异的表面。此外,C++ 有两种不同的常量。

使用const 限定符定义的常量最好被视为不可修改的变量。它具有变量的所有属性:它有一个类型,它有一个大小,它有链接,你可以获取它的地址。 (编译器可能会优化掉其中的一些属性,如果它可以摆脱它:例如,从未使用过地址的常量可能不会被发送到可执行映像中。但这只是通过 as-if 规则的恩典。 ) 对const 数据唯一不能做的就是改变它的值。用enum 定义的常量有点不同。它有一个类型和一个大小,但它没有链接,你不能取它的地址,它的类型是唯一的。这两个都是在翻译阶段 7 处理的,所以它们只能是左值或右值。 (我很抱歉前一句中的行话,但我不得不写几段。)

宏的约束要少得多:它可以扩展到任何标记序列,只要整个程序仍然是一个格式良好的程序。它没有任何变量的属性。将sizeof& 应用于宏可能会或可能不会做一些有用的事情,具体取决于宏扩展的内容。宏有时被定义为扩展为数字文字,并且这些宏有时被认为为常量,但它们不是:“编译器正确”(即翻译阶段 7)将它们视为 数字字面量.

现在通常认为是一种很好的做法,当常量可以使用时不要使用宏。宏不遵守与所有其他标识符相同的范围规则,这可能会造成混淆,如果您使用常量,您会为翻译阶段 7 提供更多信息,从而也提供给调试器。但是,宏允许您做任何其他方式无法完成的事情,如果您需要做其中一件事情,您应该毫不犹豫地使用它们。 (从这个意义上说,正在发挥作用的宏通常只是扩展为数字文字,尽管我不会说永远不会。)

编辑:这是一个宏做一些有趣事情的例子。它绝不是一个常数,形状或形式。可能有一种方法可以在没有宏的情况下获得相同的效果(如果你知道一个不涉及字符串流的方法,我很想知道它!)但我认为它很好地说明了功能和宏的危险(对于后者,请考虑如果在一个非常特定的上下文之外使用它会做什么......)

static double elapsed()
{ ... }
#define ELAPSED '[' << std::fixed << std::setprecision(2) << elapsed() << "] "

// usage:
for (vector<string>::iterator f = files.begin(); f != files.end(); f++) {
    cout << ELAPSED << "reading file: " << *f << '\n';
    process_file(*f);
}

【讨论】:

  • @Zack- +1。你能举个例子说明宏肯定需要用来完成任务吗?
  • 我想不出一个 short 的例子。一种常见的情况是,当您需要从许多样板文件中组装一个顶级构造时:例如参见mxr.mozilla.org/mozilla-central/source/xpcom/glue/…——如果没有宏,那里所做的事情将更容易出错并且编写起来乏味。 (不过,我不想把 XPCOM 当作一个很好的例子;你看到的最好被认为是很久以前做出的错误设计决定上的疤痕组织,现在很难纠正。)
  • @Mahesh:宏的字符串连接功能在调试时很方便#define DEBUG(x) cout
  • @Mahesh:用一个宏示例更新了我的答案,该宏执行一些小但不平凡的事情来完成任何其他方式。
【解决方案2】:

出于多种原因,人们应该更喜欢 const int sum = 1; 而不是 #define sum 1

基于范围的机制:

#defines 不尊重作用域,因此无法创建类作用域命名空间。而 const 变量可以在类中作用域。

在编译错误期间避免奇怪的神奇数字:

如果您使用的是#define,它们会在预编译时被预处理器替换因此,如果您在编译期间收到错误,那将会令人困惑,因为错误消息不会引用宏名称而是值,它会出现一个突然的值,人们会浪费大量时间在代码中跟踪它。

易于调试:

同样出于同样的原因,虽然调试 #define 并不会提供任何帮助。
避免以上两种情况const会是更好的选择。

【讨论】:

  • 总而言之,const 是首选,因为我们可以控制它的范围并且易于调试。但问题是为什么更喜欢全局常量而不是宏
  • @Amm Sokun:另外,因为macrosevil,因为它们总是会产生不必要的副作用。至于在 C++ 中使用宏作为 named constants 很少需要这样做,使用宏作为命名常量来自 C。
  • 我遇到过幻数,但幻数一定很特别。
【解决方案3】:

另一个区别是const 变量有内存并且可以被指针引用。宏只是在编译之前发生的自动完成,因此在编译过程中名称会丢失。

宏也可以不仅仅是一个常数。它可以是 am 表达式或任何语法正确的东西,甚至是函数的完整定义。

宏用于描述编程选择,例如堆栈大小;而cosnt 则用于描述现实世界中的常数,例如 Pi 或 e 的值。

【讨论】:

  • +1 用于明确说明宏也可以定义任何表达式或函数。一个有趣的例子是BOOST_FOREACH,它允许您以简洁明了的方式循环遍历序列中的每个元素。
【解决方案4】:

(最初为 static const vs #define 发布 - 在这里复制,因为这个问题似乎有更多的“动力”......让我知道这是否不合适......)

各有优劣,视使用情况而定:

  • 常量
    • 妥善处理范围/标识符冲突问题
    • 强、单一、用户指定的类型
      • 您可以尝试“键入”#define ala #define S std::string("abc"),但该常量可避免在每个使用点重复构造不同的临时对象
    • 一个定义规则的复杂性
    • 可以获取地址、创建对它们的 const 引用等。
  • 定义
    • “全局”范围/更容易出现冲突使用,这可能会产生难以解决的编译问题和意外的运行时结果,而不是正常的错误消息;减轻这种情况需要:
      • 长、晦涩和/或集中协调的标识符,以及对它们的访问无法从隐式匹配 used/current/Koenig-looked-up 命名空间、命名空间别名等中受益。
      • 通常需要使用所有大写字符并保留给预处理器定义(企业规模预处理器使用保持可管理性的重要指南,并且可以预期遵循哪些第 3 方库),观察这意味着迁移现有的 const 或定义的枚举涉及大小写的更改(因此会影响客户端代码)。 (就我个人而言,我将枚举的第一个字母大写,但不是 const,所以无论如何我都会被打到这里——也许是时候重新考虑一下了。)
    • 可能的更多编译时操作:字符串文字连接、字符串化(取其大小)
      • 缺点是考虑到#define X "x" 和一些客户端使用ala "pre" X "post",如果您想要或需要使X 成为运行时可更改的变量而不是常量,那么您就有麻烦了,而从@ 转换更容易987654326@ 或 const std::string 已经强制用户合并连接操作。
    • 不能直接在定义的数值常量上使用 sizeof
    • 无类型(与无符号相比,GCC 不会发出警告)
    • 某些编译器/链接器/调试器链可能不显示标识符,因此您只能查看“幻数”(字符串,等等...)
    • 无法获取地址
    • 在创建#define 的上下文中,替换值不必是合法的(或离散的),因为它在每个使用点进行评估,因此您可以引用尚未声明的对象,具体取决于“实现”不需要预先包含,创建“常量”,例如可用于初始化数组的 { 1, 2 },或 #define MICROSECONDS *1E-6 等。(绝对不推荐这样做!)
    • 一些特殊的东西,如__FILE____LINE__ 可以合并到宏替换中
  • 枚举
    • 仅适用于整数值
    • 妥善处理范围/标识符冲突问题
    • 强类型,但有足够大的有符号或无符号 int 大小,您无法控制(在 C++03 中)
    • 无法获取地址
    • 更强的使用限制(例如递增 - template &lt;typename T&gt; void f(T t) { cout &lt;&lt; ++t; } 不会编译)
    • 每个常量的类型取自封闭的枚举,因此template &lt;typename T&gt; void f(T) 从不同的枚举传递相同的数值时会获得不同的实例化,所有这些都不同于任何实际的 f(int) 实例化。
    • 即使使用 typeof,也不能指望 numeric_limits 提供有用的见解
    • 枚举的类型名可能出现在 RTTI、编译器消息等的不同位置 - 可能有用,可能会混淆

作为一般规则,我使用 const 并认为它们是最专业的通用选项(尽管其他的简单性吸引了这位老懒程序员)。

【讨论】:

    【解决方案5】:

    define 可以重新定义,但是 const 会导致编译器错误:

    示例: 来源:main.cpp

    #define int_constance 4
    #define int_constance 8 // ok, compiler will warning ( redefine macro)
    
    const int a = 2;
    const int a = 4; // redefine -> error
    
    int main(int argc, char** argv)
    {
       std::cout << int_constance ; // if remove second #define line, output will be 8
    
       return 0;
    }
    

    【讨论】:

      【解决方案6】:

      宏不尊重范围,并且宏的名称可能不适用于符号调试器。 Dan Saks 有一篇关于宏(无)、常量对象和枚举常量的相对优点的相当完整的文章。与Stephen Dewhurst 一样,Saks 更喜欢整数值的枚举常量,因为它们不占用存储空间(更准确地说,枚举常量既没有存储持续时间也没有链接)。

      【讨论】:

        【解决方案7】:

        宏总是有一个类型,例如,#define FIVE 5 是 int 类型。

        const 变量优于宏的一个优势可能是内存使用:对于宏,值可能必须在任何使用它的地方复制,而 const 变量不会在内存中复制。 (但我不确定这个区别)

        【讨论】:

        • 宏确实没有有类型。在您的示例中,5 有一个类型,但 FIVE 没有类型,因为它在引入类型之前从程序中消失(翻译阶段 4 而不是 7)。这不仅仅是规则律师:如果我写#define L_(x) x##L#define L(x) L_(x);然后L(FIVE);展开的结果将是5L,其类型为long
        猜你喜欢
        • 2011-03-14
        • 2011-05-27
        • 2016-07-19
        • 2017-10-22
        • 2019-09-10
        • 2012-10-22
        • 2011-06-26
        • 2011-02-02
        • 2014-03-24
        相关资源
        最近更新 更多