65.9K
CodeProject 正在变化。 阅读更多。
Home

C 中的智能枚举库(使用 X 宏)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (13投票s)

2016 年 8 月 18 日

CPOL

13分钟阅读

viewsIcon

20624

downloadIcon

171

在发表了关于 X 宏的第一篇文章后,我将介绍如何构建一个宏库来扩展 C 语言中枚举的功能。

引言

在我的第一篇文章中,我解释了 **X 宏**的工作原理,并提供了一个关于**如何将枚举转换为字符串**的示例。本文中的所有内容都基于**X 宏**,因此我强烈建议您在阅读本文之前先了解一下我关于此主题的文章

事实上,我选择这个例子并非偶然。我现在正在为我的项目使用这个宏库。

在本文中,我想展示如何创建一个更完整、更有用的**X 宏库**,以向**C 枚举**添加功能。

由于该主题已部分涵盖,我将重点介绍如何在X 宏文章所述内容的基础上添加更多功能。

您可以**下载**库的源代码 + 一个示例 main

下载 smartenum_example.zip - 2.4 KB

整个项目也可以在我的GitHub上找到。

基本思想

前面所述,这个库的起点是我想有一个**高效的方法**来将**枚举值显示为字符串**而不是数值。

假设我们处理这个枚举

typedef enum IceCreamFlavor
{
    CHOCOLATE = 56,
    VANILLA = 27,
    PISTACHIO = 72,
} 
IceCreamFlavor;

然后,我想要一个**自动生成的函数**,该函数**以字符串形式返回**给定枚举元素的**名称**

// what I want
printf("%s", IceCreamFlavor_toString(CHOCOLATE));
// the printed result shall be CHOCOLATE (and not 56)

这是创建用于将枚举值转换为字符串的函数的基本思想。但是宏库可以扩展以**生成更多函数**!

让我们开始编码

基本工具

在我的 C 项目中,由于我倾向于**接近面向对象编程**,我经常这样命名类函数

classname_funcname

所以我声明一个像这样的宏 $

#define $(class, method) class##_##method

所以我可以这样声明和调用类方法

int $(MyStruct, getWidth)(MyStruct* me)
{
    if(me)
    {
        return me->width;
    }
    return 0;
}

MyStruct struct;
int width = $(MyStruct, getWidth)(&struct);

因此,即使对于这个库,我也会使用宏 $

命名约定

为了**避免宏名称冲突**,我将所有子宏**前缀**为 __SMARTENUM

将 **X 宏条目**转换为代码行的子宏将前缀为 __SMARTENUM_MACRO_ENTRY_TO

骨干

在本节中,我将描述**我的代码组织方式**以及将使用的**最终接口**。

与我在X 宏文章中所做的不同,我将**声明**与**定义**分开。

该库使用**X 宏定义**来定义**枚举**,如下所示

#define SMARTENUM_IceCreamFlavor(_)\
    _(CHOCOLATE, 56)\ 
    _(VANILLA, 27)\ 
    _(PISTACHIO, 72)

然后,两个**最终宏**将是

// to declare an enum with enum_name and the MACRO_DEFINITION entries
SMARTENUM_DECLARE(MACRO_DEFINITION, enum_name)
// to define the associated functions for the enum enum_name
SMARTENUM_DEFINE(MACRO_DEFINITION, enum_name)

使用示例

/* in .h file */
#define SMARTENUM_IceCreamFlavor(_)\
    _(CHOCOLATE, 56)\
    _(VANILLA, 27)\
    _(PISTACHIO, 72)

SMARTENUM_DECLARE(SMARTENUM_IceCreamFlavor, IceCreamFlavor)
// to generate both IceCreamFlavor enum and associated functions declarations

/*-----------*/

/* in .c file */
SMARTENUM_DEFINE(SMARTENUM_IceCreamFlavor, IceCreamFlavor)
// to generate associated functions definitions

枚举声明

该库的第一步是**构建枚举**。因此,我们希望构建一个宏,该宏将**枚举的宏定义**转换为**C 枚举**。该宏将被 SMARTENUM_DECLARE 调用。

首先,我们需要一个宏来将**每个 X 宏条目**转换为**枚举定义的元素行**

#define __SMARTENUM_MACRO_ENTRY_TO_ENUM_ELEMENT(element_name, element_value)\
    element_name = element_value,

然后,我们可以创建**构建枚举**的宏

#define __SMARTENUM_DECLARE_ENUM(MACRO_DEFINITION, enum_name)\
typedef enum enum_name\
{\
    MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_ENUM_ELEMENT)\
}\
enum_name;

这就是**构建枚举**所需的一切。如果您还不信服,请查看X 宏文章

我们将其添加到 SMARTENUM_DECLARE

#define SMARTENUM_DECLARE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DECLARE_ENUM(MACRO_DEFINITION, enum_name)

添加 toString 函数

如下所述,我将**声明与定义**分开。

我想添加一个自动生成的函数,该函数**基于枚举的宏定义**,将**枚举值转换为其字符串名称**。

让我们看看这样的函数**如果手动编写**会是什么样子

const char* IceCreamFlavors_toString(IceCreamFlavors flavor)
{
    switch(flavor)
    {
    case CHOCOLATE: return "CHOCOLATE";
    case VANILLA: return "VANILLA";
    case PISTACHIO: return "PISTACHIO";
    default:
        // the error handling might seem a bit too strict !
        return 0;
    }
}

声明

对于声明,我们只需要使用传递给 SMARTENUM_DECLAREenum_name 参数。

然后我们可以使用这个宏

#define __SMARTENUM_DECLARE_FUNCTION_TOSTRING(enum_name)\
const char* $(enum_name, toString)(enum_name value);

但是,由于我喜欢**避免重复使用相同的代码**,我更喜欢

1. 创建第一个宏 __SMARTENUM_DECLARE_FUNCTION

// it just add semicolon at the end
#define __SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION, enum_name) __SMARTENUM_FUNCTION(enum_name);

2. 创建宏 __SMARTENUM_FUNCTION_TOSTRING

#define __SMARTENUM_FUNCTION_TOSTRING(enum_name)\
const char* $(enum_name, toString)(enum_name value)

3. 然后调用它进行声明

__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_TOSTRING, enum_name)

这样做,我也可以为定义重用 __SMARTENUM_FUNCTION_TOSTRING。我确保在声明和定义中**谈论的是同一件事**。

定义

对于定义,我们最需要考虑的是如何将 X 宏定义的**条目行**转换为**此函数代码片段**。

事实上,这很明显。使用

// we need two parameters because this macro is working on X macro definition entries
// that have two parameters
#define __SMARTENUM_MACRO_ENTRY_TO_TOSTRING_CASE(element_name, element_value)\
    case element_name: return #element_name;

我们将

_(CHOCOLATE, 56)
//into
case CHOCOLATE: return "CHOCOLATE";

然后,我们可以声明**构建函数的宏**

#define __SMARTENUM_DEFINE_FUNCTION_TOSTRING(MACRO_DEFINITION, enum_name)\
__SMARTENUM_FUNCTION_TOSTRING(enum_name)\
{\
    switch(value)\
    {\
        MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_TOSTRING_CASE)\
        default: return 0;\
    }\
}

填充接口

现在我们有了两部分,**声明和定义**,让我们在接口中实现它

// we add the declaration to SMARTENUM_DECLARE
#define SMARTENUM_DECLARE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DECLARE_ENUM(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_TOSTRING, enum_name)

// and the definition to the SMARTENUM_DEFINE
#define SMARTENUM_DEFINE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_TOSTRING(MACRO_DEFINITION, enum_name)\

就是这样!

此时我们可以**测试该库**

// in .h file
#define SMARTENUM_IceCreamFlavor(_)\
    _(CHOCOLATE, 56)\
    _(VANILLA, 27)\
    _(PISTACHIO, 72)

SMARTENUM_DECLARE(SMARTENUM_IceCreamFlavor, IceCreamFlavor)

// in .c file
SMARTENUM_DEFINE(SMARTENUM_IceCreamFlavor, IceCreamFlavor)

int main()
{
    IceCreamFlavor flavor = VANILLA;
    printf("%s\n", $(IceCreamFlavor, toString)(flavor)); // shall print VANILLA (if we are lucky !)
    return 0;
}

现在,我已经介绍了我的代码结构方式。我将**添加新功能的**方式将始终相同

  1. 声明用于声明和定义的 __SMARTENUM_FUNCTION(**函数签名**)
  2. 创建将**每个宏**条目转换为代码的 __SMARTENUM_MACRO_ENTRY_TO 宏。
  3. 使用**宏定义**和 __SMARTENUM_MACRO_ENTRY_TO 宏创建 __SMARTENUM_DEFINE_FUNCTION
  4. 将这个小世界绑定到我的 SMARTENUM_DECLARESMARTENUM_DEFINE 最终宏。

因此,在介绍**下一个功能**时我会更快。现在我们将更有趣!

添加功能

我想添加的第一个功能与 **toString** 定义相关

#define __SMARTENUM_DEFINE_FUNCTION_TOSTRING(MACRO_DEFINITION, enum_name)\
__SMARTENUM_FUNCTION_TOSTRING(enum_name)\
{\
    switch(value)\
    {\
        MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_TOSTRING_CASE)\
        default: return 0;\
    }\
}

在函数中,当给定值**不是有效的枚举元素**时,返回的是**NULL 指针**。这可能显得有些粗鲁。但我希望如此。

但是最粗鲁的部分是我们**没有函数可以告诉我们什么是一个有效的枚举元素**。所以让我们添加它!

添加 isValid 函数

声明

我们想要一个**返回布尔值**并接受**枚举值作为参数**的函数。所以宏将是

#define __SMARTENUM_FUNCTION_ISVALID(enum_name)\
bool $(enum_name, isValid)(enum_name value)

定义

对于定义,我从 **toString** 函数中获得灵感。我们可以切换给定**枚举值**,并且**对于每个** **宏定义条目**,返回 true。否则,我们返回 false 作为默认值。

所以 __SMARTENUM_MACRO_ENTRY_TO 宏将是

#define __SMARTENUM_MACRO_ENTRY_TO_ISVALID_CASE(element_name, element_value)\
    case element_name: return true;

函数定义将与 **toString** 定义几乎相同

#define __SMARTENUM_DEFINE_FUNCTION_ISVALID(MACRO_DEFINITION, enum_name)\
__SMARTENUM_FUNCTION_ISVALID(enum_name)\
{\
    switch(value)\
    {\
        MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_ISVALID_CASE)\
        default: return false;\
    }\
}

填充接口

现在,我们可以用我们新创建的宏来填充接口

#define SMARTENUM_DECLARE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DECLARE_ENUM(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_ISVALID, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_TOSTRING, enum_name)

#define SMARTENUM_DEFINE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_ISVALID(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_TOSTRING(MACRO_DEFINITION, enum_name)

然后测试

int main()
{
    IceCreamFlavor flavors[] = {(IceCreamFlavor)12, PISTACHIO};
    size_t number_of_flavors = sizeof(flavors)/sizeof(IceCreamFlavor);
    size_t i;
    
    for(i=0; i<number_of_flavors; ++i)
    {
        IceCreamFlavor flavor = flavors[i];
        
        if($(IceCreamFlavor, isValid)(flavor))
        {
            printf("%s\n", $(IceCreamFlavor, toString)(flavor), flavor);
        }
        else
        {
            printf("%d is not a valid IceCreamFlavor value !\n", flavor);
        }    
    }

    // shall print :
    // > 12 is not a valid IceCreamFlavor value !
    // > PISTACHIO

    return 0;
}

我们已经**确保**了 toString 函数的使用,通过添加一个**有效性检查函数**。

让我们再添加一些功能!

添加 fromString 函数

现在我们有了将**枚举值转换为字符串**的所有东西,我们可能需要相反的操作。

让我们编写代码来**将字符串转换为枚举值**。

声明

我们想要一个**返回枚举值**并接受**字符串作为参数**的函数。所以宏将是

#define __SMARTENUM_FUNCTION_FROMSTRING(enum_name)\
enum_name $(enum_name, fromString)(const char* str_value)

定义

像往常一样,我们必须构建将**X 宏定义条目**转换为**代码**的宏。我们只需要将给定的 str_value 与**每个枚举元素(作为字符串)**进行比较。如果匹配,则返回**枚举元素**

#define __SMARTENUM_MACRO_ENTRY_TO_FROMSTRING_COMPARE(element_name, element_value)\
     if(!strcmp(#element_name, str_value)) return element_name;

然后,我们将此生成的代码包含在**宏构建的函数**中

#define __SMARTENUM_DEFINE_FUNCTION_FROMSTRING(MACRO_DEFINITION, enum_name)\
__SMARTENUM_FUNCTION_FROMSTRING(enum_name)\
{\
    MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_FROMSTRING_COMPARE)\
    /* error handling : I return -1 as error value */ \
    return -1;\
}

如果我们使用示例枚举扩展此宏,则构建的函数将是

IceCreamFlavor IceCreamFlavor_fromString(const char* str_value)
{
    if(!strcmp("CHOCOLATE", str_value)) return CHOCOLATE;
    if(!strcmp("VANILLA", str_value)) return VANILLA;
    if(!strcmp("PISTACHIO", str_value)) return PISTACHIO;
    /* error handling : I return -1 as error value */
    return -1;
}
处理返回的错误值

上面介绍的函数是第一次尝试。但我对**错误处理**不太满意。

返回 -1 会**消耗一个有效的枚举条目**。然后陈述

$(MyEnum, isValid)($(MyEnum, fromString)("UNKNOWN VALUE")) == false

使用*“未知值”*表示的无效条目**并不总是正确的**。

如果*MyEnum*定义如下

#define SMARTENUM_MyEnum(_)\
    _(DOG, -1)\
    _(CAT, 0)\
    _(COW, 118)

然后*$(MyEnum, fromString)("UNKNOWN VALUE")* **将返回 -1**,它是 DOG 的值,而 DOG 是一个**有效的枚举条目**。

为了避免这种**意外行为**,我所做的是稍微**修改枚举声明宏**

// I first declare a util macro to create a element name for unknown value
// I choose a complex name to reduce the chance that it is humanly declared
#define __SMARTENUM_MACRO_ENTRY_UNKNOWN_VALUE(enum_name) __##enum_name##_UNKNOWN_VALUE__

// Then, I add this element at the end of the enum declaration, 
// so I am sure that it is not consuming a value of the declaration
#define __SMARTENUM_DECLARE_ENUM(MACRO_DEFINITION, enum_name)\
typedef enum enum_name\
{\
    MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_ENUM_ELEMENT)\
    __SMARTENUM_MACRO_ENTRY_UNKNOWN_VALUE(enum_name)\
}\
enum_name;

然后,而不是**返回 -1**(这相当随意),我返回 UNKNOWN_VALUE

#define __SMARTENUM_DEFINE_FUNCTION_FROMSTRING(MACRO_DEFINITION, enum_name)\
__SMARTENUM_FUNCTION_FROMSTRING(enum_name)\
{\
    MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_FROMSTRING_COMPARE)\
    return __SMARTENUM_MACRO_ENTRY_UNKNOWN_VALUE(enum_name);\
}

因此,调用*$(MyEnum, fromString)("UNKNOWN VALUE")*现在将返回 **119**(**__MyEnum_UNKNOWN_VALUE__**),它**不是一个有效的枚举条目**。

填充接口

现在,我们可以用我们新创建的宏来填充接口

#define SMARTENUM_DECLARE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DECLARE_ENUM(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_ISVALID, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_TOSTRING, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_FROMSTRING, enum_name)

#define SMARTENUM_DEFINE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_ISVALID(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_TOSTRING(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_FROMSTRING(MACRO_DEFINITION, enum_name)

然后测试

int main()
{
    const char* flavors_str[] = {"VANILLA", "SHAMPOO"};
    size_t number_of_flavors_str = sizeof(flavors_str)/sizeof(const char*);
    size_t j;
    
    for(j=0; j<number_of_flavors; ++j)
    {
        const char* flavor_str = flavors_str[j];
        
        IceCreamFlavor flavor = $(IceCreamFlavor, fromString)(flavor_str);
        
        if($(IceCreamFlavor, isValid)(flavor))
        {
            printf("%s is a valid IceCreamFlavor, its value is %d\n", flavor_str, flavor);
        }
        else
        {
            printf("%s is not a valid IceCreamFlavor !", flavor_str);
        }
    }

    // shall print :
    // > VANILLA is a valid IceCreamFlavor, its value is 27
    // > SHAMPOO is not a valid IceCreamFlavor !

    return 0;
}

现在我们有了一个函数可以**将字符串转换为枚举值**。如果您需要从**配置文件**中检索枚举值,这将非常有用。

但是,与 toString 函数一样,为了完全安全,我们需要一个函数来检查**给定的字符串是否是有效的枚举条目**。

添加 isStringValid 函数

声明

我们想要一个**返回布尔值**并接受**字符串作为参数**的函数。所以宏将是

#define __SMARTENUM_FUNCTION_ISSTRINGVALID(enum_name)\
bool $(enum_name, isStringValid)(const char* str_value)

定义

对于定义,我从 fromString 函数中获得了灵感。我们仍然**将给定参数**与**每个枚举元素(作为字符串)**进行比较,但不是返回元素值,而是返回 true

所以 __SMARTENUM_MACRO_ENTRY_TO 宏将是

#define __SMARTENUM_MACRO_ENTRY_TO_ISSTRINGVALID_COMPARE(element_name, element_value)\
    if(!strcmp(#element_name, str_value)) return true;

函数定义将与 fromString 定义几乎相同

#define __SMARTENUM_DEFINE_FUNCTION_ISSTRINGVALID(MACRO_DEFINITION, enum_name)\
__SMARTENUM_FUNCTION_ISSTRINGVALID(enum_name)\
{\
    MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_ISSTRINGVALID_COMPARE)\
    return false;\
}

填充接口

现在,我们可以用我们新创建的宏来填充接口

#define SMARTENUM_DECLARE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DECLARE_ENUM(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_ISVALID, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_TOSTRING, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_ISSTRINGVALID, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_FROMSTRING, enum_name)

#define SMARTENUM_DEFINE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_ISVALID(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_TOSTRING(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_ISSTRINGVALID(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_FROMSTRING(MACRO_DEFINITION, enum_name)

然后测试

int main()
{
    const char* flavors_str[] = {"VANILLA", "SHAMPOO"};
    size_t number_of_flavors_str = sizeof(flavors_str)/sizeof(const char*);
    size_t j;
    
    for(j=0; j<number_of_flavors; ++j)
    {
        const char* flavor_str = flavors_str[j];
        
        if($(IceCreamFlavor, isStringValid)(flavor_str))
        {
            printf("%s is a valid IceCreamFlavor, its value is %d\n", flavor_str, $(IceCreamFlavor, fromString)(flavor_str));
        }
        else
        {
            printf("%s is not a valid IceCreamFlavor !", flavor_str);
        }
    }

    // shall print :
    // > VANILLA is a valid IceCreamFlavor, its value is 27
    // > SHAMPOO is not a valid IceCreamFlavor !

    return 0;
}

现在,我们有了一套完整的函数来**序列化枚举值**。

遍历枚举

接下来我将展示的函数与**枚举迭代**有关。

我必须承认,起初我**对枚举迭代的实际用途没有想法**。我添加这些函数**更多是为了好玩而不是真正的需要**。

但稍微思考一下**迭代枚举的实用性**后,我发现了一个**有效示例**。

假设您必须为您的应用程序用户提供**配置文件**。在此配置文件中,用户必须**设置一个属性**,该属性是一个**枚举值**。然后,您可能希望告知用户**哪些值是有效的**。文件可能看起来像

# flavor. accepted values are : CHOCOLATE, VANILLA, PISTACHIO
flavor=CHOCOLATE

然后,当您**自动生成配置文件的注释**时,遍历枚举的不同值可能会派上用场。这样,每次您**更新枚举**(添加或删除值)时,**配置文件上的注释将始终更新**。

如何使用

在介绍所需函数之前,我将**从一些代码开始**,看看**迭代将如何工作**

size_t number_of_flavors = $(IceCreamFlavor, size)();
size_t i;

for(i=0; i<number_of_flavors; ++i)
{
    IceCreamFlavor flavor = $(IceCreamFlavor, at)(i);
}

因此,正如您所见,当我说迭代时,它非常简单。我**没有引入迭代器类**。我只想有一个函数可以返回我**枚举的第 n 个元素**,以及另一个告诉我枚举**元素总数**的函数。

计算元素数量

声明

该函数**不接受参数**并以 size_t 返回**枚举的大小**

#define __SMARTENUM_FUNCTION_SIZE(enum_name)\
size_t $(enum_name, size)()

定义

由于我的库处理**非连续枚举**,我**无法做到**像这样简单的事情

typedef enum MyEnum
{
    ONE,
    TWO,
    THREE,

    NUMBER_OF_ELEMENTS
}
MyEnum;

printf("Number of elements of MyEnum : %d", NUMBER_OF_ELEMENTS);

但我仍然有我的**X 宏定义**来帮忙。让我们看看如何使用它来**计算元素数量**。

这个想法很简单:我们只需要**为每个 X 宏条目** **递增一个变量**。

在第一个版本中,我做了类似的事情

#define __SMARTENUM_MACRO_ENTRY_TO_SIZE_COUNT(element_name, element_value)\
    ++size;

然后是完整的函数

#define __SMARTENUM_DEFINE_FUNCTION_SIZE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_FUNCTION_SIZE(enum_name)\
{\
    static size_t size = 0;\
    MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_SIZE_COUNT);\
    return size;\
}

这样做的麻烦在于,每次调用函数时,元素数量**都会在运行时计算**。我找到了一个解决方案来避免这种情况,**通过缓存结果**,但我们**仍然在第一次调用时在运行时计算该值**。

问题在于我们**所有信息都可以在编译时获得**,所以我们希望**编译器一次性为我们计算**。

所以,这是一个更智能的版本。从 __SMARTENUM_MACRO_ENTRY_TO 宏开始

#define __SMARTENUM_MACRO_ENTRY_TO_SIZE_COUNT(element_name, element_value)\
    + 1

然后是完整的函数

#define __SMARTENUM_DEFINE_FUNCTION_SIZE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_FUNCTION_SIZE(enum_name)\
{\
    static const size_t size = 0 MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_SIZE_COUNT);\
    return size;\
}

这样,我们将每个**X 宏条目**转换为一个 *+ 1*。看看我们示例宏的扩展函数

size_t $(IceCreamFlavor, size)()
{
    static const size_t size = 0 + 1 + 1 + 1;
    return size;
}

编译器将执行加法,**我们摆脱了运行时计算**!

填充接口

现在,我们可以用我们新创建的宏来填充接口

#define SMARTENUM_DECLARE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DECLARE_ENUM(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_ISVALID, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_TOSTRING, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_ISSTRINGVALID, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_FROMSTRING, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_SIZE, enum_name)

#define SMARTENUM_DEFINE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_ISVALID(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_TOSTRING(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_ISSTRINGVALID(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_FROMSTRING(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_SIZE(MACRO_DEFINITION, enum_name)

然后测试

int main()
{
    printf("IceCreamFlavor enum has %d entries\n", $(IceCreamFlavor, size)());

    return 0;
}

按索引访问元素

现在,我们需要实现**按索引访问元素**的函数。

声明

该函数**接受索引**作为参数,并返回**相应的枚举值**

#define __SMARTENUM_FUNCTION_AT(enum_name)\
enum_name $(enum_name, at)(size_t index)

定义

有**不同的实现方式**。如果我们考虑**之前的实现**,我们可以用**一系列 if 语句**来实现。效率不高,因为我们需要**检查每个先前的值**直到找到正确的值。

对于实现,我将使用一个**静态表**来存储所有枚举值。这样,枚举值就可以通过索引访问。

首先,我们需要将每个**X 宏条目**转换为**表条目**

#define __SMARTENUM_MACRO_ENTRY_TO_TABLE_ENTRY(element_name, element_value)\
    element_name,

然后我们可以实现整个函数

#define __SMARTENUM_DEFINE_FUNCTION_AT(MACRO_DEFINITION, enum_name)\
__SMARTENUM_FUNCTION_AT(enum_name)\
{\
    /* the static table with all enum values */ \
    static const enum_name indexed_values[] = \
    {\
        MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_TABLE_ENTRY)\
    };\
    /* the static size of the table */ \
    static const size_t sizeof_indexed_values = sizeof(indexed_values)/sizeof(enum_name);\
    if(index < sizeof_indexed_values)\
    {\
        return indexed_values[index];\
    }\
    /* we return the UNKNOWN_VALUE is the index is out of range */ \
    return __SMARTENUM_MACRO_ENTRY_UNKNOWN_VALUE(enum_name);\
}

填充接口

现在,我们可以用我们新创建的宏来填充接口

#define SMARTENUM_DECLARE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DECLARE_ENUM(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_ISVALID, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_TOSTRING, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_ISSTRINGVALID, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_FROMSTRING, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_SIZE, enum_name)\
__SMARTENUM_DECLARE_FUNCTION(__SMARTENUM_FUNCTION_AT, enum_name)

#define SMARTENUM_DEFINE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_ISVALID(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_TOSTRING(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_ISSTRINGVALID(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_FROMSTRING(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_SIZE(MACRO_DEFINITION, enum_name)\
__SMARTENUM_DEFINE_FUNCTION_AT(MACRO_DEFINITION, enum_name)

然后测试

int main(int argc, char *argv[])
{
    size_t size_of_IceCreamFlavor = $(IceCreamFlavor, size)();
    size_t i;

    printf("There is %d entries in IceCreamFlavor:\n", size_of_IceCreamFlavor);
    
    for(i=0; i<size_of_IceCreamFlavor; ++i)
    {
        IceCreamFlavor flavor = $(IceCreamFlavor, at)(i);
        printf("\t- %s (%d)\n", $(IceCreamFlavor, toString)(flavor), flavor);
    }
    
    return 0;
}

现在,我们已经拥有了遍历枚举所需的一切!

最终示例使用

下面,我将介绍**此库的使用示例**,使用所有已构建的函数

int main(int argc, char *argv[])
{
    char choice[100] = "";
    
    size_t size_of_IceCreamFlavor = $(IceCreamFlavor, size)();
    size_t i;
    bool flavor_chosen = false;
    IceCreamFlavor final_choice;
    
    printf("ICE CREAM SALER - Hi there, which flavor do you want ?\n");
    
    while(!flavor_chosen)
    {
        printf("ME - What do you have ?\n");
        printf("ICE CREAM SALER - I have %d different flavors:\n", size_of_IceCreamFlavor);
        for(i=0; i<size_of_IceCreamFlavor; ++i)
        {
            IceCreamFlavor flavor = $(IceCreamFlavor, at)(i);
            printf("\t- %s\n", $(IceCreamFlavor, toString)(flavor), flavor);
        }
        printf("ICE CREAM SALER - What do you want ?\n");
        printf("ME - ");
        scanf("%s", choice);
        
        if($(IceCreamFlavor, isStringValid)(choice))
        {
            final_choice = $(IceCreamFlavor, fromString)(choice);
            flavor_chosen = true;
        }
        else
        {
            printf("ICE CREAM SALER - Sorry man, I don't have %s !\n", choice);
        }
    }
    
    if($(IceCreamFlavor, isValid)(final_choice))
    {
        printf("ICE CREAM SALER - OK %s, great choice !\n",  $(IceCreamFlavor, toString)(final_choice));
    }
    else
    {
        printf("ICE CREAM SALER - Are you f*****g kidding ! I don't have this flavor !\n");
    }
    
    return 0;
}

奖励

现在,纯粹为了好玩,**再添加一些函数**。

对于这些示例,我将很快。我将只向您展示**定义部分**。我让您自己测试它们。

我没有这些函数的实际用途,但**也许有人会觉得有用**。如果觉得有用,请随时**在评论中告诉我** ;) !

另外,**我并不声称**生成的算法**是最优的**,尽管我已经尽力了!如果您有**更好的解决方案**,也可以**在评论中添加**!

第一个和最后一个元素

我们可能(也可能不需要)需要获取枚举的**第一个**或**最后一个元素**。

第一个元素

为了**返回第一个元素**,我所做的是为每个**X 宏条目**返回**枚举元素**。**第一次返回将退出**函数,而**所有其他 return 语句将被忽略**!

将每个**X 宏条目**转换为**return 语句**的宏

#define __SMARTENUM_MACRO_ENTRY_TO_FIRST_RETURN(element_name, element_value)\
    return element_name;

然后完整的函数是

#define __SMARTENUM_DEFINE_FUNCTION_FIRST(MACRO_DEFINITION, enum_name)\
__SMARTENUM_FUNCTION_FIRST(enum_name)\
{\
    MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_FIRST_RETURN);\
}

使用示例

printf("first entry is %s", $(IceCreamFlavor, toString)( $(IceCreamFlavor, first)() ));

最后一个元素

对于返回最后一个元素,我所做的是用 X 宏定义的每个条目**为同一个变量赋值**。变量的最终值将是**最后一次赋值**!

#define __SMARTENUM_MACRO_ENTRY_TO_LAST_ASSIGNMENT(element_name, element_value)\
    last = element_name;

然后完整的函数是

#define __SMARTENUM_DEFINE_FUNCTION_LAST(MACRO_DEFINITION, enum_name)\
__SMARTENUM_FUNCTION_LAST(enum_name)\
{\
    static enum_name last;\
    MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_LAST_ASSIGNMENT);\
    return last;\
}

然后,**我希望**编译器会将所有这些无用的赋值**简化为最后一个** :)

否则,我们也可以使用 atsize**函数**来返回最后一个元素!对于这个实现,我**仍然不太满意**,所以如果您有更好的解决方案,请拿走!

最小和最大元素

同样,我不确定获取枚举的**最小和最大元素**的函数的实际用途(也许是因为您懒得自己找出?)。但无论如何...

最小元素

就像在正常最小函数**循环内部**的代码一样

#define __SMARTENUM_MACRO_ENTRY_TO_MIN_COMPARE(element_name, element_value)\
    if(element_name < min) min = element_name;

然后我们可以构建完整的函数

然后完整的函数是

#define __SMARTENUM_DEFINE_FUNCTION_MIN(MACRO_DEFINITION, enum_name)\
__SMARTENUM_FUNCTION_MIN(enum_name)\
{\
    /* I use this boolean to cache the result */\
    static bool already_computed = false;\
    static enum_name min;\
    if(!already_computed)\
    {\
        /* initialization to the first element */\
        min = $(enum_name, first)();\
        MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_MIN_COMPARE)\
        already_computed = true;\
    }\
    return min;\
}

但是我不喜欢用布尔技巧来**缓存结果**,因为**每次调用函数时**,都会**检查布尔值**。为了避免这种情况,我更喜欢创建一个**私有函数来执行计算**而不缓存结果,然后我在**静态变量初始化**时调用这个函数

#define __SMARTENUM_DEFINE_FUNCTION_MIN(MACRO_DEFINITION, enum_name)\
enum_name $(enum_name, min_private)()\
{\
    enum_name min = $(enum_name, first)();\
    MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_MIN_COMPARE)\
    return min;\
}\
__SMARTENUM_FUNCTION_MIN(enum_name)\
{\
    static enum_name min = $(enum_name, min_private)();\
    return min;\
}

这样,min_private** 的代码**只会被执行一次**,当**静态变量**被初始化时。然后,调用**min**函数将只返回结果**,而不考虑任何缓存**。

最大元素

代码基本相同

#define __SMARTENUM_MACRO_ENTRY_TO_MAX_COMPARE(element_name, element_value)\
    if(element_name > max) max = element_name;

完整的函数是

#define __SMARTENUM_DEFINE_FUNCTION_MAX(MACRO_DEFINITION, enum_name)\
enum_name $(enum_name, max_private)()\
{\
    enum_name max = $(enum_name, first)();\
    MACRO_DEFINITION(__SMARTENUM_MACRO_ENTRY_TO_MAX_COMPARE)\
    return max;\
}\
__SMARTENUM_FUNCTION_MAX(enum_name)\
{\
    static enum_name max = $(enum_name, max_private)();\
    return max;\
}

到此为止!

结论

我希望您**喜欢这篇文章**,并且**不觉得它与** 我关于 X 宏的第一篇文章**太重复**。

我的目标是为您提供**增强 C 语言枚举的完整库**,详细展示**我创建它的方式**,并介绍**您可以使用 X 宏完成的各种事情**。

MACRO_DEFINITION 可以被视为一个 foreach 语句,遍历给定的条目。该语句接受一个**回调函数**作为参数,并将其应用于条目。

在最后一节中,我想展示**添加新功能有多么容易**。我停在**min**和**max**,因为它们的实际用途对我来说并不明显。但我也可以添加一个**mean**函数、一个**sort**函数,任何**适用于列表的函数**。

在这篇文章中,我想在**X 宏**的使用上更进一步。我希望它能帮助一些开发者**未来的项目**。

如果您有任何**改进该库的**想法,请随时发表评论 ;)!

 

© . All rights reserved.