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

发布于 2024-11-15 19:09:51 字数 768 浏览 5 评论 0原文

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

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

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

有人能指出任何其他区别以及应该优先选择哪个吗?

编辑:

来自 IBM C++ 文档:

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

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

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

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

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

I was asked this question in a technical interview:

What is the difference between a const and a macro in C++?

My answer was that a macro is a preprocessor directive and it could be difficult to debug the application if you use a macro since it is replaced with the constant expression before compilation, whereas a const can have a type identifier and is easy to debug.

Could anyone point out any other difference and which should be preferred?

EDIT:

From the IBM documentation for C++:

The following are some differences between #define and the const type qualifier:

  1. The #define directive can be used to create a name for a numerical, character, or string constant, whereas a const object of any type can be declared.

  2. A const object is subject to the scoping rules for variables, whereas a constant created using #define is not. Unlike a const object, the value of a macro does not appear in the intermediate source code used by the compiler because they are expanded inline. The inline expansion makes the macro value unavailable to the debugger.

  3. A macro can be used in a constant expression, such as an array bound, whereas a const object cannot. (I think we surely need to use macro to define array_size.

  4. The compiler does not type-check a macro, including macro arguments.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

浮生面具三千个 2024-11-22 19:09:51

宏和常量完全不是一回事,它们有时都适合具体情况,而您的答案仅触及差异的表面。此外,C++ 有两种不同类型的常量。

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

宏的约束要少得多:它可以扩展到任何标记序列,只要整个程序保持良好-形成的程序。它不具有变量的任何属性。将 sizeof& 应用于宏可能会也可能不会做一些有用的事情,具体取决于宏扩展的内容。宏有时被定义为扩展为数字文字,并且此类宏有时被认为为常量,但它们不是:“编译器本身”(即翻译阶段 7)将它们视为 < em>数字文字。

如今,当常量就可以时,不要使用宏,这通常被认为是良好的做法。宏不遵循与所有其他标识符相同的作用域规则,这可能会令人困惑,如果您使用常量,您将为翻译阶段 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);
}

Macros and constants are not remotely the same thing, each is sometimes appropriate for the circumstances, and your answer only scratches at the surface of the difference. Also, C++ has two different kinds of constants.

A constant defined with the const qualifier is best thought of as an unmodifiable variable. It has all the properties of a variable: it has a type, it has a size, it has linkage, you can take its address. (The compiler might optimize away some of these properties if it can get away with it: for instance, constants whose address is never used may not get emitted into the executable image. But this is only by the grace of the as-if rule.) The only thing you can't do to a const datum is change its value. A constant defined with enum is a little different. It has a type and a size, but it doesn't have linkage, you can't take its address, and its type is unique. Both of these are processed during translation phase 7, so they can't be anything but an lvalue or rvalue. (I'm sorry about the jargon in the preceding sentence, but I would have to write several paragraphs otherwise.)

A macro has far fewer constraints: it can expand to any sequence of tokens, as long as the overall program remains a well-formed program. It doesn't have any of the properties of a variable. Applying sizeof or & to a macro may or may not do something useful, depending on what the macro expands to. Macros are sometimes defined to expand to numeric literals, and such macros are sometimes thought of as constants, but they're not: "the compiler proper" (that is, translation phase 7) sees them as numeric literals.

It is generally considered good practice, nowadays, not to use a macro when a constant will do. Macros don't obey the same scoping rules as all other identifiers, which can be confusing, and if you use a constant you give more information to translation phase 7 and thus also to the debugger. However, macros permit you to do things that cannot be done any other way, and if you need to do one of those things, you should not hesitate to use them. (Macros that are pulling their weight, in this sense, generally do not just expand to numeric literals, though I am not going to say never.)

EDIT: Here's an example of a macro doing something interesting. It is in no way, shape or form a constant. There may well be a way to get the same effect without a macro (if you know one that doesn't involve stringstreams, I'd be curious to hear about it!) but I think it makes a good illustration of both the power and the danger of macros (for the latter, consider what it would do if it was used outside of one very specific context...)

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);
}
红焚 2024-11-22 19:09:51

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

基于范围的机制:

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

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

如果您使用#define,这些数字会在预编译时被预处理器替换所以如果您在编译过程中收到错误,这会令人困惑,因为错误消息不会引用宏名称,而是引用值,并且它会突然出现一个值,并且会浪费大量时间在代码中跟踪它。

易于调试:

同样出于同样的原因,在调试#define时实际上不会提供任何帮助。
为了避免上述两种情况,const 将是更好的选择。

One should prefer const int sum = 1; over #define sum 1 for a number of reasons:

Scope Based Mechanism:

#defines don't respect scopes so there is no way to create a class scoped namespace. While const variables can be scoped in classes.

Avoiding Weird magical numbers during compilation errors:

If you are using #define those are replaced by the pre-processor at time of precompilation So if you receive an error during compilation, it will be confusing because the error message wont refer the macro name but the value and it will appear a sudden value, and one would waste lot of time tracking it down in code.

Ease of Debugging:

Also for same reasons, while debugging #define would provide no help really.
To avoid both above situations const will be a better choice.

你的背包 2024-11-22 19:09:51

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

一切的优点和缺点,取决于用法:

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

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

( Originally posted for static const vs #define - reproducing here as this question seems to have more "momentum"... let me know if that's inappropriate... )

Pros and cons to everything, depending on usage:

  • consts
    • properly scoped / identifier clash issues handled nicely
    • strong, single, user-specified type
      • you might try to "type" a #define ala #define S std::string("abc"), but the constant avoids repeated construction of distinct temporaries at each point of use
    • One Definition Rule complications
    • can take address, create const references to them etc.
  • defines
    • "global" scope / more prone to conflicting usages, which can produce hard-to-resolve compilation issues and unexpected run-time results rather than sane error messages; mitigating this requires:
      • long, obscure and/or centrally coordinated identifiers, and access to them can't benefit from implicitly matching used/current/Koenig-looked-up namespace, namespace aliases etc.
      • use of all uppercase characters is generally required and reserved for preprocessor defines (an important guideline for enterprise scale preprocessor usage to remain manageable, and which 3rd party libraries can be expected to follow), observation of which implies migration of existing consts or enums to defines involves a change in capitalisation (and hence affects client code). (Personally, I capitalise the first letter of enums but not consts, so I'd be hit here anyway - maybe time to rethink that.)
    • more compile-time operations possible: string literal concatenation, stringification (taking size thereof)
      • downside is that given #define X "x" and some client usage ala "pre" X "post", you're in trouble if you want or need to make X a runtime-changeable variable rather than a constant, whereas that transition is easier from a const char* or const std::string given they already force the user to incorporate concatenation operations.
    • can't use sizeof directly on a defined numeric constant
    • untyped (GCC doesn't warn if compared to unsigned)
    • some compiler/linker/debugger chains may not present the identifier, so you'll be reduced to looking at "magic numbers" (strings, whatever...)
    • can't take the address
    • the substituted value need not be legal (or discrete) in the context where the #define is created, as it's evaluated at each point of use, so you can reference not-yet-declared objects, depend on "implementation" that needn't be pre-included, create "constants" such as { 1, 2 } that can be used to initialise arrays, or #define MICROSECONDS *1E-6 etc. (definitely not recommending this!)
    • some special things like __FILE__ and __LINE__ can be incorporated into the macro substitution
  • enums
    • only possible for integer values
    • properly scoped / identifier clash issues handled nicely
    • strongly typed, but to a big-enough signed-or-unsigned int size over which you have no control (in C++03)
    • can't take the address
    • stronger usage restraints (e.g. incrementing - template <typename T> void f(T t) { cout << ++t; } won't compile)
    • each constant's type taken from the enclosing enum, so template <typename T> void f(T) get a distinct instantiation when passed the same numeric value from different enums, all of which are distinct from any actual f(int) instantiation.
    • even with typeof, can't expect numeric_limits to provide useful insight
    • the enum's typename may appear in various places in RTTI, compiler messages etc. - possibly useful, possibly obfuscation

As a general rule, I use consts and consider them the most professional option for general usage (though the others have a simplicity appealing to this old lazy programmer).

殤城〤 2024-11-22 19:09:51

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

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

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

Another difference is that a const variable has a memory and can be referenced by a pointer. Macro is just the autocomplete that will happen before compilation, hence the name is lost during compiling.

Also macro can be just more than a constant. It can be am expression or anything that is syntactically correct, even a whole definition of a function.

Macros are used to depict programming choices e.g. stack size; while cosnt is used to depict the real world constants like value of Pi or e.

伴我老 2024-11-22 19:09:51

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;
}

define can be redefine , but const will be cause compiler error:

sample:
source : 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;
}
小兔几 2024-11-22 19:09:51

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

Macros don't respect scope, and a macro's name may not be available to a symbolic debugger. Dan Saks has a fairly complete article on the relative merits of macros (none), constant objects, and enumeration constants. Like Stephen Dewhurst, Saks prefers enumeration constants for integer values since they take up no storage (more precisely, enumeration constants have neither storage duration nor linkage).

左秋 2024-11-22 19:09:51

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

const 变量相对于宏的一个优点是内存使用量:对于宏,可能必须在使用它的所有地方重复该值,这样 const 变量就不会在内存中重复。 (但我不确定这个区别)

A macro always have a type, for instance, #define FIVE 5 is of type int.

An advantage for the const variable over the macro could be the memory usage : With a macro the value may have to be duplicated everywhere it is used will a const variable will not be duplicated in memory. (but I am not sure of this difference)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文