关于C#:“static const”vs“#define”vs“enum”

“static const” vs “#define” vs “enum”

在下面的c语句中,哪一个更适合使用?

1
static const int var = 5;

1
#define var 5

1
enum { var = 5 };


这取决于你需要的价值。你(以及到目前为止的所有人)忽略了第三种选择:

  • static const int var = 5;
  • #define var 5
  • enum { var = 5 };
  • 忽略有关名称选择的问题,然后:

    • 如果需要传递指针,必须使用(1)。
    • 因为(2)显然是一个选项,所以不需要传递指针。
    • (1)和(3)在调试器的符号表中都有一个符号,这使得调试更加容易。更可能的是(2)没有符号,让你想知道它是什么。
    • (1)不能用作全局范围内数组的维度;(2)和(3)都可以。
    • (1)不能用作函数范围内静态数组的维度;(2)和(3)都可以。
    • 在C99中,所有这些都可以用于本地阵列。从技术上讲,使用(1)意味着使用VLA(可变长度数组),尽管"var"引用的维度当然会固定在5号。
    • (1)不能在switch语句等位置使用;(2)和(3)都可以。
    • (1)不能用于初始化静态变量;(2)和(3)都可以。
    • (2)可以更改不希望更改的代码,因为预处理器正在使用它;(1)和(3)都不会有这样的意外副作用。
    • 您可以检测是否在预处理器中设置了(2);(1)和(3)都不允许这样做。

    因此,在大多数情况下,与其他选择相比,更喜欢"枚举"。否则,第一个和最后一个要点很可能是控制因素——如果你需要同时满足这两个点,你必须更加努力地思考。

    如果你问C++,那么你每次都会使用选项(1)-静态const。


    一般来说:

    1
    static const

    因为它尊重范围并且是类型安全的。

    唯一需要注意的是:如果您希望在命令行上定义变量的话。还有一种选择:

    1
    2
    3
    4
    5
    #ifdef VAR // Very bad name, not long enough, too general, etc..
      static int const var = VAR;
    #else
      static int const var = 5; // default value
    #endif

    尽可能使用类型安全的替代方法,而不是宏/省略号。

    如果您真的需要使用宏(例如,您希望使用__FILE____LINE__),那么您最好非常小心地命名宏:在其命名约定中,boost建议使用所有大写字母,从项目名称开始(此处为boost),在阅读库时,您会注意到这(通常)后面跟着部件名称。特殊区域(图书馆),然后用一个有意义的名字。

    它通常有很长的名字:)


    尤其是C语言?在c中,正确答案是:使用#define(或者,如果合适,使用enum)

    虽然有一个EDCOX1×7对象的范围和打字特性是有益的,但是在现实中EDCOX1中,C(相对于C++)中的7个对象不是真的常数,因此在大多数实际情况下通常是无用的。

    所以,在C语言中,选择应该由你打算如何使用常数来决定。例如,不能将const int对象用作case标签(而宏可以工作)。不能将const int对象用作位字段宽度(而宏可以工作)。在C89/90中,不能使用const对象来指定数组大小(而宏可以工作)。即使在c99中,当需要非VLA数组时,也不能使用const对象来指定数组大小。

    如果这对你很重要,那么它将决定你的选择。大多数时候,除了在C语言中使用#define之外,你别无选择,也别忘了另一种方法,它在C语言中产生真正的常量。

    在C++ EDCOX1中,7个对象是真正的常量,所以在C++中,最好总是选择EDCOX1×7的变体(不需要在C++中使用显式EDCOX1 6)。


    static const#define的区别在于前者使用内存,后者不使用内存进行存储。其次,您不能传递#define的地址,而可以传递static const的地址。实际上,这取决于我们所处的环境,我们需要在这两种情况中选择一种。在不同的情况下,两者都处于最佳状态。请不要以为一个比另一个好…-)

    如果是这样的话,丹尼斯·里奇会把最好的一个单独留在这里……哈哈哈…-)


    在C.#define中更受欢迎。可以使用这些值声明数组大小,例如:

    1
    2
    3
    4
    5
    #define MAXLEN 5

    void foo(void) {
       int bar[MAXLEN];
    }

    据我所知,ansi c不允许在这种情况下使用static const。在C++中,在这些情况下应该避免宏。你可以写

    1
    2
    3
    4
    5
    const int maxlen = 5;

    void foo() {
       int bar[maxlen];
    }

    甚至省略EDOCX1,6,因为内部链接是由EDOCX1,7(已经在C++中)所暗示的。


    c中const的另一个缺点是不能在初始化另一个const时使用该值。

    1
    2
    3
    4
    5
    6
    static int const NUMBER_OF_FINGERS_PER_HAND = 5;
    static int const NUMBER_OF_HANDS = 2;

    // initializer element is not constant, this does not work.
    static int const NUMBER_OF_FINGERS = NUMBER_OF_FINGERS_PER_HAND
                                         * NUMBER_OF_HANDS;

    即使这样也不适用于常量,因为编译器不将其视为常量:

    1
    2
    3
    static uint8_t const ARRAY_SIZE = 16;
    static int8_t const lookup_table[ARRAY_SIZE] = {
        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; // ARRAY_SIZE not a constant!

    我很乐意在这些情况下使用类型const,否则…


    如果你能摆脱它,static const有很多优势。它遵循正常的作用域原则,在调试器中可见,并且通常遵循变量遵守的规则。

    但是,至少在最初的C标准中,它不是一个常数。如果使用#define var 5,则可以编写int foo[var];作为声明,但不能这样做(除了作为static const int var = 5;的编译器扩展)。这不是C++中的情况,EDCOX1的2版本可以在EDCOX1×3版本的任何地方使用,我相信C99也是如此。

    但是,不要用小写名称命名#define常量。它将覆盖该名称的任何可能使用,直到翻译单元结束。宏常量应该位于它们自己的有效名称空间中,该名称空间传统上都是大写字母,可能带有前缀。


    我编写了快速测试程序来演示一个区别:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <stdio.h>

    enum {ENUM_DEFINED=16};
    enum {ENUM_DEFINED=32};

    #define DEFINED_DEFINED 16
    #define DEFINED_DEFINED 32

    int main(int argc, char *argv[]) {

       printf("%d, %d
    "
    , DEFINED_DEFINED, ENUM_DEFINED);

       return(0);
    }

    这将编译以下错误和警告:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    main.c:6:7: error: redefinition of enumerator 'ENUM_DEFINED'
    enum {ENUM_DEFINED=32};
          ^
    main.c:5:7: note: previous definition is here
    enum {ENUM_DEFINED=16};
          ^
    main.c:9:9: warning: 'DEFINED_DEFINED' macro redefined [-Wmacro-redefined]
    #define DEFINED_DEFINED 32
            ^
    main.c:8:9: note: previous definition is here
    #define DEFINED_DEFINED 16
            ^

    注意,当define给出警告时,枚举给出错误。


    如果你有像mystruct.var这样的东西,#define var 5会给你带来麻烦。

    例如,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct mystruct {
        int var;
    };

    #define var 5

    int main() {
        struct mystruct foo;
        foo.var = 1;
        return 0;
    }

    预处理器将替换它,代码将无法编译。因此,传统的编码方式建议所有常量#define使用大写字母来避免冲突。


    最好使用const,而不是定义。这是因为const由编译器处理,并由预处理器定义。这就像定义自己不是代码的一部分(粗略地说)。

    例子:

    1
    #define PI 3.1416

    编译器可能永远看不到符号名pi;它可能在源代码到达编译器之前被预处理器删除。因此,名称pi可能无法输入到符号表中。如果在编译过程中遇到涉及常量使用的错误,这可能会令人困惑,因为错误消息可能引用3.1416,而不是pi。如果pi是在一个没有写入的头文件中定义的,那么就不知道3.1416是从哪里来的。

    这个问题也可能出现在符号调试器中,因为您正在编程的名称可能不在符号表中。

    解决方案:

    1
    const double PI = 3.1416; //or static const...

    定义

    1
    const int const_value = 5;

    不总是定义常量值。一些编译器(例如TCC 0.9.26)只分配名为"const_value"的内存。使用标识符"常量值"不能修改这个内存。但是您仍然可以使用另一个标识符修改内存:

    1
    2
    3
    4
    const int const_value = 5;
    int *mutable_value = (int*) &const_value;
    *mutable_value = 3;
    printf("%i", const_value); // The output may be 5 or 3, depending on the compiler.

    这意味着定义

    1
    #define CONST_VALUE 5

    是定义一个不能用任何方法修改的常量值的唯一方法。


    不要以为"哪一个总是最好的"有答案,但正如马修所说

    static const

    类型安全。不过,我对#define最大的不满是,在Visual Studio中调试时,您无法观看变量。它给出一个错误,即找不到符号。


    顺便说一句,#define的一个替代方法是"枚举",它提供了适当的范围,但其行为类似于"真正的"常量。例如:

    1
    enum {number_ten = 10;}

    在许多情况下,定义枚举类型并创建这些类型的变量是很有用的;如果这样做了,调试器可能能够根据其枚举名称显示变量。

    然而,一个重要的警告是:在C++中,枚举类型与整数的兼容性有限。例如,默认情况下,不能对它们执行算术运算。我发现这是对枚举的一种奇怪的默认行为;虽然有一个"严格枚举"类型是好的,但是由于希望C++与C兼容,所以我认为"EnUM"类型的默认行为应该与整数互换。


    尽管这个问题是关于整数的,但值得注意的是,如果需要一个常量结构或字符串,define和enum是无用的。它们通常都作为指针传递给函数。(对于字符串,这是必需的;对于结构,它效率更高。)

    对于整数,如果您处于内存非常有限的嵌入式环境中,可能需要担心常量存储在哪里以及如何编译对它的访问。编译器可能在运行时添加两个常量,但在编译时添加两个定义。定义常量可以转换成一个或多个MOV[立即]指令,这意味着常量有效地存储在程序内存中。常量将存储在数据内存的.const节中。在具有哈佛体系结构的系统中,性能和内存使用可能存在差异,尽管它们可能很小。它们对于内部循环的核心优化可能很重要。


    一个简单的区别:

    在预处理时,常量被其值替换。因此,不能将取消引用运算符应用于定义,但可以将取消引用运算符应用于变量。

    如您所想,define比static const更快。

    例如,具有:

    1
    #define mymax 100

    你不能做printf("address of constant is %p",&mymax);

    但拥有

    1
    const int mymax_var=100

    你可以做printf("address of constant is %p",&mymax_var);

    更清楚地说,在预处理阶段,定义被它的值替换,所以我们在程序中没有存储任何变量。我们只有程序文本段中使用定义的代码。

    但是,对于静态常量,我们有一个变量被分配到某个地方。对于gcc,静态常量分配在程序的文本段中。

    上面,我想介绍一下引用操作符,所以用引用替换取消引用。


    我不确定自己是否正确,但在我看来,调用#defined值比调用任何其他通常声明的变量(或常量值)快得多。这是因为当程序运行并且需要使用一些通常声明的变量时,它需要跳转到内存中的确切位置来获取该变量。

    相反,当它使用#defined值时,程序不需要跳转到任何已分配的内存,它只需要获取该值。如果#define myValue 7和调用myValue的程序的行为与它刚才调用7时完全相同。


    我们研究了MBF16X上生成的汇编程序代码…两种变体都会产生相同的算术运算代码(例如,添加immediate)。

    因此,类型检查首选const int,而#define是旧样式。也许它是特定于编译器的。因此,请检查生成的汇编程序代码。