静态const vs #define

使用static const变量是否比使用#define预处理器更好?或者这取决于上下文?

每种方法的优点/缺点是什么?

182512 次浏览

使用静态const就像在代码中使用任何其他const变量一样。这意味着您可以跟踪信息的来源,而不是在预编译过程中简单地在代码中替换#define。

你可能想看看这个问题的c++ FAQ Lite: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7 < / p >

请看这里:Static const vs define

通常使用const声明(注意它不需要是静态的)

就我个人而言,我讨厌预处理器,所以我总是选择const

#define的主要优点是它不需要在程序中存储内存,因为它实际上只是用文字值替换一些文本。它还具有没有类型的优点,因此可以用于任何整数值而不会产生警告。

"const"的优点是它们可以有作用域,并且可以在需要传递指向对象的指针的情况下使用。

不过,我不知道你到底想从“static”部分得到什么。如果你是全局声明,我会把它放在一个匿名的命名空间,而不是使用static。例如

namespace {
unsigned const seconds_per_minute = 60;
};


int main (int argc; char *argv[]) {
...
}

如果这是一个c++问题,并且它提到#define作为替代,那么它是关于“全局”(即文件范围)常量,而不是关于类成员。当涉及到c++中的这些常量时,static const是多余的。在c++中,const默认有内部链接,没有必要将它们声明为static。所以它实际上是关于const vs. #define

最后,在c++中const更可取。至少因为这些常量是有类型和作用域的。除了少数例外,根本没有理由更喜欢#define而不是const

字符串常量,顺便说一句,就是这种例外的一个例子。使用#defined字符串常量,可以使用C/ c++编译器的编译时连接特性,如在

#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"


const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;

另外,以防万一,当有人提到static const作为#define的替代时,通常意味着他们在谈论C,而不是c++。我不知道这个问题是否标注正确…

  • 静态const是有类型的(它有类型),可以由编译器检查有效性、重定义等。
  • #define可以被重新定义为undefined。

通常你应该更喜欢静态常量。它没有缺点。预处理程序应该主要用于条件编译(有时可能用于非常脏的trics)。

__abc0、__abc1和(你已经忘记的)__abc2之间的优缺点,取决于使用情况:

  1. < p > enums:

    • 仅适用于整数值
    • 适当的作用域/标识符冲突问题得到了很好的处理,特别是在c++ 11枚举类中,enum class X的枚举通过作用域X::消除了歧义
    • 虽然c++ 11默认为int,但可以由程序员显式设置,但它是强类型的,但它是一个足够大的带符号或无符号的int大小,这在c++ 03中是无法控制的(尽管如果枚举是struct/class/union的成员,则可以指定它们应该打包到哪个位字段中)
    • 不能采取地址-没有一个作为枚举值有效地取代内联在使用点
    • 更强的使用限制(例如,递增- template <typename T> void f(T t) { cout << ++t; }将无法编译,尽管您可以将枚举包装到具有隐式构造函数、强制转换操作符和用户定义操作符的类中)
    • 每个常量的类型从封闭enum中获取,因此template <typename T> void f(T)在从不同enum传递相同数值时得到不同的实例化,所有这些都不同于任何实际的f(int)实例化。每个函数的目标代码可以是相同的(忽略地址偏移量),但我不期望编译器/链接器消除不必要的副本,尽管如果你关心的话,你可以检查你的编译器/链接器。
    • 即使使用typeof/decltype,也不能指望numeric_limits提供对有意义的值和组合集的有用洞察(实际上,“合法”的组合甚至在源代码中都没有标记,考虑enum { A = 1, B = 2 } -从程序逻辑的角度来看,A|B是“合法”的吗?)
    • 枚举的typename可能出现在RTTI、编译器消息等不同的地方——可能有用,也可能混淆
    • 你不能在没有翻译单元实际看到值的情况下使用枚举,这意味着库api中的枚举需要在头文件中公开的值,而make和其他基于时间戳的重新编译工具将在它们被更改时触发客户端重新编译(糟糕!)
    • 李< / ul > < / >

  1. < p > consts:

    • 正确处理范围/标识符冲突问题
    • 强的、单个的、用户指定的类型
      • 你可以尝试“键入”#define ala #define S std::string("abc"),但常量避免了在每个使用点重复构造不同的临时对象
      • 李< / ul > < / >
      • 定义规则的复杂性
      • 可以接受地址,创建const引用等。
      • 最类似于非-const值,在两者之间切换时将工作和影响最小化
      • 值可以放置在实现文件中,允许本地化的重新编译和客户端链接来获取更改
      • 李< / ul > < / >

  1. < p > #defines:

    • "global" scope /更容易产生冲突的用法,这可能会产生难以解决的编译问题和意外的运行时结果,而不是正常的错误消息;缓解这一问题需要:
      • 长、模糊和/或集中协调的标识符,并且对它们的访问不能从隐式匹配使用/当前/ koenigg查找的名称空间、名称空间别名等中获益。
      • 虽然最重要的最佳实践允许模板参数标识符是单字符大写字母(可能后面跟着一个数字),但其他不带小写字母的标识符的使用通常是为预处理器定义保留的(在OS和C/ c++库标头之外)。这对于保持可管理的企业级预处理器使用非常重要。第三方库可以期望遵守。观察到这一点意味着现有的const或enum到定义的迁移涉及到大写的变化,因此需要编辑客户端源代码,而不是“简单的”重新编译。(就我个人而言,我把枚举的第一个字母大写,而不是const,所以我也会在这两个字母之间迁移——也许是时候重新考虑一下了。)
      • 李< / ul > < / >
      • 可能的更多编译时操作:字符串字面值连接,字符串化(取其大小),连接到标识符
        • 缺点是,给定#define X "x"和一些客户端使用"pre" X "post",如果你想或需要使X成为一个运行时可更改的变量,而不是一个常量,你强制编辑客户端代码(而不是仅仅重新编译),而从const char*const std::string转换更容易,因为它们已经强制用户合并连接操作(例如"pre" + X + "post"用于string)。
        • 李< / ul > < / >
        • 不能直接使用sizeof定义的数字文字
        • un类型化(如果与unsigned进行比较,GCC不会发出警告)
        • 一些编译器/链接器/调试器链可能不会显示标识符,所以你将不得不查看“神奇的数字”(字符串,等等…)
        • 不能带地址
        • 在创建#define的上下文中,所替换的值不需要是合法的(或离散的),因为它在每个使用点都被求值,所以你可以引用尚未声明的对象,依赖于不需要预先包含的“实现”,创建“常量”,如{ 1, 2 },可用于初始化数组,或#define MICROSECONDS *1E-6等(肯定不建议这样做!)
        • 一些特殊的东西,如__FILE____LINE__可以被合并到宏替换中
        • 你可以在#if语句中测试是否存在和值,以有条件地包含代码(比后预处理"if"更强大,因为如果未被预处理器选中,代码不需要编译),使用#undef-ine,重定义等。
        • 替换文本必须暴露:
          • 在翻译单元中,这意味着客户端使用的库中的宏必须在头文件中,因此make和其他基于时间戳的重新编译工具在更改时将触发客户端重新编译(糟糕!)
          • 或者在命令行中,需要更加小心地确保客户端代码被重新编译(例如,提供定义的Makefile或脚本应该被列为依赖项)
          • 李< / ul > < / > 李< / ul > < / >

我的个人观点:

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

使用预处理器指令#define定义常量不建议不仅适用于C++,而且适用于C。这些常量将没有类型。即使在C中也建议使用const作为常量。

如果要定义一个在类的所有实例之间共享的常量,请使用static const。如果常量特定于每个实例,则只需使用const(但请注意,该类的所有构造函数都必须在初始化列表中初始化这个const成员变量)。

总是喜欢使用语言特性,而不是一些额外的工具,如预处理器。

ES.31:不要将宏用于常量或“函数”;

宏是bug的主要来源。宏不服从通常的作用域 以及类型规则。宏不遵循通常的参数规则 传递。宏确保人类读者看到不同的东西 从编译器看到的。

c++核心指南

#define会导致意想不到的结果:

#include <iostream>


#define x 500
#define y x + 5


int z = y * 2;


int main()
{
std::cout << "y is " << y;
std::cout << "\nz is " << z;
}

输出错误的结果:

y is 505
z is 510

然而,如果你用常量替换它:

#include <iostream>


const int x = 500;
const int y = x + 5;


int z = y * 2;


int main()
{
std::cout << "y is " << y;
std::cout << "\nz is " << z;
}

它输出正确的结果:

y is 505
z is 1010

这是因为#define只是替换了文本。因为这样做会严重打乱操作的顺序,所以我建议使用常量变量。

作为一个相当老和生疏的C程序员,他从来没有完全学会c++,因为其他东西出现了,现在正在努力掌握Arduino,我的观点很简单。

#define是一个编译器预处理器指令,应该这样使用,用于条件编译等。例如,低级代码需要定义一些可能的替代数据结构,以移植到特定的硬件。它会根据模块编译和链接的顺序产生不一致的结果。如果你需要某些东西在范围上是全局的,那么就这样正确地定义它。

Const和(static Const)应该总是用来命名静态值或字符串。它们是类型化的、安全的,调试器可以完全使用它们。

枚举总是让我感到困惑,所以我设法避免使用它们。