16

いくつかの列挙値を指定すると、印刷可能な文字列を返すヘルパー デバッガー メソッドを記述していることがよくあります。この理由は、通常、列挙型をログに記録する場合、取得するのは実際には数値だけです。その列挙型が何であるかを理解するためにソースに戻る必要はありません。だから私は次のようなことをします

typedef enum
{
   kOne = 1,
   kTwo,
   kThree,
}
MyEnum;

NSString *debugStringForEnum(MyEnum e)
{
    switch ( e )
        case kOne:
            return @"One";
        case kTwo:
            return @"Two";
        ....
}

....
NSLog(@"My debug log: %@", debugStringForEnum(someVariable));

だから私の質問は、列挙型のラベル値を見るためだけに、このヘルパーコードをすべて書くことを避ける方法はありますか?

ありがとう

4

4 に答える 4

23

他の開発者を泣かせる「いたずらな」コードを書く気があるなら、そうです。これを試して:

#define ENUM(name, ...) typedef enum { M_FOR_EACH(ENUM_IDENTITY, __VA_ARGS__) } name; \
char * name ## _DEBUGSTRINGS [] = { M_FOR_EACH(ENUM_STRINGIZE, __VA_ARGS__) };

#define ENUM_IDENTITY(A) A,
#define ENUM_STRINGIZE(A) #A,

ENUM(MyEnum,
        foo, bar, baz, boo
        )

これを機能させるには、明らかにfor-eachマクロが必要です。これが簡単なものです:

#define M_NARGS(...) M_NARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define M_NARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N

#define M_CONC(A, B) M_CONC_(A, B)
#define M_CONC_(A, B) A##B

#define M_FOR_EACH(ACTN, ...) M_CONC(M_FOR_EACH_, M_NARGS(__VA_ARGS__)) (ACTN, __VA_ARGS__)

#define M_FOR_EACH_0(ACTN, E) E
#define M_FOR_EACH_1(ACTN, E) ACTN(E)
#define M_FOR_EACH_2(ACTN, E, ...) ACTN(E) M_FOR_EACH_1(ACTN, __VA_ARGS__)
#define M_FOR_EACH_3(ACTN, E, ...) ACTN(E) M_FOR_EACH_2(ACTN, __VA_ARGS__)
#define M_FOR_EACH_4(ACTN, E, ...) ACTN(E) M_FOR_EACH_3(ACTN, __VA_ARGS__)
#define M_FOR_EACH_5(ACTN, E, ...) ACTN(E) M_FOR_EACH_4(ACTN, __VA_ARGS__)
#define M_FOR_EACH_6(ACTN, E, ...) ACTN(E) M_FOR_EACH_5(ACTN, __VA_ARGS__)
#define M_FOR_EACH_7(ACTN, E, ...) ACTN(E) M_FOR_EACH_6(ACTN, __VA_ARGS__)
#define M_FOR_EACH_8(ACTN, E, ...) ACTN(E) M_FOR_EACH_7(ACTN, __VA_ARGS__)
#define M_FOR_EACH_9(ACTN, E, ...) ACTN(E) M_FOR_EACH_8(ACTN, __VA_ARGS__)
#define M_FOR_EACH_10(ACTN, E, ...) ACTN(E) M_FOR_EACH_9(ACTN, __VA_ARGS__)

そのループを拡張して上限を長くする方法は明らかですが、この回答にはスペースに関する考慮事項があります。ループは、このビットに追加の反復をコピーして貼り付けることをいとわない限り、潜在的に長くなる可能性があります。

非デバッグビルドの場合は、#ifdefに2行目のないバージョンのENUMを選択させます。


編集:指定されたイニシャライザーのアイデアをteppicから盗むために、順序付けされていないイニシャライザー値でも機能するさらに恐ろしいバージョンを次に示します。

#define ENUM(name, ...) typedef enum { M_FOR_EACH(ENUM_ENAME, __VA_ARGS__) } name; \
char * name ## _DEBUGSTRINGS [] = { M_FOR_EACH(ENUM_ELEM, __VA_ARGS__) };

#define ENUM_ENAME(A) M_IF(M_2ITEMS(M_ID A), (M_FIRST A = M_SECOND A), (A)),
#define ENUM_ELEM(A) M_IF(M_2ITEMS(M_ID A), ([M_FIRST A] = M_STR(M_FIRST A)), ([A] = M_STR(A))),

#define M_STR(A) M_STR_(A)
#define M_STR_(A) #A
#define M_IF(P, T, E) M_CONC(M_IF_, P)(T, E)
#define M_IF_0(T, E) M_ID E
#define M_IF_1(T, E) M_ID T
#define M_2ITEMS(...) M_2I_(__VA_ARGS__, 1, 0)
#define M_2I_(_2, _1, N, ...) N
#define M_FIRST(A, ...) A
#define M_SECOND(A, B, ...) B
#define M_ID(...) __VA_ARGS__

ENUM(MyEnum,
        foo, bar, baz, boo
        )

ENUM(NotherEnum,
        A, B, (C, 12), D, (E, 8)
        )

他の誰かが維持しなければならないコードでこの種のものを使用する場合、私はあなたの個人的な安全を保証することはできません。

于 2013-03-05T22:19:01.693 に答える
8

より簡単な方法は、配列内の位置に従ってラベルを複製する文字列リテラルの配列を設定することです。

char *enumlabels[] = { NULL, "KOne", "KTwo", "KThree"};

ここでは、列挙値が配列の位置と一致するようにギャップを埋める必要があります。

または、指定されたイニシャライザを使用する C99 の場合:

char *enumlabels[] = { [1] = "KOne", [2] = "KTwo", [3] = "KThree"};

この場合、enum 宣言が最初に来る限り、配列の添え字を enum 値に直接交換して、より明確にすることができます{ [kOne] = "kOne", ... }

それからMyEnum eあなたはただ使うことができるprintf("%s\n", enumlabels[e]);か、またはそのようなものです。

これをよりよく示すために、少しコードを書きました。

typedef enum {
   white = 1,
   red   = 2,
   green = 4,
   blue  = 8,
   black = 16
} Colour;   // be sure to update array below!

char *enum_colours[] = { [white] = "white", [red] = "red", [green] = "green",
                         [blue] = "blue", [black] = "black" };

Colour c = blue;
printf("The label for %d is %s\n", c, enum_colours[c]);

Output: The label for 8 is blue

巨大な列挙型定数 (32767 など) がある場合、これは明らかに理想的なソリューションではありません。必要な配列のサイズが原因です。指定された初期化子がなければ、配列の値を直接割り当てることもできますが、より手間がかかる場合は、などを使用できますがenum_colours[white] = "white";、関数内でのみ可能です。

于 2013-03-05T22:16:51.140 に答える
2

これらのシンボルはそれまでに使用されていないため、実行時に列挙名を取得することはできませんが、複数文字の定数を使用して、より意味のある列挙型の値を作成できます。

#import <Foundation/Foundation.h>

NSString* debugString(int en) {
    return [NSString stringWithFormat:@"%c%c%c%c",
            (en>>24) & 0xff,
            (en>>16) & 0xff,
            (en>>8) & 0xff,
            en & 0xff];
}

typedef enum {
    kOne = 'One.',
    kTwo = 'Two.',
    kThree = 'Thre',
} MyEnum;

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        NSLog(@"kOne = %@", debugString(kOne));
        NSLog(@"kTwo = %@", debugString(kTwo));
        NSLog(@"kThree = %@", debugString(kThree));
    }
    return 0;
}

印刷します

kOne = 1。
kTwo = 2。
kThree = スリー

コンソールで。

debugStringガベージを生成しないようにするには、各列挙型の長さを正確に 4 文字にする必要があります (とにかく OSX 上で) 。これは、コンパイラとプラットフォームに大きく依存します。デバッグには適していますが、他にはあまりありません。

もちろん、列挙型に特定の値または相互に関連する値が必要な場合、これは機能しません。

于 2013-03-06T00:39:42.520 に答える