IT story

switch 문을 문자열에 적용 할 수없는 이유는 무엇입니까?

hot-time 2020. 5. 6. 21:05
반응형

switch 문을 문자열에 적용 할 수없는 이유는 무엇입니까?


다음 코드를 컴파일하고의 오류가 발생했습니다 type illegal.

int main()
{
    // Compilation error - switch expression of type illegal
    switch(std::string("raj"))
    {
    case"sda":
    }
}

switch또는 에서 문자열을 사용할 수 없습니다 case. 왜? 문자열 켜기와 유사한 논리를 지원하기 위해 잘 작동하는 솔루션이 있습니까?


타입 시스템과 관련이있는 이유. C / C ++는 실제로 문자열을 유형으로 지원하지 않습니다. 상수 char 배열의 아이디어를 지원하지만 실제로 문자열의 개념을 완전히 이해하지는 못합니다.

switch 문에 대한 코드를 생성하려면 컴파일러는 두 값이 동일하다는 의미를 이해해야합니다. 정수 및 열거 형과 같은 항목의 경우 이것은 간단한 비트 비교입니다. 그러나 컴파일러는 2 개의 문자열 값을 어떻게 비교해야합니까? 대소 문자 구분, 둔감, 문화 인식 등 ... 문자열을 완전히 인식하지 못하면 정확하게 대답 할 수 없습니다.

또한 C / C ++ 스위치 문은 일반적으로 분기 테이블 로 생성됩니다 . 문자열 스타일 스위치에 대한 분기 테이블을 생성하는 것은 쉽지 않습니다.


앞에서 언급했듯이 컴파일러는 switch가능할 때마다 거의 O (1) 타이밍으로 명령문을 최적화하는 조회 테이블을 빌드하는 것을 좋아 합니다. 이것을 C ++ 언어에 문자열 유형이 없다는 사실과 결합하십시오- std::string언어 자체의 일부가 아닌 표준 라이브러리의 일부입니다.

나는 당신이 고려하고 싶을 수도있는 대안을 제안 할 것이다. 나는 그것을 과거에 좋은 효과를 냈었다. 문자열 자체를 전환하는 대신 문자열을 입력으로 사용하는 해시 함수의 결과를 전환하십시오. 미리 결정된 문자열 집합을 사용하는 경우 문자열을 전환하는 것만 큼 코드가 명확 해집니다.

enum string_code {
    eFred,
    eBarney,
    eWilma,
    eBetty,
    ...
};

string_code hashit (std::string const& inString) {
    if (inString == "Fred") return eFred;
    if (inString == "Barney") return eBarney;
    ...
}

void foo() {
    switch (hashit(stringValue)) {
    case eFred:
        ...
    case eBarney:
        ...
    }
}

C 컴파일러가 switch 문을 사용하여 수행하는 작업을 거의 따르는 많은 명백한 최적화가 있습니다.


int, char 및 enum과 같은 프리미티브 스위치 만 사용할 수 있습니다. 원하는대로하는 가장 쉬운 해결책은 열거 형을 사용하는 것입니다.

#include <map>
#include <string>
#include <iostream.h>

// Value-Defintions of the different String values
static enum StringValue { evNotDefined,
                          evStringValue1,
                          evStringValue2,
                          evStringValue3,
                          evEnd };

// Map to associate the strings with the enum values
static std::map<std::string, StringValue> s_mapStringValues;

// User input
static char szInput[_MAX_PATH];

// Intialization
static void Initialize();

int main(int argc, char* argv[])
{
  // Init the string map
  Initialize();

  // Loop until the user stops the program
  while(1)
  {
    // Get the user's input
    cout << "Please enter a string (end to terminate): ";
    cout.flush();
    cin.getline(szInput, _MAX_PATH);
    // Switch on the value
    switch(s_mapStringValues[szInput])
    {
      case evStringValue1:
        cout << "Detected the first valid string." << endl;
        break;
      case evStringValue2:
        cout << "Detected the second valid string." << endl;
        break;
      case evStringValue3:
        cout << "Detected the third valid string." << endl;
        break;
      case evEnd:
        cout << "Detected program end command. "
             << "Programm will be stopped." << endl;
        return(0);
      default:
        cout << "'" << szInput
             << "' is an invalid string. s_mapStringValues now contains "
             << s_mapStringValues.size()
             << " entries." << endl;
        break;
    }
  }

  return 0;
}

void Initialize()
{
  s_mapStringValues["First Value"] = evStringValue1;
  s_mapStringValues["Second Value"] = evStringValue2;
  s_mapStringValues["Third Value"] = evStringValue3;
  s_mapStringValues["end"] = evEnd;

  cout << "s_mapStringValues contains "
       << s_mapStringValues.size()
       << " entries." << endl;
}

2001 년 7 월 25 일 Stefan Ruck이 작성한 코드 .


위의 @MarmouCorp가 아닌 http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm 의 C ++ 11 업데이트

두 개의 맵을 사용하여 문자열과 클래스 열거 형 사이를 변환합니다 (값이 범위 내에 있기 때문에 일반 열거 형보다 낫고 오류 메시지가 좋은 경우 역방향 조회).

codeguru 코드에서 static을 사용하는 것은 VS 2013 plus를 의미하는 초기화 목록을위한 컴파일러 지원으로 가능합니다. gcc 4.8.1은 어느 정도 더 호환되는지 확실하지 않습니다.

/// <summary>
/// Enum for String values we want to switch on
/// </summary>
enum class TestType
{
    SetType,
    GetType
};

/// <summary>
/// Map from strings to enum values
/// </summary>
std::map<std::string, TestType> MnCTest::s_mapStringToTestType =
{
    { "setType", TestType::SetType },
    { "getType", TestType::GetType }
};

/// <summary>
/// Map from enum values to strings
/// </summary>
std::map<TestType, std::string> MnCTest::s_mapTestTypeToString
{
    {TestType::SetType, "setType"}, 
    {TestType::GetType, "getType"}, 
};

...

std::string someString = "setType";
TestType testType = s_mapStringToTestType[someString];
switch (testType)
{
    case TestType::SetType:
        break;

    case TestType::GetType:
        break;

    default:
        LogError("Unknown TestType ", s_mapTestTypeToString[testType]);
}

C ++

constexpr 해시 함수 :

constexpr unsigned int hash(const char *s, int off = 0) {                        
    return !s[off] ? 5381 : (hash(s, off+1)*33) ^ s[off];                           
}                                                                                

switch( hash(str) ){
case hash("one") : // do something
case hash("two") : // do something
}

문제는 최적화의 이유로 C ++의 switch 문이 기본 유형 외에는 작동하지 않으며 컴파일 시간 상수와 만 비교할 수 있다는 것입니다.

아마도 제한의 이유는 컴파일러가 코드를 컴파일하는 어떤 형태의 최적화를 하나의 cmp 명령어와 런타임에 인수의 값을 기반으로 주소가 계산되는 goto로 적용 할 수 있기 때문입니다. 분기 및 루프는 최신 CPU에서 잘 작동하지 않기 때문에 중요한 최적화가 될 수 있습니다.

이 문제를 해결하려면 if 문에 의지해야합니다.


std::map 열거 형이없는 +++++ 람다 패턴

unordered_map할부 상환 가능성 O(1): C ++에서 HashMap을 사용하는 가장 좋은 방법은 무엇입니까?

#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

int main() {
    int result;
    const std::unordered_map<std::string,std::function<void()>> m{
        {"one",   [&](){ result = 1; }},
        {"two",   [&](){ result = 2; }},
        {"three", [&](){ result = 3; }},
    };
    const auto end = m.end();
    std::vector<std::string> strings{"one", "two", "three", "foobar"};
    for (const auto& s : strings) {
        auto it = m.find(s);
        if (it != end) {
            it->second();
        } else {
            result = -1;
        }
        std::cout << s << " " << result << std::endl;
    }
}

산출:

one 1
two 2
three 3
foobar -1

내부 메소드 사용 static

클래스 내에서이 패턴을 효율적으로 사용하려면 람다 맵을 정적으로 초기화하십시오. 그렇지 않으면 O(n)처음부터 빌드 할 때마다 지불 합니다.

여기 {}에서 static메소드 변수 초기화를 피할 수 있습니다 : 클래스 methods의 정적 변수 . 그러나 C ++의 정적 생성자? 개인 정적 객체를 초기화해야합니다

람다 컨텍스트 캡처 [&]를 인수로 변환해야 하거나 정의되지 않았을 수 있습니다. const 정적 자동 람다는 참조로 캡처와 함께 사용

위와 동일한 출력을 생성하는 예 :

#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

class RangeSwitch {
public:
    void method(std::string key, int &result) {
        static const std::unordered_map<std::string,std::function<void(int&)>> m{
            {"one",   [](int& result){ result = 1; }},
            {"two",   [](int& result){ result = 2; }},
            {"three", [](int& result){ result = 3; }},
        };
        static const auto end = m.end();
        auto it = m.find(key);
        if (it != end) {
            it->second(result);
        } else {
            result = -1;
        }
    }
};

int main() {
    RangeSwitch rangeSwitch;
    int result;
    std::vector<std::string> strings{"one", "two", "three", "foobar"};
    for (const auto& s : strings) {
        rangeSwitch.method(s, result);
        std::cout << s << " " << result << std::endl;
    }
}

C ++ 및 C 스위치에서는 정수 유형에서만 작동합니다. if else 사다리를 대신 사용하십시오. C ++은 분명히 문자열에 대해 일종의 swich 문을 구현했을 수 있습니다. 아무도 가치가 없다고 생각하고 동의합니다.


왜 안돼? 동등한 구문과 동일한 의미로 스위치 구현사용할 수 있습니다 . C언어는 전혀 객체와 문자열 개체가 없지만, 문자열의 C널 (null) 포인터에 의해 참조 문자열을 종료했습니다. C++언어는 객체을 비교 한 또는 객체의 평등을 확인하기 위해 과부하 기능을 할 가능성이있다. 마찬가지로 C같은 것은 C++대한 문자열 등의 스위치가 유연 충분 C언어와 모든 유형의 객체를 그 지원 comparaison 나에 대한 확인 평등 C++언어. 그리고 현대 C++11는이 스위치를 충분히 효과적으로 구현할 수 있습니다.

코드는 다음과 같습니다.

std::string name = "Alice";

std::string gender = "boy";
std::string role;

SWITCH(name)
  CASE("Alice")   FALL
  CASE("Carol")   gender = "girl"; FALL
  CASE("Bob")     FALL
  CASE("Dave")    role   = "participant"; BREAK
  CASE("Mallory") FALL
  CASE("Trudy")   role   = "attacker";    BREAK
  CASE("Peggy")   gender = "girl"; FALL
  CASE("Victor")  role   = "verifier";    BREAK
  DEFAULT         role   = "other";
END

// the role will be: "participant"
// the gender will be: "girl"

예를 들어 std::pairs등식 연산을 지원하는 구조체 나 클래스 (또는 빠른 모드의 경우 comarision)와 같이보다 복잡한 유형을 사용할 수 있습니다 .

풍모

  • 비교 또는 평등 확인을 지원하는 모든 유형의 데이터
  • 계단식 중첩 스위치 상태를 만들 가능성.
  • 사례 진술을 위반하거나 넘어 질 가능성
  • 제약이없는 사례 표현식을 사용할 가능성
  • 트리 검색으로 빠른 정적 / 동적 모드 활성화 가능 (C ++ 11)

언어 전환과의 차이는

  • 대문자 키워드
  • CASE 문에 괄호가 필요합니다
  • 세미콜론 ';' 진술의 끝에는 허용되지 않습니다
  • CASE 문의 콜론 ':'은 허용되지 않습니다.
  • CASE 문의 끝에 BREAK 또는 FALL 키워드 중 하나가 필요합니다.

를 들어 C++97언어 선형 검색을 사용했다. 들어 C++11와 사용 가능한 더 현대적인 quick곳 모드 wuth 트리 검색 반환 되는 경우에는 문이 없다 허용했다. C경우 언어 구현이 존재 char*유형과 제로 종료 문자열 comparisions 사용됩니다.

이 스위치 구현 에 대해 자세히 알아 보십시오 .


tomjen이 말했듯이 C 문자열에서 기본 유형이 아니기 때문에 문자열에서 char 배열로 생각하므로 다음과 같은 작업을 수행 할 수 없습니다.

switch (char[]) { // ...
switch (int[]) { // ...

가능한 가장 간단한 컨테이너를 사용하여 변형을 추가하려면 (주문 맵 필요 없음) 열거 형을 사용하지 않아도됩니다. 컨테이너 정의를 스위치 바로 앞에두면 어떤 숫자가 나타내는 지 쉽게 알 수 있습니다 어떤 경우.

이것은 해시 조회를 수행 unordered_map하고 관련 int사용 하여 switch 문을 구동합니다. 꽤 빨라야합니다. 참고 at대신 사용됩니다 []내가 그 컨테이너를했습니다 같은 const. []문자열이지도에 없으면 사용하면 위험 할 수 있습니다. 새지도를 작성하면 정의되지 않은 결과 나 지속적으로지도가 커질 수 있습니다.

점을 유의 at()문자열이지도에없는 경우 함수가 예외가 발생합니다. 따라서 먼저을 사용하여 테스트 할 수 있습니다 count().

const static std::unordered_map<std::string,int> string_to_case{
   {"raj",1},
   {"ben",2}
};
switch(string_to_case.at("raj")) {
  case 1: // this is the "raj" case
       break;
  case 2: // this is the "ben" case
       break;


}

정의되지 않은 문자열에 대한 테스트 버전은 다음과 같습니다.

const static std::unordered_map<std::string,int> string_to_case{
   {"raj",1},
   {"ben",2}
};
switch(string_to_case.count("raj") ? string_to_case.at("raj") : 0) {
  case 1: // this is the "raj" case
       break;
  case 2: // this is the "ben" case
       break;
  case 0: //this is for the undefined case

}

C ++에서는 int와 char에서만 switch 문을 사용할 수 있습니다


In c++ strings are not first class citizens. The string operations are done through standard library. I think, that is the reason. Also, C++ uses branch table optimization to optimize the switch case statements. Have a look at the link.

http://en.wikipedia.org/wiki/Switch_statement


You can't use string in switch case.Only int & char are allowed. Instead you can try enum for representing the string and use it in the switch case block like

enum MyString(raj,taj,aaj);

Use it int the swich case statement.


Switches only work with integral types (int, char, bool, etc.). Why not use a map to pair a string with a number and then use that number with the switch?


    cout << "\nEnter word to select your choice\n"; 
    cout << "ex to exit program (0)\n";     
    cout << "m     to set month(1)\n";
    cout << "y     to set year(2)\n";
    cout << "rm     to return the month(4)\n";
    cout << "ry     to return year(5)\n";
    cout << "pc     to print the calendar for a month(6)\n";
    cout << "fdc      to print the first day of the month(1)\n";
    cin >> c;
    cout << endl;
    a = c.compare("ex") ?c.compare("m") ?c.compare("y") ? c.compare("rm")?c.compare("ry") ? c.compare("pc") ? c.compare("fdc") ? 7 : 6 :  5  : 4 : 3 : 2 : 1 : 0;
    switch (a)
    {
        case 0:
            return 1;

        case 1:                   ///m
        {
            cout << "enter month\n";
            cin >> c;
            cout << endl;
            myCalendar.setMonth(c);
            break;
        }
        case 2:
            cout << "Enter year(yyyy)\n";
            cin >> y;
            cout << endl;
            myCalendar.setYear(y);
            break;
        case 3:
             myCalendar.getMonth();
            break;
        case 4:
            myCalendar.getYear();
        case 5:
            cout << "Enter month and year\n";
            cin >> c >> y;
            cout << endl;
            myCalendar.almanaq(c,y);
            break;
        case 6:
            break;

    }

in many cases you can avid extra work by pulling the first char from the string and switching on that. may end up having to do a nested switch on charat(1) if your cases start with the same value. anyone reading your code would appreciate a hint though because most would prob just if-else-if


More functional workaround to the switch problem:

class APIHandlerImpl
{

// define map of "cases"
std::map<string, std::function<void(server*, websocketpp::connection_hdl, string)>> in_events;

public:
    APIHandlerImpl()
    {
        // bind handler method in constructor
        in_events["/hello"] = std::bind(&APIHandlerImpl::handleHello, this, _1, _2, _3);
        in_events["/bye"] = std::bind(&APIHandlerImpl::handleBye, this, _1, _2, _3);
    }

    void onEvent(string event = "/hello", string data = "{}")
    {
        // execute event based on incomming event
        in_events[event](s, hdl, data);
    }

    void APIHandlerImpl::handleHello(server* s, websocketpp::connection_hdl hdl, string data)
    {
        // ...
    }

    void APIHandlerImpl::handleBye(server* s, websocketpp::connection_hdl hdl, string data)
    {
        // ...
    }
}

That's because C++ turns switches into jump tables. It performs a trivial operation on the input data and jumps to the proper address without comparing. Since a string is not a number, but an array of numbers, C++ cannot create a jump table from it.

movf    INDEX,W     ; move the index value into the W (working) register from memory
addwf   PCL,F       ; add it to the program counter. each PIC instruction is one byte
                    ; so there is no need to perform any multiplication. 
                    ; Most architectures will transform the index in some way before 
                    ; adding it to the program counter

table                   ; the branch table begins here with this label
    goto    index_zero  ; each of these goto instructions is an unconditional branch
    goto    index_one   ; of code
    goto    index_two
    goto    index_three

index_zero
    ; code is added here to perform whatever action is required when INDEX = zero
    return

index_one
...

(code from wikipedia https://en.wikipedia.org/wiki/Branch_table)

참고URL : https://stackoverflow.com/questions/650162/why-the-switch-statement-cannot-be-applied-on-strings

반응형