减少 if-elseif 语句的聪明方法

clever way to reduce if-elseif statements

我正在开发一个代码来将 SpinBox 限制为字母而不是整数。一切正常,但如果有任何聪明的方法,我想减少 if-elseif 语句。这是代码

std::string AlphaSpinBox::textFromValue(int value) 
{
    // I feel the code is Ok but willing to change it if there is a better way.
    // value is restricted [0-25] inclusive. 
    std::string str("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
    return std::string(str[value]);
}

int AlphaSpinBox::valueFromText(std::string &text) 
{
    // can I shorten the following?!
    // text is solely one letter (i.e. either upper or lower)
    if(text == 'A' || text == 'a')
        return 0;
    else if(text == 'B' || text == 'b' )
        return 1;
    else if(text == 'C' || text == 'c')
        return 2;
    else if(text == 'D' || text == 'd')
        return 3;
    ... to z letter
}

根据 ASCII table,每个字母都有一个整数值。如果你查一下,你还会发现字母放在 table 中很方便:a 到 z 都直接跟在后面,A 到 Z 也一样。

可以先判断小写还是大写再判断returntext[0] - 'a',或者text[0] - 'b'.

关于:

 if (text.size()>0 && std::isalpha(text[0]))
     return std::toupper(text[0])-'A'; 
 else return -1;    // or throw an exception 

这里是online demo

工作原理:它首先检查字符串是否不为空以及第一个字符是否为字母 (with isalpha()). If it's valid, as you make no difference between lowercase and uppercase, we convert the char toupper()。由于您的 return 值是按字母顺序排列的,因此我们只需要减去第一个字母即可。

您可以通过使用字符串的 find 成员函数并稍微更改函数签名来消除 valueFromText 函数中的多个 if 语句:

int valueFromText(const std::string& s, char c) {
    return s.find(std::toupper(static_cast<unsigned char>(c)));
}

编写一个小 class 或带有静态成员的结构 (std::map - look up table) 和一些充当包装器的静态方法绝对适用于您的情况。代码的使用相当干净、可读、易于使用,应该是可移植和可重用的。


注意: -只要系统的字符代码是按顺序定义的,这就可以正常工作;否则,您将需要一些其他机制来 initialize 静态地图。


AlphaSpinBox.h

#ifndef ALPHA_SPIN_BOX_H
#define ALPHA_SPIN_BOX_H

#include <string>
#include <cctype>
#include <map>

struct AlphaSpinBox {    
    // static table
    static std::map<unsigned, std::string> table_;    
    // Must be called first
    static void initializeMap();    
    // helper function
    static std::string toUpper( const std::string& str );    
    // get string from value
    static std::string textFromValue( const unsigned& val );        
    // get value from string
    static unsigned valueFromText( const std::string& text );

    // other member's, functions etc. that you may have for this class
};

#endif // !ALPHA_SPIN_BOX_H

AlphaSpinBox.cpp

#include "AlphaSpinBox.h"

// define static member
std::map<unsigned, std::string> AlphaSpinBox::table_;

void AlphaSpinBox::initializeMap() {
    // Could do some checks here to see if this function has not been called
    // then display a message to the user that this function needs to be called first;
    // and to check if it has already been called once before; warning the user
    // that this method should only initialize the map once per application run.
    static char c = 'A';
    static std::string str;
    for ( unsigned n = 0; n < 26; n++ ) {
        str.assign( &c );
        table_.insert( std::make_pair( n, str ) );
        c++;
    }
}

std::string AlphaSpinBox::toUpper( const std::string& str ) {
    std::string result = str;
    std::transform( str.begin(), str.end(), result.begin(), ::toupper );
    return result;
}

std::string AlphaSpinBox::textFromValue( const unsigned& val ) {
    // you could check to see if val is within range before returning...
    return table_[val];
}

unsigned AlphaSpinBox::valueFromText( const std::string& text ) {
    std::string upper = toUpper( text );
    for ( auto pair : table_ ) {
        if ( upper == pair.second ) {
            return pair.first;
        }
    }
    return -1;
}

main.cpp

#include <string>
#include <iostream>
#include "AlphaSpinBox.h"

int main() {

    // Must Be Called First
    AlphaSpinBox::initializeMap();

    // Remember that the map first entry's key starts at 0 and not 1
    std::cout << "The number 8 has letter: " 
              << AlphaSpinBox::textFromValue( 8 ) 
              << std::endl;

    std::cout << "The letter Q has value: " 
              << AlphaSpinBox::valueFromText( std::string( "Q" ) ) 
              << std::endl;

    // check case for lower cases being converted to upper case
    std::cout << "The letter j has value: " 
              << AlphaSpinBox::valueFromText( std::string( "j" ) ) 
              << std::endl;

    std::cout << "\nPress any key and enter to quit." << std::endl;
    char q;
    std::cin >> q;
    return 0;
}

下面是,也许,更容易理解:

int AlphaToNumeric(string &value)
{
    return (value.front() >= 'A' && value.front() <= 'Z') ? value.front() - 'A' : (value.front() >= 'a' && value.front() <= 'z') ? value.front() - 'a' : -1;
}

或者:

int AlphaToNumeric(string &value)
{
    return (value.front() >= 65 && value.front() <= 90) ? value.front() - 65 : (value.front() >= 97 && value.front() <= 122) ? value.front() - 97 : -1;
}

不确定此限定符是否为 "clever",但假设您只想查看 text 的第一个字符,您可以简单地执行

#include <string>
#include <cctype>

int AlphaSpinBox::valueFromText(const std::string &text) 
{
     std::string str("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
     int retval = -2;
     if (text.size() > 0)
     {
         char c = std::toupper(text[0]);
         std::size_t index = str.find(c);
         retval = (index != std::string::npos) ? int(index) : -1;
     }
     return retval;
}

与原始问题中的代码不同,这将编译(因为它尝试转换字符串中的第一个字符,而不是整个字符串)。但是,它还会进行更多检查,如果给定长度为零的字符串,return -2,如果字符串中的第一个字符不是字母,-1 也会进行检查。

如果您假设一个字符集,其中(大写)字母顺序出现,这可以变得更简单。这个假设并不严格适用于所有标准化字符集,但在现代系统中更常见。

#include <string>
#include <cctype>

int AlphaSpinBox::valueFromText(const std::string &text) 
{
     int retval = -2;
     if (text.size() > 0)
     {
         int c = std::toupper(text[0]);
         retval = std::isupper(c) ? c - 'A' : -1;
     }
     return retval;
}

选择使用其中一个版本还是另一个版本取决于对代码永远不会移植到具有字母不连续字符集的系统的置信度。