it-swarm.com.de

Warum kann die switch-Anweisung nicht auf Zeichenfolgen angewendet werden?

Kompilieren Sie den folgenden Code und erhalten Sie den Fehler type illegal.

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

Sie können string weder in switch noch in case verwenden. Warum? Gibt es eine Lösung, die gut funktioniert, um eine ähnliche Logik zu unterstützen wie das Einschalten von Strings?

180
yesraaj

Der Grund dafür hat mit dem Typsystem zu tun. C/C++ unterstützt Strings nicht wirklich als Typ. Es unterstützt zwar die Idee eines konstanten Char-Arrays, versteht jedoch den Begriff einer Zeichenfolge nicht wirklich. 

Um den Code für eine switch-Anweisung zu generieren, muss der Compiler verstehen, was bedeutet, dass zwei Werte gleich sind. Bei Artikeln wie Ints und Enums ist dies ein trivialer Vergleich. Aber wie soll der Compiler 2 Stringwerte vergleichen? Groß- und Kleinschreibung, Insensibilität, Kulturbewusstsein usw. Ohne eine vollständige Kenntnis einer Zeichenfolge kann dies nicht genau beantwortet werden. 

Darüber hinaus werden C/C++ - Switch-Anweisungen normalerweise als Branch-Tabellen generiert. Es ist bei weitem nicht so einfach, eine Verzweigungstabelle für einen String-Style-Schalter zu generieren. 

160
JaredPar

Wie bereits erwähnt, erstellen Compiler gerne Nachschlagetabellen, die switch-Anweisungen auf nahe O(1) Zeitpunkte optimieren, wann immer dies möglich ist. Kombinieren Sie dies mit der Tatsache, dass die C++ - Sprache keinen String-Typ hat - std::string ist Teil der Standard Library, die nicht per se zur Sprache gehört.

Ich werde eine Alternative anbieten, die Sie vielleicht in Betracht ziehen sollten. Ich habe sie in der Vergangenheit erfolgreich eingesetzt. Anstatt den String selbst umzuschalten, schalten Sie das Ergebnis einer Hash-Funktion um, die den String als Eingabe verwendet. Ihr Code ist fast so klar wie das Umschalten der Zeichenfolge, wenn Sie einen vordefinierten Satz von Zeichenfolgen verwenden:

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:
        ...
    }
}

Es gibt eine Reihe offensichtlicher Optimierungen, die ziemlich genau folgen, was der C-Compiler mit einer switch-Anweisung tun würde ... komisch, wie das passiert.

53
D.Shawley

Sie können switch nur auf primitive wie int, char und enum verwenden. Die einfachste Lösung, um dies nach Ihren Wünschen zu tun, ist die Verwendung einer Aufzählung.

#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;
}

Code geschrieben von Stefan Ruck am 25. Juli 2001.

29
MarmouCorp

C++ 11-Update von offensichtlich nicht @MarmouCorp oben, sondern http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm

Verwendet zwei Maps zum Konvertieren zwischen den Strings und der Klasse enum (besser als einfaches Enum, da die Werte innerhalb dieser Werte liegen und die Suche nach Nice-Fehlern rückgängig macht).

Die Verwendung von statisch im Codeguru-Code ist mit Compiler-Unterstützung für Initialisierungslisten möglich, dh VS 2013 plus. gcc 4.8.1 war damit einverstanden, nicht sicher, wie viel weiter es kompatibel wäre.

/// <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]);
}
12
Dirk Bester

Das Problem ist, dass die switch-Anweisung in C++ aus Optimierungsgründen nur für primitive Typen funktioniert und Sie diese nur mit Kompilierungszeitkonstanten vergleichen können.

Vermutlich liegt der Grund für die Einschränkung darin, dass der Compiler in der Lage ist, eine Form der Optimierung anzuwenden, die den Code auf einen cmp-Befehl herunterkompiliert, und einen goto, bei dem die Adresse zur Laufzeit basierend auf dem Wert des Arguments berechnet wird. Da Verzweigungen und und Schleifen mit modernen CPUs nicht gut funktionieren, kann dies eine wichtige Optimierung sein.

Um dies zu umgehen, müssen Sie leider zu den Aussagen von if zurückkehren.

11
tomjen

std::map + C++ 11 lambdas pattern ohne Aufzählung

unordered_map für das möglicherweise amortisierte O(1): Was ist der beste Weg, um eine HashMap in C++ zu verwenden?

#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;
    }
}

Ausgabe:

one 1
two 2
three 3
foobar -1

Verwendung von Methoden mit static

Um dieses Muster effizient in Klassen zu verwenden, müssen Sie die Lambda-Map statisch initialisieren. Andernfalls zahlen Sie O(n) jedes Mal, um sie von Grund auf neu zu erstellen.

Hier können wir mit der {}-Initialisierung einer static-Methodenvariablen umgehen: Statische Variablen in Klassenmethoden , aber wir könnten auch die unter: statischen Konstruktoren in C++ beschriebenen Methoden verwenden. Ich muss private statische Objekte initialisieren

Das Lambda-Kontext-Capture [&] musste in ein Argument umgewandelt werden, das wäre undefiniert gewesen: const static auto lambda, das mit capture by reference verwendet wird

Beispiel mit der gleichen Ausgabe wie oben:

#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++

constexpr Hash-Funktion:

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
}
6
Nick

In C++ und C funktionieren Switches nur für Ganzzahltypen. Verwenden Sie stattdessen eine if else Ladder. C++ hätte offensichtlich eine Art swich-Anweisung für Strings implementieren können - ich denke, niemand hat es für sinnvoll gehalten, und ich stimme ihnen zu.

6
anon

Warum nicht? Sie können die Implementierung von switch mit äquivalenter Syntax und derselben Semantik verwenden. __ Die Sprache C hat keine Objekte und Zeichenfolgenobjekte, aber Zeichenfolgen in C sind nullterminierte Zeichenfolgen, auf die der Zeiger ..__ verweist. Die C++-Sprache hat die Möglichkeit, Überladungsfunktionen für den Vergleich von __.-Objekten oder das Prüfen von Objektgleichungen zu erstellen . Als C als C++ ist ausreichend flexibel, um einen solchen Schalter für Zeichenfolgen für die C -Sprache und für Objekte jeden Typs zu verwenden, die Unterstützung bieten Vergleich oder prüfen Gleichheit für C++ Sprache. Und moderne C++11 erlauben es, diese Implementierung von Switches __ genug effektiv zu machen.

Ihr Code wird folgendermaßen aussehen:

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"

Es ist möglich, kompliziertere Typen zu verwenden, beispielsweise std::pairs oder beliebige Strukturen oder Klassen, die Gleichheitsoperationen unterstützen (oder Kompromisse für den quick -Modus).

Eigenschaften

  • jede Art von Daten, die Vergleiche oder Gleichheitsprüfungen unterstützen
  • möglichkeit, kaskadierte geschachtelte Switch-Statemens zu erstellen.
  • möglichkeit, Aussagen zu unterbrechen oder durchzugehen
  • möglichkeit, nicht konstante Fallausdrücke zu verwenden
  • schneller statischer/dynamischer Modus mit Baumsuche möglich (für C++ 11)

Sintax Unterschiede beim Sprachwechsel sind

  • schlüsselwörter in Großbuchstaben
  • brauchen Klammern für CASE-Anweisung
  • semikolon ';' am ende von aussagen ist nicht erlaubt
  • doppelpunkt ':' bei CASE-Anweisung ist nicht zulässig
  • sie benötigen am Ende der CASE-Anweisung eines der Schlüsselwörter BREAK oder FALL

Für C++97-Sprache verwendete lineare Suche . Für C++11 und modernere Versionen ist es möglich, die Wuth-Tree-Suche im quick-Modus zu verwenden, wobei die return - Anweisung in CASE nicht zulässig ist . Die C-Sprachimplementierung ist vorhanden, wobei char*-Typ und null- terminierte Stringvergleiche werden verwendet.

Lesen Sie mehr über diese Switch-Implementierung.

5
oklas

Ich denke, der Grund ist, dass in C-Strings keine primitiven Typen sind, wie Tomjen sagte, denken Sie in einem String als Char-Array, sodass Sie Folgendes nicht tun können:

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

Um eine Variante mit einem möglichst einfachen Container hinzuzufügen (keine geordnete Karte erforderlich) ... Ich würde mich nicht mit einer Aufzählung beschäftigen. Setzen Sie die Container-Definition direkt vor den Switch, damit Sie leicht erkennen können, welche Zahl darstellt Welcher Fall. 

Dies führt eine Hash-Suche im unordered_map durch und verwendet die zugehörige int zum Steuern der switch-Anweisung. Sollte ziemlich schnell sein. Beachten Sie, dass at anstelle von [] verwendet wird, da ich const enthalten habe. Die Verwendung von [] kann gefährlich sein - wenn die Zeichenfolge nicht in der Karte enthalten ist, erstellen Sie eine neue Zuordnung und können undefinierte Ergebnisse oder eine kontinuierlich wachsende Karte erhalten.

Beachten Sie, dass die Funktion at() eine Ausnahme auslöst, wenn die Zeichenfolge nicht in der Map enthalten ist. Sie können also zunächst mit count() testen.

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;


}

Die Version mit einem Test für eine undefinierte Zeichenfolge folgt:

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

}
3
rsjaffe

In C++ sind Strings keine erstklassigen Bürger. Die Zeichenfolgenoperationen werden über die Standardbibliothek ausgeführt. Ich denke, das ist der Grund. Außerdem verwendet C++ die Optimierung der Verzweigungstabelle, um die Anweisungen des Switch-Falls zu optimieren. Schauen Sie sich den Link an.

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

3
chappar

In C++ können Sie eine switch-Anweisung nur für int und char verwenden

3
CodeMonkey1313
    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;

    }
0
Juan Llanes

in vielen Fällen können Sie zusätzliche Arbeit erledigen, indem Sie das erste Zeichen aus der Zeichenfolge ziehen und aktivieren. Möglicherweise muss am Charat (1) ein verschachtelter Schalter ausgeführt werden, wenn Ihre Fälle mit demselben Wert beginnen. Jeder, der Ihren Code liest, würde sich jedoch über einen Hinweis freuen, da die meisten nur nach if-else-if suchen

0
Marshall Taylor

Funktioneller Workaround für das 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)
    {
        // ...
    }
}
0
FelikZ

Schalter funktionieren nur mit Integraltypen (int, char, bool usw.). Warum nicht eine Map verwenden, um einen String mit einer Nummer zu paaren, und dann diese Nummer mit dem Switch verwenden?

0
derpface

Sie können keine Zeichenfolge in switch case verwenden. Nur int & char ist zulässig. Stattdessen können Sie Enum für die Darstellung der Zeichenfolge verwenden und es wie im Schalterfallblock verwenden 

enum MyString(raj,taj,aaj);

Verwenden Sie es in der swich case-Anweisung.

0
anil