【问题标题】:Flags, enum (C)标志,枚举 (C)
【发布时间】:2009-10-27 14:40:11
【问题描述】:

我不太习惯使用标志进行编程,但我想我只是发现了一种有用的情况:

我有几个对象将自己注册为某些事件的侦听器。它们注册的事件取决于构造它们时发送给它们的变量。我认为这样做的一个好方法是发送按位或连接的变量,例如:TAKES_DAMAGE | GRABBABLE | LIQUID 等。然后,在构造函数中,对象可以检查设置了哪些标志并将其注册为监听器是。

但这就是我感到困惑的地方。优选地,标志将在enum 中。但这也是一个问题。如果我们有这些标志:

enum
{
    TAKES_DAMAGE,/* (0) */
    GRABBABLE, /* (1) */
    LIQUID, /* (2) */
    SOME_OTHER /* (3) */
};

那么发送标志SOME_OTHER(3)会和发送GRABBABLE | LIQUID一样,不是吗?

你是如何处理这些东西的?

【问题讨论】:

    标签: c enums flags


    【解决方案1】:

    您的枚举需要是 2 的幂:

    enum
    {
        TAKES_DAMAGE = 1,
        GRABBABLE = 2,
        LIQUID = 4,
        SOME_OTHER = 8
    };
    

    或者以更易读的方式:

    enum
    {
        TAKES_DAMAGE = 1 << 0,
        GRABBABLE = 1 << 1,
        LIQUID = 1 << 2,
        SOME_OTHER = 1 << 3
    };
    

    为什么?因为您希望能够组合没有重叠的标志,并且还能够做到这一点:

    if(myVar & GRABBABLE)
    {
        // grabbable code
    }
    

    ...如果枚举值看起来像这样,这会起作用:

     TAKES_DAMAGE: 00000001
     GRABBABLE:    00000010
     LIQUID:       00000100
     SOME_OTHER:   00001000
    

    所以,假设您已将 myVar 设置为 GRABBABLE | TAKES_DAMAGE,当您需要检查 GRABBABLE 标志时,它的工作原理如下:

     myVar:     00000011
     GRABBABLE: 00000010 [AND]
     -------------------
                00000010 // non-zero => converts to true
    

    如果您将myVar 设置为LIQUID | SOME_OTHER,则该操作将导致:

     myVar:     00001100
     GRABBABLE: 00000010 [AND]
     -------------------
                00000000 // zero => converts to false
    

    【讨论】:

    • @jldupont - 我相信你的意思是“移位操作员”? 8v)
    • 不要以为我以前同时收到 5 条回复。 o.o 感谢您的出色解释和解决方案。
    • 万岁!现在我有一种新的方式来谈论 C++ 流插入和提取:cout &lt;&lt; "that stinks!";
    • 如果您不喜欢移位运算符方法,最好使用十六进制而不是十进制:TAKES_DAMAGE=0x0001, GRABBABLE=0x0002, LIQUID=0x0004, GAS=0x0008, PUSHABLE=0x0010, INFLICTS_DAMAGE=0x0020 , OTHER=0x0040 等。这样它在代码中对齐的所有值都保持漂亮。
    • 从没想过,但这个符号很好:1
    【解决方案2】:

    另一种存储标志的方法是根本不关心底层类型。使用枚举时,枚举值默认存储为无符号整数,在普通计算机上为 32 位。这只会给你 32 个可能的标志:虽然肯定很多,但在某些情况下它还不够。

    现在你可以这样定义你的标志集了:

    typedef struct
    {
        int takes_damage : 1;
        int grabbable    : 1;
        int liquid       : 1;
        int some_other   : 1;
    } flags;
    

    如果你从来没有遇到过这种情况,':1' 部分告诉编译器只使用 1 位来存储这个结构成员。

    现在您可以定义一个变量来保存标志,并使用这些标志:

    flags myflags = {1,0,0,1}; // defines a variable holding a set of flags, with an initial value of takes_damage & some_other
    
    myflags.liquid = 1; // change the flags to include the liquid
    
    if ( myflags.takes_damage ) // test for one flag
        apply_damage();
    if ( myflags.liquid && myflags.some_other ) // test for multiple flags
        show_strange_behavior();
    

    此方法允许您无限制地定义任意数量的标志,并且您可以随时扩展标志集而不必担心溢出。缺点是测试标志的子集比较麻烦并且需要更多代码。

    【讨论】:

    • 有趣。我第一次遇到这种方法。有兴趣阅读比我更有知识的人的 cmets,了解与两个枚举的传统功能相比的优缺点。它适用于所有编译器和所有架构吗?有什么陷阱?
    • @gregschlom 我也会对更多的 cmets 感兴趣。它完全符合标准,因此它应该适用于任何编译器。我能想到的唯一陷阱是: 1. 如果结构没有打包,标志集可能会占用大量内存,其中大部分是未使用的。 2. 您不能一次指定多个标志值,例如myflags = grabbable &amp; liquid(但是,这可以通过使用包含结构和 int 值的联合来实现,但这会施加更多的约束,更依赖于编译器并且不是这篇文章的主题)
    • 谢谢!另一个问题(正如您指出的那样,与您无法测试标志子集的事实有关)是您无法轻松测试两个标志变量是否相等。 (即:你不能这样做: if (myflags == someOtherFlags) {...} )。对我来说有点阻碍,要坚持使用普通的旧枚举......
    • C 标准在哪里说枚举是无符号的? C99 6.4.4.3p2 说“声明为枚举常量的标识符具有类型 int”。
    • 使用宽度为 1 的有符号位域仅适用于二补码实现。在符号幅度实现中,所有可能的值都是 0。甚至可能存在不具有全零位的值的陷阱表示。
    【解决方案3】:

    是的。相反,让您的枚举成员的幂为 2:

    enum
    {
        TAKES_DAMAGE = (1 << 0),
        GRABBABLE = (1 << 1),
        LIQUID = (1 << 2),
        SOME_OTHER = (1 << 3)
    };
    

    【讨论】:

    • 致反对者:也许你想解释一下你认为这个答案没有用的地方?
    【解决方案4】:

    您应该使标志仅是 2 的幂,即每个标志在您存储它的任何数据类型中都有一点,并且当您按位 OR 时没有任何重叠。

    【讨论】:

      【解决方案5】:

      你不能只设置枚举中的值吗?

      enum {
       TAKES_DAMAGE = 1,
       GRABBABLE    = 2,
       LIQUID       = 4
      }
      

      之后,只需对它们执行按位 OR。

      【讨论】:

        【解决方案6】:

        你需要

        enum
        {
            TAKES_DAMAGE = 1,
            GRABBABLE = 2,
            LIQUID = 4,
            SOME_OTHER = 8
        };
        

        【讨论】:

        • 我认为你需要修复这个语法。 (我知道,问题中出现了同样的错误。不过,现在已经解决了。)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-14
        • 1970-01-01
        • 1970-01-01
        • 2011-02-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多