[英]How can I iterate over an enum?
我剛剛注意到您不能在++
或+=
等enum
上使用標准數學運算符。
那么遍歷 C++ enum
中所有值的最佳方法是什么?
典型的方式如下:
enum Foo {
One,
Two,
Three,
Last
};
for ( int fooInt = One; fooInt != Last; fooInt++ )
{
Foo foo = static_cast<Foo>(fooInt);
// ...
}
請注意,枚舉Last
旨在被迭代跳過。 利用這個“假” Last
枚舉,您不必在每次要添加新枚舉時將 for 循環中的終止條件更新為最后一個“真實”枚舉。 如果您想稍后添加更多枚舉,只需在 Last 之前添加它們。 此示例中的循環仍然有效。
當然,如果指定了枚舉值,這將失效:
enum Foo {
One = 1,
Two = 9,
Three = 4,
Last
};
這說明枚舉並不是真的要迭代。 處理枚舉的典型方法是在 switch 語句中使用它。
switch ( foo )
{
case One:
// ..
break;
case Two: // intentional fall-through
case Three:
// ..
break;
case Four:
// ..
break;
default:
assert( ! "Invalid Foo enum value" );
break;
}
如果您真的想枚舉,請將枚舉值填充到向量中並對其進行迭代。 這也將正確處理指定的枚舉值。
#include <iostream>
#include <algorithm>
namespace MyEnum
{
enum Type
{
a = 100,
b = 220,
c = -1
};
static const Type All[] = { a, b, c };
}
void fun( const MyEnum::Type e )
{
std::cout << e << std::endl;
}
int main()
{
// all
for ( const auto e : MyEnum::All )
fun( e );
// some
for ( const auto e : { MyEnum::a, MyEnum::b } )
fun( e );
// all
std::for_each( std::begin( MyEnum::All ), std::end( MyEnum::All ), fun );
return 0;
}
使用 c++11,實際上有一個替代方案:編寫一個模板化的自定義迭代器。
假設您的枚舉是
enum class foo {
one,
two,
three
};
這個通用代碼將非常有效地解決問題 - 放置在通用標頭中,它將為您可能需要迭代的任何枚舉提供服務:
#include <type_traits>
template < typename C, C beginVal, C endVal>
class Iterator {
typedef typename std::underlying_type<C>::type val_t;
int val;
public:
Iterator(const C & f) : val(static_cast<val_t>(f)) {}
Iterator() : val(static_cast<val_t>(beginVal)) {}
Iterator operator++() {
++val;
return *this;
}
C operator*() { return static_cast<C>(val); }
Iterator begin() { return *this; } //default ctor is good
Iterator end() {
static const Iterator endIter=++Iterator(endVal); // cache it
return endIter;
}
bool operator!=(const Iterator& i) { return val != i.val; }
};
你需要專攻它
typedef Iterator<foo, foo::one, foo::three> fooIterator;
然后您可以使用 range-for 進行迭代
for (foo i : fooIterator() ) { //notice the parentheses!
do_stuff(i);
}
您的枚舉中沒有空白的假設仍然是正確的; 沒有假設存儲枚舉值實際需要的位數(感謝 std::underlying_type)
這些解決方案太復雜了,我喜歡這樣:
enum NodePosition { Primary = 0, Secondary = 1, Tertiary = 2, Quaternary = 3};
const NodePosition NodePositionVector[] = { Primary, Secondary, Tertiary, Quaternary };
for (NodePosition pos : NodePositionVector) {
...
}
我經常這樣做
enum EMyEnum
{
E_First,
E_Orange = E_First,
E_Green,
E_White,
E_Blue,
E_Last
}
for (EMyEnum i = E_First; i < E_Last; i = EMyEnum(i + 1))
{}
或者如果不是連續的,但有規律的步驟(例如位標志)
enum EAnimalCaps
{
E_None = 0,
E_First = 0x1,
E_CanFly = E_First,
E_CanWalk = 0x2
E_CanSwim = 0x4,
E_Last
}
class MyAnimal
{
EAnimalCaps m_Caps;
}
class Frog
{
Frog() :
m_Caps(EAnimalCaps(E_CanWalk | E_CanSwim))
{}
}
for (EAnimalCaps= E_First; i < E_Last; i = EAnimalCaps(i << 1))
{}
如果您的枚舉以 0 開頭並且增量始終為 1。
enum enumType
{
A = 0,
B,
C,
enumTypeEnd
};
for(int i=0; i<enumTypeEnd; i++)
{
enumType eCurrent = (enumType) i;
}
如果不是,我猜唯一的原因是創建類似
vector<enumType> vEnums;
添加項目,並使用普通迭代器....
其他答案中未涵蓋的內容=如果您使用的是強類型 C++11 枚舉,則不能在它們上使用++
或+ int
。 在這種情況下,需要一些更混亂的解決方案:
enum class myenumtype {
MYENUM_FIRST,
MYENUM_OTHER,
MYENUM_LAST
}
for(myenumtype myenum = myenumtype::MYENUM_FIRST;
myenum != myenumtype::MYENUM_LAST;
myenum = static_cast<myenumtype>(static_cast<int>(myenum) + 1)) {
do_whatever(myenum)
}
你不能用枚舉。 也許枚舉不是最適合您的情況。
一個常見的約定是將最后一個枚舉值命名為 MAX,並使用它來控制使用 int 的循環。
假設枚舉按順序編號很容易出錯。 此外,您可能只想迭代選定的枚舉數。 如果該子集很小,則顯式循環它可能是一個優雅的選擇:
enum Item { Man, Wolf, Goat, Cabbage }; // or enum class
for (auto item : {Wolf, Goat, Cabbage}) { // or Item::Wolf, ...
// ...
}
enum class A {
a0=0, a3=3, a4=4
};
constexpr std::array<A, 3> ALL_A {A::a0, A::a3, A::a4}; // constexpr is important here
for(A a: ALL_A) {
if(a==A::a0 || a==A::a4) std::cout << static_cast<int>(a);
}
constexpr std::array
甚至可以迭代非順序枚舉,而無需編譯器實例化數組。 這取決於編譯器的優化啟發式方法以及您是否采用數組的地址。
在我的實驗中,我發現如果有 2 個非連續值或相當多的連續值(我測試了多達 6 個),帶有-O3
的g++
9.1 將優化上述數組。 但只有當你有if
語句時它才會這樣做。 (我嘗試了一個語句,它比較了一個大於順序數組中所有元素的整數值,盡管沒有排除任何元素,但它內聯了迭代,但是當我省略 if 語句時,這些值被放入內存中。)它還內聯了 5 [one case| 中非順序枚舉的值] https://godbolt.org/z/XuGtoc] 。 我懷疑這種奇怪的行為是由於深度啟發式與緩存和分支預測有關。
這是godbolt 上一個簡單測試迭代的鏈接,它演示了數組並不總是被實例化。
這種技術的代價是兩次寫入枚舉元素並保持兩個列表同步。
您可以嘗試定義以下宏:
#define for_range(_type, _param, _A1, _B1) for (bool _ok = true; _ok;)\
for (_type _start = _A1, _finish = _B1; _ok;)\
for (int _step = 2*(((int)_finish)>(int)_start)-1;_ok;)\
for (_type _param = _start; _ok ; \
(_param != _finish ? \
_param = static_cast<_type>(((int)_param)+_step) : _ok = false))
現在你可以使用它了:
enum Count { zero, one, two, three };
for_range (Count, c, zero, three)
{
cout << "forward: " << c << endl;
}
它可用於通過無符號、整數、枚舉和字符來向后和向前迭代:
for_range (unsigned, i, 10,0)
{
cout << "backwards i: " << i << endl;
}
for_range (char, c, 'z','a')
{
cout << c << endl;
}
盡管它的定義很尷尬,但它的優化非常好。 我查看了 VC++ 中的反匯編程序。 該代碼非常有效。 三個for語句不要被推遲:編譯器在優化后只會產生一個循環! 您甚至可以定義封閉循環:
unsigned p[4][5];
for_range (Count, i, zero,three)
for_range(unsigned int, j, 4, 0)
{
p[i][j] = static_cast<unsigned>(i)+j;
}
您顯然不能遍歷有間隙的枚舉類型。
您還可以為枚舉類型重載遞增/遞減運算符。
這是另一種僅適用於連續枚舉的解決方案。 它給出了預期的迭代,除了增量中的丑陋,這是它所屬的地方,因為這在 C++ 中被破壞了。
enum Bar {
One = 1,
Two,
Three,
End_Bar // Marker for end of enum;
};
for (Bar foo = One; foo < End_Bar; foo = Bar(foo + 1))
{
// ...
}
如果你不喜歡用最終的 COUNT 項污染你的枚舉(因為也許如果你也在開關中使用枚舉,那么編譯器會警告你缺少 case COUNT:),你可以這樣做:
enum Colour {Red, Green, Blue};
const Colour LastColour = Blue;
Colour co(0);
while (true) {
// do stuff with co
// ...
if (co == LastColour) break;
co = Colour(co+1);
}
在 Bjarne Stroustrup 的 C++ 編程語言書中,您可以讀到他建議為您的特定enum
重載operator++
。 enum
是用戶定義的類型,語言中存在針對這些特定情況的重載運算符。
您將能夠編寫以下代碼:
#include <iostream>
enum class Colors{red, green, blue};
Colors& operator++(Colors &c, int)
{
switch(c)
{
case Colors::red:
return c=Colors::green;
case Colors::green:
return c=Colors::blue;
case Colors::blue:
return c=Colors::red; // managing overflow
default:
throw std::exception(); // or do anything else to manage the error...
}
}
int main()
{
Colors c = Colors::red;
// casting in int just for convenience of output.
std::cout << (int)c++ << std::endl;
std::cout << (int)c++ << std::endl;
std::cout << (int)c++ << std::endl;
std::cout << (int)c++ << std::endl;
std::cout << (int)c++ << std::endl;
return 0;
}
測試代碼: http ://cpp.sh/357gb
請注意,我正在使用enum class
。 代碼也適用於enum
。 但我更喜歡enum class
,因為它們是強類型的,可以防止我們在編譯時出錯。
評論中已經討論了 std::initializer_list (C++11)。 我提到了迭代枚舉的示例。
或 std::initializer_list 和更簡單的語法:
enum E {
E1 = 4,
E2 = 8,
// ..
En
};
constexpr std::initializer_list<E> all_E = {E1, E2, /*..*/ En};
接着
for (auto e : all_E) {
// Do job with e
}
參考鏈接
以下是一些非常易讀且易於理解的方法,適用於弱類型C 和 C++ 常規enum
以及強類型C++ enum class
。
我建議使用-Wall -Wextra -Werror
編譯以下所有示例。 這為您提供了額外的安全性,如果您忘記在switch
case 中覆蓋任何枚舉值,您的編譯器將拋出編譯時錯誤! 這迫使您保持枚舉定義和切換案例同步,這是您的代碼的額外安全措施。 只要您:
switch
盒中的所有枚舉值,並且default
的開關盒。-Wall -Wextra -Werror
標志構建。我建議您遵循所有這 3 點,因為這是一種很好的做法並且可以創建更好的代碼。
enum
:C 定義(這也是有效的 C++):
typedef enum my_error_type_e
{
MY_ERROR_TYPE_SOMETHING_1 = 0,
MY_ERROR_TYPE_SOMETHING_2,
MY_ERROR_TYPE_SOMETHING_3,
MY_ERROR_TYPE_SOMETHING_4,
MY_ERROR_TYPE_SOMETHING_5,
/// Not a valid value; this is the number of members in this enum
MY_ERROR_TYPE_count,
// helpers for iterating over the enum
MY_ERROR_TYPE_begin = 0,
MY_ERROR_TYPE_end = MY_ERROR_TYPE_count,
} my_error_type_t;
C++ 定義:
enum my_error_type_t
{
MY_ERROR_TYPE_SOMETHING_1 = 0,
MY_ERROR_TYPE_SOMETHING_2,
MY_ERROR_TYPE_SOMETHING_3,
MY_ERROR_TYPE_SOMETHING_4,
MY_ERROR_TYPE_SOMETHING_5,
/// Not a valid value; this is the number of members in this enum
MY_ERROR_TYPE_count,
// helpers for iterating over the enum
MY_ERROR_TYPE_begin = 0,
MY_ERROR_TYPE_end = MY_ERROR_TYPE_count,
};
在這個弱類型枚舉上的C 或 C++ 迭代:
注意:不允許通過執行my_error_type++
來增加枚舉——甚至在 C 風格的枚舉上也不行,所以我們必須這樣做: my_error_type = (my_error_type_t)(my_error_type + 1)
。 但是請注意, my_error_type + 1
是允許的,因為這個弱枚舉在此處自動隱式轉換為int
以使添加成為可能,而不必手動將其轉換為 int,如下所示: my_error_type = (my_error_type_t)((int)my_error_type + 1)
。
for (my_error_type_t my_error_type = MY_ERROR_TYPE_begin;
my_error_type < MY_ERROR_TYPE_end;
my_error_type = (my_error_type_t)(my_error_type + 1))
{
switch (my_error_type)
{
case MY_ERROR_TYPE_SOMETHING_1:
break;
case MY_ERROR_TYPE_SOMETHING_2:
break;
case MY_ERROR_TYPE_SOMETHING_3:
break;
case MY_ERROR_TYPE_SOMETHING_4:
break;
case MY_ERROR_TYPE_SOMETHING_5:
break;
case MY_ERROR_TYPE_count:
// This case will never be reached.
break;
}
}
enum class
:C++ 定義:
enum class my_error_type_t
{
SOMETHING_1 = 0,
SOMETHING_2,
SOMETHING_3,
SOMETHING_4,
SOMETHING_5,
/// Not a valid value; this is the number of members in this enum
count,
// helpers for iterating over the enum
begin = 0,
end = count,
};
在這個強類型枚舉上的 C++ 迭代:
請注意強制增加enum class
變量所需的額外(size_t)
強制轉換(或(int)
也可以接受)! 我還選擇在這里使用 C++ 風格的static_cast<my_error_type_t>
演員表,但如上所述,C 風格的(my_error_type_t)
演員表也可以。
for (my_error_type_t my_error_type = my_error_type_t::begin;
my_error_type < my_error_type_t::end;
my_error_type = static_cast<my_error_type_t>((size_t)my_error_type + 1))
{
switch (my_error_type)
{
case my_error_type_t::SOMETHING_1:
break;
case my_error_type_t::SOMETHING_2:
break;
case my_error_type_t::SOMETHING_3:
break;
case my_error_type_t::SOMETHING_4:
break;
case my_error_type_t::SOMETHING_5:
break;
case my_error_type_t::count:
// This case will never be reached.
break;
}
}
還要注意范圍。 在 C++強類型enum class
中,我使用my_error_type_t::
來訪問每個作用域enum class
成員。 但是,在 C 風格的弱類型常規enum
中,可以實現非常相似的作用域,正如我所展示的,只需在每個enum
成員名稱前加上MY_ERROR_TYPE_
。 因此,C++強類型enum class
添加作用域這一事實並沒有真正增加太多價值——這實際上只是個人偏好。 C++強類型enum class
具有額外的類型安全性這一事實也有利有弊。 在某些情況下它可能會對您有所幫助,但它肯定會使增加枚舉並對其進行迭代變得很痛苦,老實說,這意味着它正在完成它的工作。 通過使增加作用域enum class
變量變得更加困難,就好像它是一個整數一樣,C++強類型enum class
正在完全按照它的設計目的進行。 您是否想要這種行為取決於您。 就個人而言,我經常不希望這種行為,因此即使在 C++ 中,我也更喜歡使用 C 風格的枚舉並不少見。
enum class
es(強類型枚舉)和常規enum
s(弱類型枚舉)之間的一些差異的回答: 如何自動將強類型枚舉轉換為 int?-Wall -Wextra -Werror
和其他構建選項的個人筆記,來自我的eRCaGuy_hello_world 存儲庫。對於 MS 編譯器:
#define inc_enum(i) ((decltype(i)) ((int)i + 1))
enum enumtype { one, two, three, count};
for(enumtype i = one; i < count; i = inc_enum(i))
{
dostuff(i);
}
注意:這比簡單的模板化自定義迭代器答案要少得多。
您可以通過使用typeof
而不是decltype
來使其與 GCC 一起使用,但我目前沒有方便的編譯器來確保它可以編譯。
優點:枚舉可以按您喜歡的任何順序具有您喜歡的任何值,並且迭代它們仍然很容易。 名稱和值在第一個#define 中定義一次。
缺點:如果你在工作中使用它,你需要一個完整的段落來向你的同事解釋它。 而且,必須聲明內存以給循環提供一些可以迭代的內容很煩人,但我不知道有一種解決方法不會將您限制在具有相鄰值的枚舉(如果枚舉總是具有相鄰值,則無論如何,枚舉可能不會給你買那么多東西。)
//create a, b, c, d as 0, 5, 6, 7
#define LIST x(a) x(b,=5) x(c) x(d)
#define x(n, ...) n __VA_ARGS__,
enum MyEnum {LIST}; //define the enum
#undef x //needed
#define x(n,...) n ,
MyEnum myWalkableEnum[] {LIST}; //define an iterable list of enum values
#undef x //neatness
int main()
{
std::cout << d;
for (auto z : myWalkableEnum)
std::cout << z;
}
//outputs 70567
使用未定義的宏包裝器聲明列表,然后在各種情況下以不同方式定義包裝器的技巧,除此之外還有很多應用程序。
將變量轉換為int&
可以讓您在保持類型可讀的同時遞增。
#include <iostream>
enum MyEnum
{
ONE,
TWO,
THREE,
FOUR,
};
int main()
{
for (MyEnum v = MyEnum::ONE; v <= MyEnum::FOUR; ++(int&)v)
{
std::cout<<v<<std::endl;
}
return 0;
}
0
1
2
3
(以馬爾斯基的回答作為一個很大的暗示......)
由於枚舉定義的主體與初始化列表相同,因此如果我們使用簡單的宏來寫出值,則可以在不重復項列表的情況下執行此操作:
#define ITEM_LIST_MACRO Wolf, Goat, Cabbage
enum Item { ITEM_LIST_MACRO }; // Define the enum
// Now iterate through it
for (auto item : { ITEM_LIST_MACRO }) {
}
優點:簡單,沒有重復,不需要維護煩人的第一個/最后一個哨兵值。 (實際上,我認為這可能是迄今為止建議的唯一解決方案,它不需要用戶記住在將新項目添加到列表時更新“結束”標記。)
缺點:不適用於范圍枚舉(枚舉類),因為初始化列表需要范圍(Item::Wolf 等)。 如果您想指定枚舉成員的值而不是讓它們默認,也不起作用。
如果您知道枚舉值是連續的,例如 Qt:Key 枚舉,您可以:
Qt::Key shortcut_key = Qt::Key_0;
for (int idx = 0; etc...) {
....
if (shortcut_key <= Qt::Key_9) {
fileMenu->addAction("abc", this, SLOT(onNewTab()),
QKeySequence(Qt::CTRL + shortcut_key));
shortcut_key = (Qt::Key) (shortcut_key + 1);
}
}
它按預期工作。
typedef enum{
first = 2,
second = 6,
third = 17
}MyEnum;
static const int enumItems[] = {
first,
second,
third
}
static const int EnumLength = sizeof(enumItems) / sizeof(int);
for(int i = 0; i < EnumLength; i++){
//Do something with enumItems[i]
}
擴展@Eponymous 的答案:很好,但不提供通用語法。 這是我想出的:
// Common/EnumTools.h
#pragma once
#include <array>
namespace Common {
// Here we forward-declare metafunction for mapping enums to their values.
// Since C++<23 doesn't have reflection, you have to populate it yourself :-(
// Usage: After declaring enum class E, add this overload in the namespace of E:
// inline constexpr auto allValuesArray(const E&, Commob::EnumAllValuesTag) { return std::array{E::foo, E::bar}; }
// Then `AllValues<NS::E>` will call `allValuesArray(NS::E{}, EnumAllValuesTag)` which will resolve
// by ADL.
// Just be sure to keep it sync'd with your enum!
// Here's what you want to use in, e.g., loops: "for (auto val : Common::AllValues<MyEnum>) {"
struct EnumAllValuesTag {}; // So your allValuesArray function is clearly associated with this header.
template <typename Enum>
static inline constexpr auto AllValues = allValuesArray(Enum{}, EnumAllValuesTag{});
// ^ Just "constexpr auto" or "constexpr std::array<Enum, allValuesArray(Enum{}, EnumAllValuesTag{}).size()>" didn't work on all compilers I'm using, but this did.
} // namespace Common
然后在您的命名空間中:
#include "Common/EnumTools.h"
namespace MyNamespace {
enum class MyEnum {
foo,
bar = 4,
baz = 42,
};
// Making this not have to be in the `Common` namespace took some thinking,
// but is a critical feature since otherwise there's no hope in keeping it sync'd with the enum.
inline constexpr auto allValuesArray(const MyEnum&, Common::EnumAllValuesTag) {
return std::array{ MyEnum::foo, MyEnum::bar, MyEnum::baz };
}
} // namespace MyNamespace
然后在任何需要使用它的地方:
for (const auto& e : Common::AllValues<MyNamespace::MyEnum>) { ... }
所以即使你已經 typedef 了:
namespace YourNS {
using E = MyNamespace::MyEnum;
} // namespace YourNS
for (const auto& e : Common::AllValues<YourNS::E>) { ... }
除了查看此頁面的每個人都想要的實際語言功能之外,我想不出比這更好的了。
未來的工作:
constexpr
函數(以及一個元函數)來過濾Common::AllValues<E>
以提供Common::AllDistinctValues<E>
用於具有重復數值的枚舉的情況,例如enum { foo = 0, bar = 0 };
.switch
-covers-all- enum
-values 來寫入allValuesArray
,這樣如果枚舉添加了一個值,它就會出錯。使用 lambda,我發現這是循環枚舉的最佳(現代)方式。 這極大地提高了抽象性。 甚至可以使其成為模板,因此它適用於任何枚舉。 此代碼既不會給您帶來 clang(-tidy) 的問題。
#include <functional>
/// @brief Loop over all enum values where the last enum value is the invalid one
void forEachAction(std::function<void(Enum)> &&doThis) {
for (int value = 0; value = static_cast<int>(Enum::LastValue); ++value ) {
doThis(static_cast<Enum>(value ));
}
}
...
forEachAction([this](Enum value) {
... // what you want to execute for every enum
});
大多數解決方案都基於 (MIN, MAX) 范圍內的循環,但忽略了枚舉中可能存在漏洞的事實。
我的建議是:
for (int i = MYTYPE_MIN; i <= MYTYPE_MAX; i++) {
if (MYTYPE_IsValid(i)) {
MYTYPE value = (MYTYPE)i;
// DoStuff(value)
}
}
C++ 沒有自省功能,因此您無法在運行時確定這種事情。
只需創建一個整數數組並遍歷該數組,但將最后一個元素設為 -1 並將其用於退出條件。
如果枚舉是:
enum MyEnumType{Hay=12,Grass=42,Beer=39};
然后創建數組:
int Array[] = {Hay,Grass,Beer,-1};
for (int h = 0; Array[h] != -1; h++){
doStuff( (MyEnumType) Array[h] );
}
這不會破壞表示中的整數,只要 -1 檢查當然不與其中一個元素發生沖突。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.