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






4.94/5 (13投票s)
在发表了关于 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_DECLARE
的 enum_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;
}
现在,我已经介绍了我的代码结构方式。我将**添加新功能的**方式将始终相同
- 声明用于声明和定义的
__SMARTENUM_FUNCTION
(**函数签名**) - 创建将**每个宏**条目转换为代码的
__SMARTENUM_MACRO_ENTRY_TO
宏。 - 使用**宏定义**和
__SMARTENUM_MACRO_ENTRY_TO
宏创建__SMARTENUM_DEFINE_FUNCTION
。 - 将这个小世界绑定到我的
SMARTENUM_DECLARE
和SMARTENUM_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;\
}
然后,**我希望**编译器会将所有这些无用的赋值**简化为最后一个** :)
否则,我们也可以使用 at
和 size
**函数**来返回最后一个元素!对于这个实现,我**仍然不太满意**,所以如果您有更好的解决方案,请拿走!
最小和最大元素
同样,我不确定获取枚举的**最小和最大元素**的函数的实际用途(也许是因为您懒得自己找出?)。但无论如何...
最小元素
就像在正常最小函数**循环内部**的代码一样
#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 宏**的使用上更进一步。我希望它能帮助一些开发者**未来的项目**。
如果您有任何**改进该库的**想法,请随时发表评论 ;)!