如何确保用户输入允许的枚举

How to make sure user enters allowed enum

本文关键字:输入 枚举 用户 何确保 确保      更新时间:2023-10-16

我必须用Enum状态编写一个程序,Enum状态是50个两字母的状态缩写(NY、FL等)。我需要制作一个程序,询问用户信息,他们用户需要键入与状态对应的2个字母。我如何检查他们的输入是否有效,即是否匹配枚举状态{AL,…,WY}中定义的2个字母的状态?我想我可以做一个巨大的if语句,检查input=="AL"||…||input=="WY"{do-struff}else{error input与状态不匹配},但必须对所有50个状态都这样做会有点可笑。有更简单的方法吗?

此外,如果枚举状态被定义为{AL,NY,FL},我如何将用户输入(可能是字符串)转换为状态?如果我把州改为{"AL"、"NY"、"FL"},这会更容易吗?或者有其他方法吗?

不幸的是,C++没有提供将枚举转换为字符串的可移植方法,反之亦然。可能的解决方案是填充std::map<std::string,State>(或哈希映射),并在转换时进行查找。您可以手动填充这样的映射,也可以创建一个简单的脚本,该脚本将在.cpp文件中生成一个函数来填充该映射,并在构建过程中调用该脚本。您的脚本可以生成if/else语句,而不是填充映射。

另一个更困难但更灵活稳定的解决方案是使用类似llvm的编译器,并根据编译器生成的语法树制作一个插件来生成这样的函数。

最简单的方法是使用STL std::map,但对于可能不允许的学术练习(例如,可能需要仅使用课程材料中涵盖的技术)。

除非显式初始化,否则枚举都是从零开始按顺序进行整数编号的。考虑到这一点,您可以扫描字符串的查找表,并将匹配的索引强制转换为枚举。例如:

enum eUSstate
{
    AL, AK, AZ, ..., NOSTATE
} ;
eUSstate string_to_enum( std::string inp )
{
    static const int STATES = 50 ;
    std::string lookup[STATES] = { "AL", "AK", "AZ" ... } ;
    int i = 0 ;
    for( i = 0; i < STATES && lookup[i] != inp; i++ )
    {
        // do nothing
    }
    return static_cast<eUSstate>(i) ;
}

如果您可能不想依赖暴力强制转换并按照与枚举相同的顺序维护查找表,那么可以使用同时具有字符串和匹配枚举的查找表。

eUSstate string_to_enum( std::string inp )
{
    static const int STATES = 50 ;
    struct
    {
        std::string state_string ;
        eUSstate state_enum ;
    } lookup[STATES] { {"AL", AL}, {"AK", AK}, {"AZ", AL} ... } ;
    eUSstate ret = NOSTATE ;
    for( int i = 0; ret == NOSTATE && i < STATES; i++ )
    {
        if( lookup[i].state_string == inp )
        {
            ret = lookup[i].state_enum ;
        }
    }
    return ret ;
}

查找可以通过利用字母顺序和执行二进制搜索来优化,但对于50个状态来说,这几乎不值得。

您需要的是一个表。因为枚举是线性的,一个简单的字符串表就足够了:

char const* const stateNames[] =
{
    //  In the same order as in the enum.
    "NY",
    "FL",
    //  ...
};

然后:

char const* const* entry 
        = std::find( std::begin( stateNames ), std::end( stateNames ), userInput );
if (entry == std::end( stateNames ) ) {
    //  Illegal input...
} else {
    State value = static_cast<State>( entry - std::begin( stateNames ) );

或者,你可以有一个数组:

struct StateMapping
{
    State enumValue;
    char const* name;
    struct OrderByName
    {
        bool operator()( StateMapping const& lhs, StateMapping const& rhs ) const
        {
            return std::strcmp( lhs.name, rhs. name ) < 0;
        }
        bool operator()( StateMapping const& lhs, std::string const& rhs ) const
        {
            return lhs.name < rhs;
        }
        bool operator()( std::string const& lhs, StateMapping const& rhs ) const
        {
            return lhs < rhs.name;
        }
    };
};
StateMapping const states[] = 
{
    { NY, "NY" },
    //  ...
};

按密钥排序,并使用std::lower_bound:

StateMapping::OrderByName cmp;
StateMapping entry =
        std::lower_bound( std::begin( states ), std::end( states ), userInput, cmp );
if ( entry == std::end( states ) || cmp( userInput, *entry) {
    //  Illegal input...
} else {
    State value = entry->enumValue;
    //  ...
}

后者可能稍微快一点,但只有50条目,我怀疑你是否会注意到差异。

当然,您不会手动编写这些代码;您生成用一个简单的脚本。(过去,我有代码解析枚举定义的C++源,并生成映射功能。它比听起来更简单,因为您可以忽略C++代码的大块,而不是用于跟踪各种嵌套。)

解决方案很简单,但只适用于字符串中的2个字符(在您的情况下):

#include <stdio.h>
#include <stdint.h>
enum TEnum
{
    AL = 'LA',
    NY = 'YN',
    FL = 'LF'
};

 int _tmain(int argc, _TCHAR* argv[])
{
    char* input = "NY";
    //char* input = "AL";
    //char* input = "FL";

    switch( *(uint16_t*)input )
    {
        case AL:
            printf("input AL");
            break;
        case NY:
            printf("input NY");
            break;
        case FL:
            printf("input FL");
            break;
    }
    return 0;
}

在上面的例子中,我使用了一个带有双字符代码的枚举(这是合法的),并向switch语句传递了一个输入字符串。我测试了它的最终工作!。请注意枚举中的单词对齐。

Ciao