使用枚举作为数组索引

64

我有这个枚举:

enum ButtonState {
    BUTTON_NORMAL = 0,
    BUTTON_PRESSED = 1,
    BUTTON_CLICKED = 2
};

const u8 NUM_BUTTON_STATES = 3;

在我的按钮类中,我有成员变量ButtonState state;ButtonColors colors[NUM_BUTTON_STATES];。在绘制按钮时,我使用colors[state]来获取按钮处于任何状态时的颜色。

我的问题:

  1. 这是好的编程风格吗?有更好的方法吗?(我通常只使用带有switch语句的枚举...使用枚举作为数组索引感觉不对。)
  2. 我必须指定枚举值吗?它似乎默认从0开始递增1,但它保证在所有编译器中都能按照这种方式工作吗?
8个回答

53

这是好的编程风格吗?

我认为是的。我经常做同样的事情。

有更好的方法吗?

class Button
{
public:
    // Used for array indexes!  Don't change the numbers!
  enum State {
    NORMAL = 0,
    PRESSED,
    CLICKED,
    NUMBER_OF_BUTTON_STATES
  };
};

缺点是NUMBER_OF_BUTTON_STATES现在是一个有效的Button::State值。如果您将这些值作为ints传递,这不是一个大问题。但是如果您实际上希望得到一个Button::State,则会有麻烦。 使用枚举作为数组索引感觉不太对。 没关系。只需记录它,这样下一个人就知道发生了什么!(这就是注释的作用。) 我必须指定枚举的值吗? 没有“=”赋值,枚举应该从零开始递增。
如果枚举条目具有“=”分配值,则后续的非“=”枚举条目继续从那里计数。
来源:The Annotated C++ Reference Manual,第113页
话虽如此,我喜欢指定初始值,以使代码更加清晰。

2
你应该使用命名空间而不是一个空类,这样更能准确地匹配你的意图。 - Tom
3
C和C++语言标准规定,枚举类型的第一个枚举常量的值为0,除非你给它赋了其他的值。任何不遵守这一规定的编译器都是不符合标准的。 - ChrisN
2
ChrisN是正确的,但我曾经使用过这样的编译器,可悲的是——而且在过去的10年里。如果您对交叉编译器兼容性有任何疑虑,请指定为零。 - Patrick Johnmeyer
类将具有ButtonState和ButtonColors成员,根据OP的要求。我将它们省略以避免分散注意力。 - Mr.Ree
2
在我之前的一份工作中,我为不同的目标使用了三个不同的编译器,所以我可能记错了——但我相信这是Green Hills C++编译器的一个特定版本。如果未设置第一个值,则第一个值可以是任何值;这基本上就像编译器对第一个值使用未初始化的变量一样。 - Patrick Johnmeyer

26

没问题,它会很好地工作。不过,在任何情况下,您都应该在枚举中添加另一个条目来定义物品数量的值:真的

enum ButtonState {
    BUTTON_NORMAL,
    BUTTON_PRESSED,
    BUTTON_CLICKED,
    STATE_COUNT
};

然后您可以像这样定义数组:
Color colors[STATE_COUNT];

否则,将状态的数量与数组的大小同步是一件混乱的事情。如果没有特别初始化,枚举将始终从零开始,然后每个附加条目将被分配一个比前一个大1的值,如果没有特别初始化。当然,如果您希望,也可以明确地放置一个零。如果您不介意添加其他代码,我建议使用类似以下函数封装对原始数组的访问:
Color & operator[](ButtonState state) {
    return array[state];
}

或者一个等效的 getColor 函数来转发请求。这将禁止直接使用某个整数索引数组,因为几乎肯定会因为索引错误而在某些时候失败。


15

使用枚举是可以的。但你不需要为每个项指定值。只需指定第一个值就足够了。我不会假设枚举从0开始,因为我用过一些编译器,它们将1作为起始值(不是PC的编译器,而是一些微控制器的编译器有一些奇怪的行为)。 此外,您可以摆脱const:

enum ButtonState {
    BUTTON_NORMAL = 0,
    BUTTON_PRESSED,
    BUTTON_CLICKED,
    NUM_BUTTON_STATES
};

5
你的意思是“你不应该假设编译器按照标准要求执行”,这并不是根本性的坏建议。标准要求enum从0开始,除非另有规定,程序员不应该回避利用它-或者寻找一个称职的编译器。 - underscore_d

5

就样式而言,它还可以。

像Delphi这样基于Pascal的语言允许将数组边界指定为枚举类型,因此您只能使用该特定类型的项作为索引。


3
问题1:我认为这是良好的编程风格,我一直在使用它。 问题2:据我所知,它保证以这种方式工作,因此您不必指定值。
并且我也会将NUM_BUTTON_STATES放入枚举中。

1

在数组索引中使用枚举是非常正常的。

您不必指定每个枚举值,它们将自动递增1。让编译器选择值可以减少输错和创建错误的可能性,但这也会使您无法查看这些值,这在调试时可能会有用。


如果枚举是作用域枚举(例如枚举结构体或类),则在C++中无法将其用于索引。对于作用域枚举,我们需要使用显式转换。在此处阅读更多信息:链接 - Shashank
@Shashank 我认为当我写这个答案时,作用域枚举并不存在,看起来它们是在C++11中添加的。 - Mark Ransom

1

这样做没问题,但我想对数组进行一些边界检查,因为如果有人添加了另一个ButtonState,你就会遇到问题。

此外,colors数组的元素是不可变的,所以也许可以考虑使用不同的集合来代替数组,以便你可以强制实现不可变性。也许使用Dictionary<ButtonState,ButtonColor>会更好。


谁说数组不能是不可变的?而且C++没有叫做“Dictionary”的标准容器;也许你指的是“map”。但是,对于大多数只有简单和连续数字“键”的情况,我并不认为这有什么意义;最多,它避免了用户强制执行/记住索引顺序,但这是以查找速度为代价的。 - underscore_d

0
一种方法是在类内部创建一个内部向量,并重写运算符[]:(这里是在线代码的链接
#include <iostream>
#include <vector>

template<typename DATA_TYPE, typename INDEX_TYPE>
class MyVector
{
protected:
    std::vector<DATA_TYPE> pp;

public:    
    template<typename ... ARGS>
    MyVector(ARGS ... args): pp{args...} {};

    DATA_TYPE& operator[](INDEX_TYPE x) { 
        return pp[static_cast<size_t>(x)];
    };

    auto size() const { return pp.size(); }

    // implement other menthods


};

enum class EWeekDays{
    SUN, MON, TUE, WED, THU, FRI, SAT
};


int main()
{
    MyVector<int, EWeekDays> q{2,3,4,5};

    std::cout << q[EWeekDays::MON] << "\n";

    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接